1.休眠
特殊的状态并且从调度器的运行队列中移走。 这个进程将不被在任何 CPU 上调度,即将不会运行。 直到发生某些事情改变了那个状态。 安全地进入休眠的两条规则:
永远不要在原子上下文中进入休眠,即当驱动在持有一个 自旋锁、seqlock或者 RCU 锁时不能睡眠; 关闭中断也不能睡眠。持有一个信号量时休眠是合法的,但你应当仔细查看代码:如果代码在持有一个信号量时睡眠,任何其他的等待这个信号量的线程也会休眠。因此发生在 持有信号量时的休眠必须短暂,而且决不能阻塞那个将最终唤醒你的进程。
必须重新检查等待条件来确保正确的响应。
其他地方唤醒休眠的进程,否则也不能睡眠。使进程可被找到意味着:需要维护一个称为等待队列的数据结构。它是一个进程链表,其中饱含了等待某个特定事件的所有进程。在 Linux 中, 一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在 中。wait_queue_head_t 类型的数据结构非常简单:
- struct __wait_queue_head {
- ;
- ;
- };
- typedef struct __wait_queue_head wait_queue_head_t;
它包含一个自旋锁和一个链表。这个链表是一个等待队列入口,它被声明做 wait_queue_t。wait_queue_head_t包含关于睡眠进程的信息和它想怎样被唤醒。
定义等待队列头
- //静态方法
- (name);
- //动态方法
- wait_queue_head_t my_queue;
- (&my_queue);
定义等待队列
- DECLARE_WAITQUEUE(name, tsk)
添加/移除等待队列
- void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
将等待队列wait添加到q/从q移除。
在等待队列上睡眠
- sleep_on(wait_queue_head_t * q);
- interruptible_sleep_on(wait_queue_head_t * q);
- //这两个函数将当前进程无条件休眠在给定的队列上,由于安全因素(sleep_on()不提供竞态的任何保护),不用这两个函数,ldd3建议弃用这两个函数
简单休眠
Linux 内核中最简单的休眠方式是称为 wait_event的宏(及其变种),它实现了休眠和进程等待的条件的检查。形式如下:
- wait_event(queue, condition)/*不可中断休眠,不推荐*/
- (queue, condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回 -ERESTARTSYS*/
- (queue, condition, timeout)
- (queue, condition, timeout)
- /*有限的时间的休眠;若超时,则不管条件为何值返回0,*/
- 唤醒休眠进程的函数称为 wake_up,形式如下:
- (wait_queue_head_t *queue);
- (wait_queue_head_t *queue);
wake_up唤醒queue中的所有进程,wake_up_interruptible 唤醒queue中的可中断休眠
约定:用 wake_up 唤醒 wait_event ;用 wake_up_interruptible 唤醒wait_event_interruptible。
2.阻塞和非阻塞操作
全功能的 read 和 write 方法涉及到进程可以决定是进行非阻塞 I/O还是阻塞 I/O操作。明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 标志来指示(定义再 被 自动包含)。浏览源码,会发现O_NONBLOCK 的另一个名字:O_NDELAY ,这是为了兼容 System V 代码。O_NONBLOCK 标志缺省地被清除,因为等待数据的进程的正常行为只是睡眠.
其实不一定只有read 和 write 方法有阻塞操作,open也可以有阻塞操作。
一个例子
- while(!have_date)
- {
- if (filp->f_flags & O_NONBLOCK)
- -EAGAIN;
- (wq,have_date);
- }//用while,而不用if,是因为中断也有可能唤醒该等待队列
- &wq);
PS:
默认情况下,读写操作应该是阻塞的
驱动程序不是一个进程,更不是一个线程,只是一些被APP进程调用的函数,其阻塞的是调用该函数的进程。
3.高级休眠
①定义并初始化一个等待队列,将进程改为TASK_UNINTERRUPTIBLE/TASK_INTERRUPTERIBLE,并将等待队列添加到等待队列头。
- set_current_state(state_value);
②通过schedule()放弃CPU,调度其他进程执行。
③进程被其他地方唤醒,将等待队列移出等待队列头。
当多个等待队列,信号量出现时,谨防死锁