微信公众号: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 内核与架构》第二章. 过程治理与调度
- 《后盾开发核心技术与利用实现》第十章. 过程