关于进程的休眠和唤醒(转)_loyal_baby的博客-程序员秘密

技术标签: Linux  磁盘  callback  up  list  signal  任务  

转自:http://bbs.java.ccidnet.com/read.php?tid=655240

LKD中的讲解
休眠(被阻塞)的进程处于一个特殊的不可执行状态。这点非常重要,否则,没有这种特殊状态的话,调度程序就可能选出一个本不愿意被执行的进程,更糟糕的是,休眠就必须以轮询的方式实现了。进程休眠有各种原因,但肯定都是为了等待一些事件。事件可能是一段时间、从文件I/O读更多数据,或者是某个硬件事件。一个进程还有可能在尝试获得一个已经占用的内核信号量时被迫进入休眠。休眠的一个常见原因就是文件I/O -- 如进程对一个文件执行了read()操作,而这需要从磁盘里读取。还有,进程在获取键盘输入的时候也需要等待。无论哪种情况,内核的操作都相同:进程把它自己标记成休眠状态,把自己从可执行队列移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒的进程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行队列。

  休眠有两种相关的进程状态:TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE。它们的惟一区别是处于TASK_UNINTERRUPTIBLE状态的进程会忽略信号,而处于 TASK_INTERRUPTIBLE状态的进程如果收到信号会被唤醒并处理信号(然后再次进入等待睡眠状态)。两种状态的进程位于同一个等待队列上,等待某些事件,不能够运行。
   
  休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。内核用wake_queue_head_t来代表等待队列。等待队列可以通过DECLARE_WAITQUEUE()静态创建,也可以有init_waitqueue_head()动态创建。进程把自己放入等待队列中并设置成不可执行状态。等与等待队列相关的事件发生的时候,队列上的进程会被唤醒。为了避免产生竞争条件,休眠和唤醒的实现不能有纰漏。
  针对休眠,以前曾经使用过一些简单的接口。但那些接口会带来竞争条件;有可能导致在判断条件变为真后进程却开始了休眠,那样就会使进程无限期地休眠下去。所以,在内核中进行休眠的推荐操作相对复杂一些.


进程通过执行下面几步将自己加入到一个等待队列中:
---------------------------------------------------------
1. 调用DECLARE_WAITQUEUE()创建一个等待队列的项
|------------------------------------------------|
|/* 'q' is the wait queue we wish to sleep on */ |
|DECLARE_WAITQUEUE(wait, current); |
|------------------------------------------------|

2. 调用add_wait_queue()把自己加入到队列中。该队列在进程等待的条件满足时唤醒它。当然我们必须在其他地方撰写相关代码,在事件发生时,对等待队列执行wake_up()操作
|-----------------------------|
|add_wait_queue(q, &wait); |
|-----------------------------|

while (!condition) { /* condition is the event that we are waiting for */ 

3. 将进程的状态变更为TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE
|----------------------------------------------|
| /* or TASK_UNINTERRUPTIBLE */ |
| set_current_state(TASK_INTERRUPTIBLE); |
|----------------------------------------------|

4. 如果状态被设置为TASK_INTERRUPTIBLE,则信号可以唤醒进程(信号和事件都可以唤醒该进程)。这就是所谓的伪唤醒(唤醒不是因为事件的发生,而是由信号唤醒的),因此检查并处理信号。
注: 信号和等待事件都可以唤醒处于TASK_INTERRUPTIBLE状态的进程,信号唤醒该进程为伪唤醒;该进程被唤醒后,如果(!condition)结果为真,则说明该进程不是由等待事件唤醒的,而是由信号唤醒的。所以该进程处理信号后将再次让出CPU控制权
|----------------------------------------------|
| if (signal_pending(current)) |
| /* handle signal */ |
|----------------------------------------------|

5. Tests whether the condition is true. If it is, there is no need to sleep. If it is not true, the task calls schedule().
本进程在此处交出CPU控制权,如果该进程再次被唤醒,将从while循环结尾处继续执行,因而将回到while循环的开始处while (!condition),进测等待事件是否真正发生.
|----------------------------------------------|
| schedule(); |
|----------------------------------------------|


6. Now that the condition is true, the task can set itself to TASK_RUNNING and remove itself from the wait queue via remove_wait_queue().
|----------------------------------------------|
|set_current_state(TASK_RUNNING); |
|remove_wait_queue(q, &wait); |
|----------------------------------------------|

  如果在进程开始睡眠之前条件就已经达成了,那么循环会退出,进程不会存在错误的进入休眠的倾向。需要注意的是,内核代码在循环体内常常需要完成一些其他的任务,比如,它可能在调用schedule()之前需要释放掉锁,而在这以后再重新获取它们,或者响应其他的事件。
  唤醒操作通过函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。它调用函数try_to_wake_up(),该函数负责将进程设置为TASK_RUNNING状态,调用activate_task()将此进程放入可执行队列,如果被唤醒的进程优先级比当前正在运行的进程的优先级高,还有设置need_resched标志。通常哪段代码促使等待条件达成,它就负责随后调用wake_up()函数。 
  关于休眠有一点需要注意,存在虚假的唤醒。有时候进程被唤醒并不是因为它所等待的条件达成了(而是接受到了信号),所以才需要用一个循环处理来保证它等待的条件真正达成。 

wait_event_interruptible的实现

> include/linux/wait.h: 
>  
> #define wait_event_interruptible(wq, condition)  
> / 
> ({  
> / 
> int __ret = 0;  
> / 
> if (!(condition))  
> / 
> __wait_event_interruptible(wq, condition,  
> __ret); / 
> __ret;  
> / 
> }) 
>  
> #define __wait_event_interruptible(wq, condition, ret)  
> / 
> do {  
> / 
> DEFINE_WAIT(__wait);  
> / 
>  
> / 
> for (;;) {  
> / 
> prepare_to_wait(&wq, &__wait,  
> TASK_INTERRUPTIBLE); / 
> if (condition)  
> / 
> break;  
> / 
> if (!signal_pending(current)) {  
> / 
> schedule();  
> / 
> continue;  
> / 
> }  
> / 
> ret = -ERESTARTSYS;  
> / 
> break;  
> / 
> }  
> / 
> finish_wait(&wq, &__wait);  
> / 
> } while (0) 
>  
>  
>  
> kernel/wait.c: 
>  
> void fastcall 
> prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) 
> { 
> unsigned long flags; 
>  
> wait->flags &= ~WQ_FLAG_EXCLUSIVE; 
> spin_lock_irqsave(&q->lock, flags); 
> if (list_empty(&wait->task_list)) 
> __add_wait_queue(q, wait); 
> /* 
> * don't alter the task state if this is just going to 
> * queue an async wait queue callback 
> */ 
> if (is_sync_wait(wait)) 
> set_current_state(state); 
> spin_unlock_irqrestore(&q->lock, flags); 
> } 
>  
> 等待队列中的进程有两种,一种是exclusive的进程,另一种是nonexclusive的进程。所谓exclusive是指唤 
> 醒的进程等待的资源是互斥的,每次只唤醒一个(唤醒多个也可以,不过最后还是只有一个会被唤醒,其余的又被重新添加到等待队列中,这 
> 样效率会大打折扣)。一般,等待函数会把进程设为nonexclusive和uninterruptible,带“interrup 
> tible”的会专门指定状态为interruptible;而带“timeout”的会在超时后退出,因为它会调用schedul 
> e_timeout();带“exclusive”的则会把进程设为exclusive。 
>  
> 总之,wait_event_interruptible是把进程状态设为TASK_INTERRUPTIBLE,nonexclu 
> sive,等待某事件的来临。 
>
举例:
假设A进程占有一个系统共享资源,它在对共享资源进行操作之前,会先将共享资源上锁.但A进程一直都有对此共享资源的控制权,直到对此共享资源的操作结束为止.
在A进程的代码操作共享资源的过程中,由于时钟中断或是其它原因,sched()执行,A进程被切换出去,sched()调度B进程执行,由于共享资源已上锁,故而B进程只能休眠,B进程休眠时再次执行sched()调度程序.
经过一段时间后,A进程再次执行,由于A进程占有共享资源,故而A进程往下执行,也就执行了唤醒wake_up(),经过唤醒,其它进程也就拥有占用共享资源的权利.
结论:
1、任何一个进程先占有共享资源,对共享资源上锁,直到对此共享资源操作完成后才会释放此共享资源。没有操作完成时,就一直占有此共享资源。
2、一个共享资源被先来的进程先占用了,后来的进程若要访问它,只能休眠等待。
3、先占有共享资源的进程操作完成后解锁共享资源,最先执行唤醒(wake_up()),让后来的进程拥有机会占用共享资源。
4、对于共享资源的操作,各个进程是互斥的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/loyal_baby/article/details/4195946

智能推荐

MySQL优化技巧_alashan007的博客-程序员秘密

MYSQL优化主要分为以下四大方面:设计:存储引擎,字段类型,范式与逆范式功能:索引,缓存,分区分表。架构:主从复制,读写分离,负载均衡。合理SQL:测试,经验。一、存储引擎在创建表的时候我们使用sql语句,Create table tableName () engine=myisam|innodb;这里就指明了存储引擎是myisam还是innodb。存储引擎是一种用来...

zoj3168------------------------Sort ZOJ7_昆libra的博客-程序员秘密

Given a string of no more than 1000 characters. You are supposed to sort the characters into a substring of allZ's followed by O's, J's, 7's, and the rest of the other characters.InputEach cas

特斯拉不顾疫情开工,马斯克放话:若要逮捕,冲我一个人来!_湾区人工智能的博客-程序员秘密

美国电动汽车生产商特斯拉位于加州的工厂复工一事持续发酵。据了解,特斯拉涉嫌违反当地政府疫情期间的规定提前开工,监管部门已经就此展开调查,而特斯拉首席执行官马斯克则表示,如果有人需要被逮捕...

3532:最大上升子序列和(2.6基本算法之动态规划)_qq_26919935的博客-程序员秘密

3532:最大上升子序列和总时间限制: 1000ms 内存限制: 65536kB 描述 一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …,aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8

dbca静默模式创建数据库_Martin201609的博客-程序员秘密_dbca静默建库

DBCA使用静默模式创建数据库基本信息数据库版本: oracle 11.2.0.4操作系统版本: rh linux 7.5在配置数据库时,我们没有用户界面,vnc没有安装,使用dbca静默模式创建数据库dbca -silent …在此处,使用模板文件,创建传统OLTP数据库模板文件的位置:$ORACLE_HOME//assistants/dbca/templatesData_Wa...

随便推点

Android 避免Activity转场动画退出时候和系统自带的一起出现_dhyjtt的博客-程序员秘密

Android 记Activity转场动画退出时候和系统自带的以前自定义转场动画一般是在style定义 <item name="android:windowIsTranslucent">true</item>//防止启动闪烁 <item name="android:windowAnimationStyle">@style/Animation.Activity.Translu

android 程序闪退 log,应用闪退log日志。。_weixin_39566593的博客-程序员秘密

应用闪退log日志。。12-04 14:04:24.121 19876 19876 E AndroidRuntime FATAL EXCEPTION: main12-04 14:04:24.121 19876 19876 E AndroidRuntime Process:...

抓包工具charles的https抓包配置_田格本5566的博客-程序员秘密

PC端安装ssl证书单击安装证书 单击下一步,修改证书存储路径,如下图单击下一步直到完成  手机客户端安装证书手机浏览器访问地址证书下载地址:http://www.charlesproxy.com/documentation/using-charles/ssl-certificates/https://www.charlesproxy.com/docum...

rt1052 usb速率_rt1052下的usb host_菩提自性的博客-程序员秘密

#include #include "board.h"#define OTG_FS_PORT 1static HCD_HandleTypeDef _stm_hhcd_fs;static struct rt_completion urb_completion;void HAL_HCD_MspInit(HCD_HandleTypeDef *hcdHandle){GPIO_InitTypeDef GPI...

Ubuntu16.10 迁移到 SSD(精简与改进)_折木的博客-程序员秘密

目录1.参考2.准备3.分区4.拷贝4.1 /boot拷贝4.2 /home拷贝4.3 / 拷贝4.3.1 不用拷贝重新新建的文件夹4.3.2 直接拷贝的文件夹4.3.3 稍微复杂一些的文件夹拷贝5.grub6.文件系统挂载7.修复引导1.参考主要参考https://www.jianshu.com/p/478567d8b14a,从中截取操...

【STM32】GPIO工作原理(八种工作方式超详细分析,附电路图)_Yngz_Miao的博客-程序员秘密_stm32上拉输入和下拉输入如何选择

STM32F1xx官方资料:《STM32中文参考手册V10》-第8章通用和复用功能IO(GPIO和AFIO ) 芯片数据手册(datasheet) STM32的GPIO介绍STM32引脚说明GPIO是通用输入/输出端口的简称,是STM32可控制的引脚。GPIO的引脚与外部硬件设备连接,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。STM32F103ZET6芯片...

推荐文章

热门文章

相关标签