微信公众号:LinuGo,欢迎关注获取更多细节很多,比如存储数据的内存地址空间,执行线程,打开的文件,挂起的信号,处理器状态等。进程在创建时就开始存活。Linux系统会调用fork()方法复制一个已有的进程来创建一个全新的进程。新生成的进程是子进程,creator进程是父进程。当程序运行结束后,通过exit()系统调用退出执行,释放进程占用的资源,包括内存空间和线程。进程族树进程都是由其他进程创建的,每个进程都有自己的PID(进程标识号),Linux系统中进程之间存在继承关系,所有进程都是init进程的后代。您可以使用pstree命令查看进程的谱系。系统中的每个进程都必须有一个父进程。可以在/proc文件系统中看到进程对应的父进程号,也可以使用ps-ef命令。Linux进程所有与进程相关的操作都围绕进程描述符展开,也就是task_struct结构体。该结构包含数百个字段,可以完整描述一个正在执行的程序,如虚拟地址空间信息、打开的文件、进程执行状态和信息、命名空间、身份信息等。在每个进程的task_struct中,使用字段parent表示进程的父进程,sibling表示兄弟进程的链表,children表示子进程的链表。理论上,任何进程都可以通过每个进程得到。//结构体中的相关字段structtask_struct{...structtask_struct*parent;/*父进程*/structlist_headchildren;/*子进程列表*/structlist_headsibling;/*连接到父进程的子进程列表,兄弟进程*/....}进程终止当一个进程终止时,系统需要释放它占用的所有资源。进程通过exit()系统调用结束进程。此调用可能来自进程内部的exit()或来自外部信号。最后,进程会使用系统调用释放自己的空间,包括引用的文件、内存描述符,并向其父进程发送信号为其子进程寻找父进程。调用结束后,此时进程并没有完全从系统中消失,进程的进程描述符仍然存在于系统中,存在的唯一目的就是为父进程提供信息。与自然法则相反,进程的收尾工作总是由进程的父进程来完成。父进程会通过wait()系统调用释放进程最后剩余的进程标识符、slabcache等。这个调用会阻塞当前的父进程,直到其中一个子进程退出。关于进程退出,可以看看linuxbash是怎么做的。首先使用ps命令获取bash进程的PID,然后打开一个bash页面查看之前bash进程的系统调用。$sudostrace-p,可以看到这个进程的系统调用已经被捕获了。输入命令,回车,观察系统调用。这里一条命令相当于bash的一个子进程。这里我们以tail为例。可以看出系统调用是阻塞的,也就是等待回收子进程。使用Ctrl+C结束进程tail时,返回tail进程的Pid。考虑到这一点,就很容易理解僵尸进程和孤儿进程。我们通过一个案例来说明。僵尸进程当进程exit()退出时,其父进程不会通过wait()系统调用回收其进程描述符的信息,该进程将继续留在系统的进程表中,占用内核资源。这样的进程就是僵尸进程。接下来通过demo构造僵尸进程。#include#includeintmain(){/*fpid表示fork函数返回的值,fork会返回两次,一次为父进程,返回值为pid的子进程,在子进程中会返回0*/pid_tfpid;fpid=fork();//fork后会出现两个分支执行下面的代码,一个是父进程,一个是新的子进程if(fpid<0)printf("forkerror!");elseif(fpid==0){//printf("孩子id是%dn",getpid());sleep(30);//睡眠30s,在父进程之前退出printf("childfinally...");}else{//父进程printf("parentidis%dn",getpid());睡觉(60);printf("父母终于...");}}编译并运行这段代码。$gcc-ocorpsecorpse.c$./corposepstree查看进程树$pstree-p9106等待子进程退出,查看子进程状态$cat/proc//status等到父进程进程退出,再查看系统,系统中找不到僵尸进程。父进程退出并没有为子进程“收尸”,但是子进程也会一起退出。如何才能做到这一点?这涉及孤儿进程。孤儿进程当一个进程正在运行时,其父进程突然退出,此时该进程就是一个孤儿进程。作为一个进程,必须要找到一个父进程,否则进程退出后就没人会回收它的进程描述符,消耗内存。这时候进程就会找到一个父进程。如果没有人采用它所属的进程组,它就会成为init进程的子进程。构造一段测试代码:#include#includeintmain(){/*fpid表示fork函数返回的值,fork会返回两次,一次是父进程,一次是returnvalue为子进程Pid,子进程会返回0*/pid_tfpid;fpid=fork();//fork后会出现两个分支执行下面的代码,一个父进程和一个新的子进程if(fpid<0)printf("forkerror!");elseif(fpid==0){//printf("孩子id是%dn",getpid());睡觉(100);}else{//父进程printf("parentidis%dn",getpid());sleep(30);//睡眠30s,在子进程之前退出printf("parendfinally...");}}编译运行$gcc-oorphanorphan.c$./orphanpstree查看进程树等到父进程退出再查看进程信息,可以看到进程的父进程变成了1,这是init进程。init进程会对每个子进程使用wait系统调用来保证不会产生僵尸进程。这里的wait系统调用指的是waitpid(),会传入一个要等待的进程Pid,指定要等待的进程,不会阻塞当前等待的进程。进程退出后,进程的进程描述符等信息会被init进程回收,不会形成僵尸进程。回到之前的僵尸进程案例,父进程退出后,进程会找到init作为父进程,init进程回收子进程调用wait()系统调用给进程。这就是无法查询进程的原因。处置方法孤儿进程会被init进程收养为子进程,所以不会有危害;僵尸进程会占用进程号和未回收文件描述符占用的空间。如果产生大量僵尸进程,系统无法分配进程号,说明父进程的代码编写有问题。$ps-aux|grepZ理想情况下可以通过kill命令杀掉进程的父进程来结束僵尸进程。当然也要结合具体的场景来对待。微信关注LinuGo,回复标题即可获取下方图书pdf版。参考书籍《Linux内核设计与实现》第三章流程管理