前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux并发(进程的生老病死)

Linux并发(进程的生老病死)

作者头像
用户2617681
发布2019-08-08 15:25:22
1.2K0
发布2019-08-08 15:25:22
举报
文章被收录于专栏:秘籍酷秘籍酷

Linux中的进程有生老病死,就跟人一样,我们尤其关注其死,因为进程死后如果不处理,它会变成僵尸!

拓展:

说进程是动态的活动的实体,指的是进程会有很多种运行状态,一会儿睡眠、一会儿暂停、一会儿又继续执行。下图给出Linux进程从被创建(生)到被回收(死)的全部状态,以及这些状态发生转换时的条件:

结合该图,一起理一理进程从生到死的过程:

1,从“蛋生”可以看到,一个进程的诞生,是从其父进程调用fork( )开始的。

2,进程刚被创建出来的时候,处于TASK_RUNNING状态,从图中看到,处于该状态的进程可以是正在进程等待队列中排队,也可以占用CPU正在运行,我们习惯上称前者为“就绪态”,称后者为“执行态”。当进程状态为TASK_RUNNING并且占用CPU时才是真正的运行。

3,刚被创建的进程都处于“就绪”状态,等待系统调度,内核中的函数sched()被称为调度器,他会根据各种参数来选择一个等待的进程去占用CPU。进程占用CPU之后就可以真正运行了,运行时间有个限定,比如20毫秒,这段时间被称为time slice,即“时间片”的概念。

换句话说:进程跟人一样,从来都没有什么平等可言,有贵族就有屌丝,他们要处理的事情有不同的轻重缓急之分。

4,进程处于“执行态”时,可能会由于某些资源的不可得而被置为“睡眠态/挂起态”,比如进程要读取一个管道文件数据而管道为空,或者进程要获得一个锁资源而当前锁不可获取,或者干脆进程自己调用sleep( )来强制自己挂起,这些情况下进程的状态都会变成TASK_INTERRUPIBLE或者TASK_UNINTERRUPIBLE,他们的区别是一般后者跟某些硬件设置相关,在睡眠期间不能响应信号,因此TASK_UNINTERRUPIBLE的状态也被称为深度睡眠,相应地TASK_INTERRUPIBLE期间进程是可以响应信号的。当进程所等待的资源变得可获取时,又会被系统置为TASK_RUNNING状态重新就绪排队。

5,当进程收到SIGSTOP或者SIGTSTP中的其中一个信号时,状态会被置为TASK_STOPPED,此时被称为“暂停态”,该状态下的进程不再参与调度,但系统资源不释放,直到收到SIGCONT信号后被重新置为就绪态。当进程被追踪时(典型情况是被调试器调戏时),收到任何信号状态都会被置为TASK_TRACED,该状态跟暂停态是一样的,一直要等到SIGCONT才会重新参与系统进程调度。

6,运行的进程跟人一样,迟早都会死掉。进程的死亡可以有多种方式,可以是寿终正寝的正常退出,也可以是被异常杀死。比如上图中,在main函数内return或者调用exit(),包括在最后线程调用pthread_exit()都是正常退出,而受到致命信号死掉的情况则是异常死亡,不管怎么死,最后内核都会调用一个叫do_exit()的函数来使得进程的状态变成所谓的僵尸态EXIT_ZOMBIE,单词ZOMBIE对于玩过“植物大战僵尸”的童鞋都不会陌生,这里的“僵尸”指的是进程的PCB(进程控制块)。

为什么一个进程的死掉之后还要把尸体留下呢?因为进程在退出的时候,将其退出信息都封存在他的尸体里面了,比如如果他正常退出,那退出值是多少呢?如果被信号杀死?那么是哪个信号呢?这些“死亡信息”都被一一封存在该进程的PCB当中,好让别人可以清楚地知道:我是怎么死的。

那谁会关心他是怎么死的呢?答案是他的父进程,他的父进程之所以要创建他,很大的原因是要让这个孩子去干某一件事情,现在这个孩子已死,那事情办得如何,孩子是否需要有个交代?但他又死掉了,所以之后将这些“死亡信息”封存在自己的尸体里面,等着父进程去查看,比如父子进程可以约定:如果事情办成了退出值为0,如果权限不足退出值为1,如果内存不够退出值为2等等等等。父进程可以随时查看一个已经死去的孩子的事情究竟办得如何。可以看到,在工业社会中,哪怕是进程间的协作,也充满了契约精神。

7,父进程调用wait()/waitpid()来查看孩子的“死亡信息”,顺便做一件非常重要的事情:将该孩子的状态置为EXIT_DEAD,即死亡态,因为处于这个状态的进程的PCB才能被系统回收。由此可见,父进程应该尽职尽责地及时调用wait()/waitpid(),否则系统会充满越来越多的“僵尸”!

问题是,如何保证父进程一定要及时地调用wait()/waitpid()从而避免僵尸进程泛滥呢?答案是不能,因为父进程也许需要做别的事情没空去帮那些死去的孩子收尸,甚至那些孩子在变成僵尸的时候,他的父进程已经先他而去了!

后一种情况其实比较容易解决:如果一个进程的父进程退出,那么祖先进程init(该进程是系统第一个运行的进程,他的PCB是从内核的启动镜像文件中直接加载的,不需要别的进程fork()出来,因此他是无父无母的石头爆出来的,系统中的所有其他进程都是他的后代)将会收养(adopt)这些孤儿进程。换句话说:Linux系统保证任何一个进程(除了init)都有父进程,也许是其真正的生父,也许是其祖先init。

而前一种情况是:父进程有别的事情要干,不能随时执行wait()/waitpid()来确保回收僵尸资源。在这样的情形下,我们可以考虑使用信号异步通知机制,让一个孩子在变成僵尸的时候,给其父进程发一个信号,父进程接收到这个信号之后,在对其进行处理,在此之前想干嘛就干嘛,异步操作,大家happy。但是即便是这样也仍然存在问题:如果两个以上的孩子同时退出变僵尸,那么他们就会同时给其父进程发送相同的信号,而相同的信号将会被淹没。怎么解决这个问题,请关注:

林老师的新书:《Linux环境编程图文指南》

书号:ISBN 978-7-121-28075-7

以上内容也是该书节选片段,欢迎查询订购。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 秘籍酷 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档