read遇到EINTR
参考: https://www.xuebuyuan.com/1470645.html
我们经常在网络编程中会看到这样,当执行一个可能会阻塞的系统调用后,在返回的时候需要检查下错误码(if errno == EINTR),如果是这样的错误,那我们一般会重新执行该系统调用。所以经常的写法是:
repeat:
if(read(fd, buff, size) < 0)
{
if(errno == EINTR)
goto repeat;
else
printf("read failed");
}
但一般我们在读/写磁盘文件的时候却不太会判断这个错误,那我们到底什么时候该判断而什么时候又不要去判断呢?
向read发送信号 while true; do pkill -HUP read; done
一般来说,信号处理函数会在进程执行系统或者库函数调用退出时刻被执行,也就是说,进程的信号处理是在系统调用从内核态返回至用户态之前被执行的,如果该进程收到了信号的话。对于一直处于RUNNING态的进程来说,会在系统调用执行完成后,返回用户态前夕执行信号处理函数。
但是,如果进程在内核态进行了状态转换,这时候处理流程就有点微妙的变化了。如由于等待某些事件的发生(最典型的IO等待),进程可能会从RUNNING状态转变为休眠状态,休眠状态的进程会被切换出CPU。但休眠状态有两种:第一是INTERUPTIBLE进程,第二种UNINTERUPTIBLE进程。第一种进程是可被信号唤醒的进程,第二种是不可被信号唤醒的进程,这就是问题的关键。
- 对于INTERUPTIBLE状态的进程,一旦被信号唤醒后,会退出内核态执行,退出内核态之前执行信号处理函数。如果资源没准备好,那此时可能会设置错误码为EINTR。能被信号唤醒的休眠
- 但是对于处于UNTERUPTIBLE状态的进程,该进程是不可被信号唤醒的,也就是说,当进程休眠时,会屏蔽所有的信号,直到它从休眠状态返回至RUNNING状态,执行完成后,返回上次执行的地方继续运行,然后退出内核态时候执行所有信号的处理函数。也就是说,这个状态的进程应该是不会被信号中断的,只会等到资源准备妥当时候才会被唤醒,这时候应该不存在会返回EINTR错误的情况。
为了验证这种情况,我们使用ps -aux查看了上述几种情况下进程所处的状态。
首先是读磁盘,ps-aux发现其显示状态为D(即UNTERUPTIBLE),接下来读terminal,ps -aux显示其状态为S(INTERUPTIBLE)。
这就能解释上述两种情况显示的现象不一致的原因了。read磁盘的时候(ret = read(fd, buf, 10);)进程是处于UNINTERUPTIBLE状态,没法被信号唤醒,只能等到read到的数据准备好的时候被唤醒,这个时候再从内核态返回至用户态处理信号处理函数时,并不会出现EINTR错误。
而对于read读终端(ret = read(STDIN_FILENO, buf, 10);),情况则不一样,它处于INTERUPTIBLE状态,当被信号唤醒时,会直接退出内核态,此时应该提醒用户态资源并没有准备好,因此应该返回EINTR错误。以便用户态可以做出自己的决定。
备注:如果不想内核在系统调用返回EINTR错误,那么可以将信号处理函数的标记位设置SA_RESTART。
SA_INTERRUPT:由此信号中断的系统调用不会自动重启动。

浙公网安备 33010602011771号