[技術] Interrupted system calls

Written on 2:41 下午 by Yu Lai

這是最近寫程式遇到的,沒想到使用好好的UNIX Socket API居然會有爆走的時候。
在Survey了老半天,總算知道問題點所在。以下是看完The Linux GCC HOWTO的心得。

會發生Interrupted system calls主要是POSIX的系統檢查信號的次數,比起一些舊版的Unix是要多那麼一點。依照此規範,在Linux裡會在下列系統呼叫(system calls)的執行期間執行signal handlers: select(), pause(), connect(),accept(), read() on terminals, sockets, pipes or files in /proc, write() on terminals, sockets, pipes or the line printer, open() on FIFOs, PTYs or serial lines,ioctl() on terminals, fcntl() with command F_SETLKW, wait4(), syslog(), any TCP or NFS operations。而就其它的作業系統而言,你需要注意的可能就是下面這些系統呼叫(system calls)了: creat(), close(), getmsg(), putmsg(), msgrcv(), msgsnd(), recv(), send(), wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop() to this list.

而問題點在於在系統呼叫(system calls)期間,若有一信號(那支程式本身應準備好handler因應了)產生,handler就會被呼叫。當handler將控制權轉移回系統呼叫時,它會偵測出它已經產生中斷,而且傳回值會立刻設定成-1,errno設定成EINTR。程式並沒有想到會發生這種事,所以就會bottles out了。

有兩種修正的方法可以選擇:

(1) 對每個你自行安裝(install)的signal handler,都須在sigaction旗號加上SA_RESTART。例如,把下列的程式,

     signal (sig_nr, my_signal_handler);

改成
     signal (sig_nr, my_signal_handler);
{ struct sigaction sa;
sigaction (sig_nr, (struct sigaction *)0, &sa);
#ifdef SA_RESTART
sa.sa_flags |= SA_RESTART;
#endif
#ifdef SA_INTERRUPT
sa.sa_flags &= ~ SA_INTERRUPT;
#endif
sigaction (sig_nr, &sa, (struct sigaction *)0);
}

要注意的是,當這部份的變更大量應用到系統呼叫之後,呼叫read(), write(),ioctl(), select(), pause() 與 connect()時,你仍然得自行檢查(check for)EINTR。如方法二所示。

(2) 你自己得很明確地(explicitly)檢查EINTR:

這裡有兩個針對read()與ioctl()的例子。

原始的程式片段,使用read()。
   int result;
while (len > 0) {
result = read(fd,buffer,len);
if (result <> 0) {
result = read(fd,buffer,len);
if (result < 0) { if (errno != EINTR) break; }
else { buffer += result; len -= result; }
}

原始的程式片段,使用ioctl()。
   int result;
result = ioctl(fd,cmd,addr);

修改成
   int result;
do { result = ioctl(fd,cmd,addr); }
while ((result == -1) && (errno == EINTR));

注意一點,有些版本的BSD Unix,其內定的行為(default behaviour)是重新執行系統呼叫。若要讓系統呼叫中斷,得使用SV_INTERRUPT或SA_INTERRUPT旗號。

另外補充一個由Richard Steven所提供給connect()的修改法。
   /* Start with fd just returned by socket(), blocking, SOCK_STREAM... */
while ( connect (fd, &name, namelen) == -1 && errno != EISCONN )
if ( errno != EINTR ) {
perror ("connect");
exit (EXIT_FAILURE);
}

If you enjoyed this post Subscribe to our feed

No Comment

張貼留言