日期:2014-05-16  浏览次数:20780 次

Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠

Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠
2011年01月10日
  第六章:高级字符驱动程序操作(续1)
  以下是第2部分:掌握如何使进程休眠(并唤醒)
  分为4个小的部分(都是通过分析源码的形式,必要时加以总结):
  1、进程休眠的细节
  2、进程唤醒的细节
  3、scullpipe中read的实现
  4、scullpipe中write的实现
  1、 进程休眠的细节
  Linux内核中最简单的休眠方式就是称为wait_event的宏(以及它的几个变种),形式如下:
  wait_event(queue, condition) wait_event_interruptible(queue, condition) wait_event_timeout(queue, condition, timeout) wait_event_interruptible_timeout(queue, condition, timeout)     进程调用上面某一个宏进入休眠,最常用的是wait_event_interruptible,这个宏的具体细节如下:
  #define wait_event_interruptible(wq, condition) \ ({ \ int __ret = 0; \ if (!(condition)) //这里面包含了另一个宏 \ __wait_event_interruptible(wq, condition, __ret); \ __ret; \ })          看一看__wait_event_interruptible这个宏
  #define __wait_event_interruptible(wq, condition, ret) \ do { //第一个步骤是建立并初始化一个等待队列入口 //也就是分配并初始化一个wait_queue_t结构 //通过调用DEFINE_WAIT宏来实现 // 这个宏的定义如下: // #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) // #define DEFINE_WAIT_FUNC(name, function) \ // wait_queue_t name = { \ // .private = current, \ // .func = function, \ // .task_list = LIST_HEAD_INIT((name).task_list), \ // } DEFINE_WAIT(__wait); //建立并初始化了一个名为__wait的等待队列入口 //第二个步骤是将等待队列入口添加到队列中,并设置进程状态 for (;;) { // 调用prepare_to_wait函数,可以在wait.c中看到定义。此函数的功能是: // 1. 将等待队列入口添加到队列中。这步通过__add_wait_queue完成 // 2. 设置进程状态为TASK_INTERRUPTIBLE。 这步通过set_current_state(state)完成 prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \ //在进行上面的操作是条件可能变化了,所以这里还要再判断一次 if (condition) \ break; \ if (!signal_pending(current)) { \ //调用schedule函数,对于这个进程调度函数我没有研究。大概的理解是进程在这里让出了CPU,用某一个进程替换了当前的进程。 schedule(); \ continue; \ } \ //一旦schedule返回,则退出for循环 ret = -ERESTARTSYS; \ break; \ } \ //接下来进行清理工作。调用finish_wait函数,可以在wait.c中看到定义。该函数的作用和前面的 //prepare_to_wait相反。 // 1.设置进程状态为TASK_RUNNING // 2.将__wait从等待队列中移除。该步调用了list_del_init函数 finish_wait(&wq, &__wait); \ } while (0)
  总之,调用了wait_event或其变种,则进程进入休眠。
  2. 进程唤醒的细节
  与休眠细节相似,唤醒是通过调用wake_up宏来实现的,最常用的变种是wake_up_interruptible。这个宏的具体细节如下: #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) //__wake_up函数定义在sched.c中 void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); //自旋锁 __wake_up_common(q, mode, nr_exclusive, 0, key); //wakeup函数的核心,定义在下边 spin_unlock_irqrestore(&q->lock, flags); //解锁 } static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int wake_flags, void *key) { wait_queue_t *curr, *next; //下面调用了一个宏list_for_each_entry_safe //关于这个宏可以参考转载的"关于linux内核中等待队列数据结构之思考"一文,感谢wangchaoxjtuse //这个宏展开是一个for循环,功能就是遍历这个链表,把curr逐一指向链表中的每个项 //对于每个链表项,都调用该结构中的 wait_queue_func_t func函数来尝试唤醒该项进程 //关于func的细节见下面的源码分析 list_for_each_entry_safe(curr, next, &q->task_list, task_list) { unsigned flags = curr->flags; if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; } }  
  ================分析wait_queue_func_t的源码================
  在wait.h中可以看到:
  typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key); int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key)      
  其中default_wake_function定义在sched.c中, 如下
  int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, void *key) { return try_to_wake_up(curr->private, mode, wake_flags); }  
  看看try_to_wake_up函数,这是唤醒进程的核心函数:
  /*** * try_to_wake_up - wake up a thread * @p: the to-be-woken-up thread * @state: the mask of task states that can be woken * @sync: do a synchronous wakeup? * * Put it on the run-queue if it's not already there. The "current" * thread is always on