面试题:什么是孤儿进程,什么是僵尸进程?如何解决僵尸进程?
针对该面试题,我在这里谈谈我自己的理解。
子进程和fork( )系统调用
要想了解孤儿进程和僵尸进程,我们首先需要了解子进程这个概念。
- 进程在执行期间,可以通过fork( )系统调用来创建一个属于自己的子进程。这时,称调用fork的进程为父进程,由fork创建的进程为该父进程的子进程。一般而言,父子进程共享代码段,但对于数据段、栈段等其他资源,父进程在调用fork函数时会将该部分资源完全复制到子进程中去,父子进程对该部分的资源并不共享。
- 创建完子进程后,父进程从fork( )的返回点继续执行,而子进程也是从fork( )的返回点开始执行。一般而言,要么先执行子进程,要么父子进程并行执行(这取决于操作系统设计者的设计)。
- 父进程通过调用wait( )系统调用来将其子进程回收释放。
了解了fork( )系统调用和子进程的概念后,我们可以来谈谈什么是孤儿进程,什么是僵尸进程。
孤儿进程
其父进程已经终止,但父进程没有调用wait( )将其回收,那么该进程为孤儿进程。
僵尸进程
进程已经终止,但其父进程仍未调用wait( )将其回收,则该进程为僵尸进程(此时父进程并没有终止)。一般而言,僵尸进程只是短暂存在,当其被父进程回收释放时,那么该进程便不再存在。
- 孤儿进程与僵尸进程的共同点是进程都是子进程,且都已执行完成,但仍未释放资源;区别在于其父进程是否已经终止。
- 僵尸进程有可能会变为孤儿进程。
如何处理僵尸进程
处理僵尸进程,实际上就是父进程调用wait( )系统调用回收执行完的子进程的过程。
父进程在执行完成后都会通过调用exit( )系统调用来对自己进行释放回收。
在每一个进程的PCB中都存储了进程对应的状态,当进程执行完毕时,我们会将其状态设置为DEAD状态。
因此,僵尸进程可以通过下方方法进行处理:
- 当父进程执行完成后,调用exit( )前,首先调用wait( )来回收已执行完成的子进程。
- wait( )的实现规则为: 1)在wait函数中,它会一直循环查找调用wait系统调用的这一进程的子进程(每一个父进程都会有一个子进程队列,查找时顺序查找该队列)。如果所有进程都查找了一遍都没有当前进程的子进程,说明该进程已经不存在子进程,那么我们直接返回,结束函数;
3)如果在查找的过程中发现了一个状态为DEAD的子进程,那么这就是我们要释放的子进程,我们将其释放;
wait( )的实现代码,大家可以简略了解下大致的实现过程:
int ProgramManager::wait(int *retval)
{
PCB *child;
ListItem *item;
bool interrupt, flag;
while (true)
{
interrupt = interruptManager.getInterruptStatus();
interruptManager.disableInterrupt();
item = this->allPrograms.head.next;
//查找子进程
flag = true;
while (item)
{
child = ListItem2PCB(item, tagInAllList);
if (child->parentPid == this->running->pid)
{
flag = false;
if (child->status == ProgramStatus::DEAD)
{
break;
}
}
item = item->next;
}
if (item) // 找到一个可回收的子进程
{
if (retval)
{
*retval = child->retValue;
}
int pid = child->pid;
releasePCB(child);
interruptManager.setInterruptStatus(interrupt);
return pid;
}
else
{
if (flag) // 子进程已返回
{
interruptManager.setInterruptStatus(interrupt);
return -1;
}
else // 存在子进程,但子进程的状态不为DEAD
{
interruptManager.setInterruptStatus(interrupt);
schedule();
}
}
}
} 如何处理孤儿进程
僵尸进程的处理如上所述,那么对于孤儿进程,我们又应该如何对其进行回收呢?
在这里我们需要知道,父进程是如何通过调用exit( )来回收自身的。
实际上,当父进程执行结束后(亦或者是时间片用完),会立即调用schedule函数来将就绪队列中的下一个进程换上cpu。而在schedule函数中,首先判断当前进程是否已执行完毕,即检查状态是否为DEAD,如果为DEAD则会调用exit( )将其回收释放,然后再换上就绪队列中的下一个进程。
据此,我们可以设置一个专门存放状态为DEAD的进程的队列,通过在schedule函数中回收该队列的进程来实现对孤儿进程的回收。
