进程控制、孤儿进程和僵尸进程
一、进程控制
1、进程标识
1、操作系统里每打开一个进程都会创建一个进程ID,这是唯一标识进程的编号,即PID。
2、PID 在任何时刻都是唯一的,但是可以重用。当进程终止并被回收以后,其 PID 就会被系统回收
3、进程的 PID 由系统内核根据延迟重用算法生成,以确保新进程的 PID 不同于最近终止进程的 PID。
4、进程PID的最大值是有限的(因系统的不同而不同),需要及时回收
2、特殊进程
0 号进程:通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,所有进程的根进程,它并不执行任何磁盘上的程序,因此也被称为系统进程。
1 号进程:通常是 init 进程,在自举过程结束时由内核调用。
2 号进程:页守护进程:负责虚拟内存系统的分页操作
3、进程相关函数
1、创建新进程(子进程)--fork()
当调用fork()时,将执行以下动作:
1、向系统申请一个新PID
2、创建子进程,复制父进程的PCB,获得父进程的数据空间、堆、栈等资源的副本(但不是共享资源)
3、在父进程中返回子进程的PID,在子进程中返回0
执行完以上动作后,父进程和子进程便开始并发执行了。
注意:fork()函数可能有三种不同的返回值,并且调用一次,会返回两个值:
1、返回0,表示当前在子进程中运行
2、返回创建子进程的PID,表示当前在父进程中运行
3、返回一个负数,表示创建进程失败
为什么调用fork()一次会返回两个不同的值?
调用fork()函数之前,只有一个进程在运行(父进程),调用之后有两个进程在运行,分别是父进程和子进程,注意,这个时候父进程和子进程都处于fork()函数之后的位置,不同的是父进程调用了fork()函数创建了子进程,但是子进程还没有调用fork()函数创建自己的子进程,所以这两个进程执行下去会返回不同的值
用代码举个例子
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main () { pid_t fpid; //fpid表示fork函数返回的值 int count=0; fpid=fork(); //假设这里有一个断点,父进程调用了fork()函数产生了子进程, //但是子进程没有调用产生自己的子进程,所以父、子进程往下运行时会有不同的返回值 if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am the child process, my process id is %d/n",getpid()); printf("我是爹的儿子/n");//对某些人来说中文看着更直白。 count++; } else { printf("i am the parent process, my process id is %d/n",getpid()); printf("我是孩子他爹/n"); count++; } printf("统计结果是: %d/n",count); return 0; }
同时,根据父进程和子进程返回的值不同,我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
2、exec()函数:加载新程序取代当前运行进程,pid保持不变
3、exit()函数:程序退出函数。返回0表示正常退出,其它退出异常
4、wait()函数:
功能(解决僵尸进程的方法)
父进程一旦调用了wait就立即阻塞自己,由wait自动分析当前进程的某个子进程是否已经正常退出,如果让它找到了这样一个已经变成僵尸的子进程(异常),wait就会收集这个子进程的信息,并把它彻底销毁后返回,并唤醒父进程;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
二、孤儿进程和僵尸进程(进程终止最终是资源的回收)
1、孤儿进程:
父进程早于子进程退出时候子进程还在运行,子进程会成为孤儿进程。linux会对孤儿进程的处理,把孤儿进程的父进程设为1,也就是由init进程来托管。init进程负责子进程退出后的善后清理工作。
2、僵尸进程:
子进程退出后留下的进程信息没有被收集,会导致占用的进程控制块PCB不被释放,形成僵尸进程。进程已经死去,但是进程资源没有被释放掉。
3、问题及危害
1、孤儿进程的资源收集由init进程负责,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会处理它的一切善后工作。因此孤儿进程并不会有什么危害。
2、如果系统中存在大量的僵尸进程,他们的进程号就会一直被占用,但是系统所能使用的进程号是有限的,系统将因为没有可用的进程号而导致系统不能产生新的进程.。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个
子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时
处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状程的身份对僵尸状态的子进程进行处理。
4、怎样避免僵尸进程
1、通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
2、父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
3、通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
4、杀死父进程。 如果僵尸进程的父进程还存在,找到这个父进程,kill掉它。这样就会变成3的情况,init会负责善后工作。
部分转载自https://www.cnblogs.com/sinpo828/p/10913249.html