信号对慢系统调用的影响
信号从接受到处理是有一个过程的,因为这个时候信号集要进内核,并从内核出来再执行信号处理函数。就可能会导致一些慢系统调用函数的假性出错(就是这个函数本身没有出错,而是由于内核执行了信号处理,才导致它出错),有些函数假错是可以重新启动的,比如说:accept,listen,read,write等;但是connect是不能重新启动的,如果对connect重连,会直接返回错误,正确的做法是关闭套接字,重新打开进行连接。
信号打断accept:
几乎所有的慢系统调用含税被信号打断,都会报一个EINTR错误,我们只要显示处理这个错误就行了。一般来讲,可以重新启动的就重新启动,不能重新启动的就再进行别的操作。
while (true)
{
int connect_socket;
struct sockaddr_in client_address;
socklen_t client_address_len = sizeof(client_address);
// 接受连接
connect_socket = accept(listen_socket, (struct sockaddr*)&client_address, &client_address_len);
// 这是因为慢系统调用被信号打断,假错为EINTR,要单独判断,不是所有系统都会重启系统调用;
// 基本所有的慢系统调用都有被信号误打断的可能, 所以要记住这一点;
// connect不能重启,重启会直接报错,必须重新建立socket
if (errno == EINTR)
{
continue;
}
// 创建子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
exit(-1);
}
if (pid == 0) // 子进程
{
close(listen_socket);
str_echo(connect_socket);
exit(0);
}
// 父进程继续从就绪队列取出连接
close(connect_socket);
}
用于回收子进程的信号处理函数应该怎么写?
信号默认是不排队的。
也就是说:当几乎同一时刻有多个相同的信号到来的时候,可能信号处理函数只会执行一次。
这好像没什么问题,但是对下列使用信号回收子进程就会有问题:
void hander(int p)
{
pid_t pid;
int stat;
pid = wait(&stat);
printf("child %d terminated.\n", pid);
return;
}
如果有多个客户端同时断开连接,就会同时有多个子进程关闭(变成僵尸进程),而发送的多次SIGCHLD信号,只会被执行一次,或少于应该执行的次数,这就会导致有子进程没有被收尸,变成僵尸进程。虽然僵尸进程占用的内存资源不多,但是它占用一个非常宝贵的资源——进程pid号。
正确的收尸方法:
/**
* @brief 信号处理函数
* 负责给断开连接的客户端的子进程收尸
*/
void hander(int p)
{
pid_t pid;
int stat;
// 必须要waitpid,-1表示可以回收任何进程
// WNOHANG表示如果没有需要回收的进程,不阻塞
// 如果调用wait,只会回收一个,而信号默认是不排队的;
// 如果同一时刻大量信号涌来,信号处理函数就只会被执行一次,导致回收不干净
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("child %d terminated.\n", pid);
}
// 正常规范要return
return;
}