一、被中断的系统调用(EINTR)的理解
1. 慢系统调用是?
慢系统调用指可能永远阻塞的系统调用。
也就是处于阻塞状态的系统调用,如果不收到需要的信息,就会一直阻塞在那里。例如accept
:在服务器等待客户端建立连接时,如果没有客户端来请求连接,那么accept
就会一直阻塞,直到有客户端请求连接为止。像这种系统调用,就称为慢系统调用。
2. 慢系统调用的类别
- 对管道的读写
- 对终端设备设备的读写
- 对网络连接的读写
- ……
值得注意的是,读写磁盘文件一般不会阻塞,一般会返回给调用者(在没有硬件故障的条件下)
3. EINTR产生的原因
当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。有些内核会自动重启某些被中断的系统调用。
对这句话的理解:
慢系统调用在阻塞状态中,如果收到了某个信号,该系统调用就会被中断,转而去执行对应的信号处理函数,在信号处理函数执行完后,被中断的系统调用就会返回EINTR。有的内核会在被中断的系统调用返回EINTR后重新执行。
以accept
为例,在服务器阻塞于accept
时,这时之前已连接的客户端关闭了,对应该客户端的子进程就会退出,并发送SIGCHLD
信号。如果定义了信号处理函数,就会造成accept的中断,然后执行该信号处理函数,执行完后accept
就会返回EINTR
。
5. 一般处理方法
推荐将对accept的调用改为:
while( true ) {
int clientlen = sizeof( cliaddr );
int connfd = accept( sockfd, (struct sockaddr *)&cliaddr, &clietnlen );
if( connfd < 0 ) {
if( errnp == EINTR ) { //如果返回EINTR,就重启该系统调用
continue;
} else {
perror( "accept " );
}
}
// do other things
}
还有两种方法:设置SA_RESTART属性、忽略信号,不过推荐使用上述方法。
二、SIGCHLD信号的处理
1. SIGCHLD信号的产生
在子进程退出时,会发送给父进程一个SIGCHLD信号来表明子进程已退出,此时可以使用wait/waitpid来等待子进程退出并回收子进程占用的资源,避免产生僵尸进程。该信号被默认为忽略。
2. SIGCHLD信号的处理
可以定义该信号的信号处理函数,并使用signal()
来运行该信号对应的信号处理函数。
void sig_chld( int signo ) {
pid_t pid;
int stat;
while( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) {
printf("child %d terminated\n", pid );
}
return ;
}
signal( SIGCHLD, sig_chld );
其中,我们必须要为waitpid()函数设置WNOHANG
属性,以便告知waitpid在仍有子进程尚未终止时不要阻塞。
循环中不能使用wait()函数,因为我们不能保证wait在还有子进程运行时不会发生阻塞。
在使用循环判断后,就可以将所有已结束的子进程的资源进行回收。
3. 不处理SIGCHLD的后果
如果我们在收到SIGCHLD信号后忽略,而且也没有在父进程最后wait/waitpid子进程结束,就会有僵尸进程产生。僵尸进程虽然不再占有内存等资源,但是会保留进程表中的表项,因此会造成内存泄漏。
linux下,可以使用ps命令查看,stat
一栏标志为Z
的就是僵尸进程。
三、示例代码
- server.cpp
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <wait.h>
using namespace std;
const int PORT = 8080;
/* 接收客户端信息 */
void str_echo( int conn_fd ) {
ssize_t n;
char buf[1024] = {0};
while( ( n = read( conn_fd, buf, 1024 ) ) > 0 ) {
write( conn_fd, buf, n );
}
if( n < 0 && errno == EINTR ) {
continue;
} else if( n < 0 ) {
perror( "read " );
}
}
void sig_chld( int signo ) {
pid_t pid;
int stat;
while( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) {
printf("child %d terminated\n", pid );
}
return ;
}
int main() {
int sock_fd = socket( AF_INET, SOCK_STREAM, 0 );
if( sock_fd < 0 ) {
perror( "socket " );
}
struct sockaddr_in servaddr;
memset( &servaddr, 0, sizeof( servaddr ) );
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons( PORT );
servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
int ret = bind( sock_fd, ( struct sockaddr * )&servaddr, sizeof( servaddr ) );
if( ret < 0 ) {
perror( "bind " );
}
ret = listen( sock_fd, 5 );
if( ret < 0 ) {
perror( "bind " );
}
signal( SIGCHLD, sig_chld );
while( true ) {
struct sockaddr_in clieaddr;
socklen_t len;
memset( &clieaddr, 0, sizeof( clieaddr ) );
int conn_fd = accept( sock_fd, ( sockaddr * )&clieaddr, &len );
if( conn_fd < 0 ) {
if( errno == EINTR ) {
cout << "EINTR\n";
continue;
} else {
perror( "accept " );
}
}
if( fork() == 0 ) {
close( sock_fd );
str_echo( conn_fd );
exit(0);
}
close( conn_fd );
}
return 0;
}
- client.cpp
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
const int PORT = 8080;
/* 给服务器发送、接收消息的函数 */
void str_cli( FILE *fp, int sock_fd ) {
char recvline[1024] = {0}, sendline[1024] = {0};
while( fgets( sendline, 1024, fp ) != NULL ) {
write( sock_fd, sendline, strlen(sendline) );
if( read( sock_fd, recvline, 1024 ) == 0 ) {
perror( "readline " );
}
fputs( recvline, stdout );
}
}
int main( int argc, char **argv ) {
if( argc != 2 ) {
cout << "Usage : ./client + ip\n";
return 0;
}
int sock_fd[5] = {0};
for( int i = 0; i < 5; i++ ) { // 创建5个连接
sock_fd[i] = socket( AF_INET, SOCK_STREAM, 0 );
if( sock_fd[i] < 0 ) {
perror( "socket " );
}
sockaddr_in servaddr;
memset( &servaddr, 0, sizeof( servaddr ) );
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons( PORT );
inet_pton( AF_INET, argv[1], &servaddr.sin_addr );
int ret = connect( sock_fd[i], (sockaddr *)&servaddr, sizeof( servaddr ) );
if( ret < 0 ) {
perror( "connect " );
}
}
str_cli( stdin, sock_fd[0] );
return 0;
}
运行结果: