Linux 慢系统调用与EINTR(被中断)
1. 慢系统调用
UNP 5.9提到, 慢系统调用(slow system call)指不会立即返回的系统调用, 可能永远阻塞而无法返回. 诸如多数网络支持函数, 包括read/write, connect, accept等, 都属于这一类.
慢系统调用, 主要分为以下类别:
-
读写"慢"设备
包括pipe, fifo, 终端设备, 网络连接等. 读时, 数据不存在, 需要等待缓冲区有数据输入; 写时, 缓冲区满, 需要等待缓冲区有空闲位置.
注意: 读写磁盘文件一般不会阻塞. -
打开某些特殊文件时, 需要等待某些条件才能打开
如打开终端设备, 需要等待连接设备的modern响应, 才能打开 -
pause和wait系统调用
pause阻塞进程, 直到收到信号唤醒;
wait等待任意子进程终止; -
某些ioctl操作
-
某些IPC操作
如pipe, fifo, 没有指定NON_BLOCKING选项时的写操作, 如果管道缓冲区满, write阻塞;
互斥锁, 条件变量, 信号量, 记录锁等等.
2. EINTR
EINTR是什么?
EINTR指Interrupted system call, 表示由于中断而错误返回.
适用于满系统调用的基本规则: 当阻塞某个慢系统调用的一个进程捕获某个信号, 且相应信号处理函数返回时, 该系统调用返回一个EINTR错误.
通常, 也会置errno = EINTR
3. 如何处理被中断的系统调用?
编写捕获信号的程序时, 必须对慢系统调用返回EINTR有所准备. 通常, 有三种处理方式:
1) 人为启动被中断的系统调用
有些系统调用被中断后能重启, 有些不能. 所谓重启, 是指再次调用系统调用, 对于那些由于中断导致临时返回错误的情况, 可以再次调用获得正确结果; 而对于那些已经修改了内部状态的情况如connect会修改套接字状态, 则不能再次调用.
如open, read, write, select(源自Berkeley的实现不自动重启select), accept/recvfrom(有些实现不重启) , 能人为重启;
而如connect不能重启, 因为connect失败时, 可能导致socket状态改变, 不能直接重新调用connect. 可以调用select等待连接完成; 当然也可以创建新socket, 然后再次调用connect.
下面这段人为重启被中断系统调用的代码, 适用于能重启的系统调用:
for (; ;) {
clilen = sizeof (cliaddr);
if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {
if (errno == EINTR) continue;
else err_sys("accept error");
}
}
2) 安装信号时设置SA_RESTART属性, 实现自动重启(非适用于所有被中断系统调用)
只能针对设置的信号, 发生捕获返回后, 恢复被中断的系统调用.
比如, 针对捕获SIGALRM信号导致中断系统调用后, 利用设置SA_RESTART属性自动重启系统调用:
struct sigaction sa
sa.sa_handler =
sigsetempty(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_flags |= SA_RESTART;
sigaction(SIGALRM, &sa, NULL);
注意: 并不是所有系统调用都支持SA_RESTART属性, 自动重启, 比如msgsnd/msgrcv(System V队列 消息发送/接收).
a signal is caught, in which case the system call fails with errno set to EINTR;see sig‐
nal(7). (msgsnd() is never automatically restarted after being interrupted by a signal
handler, regardless of the setting of the SA_RESTART flag when establishing a signal han‐
dler.)
* The calling process catches a signal. In this case, the system call fails with errno set
to EINTR. (msgrcv() is never automatically restarted after being interrupted by a signal
handler, regardless of the setting of the SA_RESTART flag when establishing a signal han‐
dler.)
3) 忽略信号
设置信号处置方式为忽略(SIG_IGN), 告知进程不会处理对应信号, 也就不会因为该信号而中断了.
例如, 忽略SIGALRM信号
signal(SIGALRM, SIG_IGN);