微信公众号:LinuGo,欢送关注获取更多详情
前言
过程就是运行起来的一个程序,然而过程并不局限于执行起来的代码,他的作用范畴还有很多,如存放数据的内存地址空间,执行线程,关上的文件,挂起的信号,处理器状态等。
过程在创立的时候开始存活,Linux零碎会调用fork()办法复制一个现有过程来创立一个全新的过程,新产生的过程为子过程,创建者过程为父过程。当程序完结运行时,通过exit()零碎调用退出执行,该过程占用的资源包含内存空间,线程等被开释掉。
过程家族树
过程都是由其余过程创立进去的,每个过程都有本人的PID(过程标识号),在Linux零碎的过程之间存在一个继承关系,所有的过程都是init过程的后辈。能够通过pstree命令查看到过程的族谱。零碎中的每个过程必定会有一个父过程,能够在/proc文件系统中看到过程对应的父过程号,也能够通过ps -ef命令。
Linux过程波及过程的所有操作都围绕过程描述符开展,就是task_struct构造体。构造体蕴含好几百个字段,该构造体能够残缺的形容一个正在执行的程序,如虚拟地址空间的信息,关上的文件,过程的执行状态和信息,命名空间,身份信息等。
在每个过程的task_struct中,用字段parent示意该过程的父过程,sibling代表兄弟过程链表,children代表子过程链表,实践上能够通过每一个过程获取任何过程。
//构造体中相干字段
struct task_struct{
...
struct task_struct *parent; /* 父过程 */
struct list_head children; /* 子过程链表 */
struct list_head sibling; /* 连贯到父过程的子过程链表,兄弟过程 */
....
}
过程终结
当过程终结时候,零碎须要开释他所占有的所有资源。过程通过exit()零碎调用完结过程,这个调用可能是来自过程外部的exit(),也可能来自内部的信号。在完结时候,该过程会应用该零碎调用开释本人的空间,包含援用的文件,内存描述符,还会给本人的父过程发送信号,给本人的子过程寻找一个父过程等操作。
调用完结后,此时该过程并没有齐全从零碎上隐没,过程的过程描述符仍然存在于零碎中,存在的惟一目标就是向父过程提供信息。
与自然规律相同,过程的收尾工作总是由该过程的父过程来做的,父过程会通过wait()零碎调用来开释该过程最初残余的过程标识符,slab缓存等,该调用会阻塞以后父过程,直到某个子过程退出。
对于过程退出,能够联合看一下Linux bash怎么做的。
- 首先ps命令获取bash的过程PID
- 再开一个bash页面,查看上个bash过程的零碎调用
$ sudo strace -p <Pid>
能够看到该过程的零碎调用曾经被捕捉。
- 输出一条命令回车,察看零碎调用状况
这里一条命令相当于一个bash的子过程,这里以tail为例。能够看到阻塞到该零碎调用,也就是在期待回收子过程。
应用Ctrl+C完结过程tail时候,返回了tail过程的Pid。
理解了这些后,接下来了解僵尸过程和孤儿过程就很容易了。上面通过案例解说一下。
僵尸过程
当过程exit()退出之后,他的父过程没有通过wait()零碎调用回收他的过程描述符的信息,该过程会持续停留在零碎的过程表中,占用内核资源,这样的过程就是僵尸过程。
接下来通过一个demo结构一个僵尸过程。
#include <unistd.h>
#include <stdio.h>
int main ()
{
/*fpid示意fork函数返回的值,fork会返回两次,
一次是父过程,返回值是子过程的Pid,在子过程会返回0*/
pid_t fpid;
fpid=fork();//fork后会呈现两个分支执行上面的代码,一个父过程,一个新的子过程
if (fpid < 0)
printf("fork error!");
else if (fpid == 0) { //
printf("child id is %dn",getpid());
sleep(30);//睡眠30s,在父过程之前退出
printf("child finally...");
}
else { //父过程
printf("parent id is %dn",getpid());
sleep(60);
printf("parend finally...");
}
}
- 编译并运行这段代码。
$ gcc -o corpse corpse.c
$ ./corpose
- pstree查看过程树
$ pstree -p 9106
- 期待子过程退出,查看子过程的状态
$ cat /proc/<Pid>/status
等到父过程退出之后,再来查看零碎,该僵尸过程在零碎中找不到了。
父过程退出,没有为子过程”收尸”,然而子过程也会一并退出,怎么做到的呢?这就波及到了孤儿过程。
孤儿过程
当一个过程正在运行时,他的父过程突然退出,此时该过程就是一个孤儿过程。作为一个过程,须要找到一个父过程,否则这种过程在退出之后没人回收他的过程描述符,空耗内存。此时该过程会找到一个父过程,如果本人所在的过程组没人收养,那就作为init过程的子过程。
结构一个测试代码:
#include <unistd.h>
#include <stdio.h>
int main ()
{
/*fpid示意fork函数返回的值,fork会返回两次,
一次是父过程,返回值是子过程的Pid,在子过程会返回0*/
pid_t fpid;
fpid=fork();//fork后会呈现两个分支执行上面的代码,一个父过程,一个新的子过程
if (fpid < 0)
printf("fork error!");
else if (fpid == 0) { //
printf("child id is %dn",getpid());
sleep(100);
}
else { //父过程
printf("parent id is %dn",getpid());
sleep(30);//睡眠30s,在子过程之前退出
printf("parend finally...");
}
}
- 编译并运行
$gcc -o orphan orphan.c
$./orphan
- pstree查看过程树
- 等到父过程退出再看过程信息
能够看到看到该过程的父过程变为1,也就是init过程。
Init过程会为每一个子过程应用wait零碎调用,确保不会产生僵尸过程。这里的wait零碎调用指的是waitpid(),会传入一个要期待的过程Pid,期待的指定过程,而不阻塞以后过程去期待。
等到该过程退出后,该过程的过程描述符等信息会被init过程回收,不会造成僵尸过程。
回到上一个僵尸过程的案例中,父过程退出掉后,该过程会找到init作为父过程,init过程针对给过程调用wait()零碎调用回收了该子过程。这就是查问不到这个过程的起因。
处理形式
孤儿过程会由init过程收养作为子过程,所以不会有什么危害;僵尸过程会占用过程号,以及未回收的文件描述符占用空间,如果产生大量的僵尸过程,将会导致系统无奈调配过程号,阐明父过程的代码编写有问题。
$ ps -aux|grep Z
在现实状况下,能够通过kill命令将过程杀死该过程的父过程来完结僵尸过程。当然也要联合具体场景来看待。
微信关注LinuGo回复书名即可获取下方书籍pdf版
参考书籍
- 《Linux内核设计与实现》第三章.过程治理
- 《深刻Linux内核与架构》第二章.过程治理与调度
- 《后盾开发核心技术与利用实现》第十章.过程