简单的说,休眠是为在一个当前进程等待暂时无法获得的资源或者一个event的到来时(原因),避免当前进程浪费CPU时间(目的),将自己放入进程等待队列中,同时让出CPU给别的进程(工作)。休眠就是为了更好地利用CPU。 一旦资源可用或event到来,将由内核代码(可能是其他进程通过系统调用)唤醒某个等待队列上的部分或全部进程。从这点来说,休眠也是一种进程间的同步机制。 休眠是针对进程,也就是拥有task_struct的独立个体。 当进程执行某个系统调用的时候,暂时无法获得的某种资源或必须等待某event的到来,在这个系统调用的底层实现代码就可以通过让系统调度的手段让出CPU,让当前进程处于休眠状态。
进程进入休眠状态,必然是他自己的代码中调用了某个系统调用,而这个系统调用中存在休眠代码。这个休眠代码在某种条件下会被激活,从而让改变进程状态,说到底就是以各种方式包含了: 1、条件判断语句 2、进程状态改变语句 3、schedule();
进程被置为休眠,意味着它被标识为处于一个特殊的状态(TASK_UNINTERRUPTIBLE或 TASK_INTERRUPTIBLE),并且从调度器的运行队列中移走。这个进程将不在任何 CPU 上被调度,即不会被运行。 直到发生某些事情改变了那个状态(to TASK_WAKING)。这时处理器重新开始执行此进程,此时进程会再次检查是否需要继续休眠(资源是否真的可用?),如果不需要就做清理工作,并将自己的状态调整为TASK_RUNNING。过程如下图所示:
进程在休眠后,就不再被调度器执行,就不可能由自己唤醒自己,也就是说进程不可能“睡觉睡到自然醒”。唤醒工作必然是由其他进程或者内核本身来完成的。唤醒需要改变进程的task_struct中的状态等,代码必然在内核中,所以唤醒必然是在系统调用的实现代码中(如你驱动中的read、write方法)以及各种形式的中断代码(包括软、硬中断)中。 如果在系统调用代码中唤醒,则说明是由其他的某个进程来调用了这个系统调用唤醒了休眠的进程。 如果是中断中唤醒,那么唤醒的任务可以说是内核完成了。 · 如何找到需要唤醒的进程:等待队列 上面其实已经提到了:休眠代码的一个工作就是将当前进程信息放入一个等待队列中。它其实是一个包含等待某个特定事件的所有进程相关信息的链表。一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在<linux/wait.h>中。 wait_queue_head_t 类型的数据结构如下:
- struct __wait_queue_head {
- spinlock_t lock;
- struct list_head task_list;
- };
- typedef struct __wait_queue_head wait_queue_head_t;
它包含一个自旋锁和一个链表。这是一个等待队列链表头,链表中的元素被声明做wait_queue_t。自旋锁用于包含链表操作的原子性。 wait_queue_t包含关于睡眠进程的信息和唤醒函数。
- typedef struct __wait_queue wait_queue_t;
- 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);
- struct __wait_queue {
- unsigned int flags;
- #define WQ_FLAG_EXCLUSIVE 0x01 /* 表示等待进程想要被独占地唤醒 */
- void *private; /* 指向等待进程的task_struct结构图 */
- wait_queue_func_t func; /* 用于唤醒等待进程的处理例程,在其中实现了进程状态的改变和将自己从等待队列中删除的工作 */
- struct list_head task_list; /* 双向链表结构体,用于将wait_queue_t链接到wait_queue_head_t */
- };
他们在内存中的结构大致如下图所示: 等待队列头wait_queue_head_t一般是定义在模块或内核代码中的全局变量,而其中链接的元素 wait_queue_t的定义被包含在了休眠宏中。 休眠和唤醒的过程如下图所示:
|