第二部分介绍了基于多进程模型的回射服务器的实现,对于每一个客户请求服务器端创建一个子进程来处理和响应客户的请求。最后提到了,服务器主进程没有对子进程的结束进行处理,没有捕捉SIGCHLD信号,导致子进程结束后变成了僵尸进程,仍然存在于系统,僵尸进程将消耗内核资源。接下来总结了linux 系统信号处理相关的知识点,并对第二部分的回射服务器程序完成,增加对SIGHLD信号的处理,避免子进程成为僵尸进程。
1.linux信号处理基本知识
信号是发生某件事时对进程的通知,信号有时称为软中断。除了SIGKILL和SIGSTOP信号外,其他的信号都可以注册自己的信号处理函数,当进程收到该信号时将自动调用信号处理函数。信号处理函数的原型:
void handler(int signo)
可以通过函数sigaction来针对某一个信号注册自己的信号处理函数或者指定SIG_IGN和SIG_DEF,不过sigaction的调用较复杂,需要自己填充一个数据结构,另外也可以通过系统调用signal,但是对于signal函数不同的实现具有不用的语义,解决的方法是利用sigaction函数实现自己的signal函数。
typedef void Sigfunc(int); /* 定义信号处理函数的类型 */ Sigfunc * signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; /* 设置信号处理函数 */ /* 信号屏蔽置为空,在执行信号处理程序时没有其他的信号阻塞, 信号处理程序执行期间,被捕获的信号默认是阻塞的*/ sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signo == SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x, SIGALRM信号中断的系统调用,不被重启 */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 44BSD, 被打断的系统调用将被内核重启 */ #endif } if (sigaction(signo, &act, &oact) < 0) /* 注册新的信号处理方式,并保存原来的信号处理方式 */ return(SIG_ERR); return(oact.sa_handler); /* 返回原来的信号处理函数指针 */ }
Posix兼容系统关于信号处理的方式:
a. 一旦安装了信号处理程序,它便一直安装着。
b. 一个信号处理期间,被捕获的信号是被阻塞的,sa_mask信号集中的信号也是被阻塞的。
c. 如果一个信号被阻塞时,生成了一次或者多次,信号解除阻塞时该信号仅被递交一次。
2.wait和waitpid函数
wait和waitpid函数用来获取结束的子进程的状态,当没有结束的子进程时,调用进程将阻塞。
#include<sys/wait.h> /* 功能:等待某一个子进程终止, * 并获得子进程终止状态,存放于statloc中。 * 如果没有终止的子进程,但有一个或多个子进程在运行, * 调用进程将阻塞,知道某一个子进程终止。 * 如果进程没有子进程,wait返回-1表示失败。 */ pid_t wait(int *statloc); /* 功能:和wait功能类似,不过具有更多的可操作性。 * pid参数可以指定要等待哪一个进程终止: * -1 表示任意一个,小于-1或0表示进程组相关,大于0表示等待的进程ID。 * options参数可以传递额外的控制选项,常用WNOHANG,表示没有结束的子进程时不阻塞。 */ pid_t waitpid(pid_t pid, int *statloc, int options);
3.基于多进程的简单回射服务器代码(多进程+信号处理)
和第二部分唯一的不同是,增加了SIGCHLD信号处理,在信号处理函数里面调用waitpid获取结束的子进程状态,并考虑多个子进程同时结束的情况。
int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0);/* 创建监听fd */ bzero(&servaddr, sizeof(servaddr)); /* 初始化服务器监听套接口地址 */ servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); /* 服务器端套接口地址绑定 */ Listen(listenfd, LISTENQ); /* 服务器端监听 */ Signal(SIGHLD, sig_child); /* 捕获SIGCHLD信号,获取终止子进程的状态 */ for ( ; ; ) { clilen = sizeof(cliaddr); if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {/* 接受连接 */ if (errno == EINTR) continue; /* back to for() */ else err_sys("accept error"); } /* 创建子进程,处理客户连接 */ if ( (childpid = Fork()) == 0) { Close(listenfd); /* 关闭监听fd */ str_echo(connfd); /* 处理客户请求 */ exit(0); } Close(connfd); /* 关闭连接fd */ } } /* SIGCHLD信号处理函数 */ void sig_child(int signo) { pid_t pid; int stat; /* 必须考虑多个进程同时结束,而posix信号不排队的情况, * 因而此处使用waitpid以及循环处理多个子进程同时结束的情况。 */ while((pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated ", pid); return; }