深度睡眠与浅度睡眠

众所周知,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,所以深度睡眠必须存在是一个客观的残酷的现实(cold fact)

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

祖师爷没有点明为什么磁盘读的时候不应该跑用户态去执行信号处理函数,为什么引发application break。我理解其中的一个场景如下:Linux对于代码段、数据段、堆和栈都通常依赖demanding page在发生page fault的时候从磁盘swap进来的,从而导致磁盘读的行为。在这个过程中,如果我们执行浅度睡眠并响应信号而跳过去执行应用程序代码段设置的信号处理函数,则此信号处理函数的执行可能再次因为swap in的需求引发进一步的磁盘读,造成double page fault的场景。磁盘的读很大程度上不一定是read系统调用引发的,考虑到代码段、数据段、堆和栈的往往是发生了page fault后才去从磁盘swap进来。磁盘有其特殊性,在Linux这样的操作系统里面,磁盘某种意义上是"内存"。

但是,如果响应信号后,哪怕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。

所以,足够致命的信号就是SIGKILL。SIGKILL何许人也,就是传说中的信号9,无法阻挡无法被应用覆盖的终极杀器:

仅仅从这个代码可以看出来,只有信号9才属于fatal signals。那么是不是只有信号9,才可以杀死TASK_KILLABLE的进程,信号2(CTRL+C)是否无能为力呢?

猜想再多,不如玩一个真实的代码,我们下面来改造下,把globalfifo.c的read改造为TASK_KILLABLE。

加载这个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的位)。这里发生了神奇的化学反应!!!

这踏马到底是怎么回事?不是一定致命的信号2,为什么转化为了最最致命的信号9呢?

信号2是如何转化为信号9的?

这个时候我们重点关注kernel/signal.c内核代码中的complete_signal()函数:

实际上,当Linux内核发现进程(线程组)收到了一个sig_fatal()的信号的时候,会给这个进程中的每个线程人为地插入一个SIGKILL信号,这个从while_each_thread循环可以看出。

sig_fatal()和fatal_signal_pending()不是一个概念。我们看看sig_fatal()的代码:

基本上,一个信号的行为如果是缺省的(SIG_DFL),它又没有被忽略,那么它就是满足sig_fatal()条件的。

如下图,流程大概是:

当我们给进程P1(假设内部有线程T1和T2,那么每个线程会有个tast_struct)发送信号2,这个2会填入T1和T2共享的进程级signal pending,由于我们对信号2没有绑定和忽略而是采用了默认行为,于是导致sig_fatal()条件满足。内核就会在T1和T2的各自独占的一份signal pending里面填入9,从而刺激fatal_signal_pending()条件的满足。

有的童鞋说,如果我的进程只有一个线程呢?那去掉上图中的T2以及T2独占的signal pending框即可:

为了进行验证,我们不再使用cat。而是自己写个app去访问globalfifo,而在此app里面修改信号2的行为:

我们通过signal(2, sigint)给信号2绑定了信号处理函数sigint(),这个时候read(fd, buf, 10)引发TASK_KILLABLE睡眠,我们无论怎么kill -2,都杀不死上面这个app。2到9的转化过程不再发生。

下面的修改也可达到类似效果:

上面我们是把信号2进行了SIG_IGN的忽略处理。

不仅信号2是这样的,其他的很多信号也类似,比如SIGHUP、SIGIO、SIGTERM、SIGPIPE等都可以在没有绑定和忽略的情况下,转化为信号9。但是SIGCHLD显然不一样,因为SIGCHLD默认就是忽略的。

(END)

Linux阅码场原创精华文章汇总

更多精彩,尽在"Linux阅码场",扫描下方二维码关注

点一点右下角”在看”,为阅码场打Call~

宋宝华:可以杀死的深度睡眠TASK_KILLABLE状态(最透彻一篇)相关推荐

  1. 可以杀死的深度睡眠TASK_KILLABLE状态

    深度睡眠与浅度睡眠 众所周知,Linux的进程睡眠有两种常规状态: TASK_INTERRUPTIBLE(浅度睡眠):可以被等待的资源唤醒,也能被signal唤醒: TASK_UNINTERRUPTI ...

  2. 宋宝华:评Linux 5.13内核

    目录 Misc cgroup Landlock安全模块 系统调用的堆栈随机化 printk无锁ringbuffer的进一步优化 BPF可调用内核函数 公共的IO PAGE Fault支持 Linux ...

  3. linux 没有windows.h头文件_宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)...

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...

  4. 宋宝华_2010年11-12月Linux驱动和内核讲座PPT下载

        12月29日,宋宝华老师在线讲座(按键和LCD驱动) cloudquan 2010-12-20 2/146 heyan0208 3 天前 00:37     宋宝华_2010年12月11日_& ...

  5. 宋宝华:LEP(Linux易用剖析器) 是什么,为什么以及怎么办(2)

    LEP(LINUX EASY PROFILING) 是Linuxer之LEP项目组(Barry Song,Mac Xu,陈松等以及陈莉君教授/西邮Linux 3+1实验室)正在致力于打造的一个开源项目 ...

  6. 宋宝华:论一切都是文件之匿名inode

    01 唯有文件得人心 当一个女生让你替她抓100只萤火虫,她一定不是为了折磨你,而是因为她爱上了你.当你们之间经历了无数的恩恩怨怨和彼此伤害,她再次让你替她抓100只萤火虫,那一定是因为她还爱着你. ...

  7. linux pdf 宋宝华,51CTO博客-专业IT技术博客创作平台-技术成就梦想

    原创 宋宝华 Linux阅码场 2018-04-10 前言 网上关于BIO和块设备读写流程的文章何止千万,但是能够让你彻底读懂读明白的文章实在难找,可以说是越读越糊涂! 我曾经跨过山和大海 也穿过人山 ...

  8. 宋宝华: 用off-cpu火焰图进行Linux性能分析

    在<宋宝华:火焰图:全局视野的Linux性能剖析>一文中,我们主要看了on-cpu火焰图,理解了系统的CPU的走向的分析.但是,很多时候,单纯地看on-cpu的情况(什么代码在耗费CPU) ...

  9. 宋宝华: 关于DMA ZONE和dma alloc coherent若干误解的彻底澄清

    原创 宋宝华 Linux阅码场 2018-01-22 作者简介 宋宝华,他有10几年的Linux开发经验.他长期在大型企业担任一线工程师和系统架构师,编写大量的Linux代码,并负责在gerrit上r ...

最新文章

  1. TPAMI 2022 | 国防科大等高校提出光场解耦机制,在超分辨与视差估计任务上取得优异性能...
  2. ORACLE的直方图的一些试验
  3. java编写存钱_用Java编写一个简单的存款
  4. 《深入浅出Nodejs》笔记——模块机制(2)
  5. ansible模块---续
  6. 宝塔网设置伪静态进行隐藏php后缀名,nextcloud宝塔面板nginx伪静态-去除index.php
  7. UNIX网络编程——sockatmark函数
  8. vivo9.0系统设备最简单激活XPOSED框架的步骤
  9. intellig idea中jsp或html数据没有自动保存和更换字体
  10. 好久没敲代码了(强行补上今天的博客。。。)
  11. python编程 迷你世界_迷你编程电脑版|迷你世界迷你编程下载 v1.0官方版 - 绿点软件站...
  12. 开源论坛之discourse搭建
  13. 链表的应用 —— 多项式运算(加法+乘法)
  14. 电脑ip地址设置_关于路由器动态IP如何设置教程
  15. centos安装activitymq
  16. 计算机无线网络怎么连接,怎么连接无线网络 电脑怎么添加无线网络连接
  17. 微信小程序开发者文档教程,从入门到精通 (附超过100个完整项目源代码、文档)
  18. [Win32] 打字游戏MFC版
  19. html输入后面消失,excel输入减号为什么会消失
  20. VSCode Python运行环境配置

热门文章

  1. windows下python实现按键退出程序
  2. java 整数除法整数_Java:整数除法
  3. 转炉流程轴承钢技术的开发是轴承钢的冶炼时间缩短
  4. 好用的截图工具Snipaste (windows/mac都可以用)
  5. Jdbc--2--jdbc的使用,数据库schema和catalog介绍
  6. python255多少钱_CS255留学生作业代做、代写Python语言作业、Timetabling作业代做、Python编程设计作业调试...
  7. 做个小程序商城大约多少钱_分享小程序商城制作教程
  8. 杰里之充电仓耳机负载检测电路电源域配置【篇】
  9. 结婚礼服时装秀 机器人新娘上阵
  10. 2022-07-09 第九小组 韩文清