关于信号中断与慢系统调用的深度发现

这段时间在看Unix网络编程卷1,在5.9节处理SIGCHLD信号,关于处理僵死进程第四步如下写道:信号是在父进程阻塞于慢系统调用(accept)时由父进程捕获的,内核就会使慢系统调用(accept)返回一个EINTR错误。

看到上面那段落的时候,想到我前段时间写网络服务器遇到的问题,链接地址:http://bbs.csdn.net/topics/391032981,其实里面也有我关于这方面问题的困惑。

总结一下我论坛的那个问题,其实我无论如何是不能通过信号中断,测试epoll_wait出错errno置EINTR进而执行程序退出的,说个很简单的理由,当选用IDE进行调试程序的时候,若程序增加的有断点,程序会在步入断点的时候接收到SIGTRAP信号,这样一样会使慢系统调用返回错误并置errno为EINTR,这个时候的我肯定是不想程序就此退出。当然那个问题后来我是通过SocketPair解决的,其一用于epoll_wait监听,另一个在需要退出的时候写入数据,epoll_wait返回的时候,首先判断用于监听的那个套接字是否需要接收数据,若有数据需要接收那就是我通知程序退出呢。

接下来关于信号中断与慢系统调用的两个测试,测试环境Centos7 64,所有代码均无错误处理,请自行包含相关头文件

 

void sig_func(int signo)
{
	printf("catch %d\n", signo);
}

int main(int argc, char *argv[])
{
	signal(SIGINT, sig_func);

	int lfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(1234);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

	bind(lfd, (struct sockaddr *)&addr, sizeof(addr));

	listen(lfd, 10);

	while (true) {
		struct sockaddr_in caddr;
		socklen_t len = sizeof(caddr);

		int cfd = accept(lfd, (struct sockaddr *)&caddr, &len);
		printf("accept %d\n", cfd);
	}
}

 

多次Ctrl+C,程序作如下输出:

 

[root@bogon Debug]# ./server 
^Ccatch 2
^Ccatch 2

 

现像是通过Ctrl+C产生中断,并不会使accept函数返回,这和我们预想的不一样啊,这可能就是因为系统的signal已如Unix网络编程书中所说的那样,默认增加了自动重启这个标志,这时只能求助于手册,手册中有如此写道:

 

Furthermore, certain
       blocking system calls are automatically restarted if interrupted by a signal handler (see signal(7)).  The BSD semantics are equivalent to calling sigaction(2) with the
       following flags:

           sa.sa_flags = SA_RESTART;

 

 

void sig_func(int signo)
{
	printf("catch %d\n", signo);
}

int main(int argc, char *argv[])
{
	signal(SIGINT, sig_func);

	int lfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(1234);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

	bind(lfd, (struct sockaddr *)&addr, sizeof(addr));

	listen(lfd, 10);

	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(lfd, &fds);
	int res = select(lfd+1, &fds, NULL, NULL, NULL);
	printf("select %d -- errno %d\n", res, errno);
}

 

单次Ctrl+C,程序作如下输出:

 

 

[root@bogon Debug]# ./server 
^Ccatch 2
select -1 -- errno 4

 

代码其实基本还是上面的代码,只是将想验证的系统调用换成了select调用,这个又和我们预想的一样,但是若真如我们手册中看到那样,这又是错误的啦,这时我们只能再次求助于帮助手册,又好好翻了翻手册,终于找到原因啦。

 

   Interruption of system calls and library functions by signal handlers
       If a signal handler is invoked while a system call or library function call is blocked, then either:

       * the call is automatically restarted after the signal handler returns; or

       * the call fails with the error EINTR.

       Which of these two behaviors occurs depends on the interface and whether or not the signal handler was established  using  the  SA_RESTART  flag
       (see sigaction(2)).  The details vary across UNIX systems; below, the details for Linux.

       If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call will be automatically restarted after the
       signal handler returns if the SA_RESTART flag was used; otherwise the call will fail with the error EINTR:

           * read(2), readv(2), write(2), writev(2), and ioctl(2) calls on "slow" devices.  A "slow" device is one where the I/O call may block for  an
             indefinite time, for example, a terminal, pipe, or socket.  (A disk is not a slow device according to this definition.)  If an I/O call on
             a slow device has already transferred some data by the time it is interrupted by a signal handler, then the call  will  return  a  success
             status (normally, the number of bytes transferred).

           * open(2), if it can block (e.g., when opening a FIFO; see fifo(7)).

           * wait(2), wait3(2), wait4(2), waitid(2), and waitpid(2).

           * Socket  interfaces: accept(2), connect(2), recv(2), recvfrom(2), recvmsg(2), send(2), sendto(2), and sendmsg(2), unless a timeout has been
             set on the socket (see below).

           * File locking interfaces: flock(2) and fcntl(2) F_SETLKW.

           * POSIX message queue interfaces: mq_receive(3), mq_timedreceive(3), mq_send(3), and mq_timedsend(3).

           * futex(2) FUTEX_WAIT (since Linux 2.6.22; beforehand, always failed with EINTR).

           * POSIX semaphore interfaces: sem_wait(3) and sem_timedwait(3) (since Linux 2.6.22; beforehand, always failed with EINTR).

       The following interfaces are never restarted after being interrupted by a signal handler, regardless of the use of SA_RESTART; they always  fail
       with the error EINTR when interrupted by a signal handler:

           * Socket  interfaces,  when  a timeout has been set on the socket using setsockopt(2): accept(2), recv(2), recvfrom(2), and recvmsg(2), if a
             receive timeout (SO_RCVTIMEO) has been set; connect(2), send(2), sendto(2), and sendmsg(2), if a send timeout (SO_SNDTIMEO) has been set.

           * Interfaces used to wait for signals: pause(2), sigsuspend(2), sigtimedwait(2), and sigwaitinfo(2).

           * File descriptor multiplexing interfaces: epoll_wait(2), epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).

           * System V IPC interfaces: msgrcv(2), msgsnd(2), semop(2), and semtimedop(2).

           * Sleep interfaces: clock_nanosleep(2), nanosleep(2), and usleep(3).

           * read(2) from an inotify(7) file descriptor.

           * io_getevents(2).

       The sleep(3) function is also never restarted if interrupted by a handler, but gives a success return: the number of seconds remaining to sleep.

 

这里大致说明一下就是:当一个信号被捕获处理的时正阻塞着系统调用或是库函数,将会有两种情况发生,一种是调用将会自动重启当信号处理函数返回的时候,另一种是调用失败返回errno置EINTR。因为signal默认增加SA_RESTART标志,所以上面的是中断因为有标志自动重启的,下面的是无论如何都会返回错误的。

 

经过测试,若自己通过sigaction函数提供Signal函数调用,并在函数中取消SA_RESTART标志,这样的话,第一个测试程序将会有如下输出:

 

[root@bogon Debug]# ./server 
sigaction successed.
^Ccatch 2
accept -1
^Ccatch 2
accept -1

 

关于Signal函数这里不再实现。

 

这里我们就不再验证epoll_wait,因为根据论坛提的问题来看,epoll_wait也是会返回错误的。至于为什么在多线程的时候某种情况下就不会返回,这就要牵涉到捕获信号的机制了吧,相关书籍我没有看过,我这里用不太准确的话描述一下,就是若假定程序有两个线程,主线程启动,执行signal捕获信号(这里是主线程执行该语句哦),另一个线程执行epoll_wait,这个时候若产生信号,将会由主线程负责捕获和处理所有信号,这对另一个线程没有产生任何影响,所以epoll_wait才不会返回的。总结性来说,多线程中只会有一个线程来捕获和处理信号。

你可能感兴趣的:(C)