僵尸进程
僵尸进程一般出现在子进程中。如果子进程先于父进程退出,父进程没有调用wait()/waitpid()函数等待子进程结束来回收子进程资源,此时子进程处于“僵尸状态”,占用进程号和系统资源。可以通“ps”命令查看是否存在僵尸进程, 带有“< defunct >”标识的就是僵尸进程。
引起原因
一个进程在调用exit()函数结束时,并没有真正的被销毁,部分占用的系统资源、进程号等并没有释放,系统中存在一个称为僵尸进程(zombie)的数据结构,该进程并不能参与调度,处于“僵尸状态”,仅仅在进程列表(processs table)中保留一个位置(slot),记录该进程的退出状态等信息。
退出的进程需要其父进程来回收资源(俗称“收尸”),如果其父进程没创建SIGCHLD信号处理函数调用wait()/waitpid()等待子进程结束,又没有显式忽略该信号,该进程就一直处于僵尸状态。如果此时父进程结束了,init进程自动会“接管”这个子进程,退出僵尸状态。但是如果父进程是一个无限循环执行,那么子进程就会一直保持僵尸状态。
人为“创建”一个僵尸进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int pid ;
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
if(pid == 0)
{
/* todo */
printf("Child process,PID is: %d,PPID is: %d.\n", getpid(), getppid());
_exit(0);
}
else if (pid > 0)
{
printf("Parent process,PID is: %d.\n", getpid());
for(;;)
{
sleep(1); /* 父进程无限循环 */
}
}
return 0 ;
}
人为故意产生僵尸进程,t1< defunc > (进程ID2363)为僵尸进程。
僵尸进程危害
僵尸进程依然占用部分系统资源,如进程号;如果系统产生大量僵尸进程,系统会因为分配不到进程号而导致不能创建新的进程,这种后果是灾难性的。
僵尸进程释放
僵尸进程使用“kill -9”命令是无法杀掉的,但可以杀掉(kill)其父进程来间接杀掉。父进程死后,僵尸进程成为"孤儿进程",init进程会接管孤儿进程并清理回收资源。如上面图中僵尸进程可以这样释放:
kill -9 2362
僵尸进程的避免
【1】父进程通过wait或者waitpid函数等待子进程结束;
【2】使用signal函数为SIGCHLD重写回调函数(handler),子进程结束后,父进程收到该信号,在信号handler中调用wait回收;
【3】如果父进程不关心子进程结束时间,可以使用signal(SIGCHLD、SIG_IGN等)通知内核,子进程结束后,由内核将其回收,而父进程则忽略该信号;
【4】特殊用法:fork两次,父进程fork一个子进程,子进程作为父进程再fork一 个子进程(孙进程?)然后退出,父进程wait子进程结束;子进程的子进程失去其父进程(第一个子进程),会被init进程接管,其结束后,被init进程回收,父进程无需再处理。
针对以上四种方式,编码例程:
【1】
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int pid ;
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
if(pid == 0)
{
/* todo */
printf("Child process,PID is: %d,PPID is: %d.\n", getpid(), getppid());
_exit(0);
}
else if (pid > 0)
{
printf("Parent process,PID is: %d.\n", getpid());
if (waitpid(pid, NULL, 0) < 0)
{
perror("waitpid");
return -1;
}
for(;;)
{
sleep(1);
}
}
return 0 ;
}
【2】
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void child_handler(int signo)
{
printf("Receive process child signal\n");
if(waitpid(-1, NULL, 0) < 0) /* 等待所有子进程,相当于wait() */
{
perror("waitpid");
}
}
int main(int argc, char** argv)
{
int pid ;
if(signal(SIGCHLD, child_handler) == SIG_ERR) /* 安装 SIGCHLD信号 */
{
perror("signal");
return -1;
}
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
if(pid == 0)
{
/* todo */
printf("Child process,PID is: %d,PPID is: %d.\n", getpid(), getppid());
_exit(0);
}
else if (pid > 0)
{
printf("Parent process,PID is: %d.\n", getpid());
for(;;)
{
sleep(1);
}
}
return 0 ;
}
【3】
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main3(int argc, char** argv)
{
int pid ;
if(signal(SIGCHLD, SIG_IGN) == SIG_ERR) /* 安装显式忽略信号,由init进程接管子进程释放 */
{
perror("signal");
return -1;
}
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
if(pid == 0)
{
/* todo */
printf("Child process,PID is: %d,PPID is: %d.\n", getpid(), getppid());
_exit(0);
}
else if (pid > 0)
{
printf("Parent process,PID is: %d.\n", getpid());
for(;;)
{
sleep(1);
}
}
return 0 ;
}
【4】
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int pid0,pid1;
pid0 = fork();
if(pid0 < 0)
{
perror("fork error");
return -1;
}
if(pid0 == 0)
{
/* todo */
printf("First Child process,PID is: %d,PPID is: %d.\n", getpid(), getppid());
pid1 = fork();
if(pid1 < 0)
{
perror("fork error");
return -1;
}
else if(pid1 > 0)
{
printf("First Child process [%d] exit.\n", getpid()); /* 第一个子进程退出 */
_exit(0);
}
sleep(2); /* 让父进程先运行 */
printf("Second Child process,PID is: %d,PPID is: %d.\n", getpid(), getppid());
printf("Second Child process [%d] exit.\n", getpid()); /* 第一个子进程的子进程退出,由init进程接管 */
_exit(0);
}
else if (pid0 > 0)
{
if (waitpid(pid0, NULL, 0) < 0) /* 等待第一个子进程退出 */
{
perror("waitpid error");
return -1;
}
printf("Parent process,PID is: %d.\n", getpid());
for(;;)
{
sleep(1);
}
}
return 0 ;
}