1. Linux内核等待队列
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步时间通知机制。在网络编程中,我们发送数据总要在收到反馈信息的时候才能发送下一帧数据,上层应用开发我们使用的iotcl进行读写控制时,底层便是以此开发比较合理。Linux内核休眠唤醒机制,在本文指的是在驱动运行的过程中可以使进程以休眠的方式等待数据,当事件发生的时候将其唤醒。当前在工作过程中主要用到的方式为:
- 音频codec芯片从DMA块获取缓存数据,将缓存数据分包发送。当没有音频数据时等待休眠,音频数据准备好启动socket进行UDP发送。
- 应用层与内核层进行数据交互,应用层以IOTCL形式向内核层传递数据,内核层通过数据响应上传应用层,无数据时等待休眠。
TASK_RUNNING:在Linux中,仅等待 CPU 时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的进程时间片用完, Linux 内核的调度器会剥夺这个进程对 CPU 的控制权,并且从运行队列中选择一个合适的进程投入运行。
TASK_INTERRUPTIBLE:可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放 进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。
TASK_UNINTERRUPTIBLE:不可中断睡眠状态一般较少用到,但在一些特定情况下这种状态还是很有用的,比如说:进程必须等 待,不能被中断,直到某个特定的事件发生。
2. Linux等待队列使用
2.1 头文件(include\linuxwait.h)
#include <linux/sched.h>
#include <linux/wait.h>
2.2 定义等待队列
wait_queue_head_t wait_head;
2.3 初始化等待队列
static inline void init_waitqueue_head(wait_queue_head_t *q)
{
spin_lock_init(&q->lock);
INIT_LIST_HEAD(&q->task_list);
}
2.4 唤醒等待队列
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
- wake_up(wq) 能用于唤醒各种方式进入休眠的进程,只唤醒队列上的一个进程。
- wake_up_all(wq)效果和wake_up相同,只是能唤醒队列上所有的进程。
- wake_up_interruptible_all(wq)能唤醒队列所有 使用wait_event_interruptible*休眠的进程。
wq 是指针。
2.5 判断队列是否唤醒
事件唤醒队列(当cond条件是false,则休眠)。等待休眠直到condition的表达式为真,每次唤醒队列都会检查condition,如果函数被中断,函数将返回-ERESTARTSYS信号,condition为真则返回0。
/**
* wait_event_interruptible - sleep until a condition gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* The function will return -ERESTARTSYS if it was interrupted by a
* signal and 0 if @condition evaluated to true.
*/
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
- wait_event(wq, condition) ;建立不可以杀进程(信号不能唤醒,效果和msleep相同)
- wait_event_timeout(wq, condition, timeout) ;休眠期间效果和 wait_event ,但是有一个超时时间 ,时间到,不管条件如何,直接返回。
- wait_event_interruptible_timeout(wq, condition, timeout);休眠期间效果和 wait_event_interruptible相同,区别是有超时功能。时间到,不管条件如何,直接返回。当返回值大于0时,表示返回的是剩余的时间(以jiffy为单位),条件满足,也就是还未超时,条件已经达成了,被唤醒了。当返回值为0时,表示超时,自动唤醒,此时根据情况进行错误处理吧。
#define __wait_event_interruptible_timeout(wq, condition, ret)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
if (condition)
break;
if (!signal_pending(current)) {
ret = schedule_timeout(ret);
if (!ret)
break;
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait);
} while (0)
/**
* wait_event_interruptible_timeout - sleep until a condition gets true or a timeout elapses
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
* @timeout: timeout, in jiffies
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* The function returns 0 if the @timeout elapsed, -ERESTARTSYS if it
* was interrupted by a signal, and the remaining jiffies otherwise
* if the condition evaluated to true before the timeout elapsed.
*/
#define wait_event_interruptible_timeout(wq, condition, timeout)
({
long __ret = timeout;
if (!(condition))
__wait_event_interruptible_timeout(wq, condition, __ret);
__ret;
})
ret = wait_event_interruptible_timeout(wait_queue,ev_recv,msecs_to_jiffies(60));
用wake_up_interruptible()唤醒后,wait_event_interruptible(wq, condition)宏,自身再检查“condition”这个条件以决定是返回还是继续休眠,真则返回,假则继续睡眠,不过这个程序中若有中断程序的话,中断来了,还是会继续执行中断函数的。只有当执行wake_up_interruptible()并且condition条件成立时才会把程序从队列中唤醒。
3. 总结
主要使用在某个条件满足执行线程内容进行获取,当用户空间进程读取某一设备时,如果设备此时没有数据则调用wait_event_interruptible
等函数,该进程将进入等待状态, 等待某一事件发生且条件成立; 当某事件发生后,调用wake_up_interruptible
等函数进行唤醒,唤醒后,其会判断条件是否成立, 如果不成立则继续进入休眠状态; 反之条件成立,则立即退出等待状态;此时程序中有中断程序,程序仍会执行中断函数,只有执行wait_event_interruptible
并且condition
条件成立时才会把程序从队列唤醒。
4. 参考文档
Linux内核之休眠与唤醒
Linux内核驱动休眠和唤醒机制(select系统调用内核驱动poll实现)
Linux时间转换msecs_to_jiffies