进程遗言前言本文主要介绍父子进程之间的关系,以及它们之间的交互和可能的状态,帮助大家理解父子进程之间的关系父子进程之间,以及它们之间的交互。ZombieprocessandorphanprocessZombieprocess在Unix操作系统和类Unix操作系统中,当子进程退出时,父进程可以从子进程获取子进程的退出信息,所以在类Unix操作系统中,只有父进程通过wait系统调用在子进程完全退出前读取子进程的退出状态信息。那么子进程执行完程序后(调用_exit系统调用后),父进程执行wait系统调用获取子进程退出状态信息前,这段时间进程的状态就是state僵尸进程。正式定义:在Unix或类Unix操作系统中,僵尸进程是指那些已经完成程序执行(完成_exit系统调用退出程序),但内核中存在属于该进程的进程表项的进程。这个入口的作用主要是让父进程读取子进程的退出状态信息(exitstatus)。下面我们举例详细分析这个退出状态的相关信息。一旦父进程通过wait系统调用读取完子进程的退出状态信息,僵尸进程的进程表项就会从进程表(processtable)中移除,进程彻底死亡(reaped).如果系统中有很多僵尸进程,而父进程没有使用wait系统调用获取子进程的退出状态,那么系统中就会有大量的内存没有被释放,会导致资源泄漏。下面是一个僵尸进程的例子,对应的代号是Z.c:#include#includeintmain(){printf("parentpid=%d\n",getpid());如果(fork()){而(1);}printf("子进程pid=%d\n",getpid());return0;}上述C语言对应的python代码如下:importosif__name__=="__main__":print(f"parentpid={os.getpid()}")pid=os.fork()ifpid!=0:#parentprocesswillneverexitwhileTrue:pass#childprocesswillexitprint(f"childprocesspid={os.getpid()}")现在执行上面的程序,结果如下:从上图可以看出,父进程一直在死循环运行,而子进程退出程序,现在应该是僵尸进程状态。而我们通过ps命令得到的进程状态的结果,根据进程号,子进程的状态为Z+,代表该进程是僵尸进程。这里简单说一下命令ps对进程状态的各种表示:STAT中字母含义表:表项D表示不可中断的睡眠操作,例如空闲线程R在内核正在执行的IO操作I或者就绪队列中的进程S可以被打断进入休眠状态,通常是等待事件触发T被其他进程发送的信号停止T被调试或者tracing中的Z表示processisazombieprocess<表示高优先级N表示低优先级L的页面位于内存中,也就是说这个页面不会被操作系统换出。在交换区中,s表示该进程是会话领导者。l为多线程程序+表示在前台进程组中,可以根据上表内容对应程序的状态,会发现子进程当前处于僵尸进程状态。Orphanprocess孤儿进程:当一个进程还在执行,但是它的父进程已经退出了,那么这个进程就变成了孤儿进程,然后就会被init进程(进程ID=1)“收养”,然后init的进程会调用wait系统调用来回收本进程的资源。下面是一个孤儿进程的例子,我们可以看到子进程的父进程的输出是什么:#include#include#includeintmain(){if(fork()){睡眠(1);//父进程退出exit(0);}while(1){printf("pid=%dparentpid=%d\n",getpid(),getppid());睡觉(1);}return0;}对应的python代码如下:importosimporttimeif__name__=="__main__":pid=os.fork()ifpid==0:whileTrue:print(f"pid={os.getpid()}parentpid={os.getppid()}")time.sleep(1)程序执行结果如下:可以看到子进程的父进程发生了变化。当父进程退出时,子进程进程的父进程成为init进程,进程号等于1wait系统调用waitpid及其参数分析上一篇主要讲了两个不同的进程,其中我们主要讲了wait系统调用对于僵尸进程的重要性。在linux中,与wait相关的系统调用主要有两个:pid_twaitpid(pid_tpid,int*wstatus,intoptions);pid_t等待(int*wstatus);其中wait系统调用是waitpid系统调用的一个特例,我们先解释一下waitit系统调用。以上两个系统调用主要用于等待子进程的状态变化,以及从子进程中获取额外的状态信息(statusinformation)。只有当子进程的状态发生变化时,wait系统调用才能返回。主要有以下几种状态:子进程结束。子进程被另一个进程发送的信号停止(SIGSTOP和SIGTSTP会导致进程挂起并停止执行)。停止运行的进程被信号唤醒继续执行(SIGCONT可以唤醒进程继续执行)。当子进程出现上述状态时,wait或waitpid系统调用会立即返回,否则wait或waitpid系统调用会一直阻塞。waitpid的几个参数:pid:pid<-1表示等待任意进程组中进程的子进程-pid。pid==-1表示等待任何子进程。pid==0表示等待子进程,这些子进程的进程组号(processgroupid)等于本进程(调用waitpid函数的进程)的进程号。pid>0表示等待进程号等于pid的子进程。options:WNOHANG:如果options等于这个值,表示如果子进程还没有执行完,直接返回,不等待。WUNTRACED:如果子进程被另一个进程发送的信号停止,等待函数也返回。WCONTINUED:如果子进程被另一个进程发送的信号(SIGCONT)恢复,等待函数也返回。根据上面的分析,waitpid(-1,&wstatus,0)==wait(&wstatus)wstatus:是我们传递给wait或者waitpid函数的一个指针,系统调用会把子进程的很多状态信息放入wstatus指针所指向的数据当中,我们可以使用下面的宏来判断一些信息。WIFEXITED(wstatus):如果子进程正常退出(使用exit、_exit或者直接从main函数返回),这行代码返回true。WEXITSTATUS(wstatus):这个宏主要返回程序退出的退出码。关于exitcode的内容可以参考这篇文章ShellDemystified-ProgramExitStatusCode。WIFSIGNALED(wstatus):表示子进程是否被其他进程发送,导致程序退出。WTERMSIG(wstatus):如果子进程是另一个发送信号导致程序退出的进程,我们可以使用这个宏来获取具体的信号值。WCOREDUMP(wstatus):表示子进程是否有coredump然后退出。WIFSTOPPED(wstatus):当子进程从另一个进程接收到导致程序挂起的信号时,该宏返回真。WSTOPSIG(wstatus):返回挂起信号的具体信号值。WIFCONTINUED(wstatus):如果子进程接收到SIGCONT信号以恢复程序执行,则返回true。例子解释下面是一个几乎综合了以上所有信息的例子。让我们仔细看看这个程序的输出:#include#include#include#include#includeintmain(intargc,char*argv[]){pid_tcpid,w;内部状态;cpid=fork();如果(cpid==-1){perror(“fork”);退出(退出失败);}if(cpid==0){/*child执行的代码*/printf("ChildPIDis%jd\n",(intmax_t)getpid());如果(argc==1)暂停();//子进程会在这里等待接收信号,直到信号处理函数返回/*Waitforsignals*/_exit(atoi(argv[1]));}else{/*父级执行的代码*/do{w=waitpid(cpid,&wstatus,WUNTRACED|WCONTINUED);如果(w==-1){perror("waitpid");exit(EXIT_FAILURE);//EXIT_FAILURE是一个等于1的宏}//程序是否正常退出if(WIFEXITED(wstatus)){printf("退出,status=%d\n",WEXITSTATUS(wstatus));}elseif(WIFSIGNALED(wstatus)){//是否被信号杀死printf("killedbysignal%d\n",WTERMSIG(wstatus));}elseif(WIFSTOPPED(wstatus)){//是否停止printf("stoppedbysignal%d\n",WSTOPSIG(wstatus));}elseif(WIFCONTINUED(wstatus)){//是否处于停止状态然后唤醒printf("continued\n");}//判断程序是正常退出还是信号退出。如果程序退出父进程,则退出while循环}while(!WIFEXITED(wstatus)&&!WIFSIGNALED(wstatus));退出(退出成功);//EXIT_SUCCESS是一个等于0的宏}}在上面的例子中,我们先在上层终端执行wait.out程序,然后在下层终端我们先给子进程发送一个SIGSTOP信号,先让进程stop,然后发送一个SIGCONT信号让进程继续执行,最后发送一个SIGKILL信号让子进程退出,可以看到我们上面说的宏操作都是一一实现的,信号和exit状态在父进程中被一一接受让我们演示另一个coredump示例:#include#include#include#includeintmain(){if(fork()){诠释;等待(&s);if(WCOREDUMP(s)){printf("核心转储为真\n");}}else{inta=*(int*)NULL;}}childthreaddereferenceNULL会造成segmentationfault(coredump),然后父进程收到子进程的exitstatus,然后判断status,是否是coredump导致的exit:我们判断exitcode父进程中的子进程,如果子进程退出的原因是coredump会打印出来,而在上面的程序输出中,我们可以看到程序是有输出的,所以父进程可以判断出子进程由于核心转储,进程退出了程序。从子进程中获取系统资源信息除了上述等待子进程退出的方法外,我们还可以获取子进程执行时的状态信息,比如子进程占用的最大内存空间运行时,进程有多少上下文切换等。主要有两个系统调用:pid_twait3(int*wstatus,intoptions,structrusage*rusage);pid_twait4(pid_tpid,int*wstatus,intoptions,structrusage*rusage);其中3和4代表相应的函数的参数个数。上面两个函数中,有一个比较重要的数据类型structrusage,我们来看看这个结构体的内容以及对应字段的含义:structrusage{structtimevalru_utime;/*userCPUtimeused*///程序在用户模式下使用了多少CPU时间structtimevalru_stime;/*systemCPUtimeused*///程序在内核模式下使用了多少CPU时间longru_maxrss;/*maximumresidentsetsize*///使用内存的峰值单位是kblongru_ixrss;/*整数共享内存大小*///longru_idrss暂时不用;/*整数非共享数据大小*///longru_isrss暂时不用;/*整数非共享栈大小*///暂时不用longru_minflt;/*pagereclaims(softpagefaults)*///没有IO操作时的pagefault次数longru_majflt;/*pagefaults(hardpagefaults)*///有IO操作时的pagefaultlongru_nswap的个数;/*swaps*///longru_inblock暂时不用;/*块输入操作*///文件系统写操作的数量longru_oublock;/*块输出操作*///文件系统读取操作的数量longru_msgsnd;/*已发送的IPC消息*///未使用longru_msgrcv;/*接收到的IPC消息*///Longru_nsignals暂时不用;/*接收到的信号*///Longru_nvcsw暂时不用;/*自愿上下文切换*///活动上下文切换次数longru_nivcsw;/*非自愿上下文切换*///非活动上下文切换的次数};下面通过一个例子来看看子进程执行时如何获取子进程的一些详细数据信息:下面是子进程的代码,我们在forkexecv加载如下程序后使用:#includeintmain(){printf("你好世界\n");return0;}父进程代码:#include#include#include#include#includeintmain(intargc,char*argv[]){if(fork()!=0){结构用法;//定义一个统计资源结构体intpid=wait4(-1,NULL,0,&usage);//传入这个结构的地址,让内核告诉相应的信息,指针指向where//打印内存使用峰值printf("pid=%dmemoryusagepeek=%ldkb\n",pid,usage.ru_maxrss);}else{execv("./getrusage.out",argv);}return0;}上面程序的执行结果如下图所示:可以看出我们得到了内存使用的峰值。其实我们还可以使用time命令来查看某个进程执行的时间从上面的结果我们可以看出使用time命令得到的结果和我们自己的程序得到的结果是一样的,这也从侧面验证了我们的程序。如果要达到上面的效果,需要注意使用绝对地址命令,因为时间是shell的保留字。综上所述,本文主要介绍了僵尸进程、孤儿进程、父进程从子进程获取进程退出信息,以及它们形成的原因,并通过实例进行验证。这部分知识练习比较紧密。希望大家有所收获!以上就是本文的全部内容,我是LeHung,我们下期再见!!!更多精彩内容合集可以访问项目:https://github.com/Chang-LeHu...关注公众号:一个没用的研究僧,学习更多计算机知识(Java,Python,计算机系统基础,算法和数据结构)知识。