今天在调试程序时,sem_timedwait居然返回了一个Interrupted system call,错误码为EINTR。系统中断这东西我一向只闻其名,不见其"人",不想今天遇上了,脑中一片空白,赶紧上网恶补一下。

系统中断,要从慢系统调用(slow system call)说起,请参考 http://blog.csdn.net/benkaoya/article/details/17262053 。慢系统调用就是会阻塞的函数调用,一般是IO调用或有锁的函数。如read,sem_timedwait,这些函数可以会阻塞当前进程一段时间,所以"慢"。在阻塞的这段时间里,进程可能会收到内核的一些信号,这些信号优先级高,需要优先处理的,不能等这些调用完成后才处理信号。于是系统先去处理信号,然后强制这些函数以出错的形式返回,其错误码(errno)就是EINTR。这整个过程就是系统中断。

程序长时间运行,可能会遇到系统中断,如调试时会触发SIGTRAP,子进程当掉会遇到SIGCHLD...这些信号,有可能是正常的,或者不影响程序的运行。如果捕捉到错误就粗暴地将进程关闭,可能造成不必要的损失。因此,在这些慢调用的地方,需要检测错误是否为EINTR。如:

int wait()
{
	struct timespec ts;
	if ( clock_gettime( CLOCK_REALTIME,&ts ) < 0 )
		return -1;
	ts.tv_sec  += sec;
	ts.tv_nsec += nano_sec;
	ts.tv_sec += ts.tv_nsec/NSECTOSEC;
	ts.tv_nsec = ts.tv_nsec%NSECTOSEC;
	int ret = sem_timedwait( m_psem,&ts );
	if ( EINTR == ret )
		ret = wait();	/* 遇到系统中断,再等一次 */
	return ret;
}

这是老的处理方法(signal函数),在新的信号处理函数sigaction中可以指定收到某个信号时是否可以自动恢复函数执行(即在处理完消息后,继续执行原来中断的函数,像什么也没发生一样)。参考http://www.gnu.org/software/libc/manual/html_node/Flags-for-Sigaction.html,设置flag为SA_RESTART,表示继续原来的函数。

Macro: int SA_RESTART

This flag controls what happens when a signal is delivered during certain primitives (such as open , read or write ), and the signal handler returns normally. There are two alternatives: the library function can resume, or it can return failure with error code EINTR .

The choice is controlled by the SA_RESTART flag for the particular kind of signal that was delivered. If the flag is set, returning from a handler resumes the library function. If the flag is clear, returning from a handler makes the function fail. See Interrupted Primitives .

在http://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html#Interrupted-Primitives中,说明了处理了系统中断的三种处理方法。

  • 自己检测EINTR并手动重新调用自己的代码
  • 使用TEMP_FAILURE_RETRY,在表达式返回EINTR并自动调用表达式,就是第一种方法的封装。
  • sigaction中指定SA_RESTART

在处理系统中断时,会中断当前代码,去执行信息处理函数,然后继续执行当前代码。这相当于一种异步调用,需要考虑函数是否可重入。比如:

char *p = new char[DEFAULT_LEN];
void sig_handler(int signum)
{
	delete []p;
	p = NULL;
}
void read()
{
	if ( NULL == p )
		return;
	if ( wait() < 0 ) /* wait 函数见上面 */
		return;
	read( fd,p,DEFAULT_LEN );
}

在这段代码中,read函数看似安全,因为已经检测了p指针。但是,如果在wait的时候触发了信号,调用了sig_handler,处理完sig_handler后继续执行read函数,会操作空指针。还有一些系统函数也是需要检测可重入的。见http://www.ibm.com/developerworks/cn/linux/l-reent.html#resources

PS:之前一直没接触过系统中断,这次收获不少。学习信号时,一直以为SA_RESTART是各 和SA_RESETHAND 相对应的。一个重复设置信号的处理函数,一个是只设置一次信号的处理函数,然后设置信号的处理函数为默认。没想到SA_RESTART其实是继续执行中断的函数,默认信号的处理函数是重复设置的。