信号的基本操作
ctrl + c --> 2)SIGINT
ctrl + \ --> 3)SIGQUIT
13)SIGPIPE 当管道读端关闭,再往管道写东西,会发出SIGPIPE信号
17)SIGCHLD 子进程退出会向父进程发出SIGCHLD信号,系统默认处理是忽略掉该信号
#include<stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <signal.h> void handler(int num) { pid_t pid ; printf("sig_num: %d \n", num); pid = wait(NULL); printf("wait : %u \n", pid); } int main(int argc, char* argv[]) { int fds[2];// fds[0] r fds[1] w pipe(fds); char buf[1024]; signal(SIGPIPE, handler); signal(SIGCHLD, handler); if(fork() == 0) { close(fds[1]); while(memset(buf, 0, 1024), read(fds[0], buf, 1024)) { write(1, buf, strlen(buf)); } printf("child over ! \n"); exit(0); } if(fork() == 0) { exit(0); } if(fork() == 0) { exit(0); } close(fds[0]) ; while(memset(buf, 0, 1024), read(0, buf, 1024)) { write(fds[1], buf, strlen(buf)); } //while(1) ; return 0 ; }
程序运行时,先是两个子进程先后退出,向父进程发送SIGCHLD 信号,接着执行handler函数,回收两个子进程资源。接着程序执行到父进程的while循环,由于read是阻塞函数,在我们没有按下enter或者ctrl+d之前,时间片会在第一个子进程和父进程之间来回切换。如果输入字符按enter,那么也就是父进程将其写入管道,子进程将其从管道中取出,并显示在屏幕上。
如果按ctrl+d,则父进程退出while循环,并且退出程序。此时第一个子进程成为孤儿进程,被init收养,子进程退出时,会向init发送SIGCHLD 信号,由init回收资源。
我们之所以将回收子进程资源的wait函数写在信号处理函数中,是因为wait是阻塞函数。如果父进程阻塞了就不能处理自己的工作了。
之前我们已经提过,当子进程终止时,会给父进程发送SIGCHLD信号,虽然该信号的默认处理动作是忽略,但是父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程,当子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程资源即可。
通常情况下服务器是永远不会退出的,因此我们可以在之前的程序中 return 0 前加个while(1),以此来模拟服务器。
如果父进程先于子进程退出,则子进程成为孤儿进程,此时将自动被PID为1的进程(即init)接管。孤儿进程退出后,它的清理工作有祖先进程init自动处理。但在init进程清理子进程之前,它一直消耗系统的资源,所以要尽量避免。
如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸(zombie)进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。