进程:占用内存空间的正在运行的程序,是系统进行资源分配和调度的基本单位。
进程在完成工作后应该被销毁,如果完成工作后,仍占用系统资源不被销毁,就会变为僵尸进程,给系统带来负担。
僵尸进程的产生
向exit函数传递参数值,或者是通过return语句返回的值都会传递给操作系统,而操作系统如果没有把这些值传递给产生该子进程的父进程,那么操作系统就不会销毁子进程.处于这种状态下的进程就是僵尸进程。
下面是一个产生僵尸进程的示例
zombie.c
#include<stdio.h>
#include<unistd.h>
int main(int argc,char *argv[]){
pid_t pid = fork();
if(pid == 0){
fprintf(stdout,"child process.\n");
}
else{
fprintf(stdout,"child process ID:%d\n",pid);
sleep(30);
}
if(pid == 0){
fprintf(stdout,"End child process.\n");
}
else{
fprintf(stdout,"End parent process.\n");
}
return 0;
}
父进程在休眠,而子进程已经结束,且父进程并未处理子进程的返回值,这时子进程就会变为僵尸进程.
如图,这里3636就是产生的僵尸进程,进程stat为Z
销毁僵尸进程
如果销毁呢?可以考虑产生僵尸进程的原因。
把子进程exit参数值或return语句返回值传递给父进程,即可消灭僵尸进程。
1.wait 函数
#include<sys/wait.h>
pid_t wait(int *statloc);
调用该函数时,会阻塞进程,如果已经有子进程终止,那么子进程终止时传递的返回值将保存到statloc所指向的内存空间。
但该空间还保存有其他信息,需要利用宏来分离.
WIFEXITED();//子进程正常终止时返回真.
WEXITSTATUS();//返回子进程的返回值.
wait.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int status;
pid_t pid = fork();
if(pid == 0){
return 3;
}
else{
fprintf(stdout,"Child PID: %d\n",pid);
pid = fork();
if(pid == 0){
exit(4);
}
else{
fprintf(stdout,"Child PID: %d\n",pid);
wait(&status);
if(WIFEXITED(status))
fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
fprintf(stdout,"status = %d\n",status);
wait(&status);
if(WIFEXITED(status))
fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
sleep(20);
}
}
return 0;
}
这样就不会产生僵尸进程了。不过该函数会一直阻塞进程,直到有子进程终止,所以有可能会造成不必要的麻烦。
2.waitpid 函数
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *statloc,int options);
pid:等待终止的的目标子进程ID,若传递-1,则与wait函数相同,等待任意进程的终止.
statloc:保存返回信息的空间指针
options: 有以下几个选项
(1) WNOHANG:若制定的pid未终止,则返回0,不会阻塞进程.
(2) WUNTRACED: 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。WIFSTOPPED(status)宏确定返回值是否对应与一个暂停子进程。
waitpid.c
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int status;
pid_t pid = fork();
if(pid == 0){
sleep(10);
return 2;
}
else{
while(!waitpid(-1,&status,WNOHANG)){
sleep(1);
fprintf(stdout,"sleep 1 sec.\n");
}
if(WIFEXITED(status))
fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
}
return 0;
}
很显然这里的父进程并为被阻塞.但仍存在一个问题,就是,我们要一直等待waitpid函数,如果不等待,我们不知道何时子进程终止。这也会对程序造成问题。
信号处理
子进程终止是被操作系统所识别的,如果操作系统能把自己进程终止这一消息告诉父进程,那么我们就能很好的处理了。
singal函数
#include<signal.h>
void (*signal(int signo,void (*func)(int)))(int);
signo:特殊情况的信息,有如下:
- SIGALRM:已到通过调用alarm函数注册的时间
- SIGINT:输入CTRL+C.
- SIGCHLD:子进程终止.
func:发生特殊情况时,调用的函数的指针.
介绍一个alarm函数
#include<unistd.h>
unsigned int alarm(unsigned int sec);
相应sec时间后,将产生一个SIGALRM信号.
一个示例
signal.c
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void timeout(int sig){
if(sig == SIGALRM)
fprintf(stdout,"Time out!\n");
alarm(2);
}
void keycontrol(int sig){
if(sig == SIGINT)
fprintf(stdout,"CTRL+C pressed!\n");
}
int main(int argc,char *argv[]){
signal(SIGALRM,timeout);
signal(SIGINT,keycontrol);
alarm(2);
for(int i=0;i<3;++i){
fprintf(stdout,"wait...\n");
sleep(20);
}
return 0;
}
执行结果
也可以尝试在执行中输入CTRL+C,会调用keycontrol函数
注意在这里主进程虽然有sleep(20),但等待6秒左右,就会结束整个程序。
因为进程在休眠中无法调用函数,但此时有信号产生,为了调用信号处理函数,会把休眠的进程唤醒。而且进程一旦被唤醒,即使为达到sleep函数调用的时间也不会再继续休眠,除非再次调用sleep。
**sigaction **
#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction * oldact);
- signo:与signal函数相同,传递信号信息
- act:相当于信号处理函数
- oldact:通过此参数获取之前注册的信号处理函数指针,若无需要则传递0
sigaction结构体
struct sigaciont{
void(*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
}
- sa_handler:指向信号处理函数
- sa_mask和sa_flags:指定信号相关的选项和特性
利用信号技术消灭僵尸进程
signal_remove_zombie.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
void read_child_send(int sig){
int status;
pid_t pid = waitpid(-1,&status,WNOHANG);
if(WIFEXITED(status)){
fprintf(stdout,"Removed PID:%d\n",pid);
fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
}
}
int main(int argc,char *argv[]){
pid_t pid;
struct sigaction act;
act.sa_handler = &read_child_send;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD,&act,0);
pid = fork();
if(pid == 0){
fprintf(stdout,"Child process running.\n");
sleep(1);
return 2;
}
else{
fprintf(stdout,"Child process PID:%d\n",pid);
pid = fork();
if(pid == 0){
fprintf(stdout,"Child process running.\n");
sleep(2);
return 3;
}
else{
fprintf(stdout,"Child process PID:%d\n",pid);
for(int i=0;i<3;++i){
fprintf(stdout,"wait...\n");
sleep(2);
}
}
}
return 0;
}
运行结果:
参考资料:
- 《TCP/IP网络编程》 尹圣雨