Linux内核知识点---线程、锁、中断下半部、定时器

背景

前面学习了不少的内核驱动的知识,不过其中有很多小的知识点,还是需要进一步学习,例如线程,自旋锁之类的知识,在驱动中用到了,但是并没有详细了解,今天来把这些零碎的知识点补充强化一下。不过还是以正确使用为主,概念的东西不多。

线程

线程的创建,目前有三种方式,涉及三个函数

  • kernel_thread
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

这个貌似已经不再使用,编译无法通过。

  • kthread_create

这个就是最常用的创建线程

#define kthread_create(threadfn, data, namefmt, arg...) \
	kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)

,不过要配合wakeup函数来让线程执行。

int wake_up_process(struct task_struct *p)
  • kthread_run
    这个就将前面的两个操作合二为一,创建好就直接运行。
#define kthread_run(threadfn, data, namefmt, ...)			   \
({									   \
  struct task_struct *__k						   \
  	= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
  if (!IS_ERR(__k))						   \
  	wake_up_process(__k);					   \
  __k;								   \
})

后两种方法的使用,因情况而定,就看你是否需用统一控制线程开启。

  • 线程退出
int kthread_stop(struct task_struct *k)

linux内核锁机制

linux内核锁机制有信号量、互斥锁、自旋锁还有原子操作。

信号量(struct semaphore)

是用来解决进程/线程之间的同步和互斥问题的一种通信机制,是用来保证两个或多个关键代码不被并发调用。
信号量(Saphore)由一个值和一个指针组成,指针指向等待该信号量的进程。信号量的值表示相应资源的使用情况。信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个资源,因此S的值减1;当S<0时,表示已经没有可用资源,S的绝对值表示当前等待该资源的进程数。请求者必须等待其他进程释放该类资源,才能继续运行。而执行一个V操作意味着释放一个资源,因此S的值加1:若 S<0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。
信号量是选择睡眠的方式来对共享工作停止访问的。
也就是说信号量通过PV操作同步解决了进程/线程对临界资源利用的冲突问题。
初始化

static inline void sema_init(struct semaphore *sem, int val);

获取信号量

//获取信号量,如果计数值是0,进入深度睡眠
extern void down(struct semaphore *sem);
//获取信号量,如果计数值是0,进入轻度睡眠
extern int __must_check down_interruptible(struct semaphore *sem);
//获取信号量,如果计数值是0,进入中度睡眠
extern int __must_check down_killable(struct semaphore *sem);
//获取信号量,如果计数值是0,进程不等待
extern int __must_check down_trylock(struct semaphore *sem);
//获取信号量,指定等待时间
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);

释放信号量

//释放信号量
extern void up(struct semaphore *sem);

互斥锁:(mutex lock)

互斥锁同样也是对线程间(不能对进程)同步和互斥的一种另一种机制。
互斥锁更多的是强调对共享资源的锁定作用,当一个线程占用了当前共享资源,使用互斥锁将其lock住之后,其他线程就无法访问,必须等到unlock之后,其他线程才能利用共享资源里面的内容。互斥锁是选择睡眠的方式来对共享工作停止访问的,也就是说互斥锁通过对共享资源的锁定和互斥解决利用资源冲突问题。
互斥锁操作函数
定义

static struct mutex MyMutex;

初始化互斥锁

mutex_init(&MyMutex);

上锁

//申请互斥锁,如果锁被占有,进程深度睡眠
extern void mutex_lock(struct mutex *lock);
//申请互斥锁,如果锁被占有,进程轻度睡眠
extern int __must_check mutex_lock_interruptible(struct mutex *lock);
//申请互斥锁,如果锁被占有,进程中度睡眠
extern int __must_check mutex_lock_killable(struct mutex *lock);
//
extern void mutex_lock_io(struct mutex *lock);
//申请互斥锁,如果锁被占有,不等待
/*
 * NOTE: mutex_trylock() follows the spin_trylock() convention,
 *       not the down_trylock() convention!
 *
 * Returns 1 if the mutex has been acquired successfully, and 0 on contention.
 */
extern int mutex_trylock(struct mutex *lock);

解锁

mutex_unlock(&MyMutex);

自旋锁(spin lock):

是为实现保护共享资源而提出一种销机制。其实,自旋销与万斥销比较类似,它们都是为了解决对某项资源的万后使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
自旋锁操作函数
定义

static spinlock_t my_spin_lock;

初始化

spin_lock_init(&my_spin_lock);

获取

//申请自旋锁,如果锁被其他处理器占有,当前处理器自旋等待
static __always_inline void spin_lock(spinlock_t *lock);
//申请自旋锁,如果锁被其他处理器占有,当前处理器不等待
static __always_inline int spin_trylock(spinlock_t *lock);
//申请自旋锁,并且禁止当前处理器的软中断
static __always_inline void spin_lock_bh(spinlock_t *lock);
//申请自旋锁,并且禁止当前处理器的硬中断
static __always_inline void spin_lock_irq(spinlock_t *lock);

释放

//释放自旋锁
static __always_inline void spin_unlock(spinlock_t *lock);
//释放自旋锁,并且开启当前处理器的软中断
static __always_inline void spin_unlock_bh(spinlock_t *lock)
//释放自旋锁,并且开启当前处理器的硬中断
static __always_inline void spin_unlock_irq(spinlock_t *lock)

原子操作

所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。因此这里的原子实际是使用了物理学里的物质微粒的概念。
原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。
原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的

定义

atomic_t v=ATOMIC_INIT(O):/定义原子变量v并初始化为0;

该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。

atomic_read(atomic_t * v);

该函数设置原子类型的变量v的值为i。

atomic_set(atomic_t * v, int i);

该函数给原子类型的变量v增加值i。

void atomic_add(int i, atomic_t *v);

该函数从原子类型的变量v中减去i。

atomic_sub(int i, atomic_t *v);

该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_sub_and_test(int i, atomic_t *v);

该函数对原子类型变量v原子地增加1。

void atomic_inc(atomic_t *v);

该函数对原子类型的变量v原子地减1。

void atomic_dec(atomic_t *v);

该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_dec_and_test(atomic_t *v);

该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_inc_and_test(atomic_t *v);

该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。

int atomic_add_negative(int i, atomic_t *v);

该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。

int atomic_add_return(int i, atomic_t *v);

该函数从原子类型的变量v中减去i,并且返回指向v的指针。

int atomic_sub_return(int i, atomic_t *v); 

该函数对原子类型的变量v原子地增加1并且返回指向v的指针。

int atomic_inc_return(atomic_t * v); 

该函数对原子类型的变量v原子地减1并且返回指向v的指针。

int atomic_dec_return(atomic_t * v);

参考资料
《linux内核互斥简介》

中断的下半部

中断一般分为两部分,不可中断的上半部,和比较耗时的下半部,上半部一般用来操作硬件,然后及时退出中断,下半部用来执行一些比较耗时的处理。
Linux内核知识点---线程、锁、中断下半部、定时器_第1张图片
那么下半部的方法都有那些?
tasklet,工作队列,软中断和线程化irq。

tasklet小任务

比较简单,一个简单的例子就能看出来了
定义一个结构和一个处理函数

static struct  t asklet_struct my_tasklet;

static void tasklet_handler (unsigned long d ata)
{
        printk(KERN_ALERT,"tasklet_handler is running./n");
}

使用的时候,通常在中断里

tasklet_init(&my_tasklet,tasklet_handler,0);
tasklet_schedule(&my_tasklet);

工作队列

也比较简单,还是一个例子
创建工作队列

static struct workqueue_struct *queue = NULL;
static struct work_struct work;

static void work_handler(struct work_struct *data)
{
        printk(KERN_ALERT "work handler function./n");
}

使用的时候

queue = create_singlethread_workqueue("helloworld"); /*创建一个单线程的工作队列*/
if (!queue)
        goto err;

INIT_WORK(&work, work_handler);
schedule_work(&work);

定时器

关于定时器的接口都在timer.h中
定时器初始化

/**
 * timer_setup - prepare a timer for first use
 * @timer: the timer in question
 * @callback: the function to call when timer expires
 * @flags: any TIMER_* flags
 *
 * Regular timer initialization should use either DEFINE_TIMER() above,
 * or timer_setup(). For timers on the stack, timer_setup_on_stack() must
 * be used and must be balanced with a call to destroy_timer_on_stack().
 */
#define timer_setup(timer, callback, flags)			\
	__init_timer((timer), (callback), (flags))

#define timer_setup_on_stack(timer, callback, flags)		\
	__init_timer_on_stack((timer), (callback), (flags))

启动定时器

void add_timer(struct timer_list *timer);
void add_timer_on(struct timer_list *timer, int cpu);

修改定时器,到达新的时间点后执行处理函数

extern int mod_timer(struct timer_list *timer, unsigned long expires);
extern int mod_timer_pending(struct timer_list *timer, unsigned long expires);

删除定时器

int del_timer(struct timer_list * timer);

来一个处理按键抖动的演示。
首先定义定时器和延迟处理函数

struct timer_list key_timer;

//延迟读取函数
static void key_timer_expire(struct timer_list *t)
{
	struct gpio_key *gpio_key = from_timer(gpio_key, t, key_timer);
	int val;
	int key;
	val = gpiod_get_value(gpio_key->gpiod);
	printk("key_timer_expire key %d %d\n", gpio_key->gpio, val);
	key = (gpio_key->gpio << 8) | val;
	put_key(key);
}

在初始化的时候,定义定时器,并启动,但是超时时间无穷

timer_setup(&key_timer, key_timer_expire, 0);
key_timer.expires = ~0;
add_timer(&key_timer);

在中断函数里,启动定时器,延迟20ms再读取值

mod_timer(&key_timer, jiffies + HZ/50);

你可能感兴趣的:(驱动开发,操作系统,linux知识,linux,内核,线程,锁)