1.休眠


特殊的状态并且从调度器的运行队列中移走。 这个进程将不被在任何 CPU 上调度,即将不会运行。 直到发生某些事情改变了那个状态。 安全地进入休眠的两条规则:


永远不要在原子上下文中进入休眠,即当驱动在持有一个 自旋锁、seqlock或者 RCU 锁时不能睡眠; 关闭中断也不能睡眠。持有一个信号量时休眠是合法的,但你应当仔细查看代码:如果代码在持有一个信号量时睡眠,任何其他的等待这个信号量的线程也会休眠。因此发生在 持有信号量时的休眠必须短暂,而且决不能阻塞那个将最终唤醒你的进程。


必须重新检查等待条件来确保正确的响应。


其他地方唤醒休眠的进程,否则也不能睡眠。使进程可被找到意味着:需要维护一个称为等待队列的数据结构。它是一个进程链表,其中饱含了等待某个特定事件的所有进程。在 Linux 中, 一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在 中。wait_queue_head_t 类型的数据结构非常简单:


  1. struct __wait_queue_head {
  2. ;
  3. ;
  4. };
  5. typedef struct __wait_queue_head wait_queue_head_t;


它包含一个自旋锁和一个链表。这个链表是一个等待队列入口,它被声明做 wait_queue_t。wait_queue_head_t包含关于睡眠进程的信息和它想怎样被唤醒。


定义等待队列头


  1. //静态方法
  2. (name);
  3. //动态方法
  4. wait_queue_head_t my_queue;
  5. (&my_queue);


定义等待队列


  1. DECLARE_WAITQUEUE(name, tsk)


添加/移除等待队列


  1. void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
  2. void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);


将等待队列wait添加到q/从q移除。


在等待队列上睡眠


  1. sleep_on(wait_queue_head_t * q);
  2. interruptible_sleep_on(wait_queue_head_t * q);
  3. //这两个函数将当前进程无条件休眠在给定的队列上,由于安全因素(sleep_on()不提供竞态的任何保护),不用这两个函数,ldd3建议弃用这两个函数


简单休眠


Linux 内核中最简单的休眠方式是称为 wait_event的宏(及其变种),它实现了休眠和进程等待的条件的检查。形式如下:

  1. wait_event(queue, condition)/*不可中断休眠,不推荐*/
  2. (queue, condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回 -ERESTARTSYS*/
  3. (queue, condition, timeout)
  4. (queue, condition, timeout)
  5. /*有限的时间的休眠;若超时,则不管条件为何值返回0,*/
  6. 唤醒休眠进程的函数称为 wake_up,形式如下:
  7. (wait_queue_head_t *queue);
  8. (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也可以有阻塞操作。


一个例子

  1. while(!have_date)
  2. {
  3. if (filp->f_flags & O_NONBLOCK)
  4. -EAGAIN;

  5. (wq,have_date);
  6. }//用while,而不用if,是因为中断也有可能唤醒该等待队列
  7. &wq);

PS:

默认情况下,读写操作应该是阻塞的

驱动程序不是一个进程,更不是一个线程,只是一些被APP进程调用的函数,其阻塞的是调用该函数的进程。


3.高级休眠

①定义并初始化一个等待队列,将进程改为TASK_UNINTERRUPTIBLE/TASK_INTERRUPTERIBLE,并将等待队列添加到等待队列头。

  1. set_current_state(state_value);

②通过schedule()放弃CPU,调度其他进程执行。

③进程被其他地方唤醒,将等待队列移出等待队列头。

    1. void test(void)
    2. {
    3. (wait_name, current);
    4. (&my_queue, &wait_name);
    5.
    6. (TASK_INTERRUPTIBLE);
    7.
    8. ();
    9. if (signal_pending(current))
    10. {
    11. = -ERESTARTSYS;
    12. ;
    13. }
    14.
    15. :
    16. (&my_queue, &wait_name);
    17. (TASK_RUNNING);
    18.
    19. ;
    20. }
    void test2(void)

    {

    wake_up(&
    my_queue
    );

    }

    当多个等待队列,信号量出现时,谨防死锁