您的位置:首页 > 博客中心 > 网络系统 >

宋宝华: 聊一聊进程深度睡眠的TASK_KILLABLE这个状态

时间:2022-04-03 16:17

原创 宋宝华 Linux阅码场 3月11日


众所周知,Linux的进程睡眠有两种常规状态:

  • TASK_INTERRUPTIBLE(浅度睡眠):可以被等待的资源唤醒,也能被signal唤醒;
  • TASK_UNINTERRUPTIBLE(深度睡眠):可以被等待的资源唤醒,但是不能被signal唤醒。
    简单来说,深度睡眠的进程必须等待资源来了才能醒,在此之前,甚至你给它发任何的信号,它都不可能醒来。

浅度睡眠的进程,则可以被信号唤醒,对于常规的键盘、串口、触摸屏等等这些I/O设备,显然符合此类模型。所以Linux内核的代码里面经常看到这样的代码模板,笔者在《Linux设备驱动开发详解》一书中也花了大篇幅解释如下模板:
技术图片

调用_ _set_current_state(TASK_INTERRUPTIBLE)并schedule()出去的进程,醒来第一件事往往就是通过signal_pending(current)查看信号是否存在,如果存在,就跳出去处理信号,无需等待I/O的完成(大不了信号处理完了再重新read)。

TASK_INTERRUPTIBLE看起来很理想,不至于在I/O没完成的时候,连CTRL+C都不响应(当然也不会响应其他SIGIO、SIGUSR1等信号)。
那么,有的童鞋就会问,既然浅度睡眠这么好,那么还要TASK_UNINTERRUPTIBLE这种完全不响应信号的深度睡眠干什么?
正在读本文的你,可能都有过这样的悲催经历,在NFS文件系统上面运行程序,但是NFS服务器挂了,你怎么都ctrl + c不掉那个进程,因为它就是个深度睡眠的场景。你徘徊,你迷茫,你问能不能直接都改为TASK_INTERRUPTIBLE,彻底删除TASK_UNINTERRUPTIBLE呢?

对此,祖师爷Linus的答复是:不可能。请看他2002年的邮件:

技术图片

对于磁盘读等场景,如果读还没完成,就跳出去响应信号,application可能break,所以深度睡眠必须存在是一个客观的冷酷的现实(code fact)。

祖师爷还有更猛的一锤定音:

技术图片

但是,如果application break已经不再重要?如果干脆就是一个致命的信号,本身就是杀死应用的信号(SIGKILL),那么application break这个就显得无关紧要了,因为我们本身就不打算继续玩下去了!这样就使得深度睡眠的进程,还可以被杀死,妈妈再也不用担心NFS服务器挂了后,我痛苦,我孤独,我精分了!

Linux因此推出了一个特殊的深度睡眠状态,叫做

  • TASK_KILLABLE(可杀的深度睡眠):可以被等到的资源唤醒,不能被常规信号唤醒,但是可以被致命信号唤醒。
    TASK_KILLABLE状态的定义是:

#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)


所以它显然是属于TASK_UNINTERRUPTIBLE的,只是可以被TASK_WAKEKILL。
什么叫致命信号呢?talk is cheap,show me the code。
![](https://s4.51cto.com/images/blog/202011/27/30b611b5cf4a8369967d84b49c903711.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
所以,足够致命的信号就是SIGKILL。SIGKILL何许人也,就是传说中的信号9,无法阻挡无法被应用覆盖的终极杀器:
![](https://s4.51cto.com/images/blog/202011/27/92bad028708841d3671efa5cb8c3f8a0.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
仅仅从这个代码可以看出来,只有信号9才属于fatal signals。那么是不是只有信号9,才可以杀死TASK_KILLABLE的进程,信号2(CTRL+C)是否无能为力呢?
猜想再多,不如玩一个真实的代码,我们下面来改造下,把globalfifo.c的read改造为TASK_KILLABLE。
![](https://s4.51cto.com/images/blog/202011/27/bde30ec6dab3dfb8216a0532c5bbbaee.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
加载这个driver后,我们来读取它:

insmod globalfifo.ko

insmod globalfifo-dev.ko

cat /dev/globalfifo

这个时候,我们ps命令看一下,可以清楚到看到cat进程处于D状态:

root 7658 0.0 0.0 16800 752 pts/1 D+ 19:21 0:00 cat /dev/globalfifo


从前面的代码可以看出,CTRL+C是不应该可以杀死这个cat进程的,因为它不是SIGKILL。但是我们来实际测试一下:

cat /dev/globalfifo

^C
#

实际却是可以杀死!!!
我们查看一下我们加的那个内核打印代码,看一下signal pending的情况:

dmesg

[ 4670.082548] wake-up by fatal signal 100

明明我们发的是信号2,但是被置上的就是信号9(0x100的1对应SIGKILL的位)。这里发生了神奇的化学反应!!!
**这踏马到底是怎么回事**?这说明kernel把其他的可能杀死这个进程的信号,譬如SIGINT,也转化为了致命的SIGKILL信号。我们现在把代码改一行,要求kernel不要把SIGINT转换为SIGKILL:
![](https://s4.51cto.com/images/blog/202011/27/474aebb33128a1e6d4979913b15fe83e.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
这个时候,我们用CTRL+C就杀不死它了:

cat /dev/globalfifo

^C^C^C^C

但是它还是可以被9杀死:

$ sudo kill -9 8792



看看allow_signal()的代码:

![](https://s4.51cto.com/images/blog/202011/27/e2986c0855b88eb27728d20e1772fe1a.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
(END)

本类排行

今日推荐

热门手游