当我们进程去访问设备的时候,经常需要等待有特定事件发生以后在继续往下运行,这 个时候就需要在驱动里面实现当条件不满足的时候进程休眠,当条件满足的时候在由内核唤醒 进程。 那么等待队列就实现了在事件上的条件等待。
<1> 等待队列头 等待队列头就是一个等待队列的头部, 每个访问设备的进程都是一个队列项, 当设备 不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。 等待队列头使用结构体 wait_queue_head_t 来表示,这个结构体定义在文件 include/linux/wait 里面,结构体内容如下:
struct __wait_queue_head {
spinlock_t lock; // 自旋锁
struct list_head task_list; // 链表头
};
typedef struct __wait_queue_head wait_queue_head_t;
类型名是 wait_queue_head_t , 只需要记住这个即可。
定义一个等待队列头: wait_queue_head_t test_wq; // 定义一个等待队列的头
定义等待队列头以后需要初始化,可以使用 init_waitqueue_head 函数初始化等待队列头, 函数原型如下:
void init_waitqueue_head(wait_queue_head_t *q)
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化。
DECLARE_WAIT_QUEUE_HEAD (wait_queue_head_t *q);
三.等待队列相关函数
<1>init_waitqueue_head 宏
原型: void init_waitqueue_head(wait_queue_head_t *q)
作用:动态初始化等待队列头结构
参数:
q 是 wait_queue_head_t 指针
<2>wait_event 宏
原型: wait_event(wq, condition)
功能:不可中断的阻塞等待,让调用进程进入不可中断的睡眠状态,在等待队列里面睡眠直到 condition 变成真,被内核唤醒。
参数:
wq : wait_queue_head_t 类型变量。
condition 为等待的条件,为假时才可以进入休眠
注意:调用的时要确认 condition 值是真还是假,如果调用 condition 为真,则不会休眠。
<3>wait_event_interruptible 宏
原型: wait_event_interruptible(wq, condition)
功能:可中断的阻塞等待,让调用进程进入可中断的睡眠状态,直到 condition 变成真被内核 唤醒或被信号打断唤醒。
参数:
wq : wait_queue_head_t 类型变量。
condition 为等待的条件,为假时才可以进入休眠
返回:判断 condition 是否为真 , 如果为真则返回 , 否则检查如果进程是被信号唤醒 , 会返回 ERESTARTSYS 错误码 . 如果是 condition 为真 , 则 返回 0
<4>wake_up 宏
原型: wake_up(x)
功能:唤醒所有休眠进程
参数:
x : 等待队列头结构指针。
<5>wake_up_interruptible 宏
原型: wake_up_interruptible(x)
功能:唤醒可中断的休眠进程
参数:
x : 等待队列头结构指针。
定义并且初始化等待队列:
DECLARE_WAIT_QUEUE_HEADER(key_wq);
int wq_flags = 0;//定义标志位
中断函数:
irq_handler_t test_key(int irq, void *args)//中断处理函数
{
value = !value;
wq_flags = 1;
wake_up(&key_wq);//唤醒进程
return IRQ_HANDLED;
}
传送到应用层的代码
int misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
wait_event_interruptible(key_wq,wq_flags);
if(copy_to_user(ubuf,value,sizeof(value))!=0)
{
printk("copy to user error\n");
return -1;
}
wq_flags = 0;
return 0;
}
实验结果:阻塞打印。
1、 什么是工作队列?
工作队列( workqueue ) 是实现中断下文的机制之一,是一种将工作推后执行的形式。 那工作队列和我们之前学的 tasklet 机制有什么不同呢? tasklet 也是实现中断下文的机制。 他们俩个最主要的区别是 tasklet 不能休眠,而工作队列是可以休眠的。 所以,tasklet 可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。
2、工作队列( workqueue )的工作原理
Linux 系统在启动期间会创建内核线程,该线程创建以后就处于 sleep 状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。工作队列的实现机制实际上是非常复杂的,初学阶段只需要了解这些基本概念接口。 类比理解:
流水线上的机械: Linux 系统自动会创建一个。 多种不同的物料使用同一个流水线机械, 那么这个就是共享工作队列的概念。
如果当前的流水线机械不能满足我们加工的物料,就需要重新定制一台流水线机器呢,这个就是自定义工作队列的概念。
共享工作队列有缺点:
不需要自己创建,但是如果前面的工作比较耗时间,就会影响后面的工作。 自定义工作队列有什么优缺点呢?
需要自己创建,系统开销大。优点是不会受到其他工作的影响。(因为这个流水线就是专门加 工这一种零件的。)
尽管工作队列的实现机制非常复杂,但是使用工作队列其实就是在这个流水线上添加自己的物料,然后等待执行即可。
一个具体的工作(类比就是流水线上的物料)使用结构体 work_struct 来描述 的,定义在 Linux\work\queue.h 里面。
所以使用工作队列的第一步就是先定义个工作队列。
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
}
在这个结构体里面只需要关注 func 这个成员就可以了,他是一个函数指针,因为要把需要完成的工作写在这个函数里面。
<1> 宏 DECLARE_WORK 原型: #define DECLARE_WORK(n, f)
作用:静态定义并且初始化工作队列。
<2> 宏 INIT_WORK 原型: #define INIT_WORK(_work, _func)
作用:动态定义并初始化工作结构
参数:
work : 工作队列地址;
func : 工作函数
举例:
静态初始化:
struct work_struct test;
在模块的初始化函数中:
INIT_WORK(&test, func) ;
动态初始化:
相当于:DECLARE_WORK(test, func);
<3>schedule_work
原型:int schedule_work(struct work_struct *work);
作用:调度工作,把 work_struct 挂到 CPU 相关的工作结构队列链表上,等待工作者线程处理。
参数: _work 工作队列地址;
需要注意的是,如果调度完工作,并不会马上执行,只是加到了共享的工作队列里面去, 等轮到他才会执行。
如果我们多次调用相同的任务,假如上一次的任务还没有处理完成,那么多次调度相同的任务是无效的。
包含头文件:
#include
定义结构体:
struct work_struct key_test;
在probe中进行初始化:
//初始化
INIT_WORK(&key_test,test);
中断函数初始化
void test(unsigned long data){
int i = 100;
while(i--)
printk("test_key is %d\n",i);
}
irq_handler_t test_key(int irq, void *args)//中断处理函数
{
schedule_work(&key_test);
return IRQ_HANDLED;
}
现象:按下按键,打印100次i的数值。