被信号中断的系统调用
1:EINTR
一般来说, 一个阻塞的慢系统调用(ex: read, write)被信号中断后, 有以下几种情况.
1.1 按照信号默认的处理方式, 如本进程直接退出
1.2 如果有信号处理函数, 系统调用返回-1(一般的错误返回), 且errno的值被系统置为EINTR(数值为4), 这时并不表示系统调用失败, 应该要重启系统调用
2:重启系统调用
所谓重启系统调用, 就是某个系统调用执行期间被某个信号中断, 但系统调用不立即返回错误, 而是重启, 重启系统调用就是设置某个信号的SA_RESTART标志, 被此信号中断的系统调用将重启而不是返回错误, 但SA_RESTART标志并不是对所有的系统调用都有效。
2.1 设置SA_RESTART
#include#include #include #include #include void int_handler(int signo){ printf("Got SIGINT signal\n");}int main(int argc, char const *argv[]){ char buf[1024] = { 0}; int n; struct sigaction act, oldact; act.sa_handler = int_handler; sigemptyset(&act.sa_mask); act.sa_flags |= SA_RESTART; sigaction(SIGINT, &act, &oldact); while(1){ memset(buf, 0, sizeof buf); if((n = read(STDIN_FILENO, buf, sizeof buf)) < 0){ if(errno == EINTR){ // 等待终端输入时被SIGINT中断 perror("read interrupt by sigint"); continue; } } if(buf[0] == 'q' || buf[0] == 'Q') break; printf("Got:%s\n", buf); } sigaction(SIGINT, &oldact, NULL); return 0;}
结果及分析:
stone@cdWSCMPL07:~/test_my$ ./read ^CGot SIGINT signal^CGot SIGINT signal^CGot SIGINT signal^CGot SIGINT signal^CGot SIGINT signal^CGot SIGINT signal^CGot SIGINT signal^CGot SIGINT signalqstone@cdWSCMPL07:~/test_my$
如上, read函数在阻塞过程中被SIGINT中断, 因为信号SIGINT设置了SA_RESTART标志的原因, 所以程序仅仅执行了信号处理函数, read函数并没有返回, 而被重启并进入下一次阻塞。
2.2 取消设置SA_RESTART
将语句 act.sa_flags |= SA_RESTART; 屏蔽
结果及分析:
stone@cdWSCMPL07:~/test_my$ ./read ^CGot SIGINT signalread interrupt by sigint: Interrupted system call^CGot SIGINT signalread interrupt by sigint: Interrupted system call^CGot SIGINT signalread interrupt by sigint: Interrupted system call^CGot SIGINT signalread interrupt by sigint: Interrupted system callqstone@cdWSCMPL07:~/test_my$
如上, 没有设置SA_RESTART的read被中断, read返回之后系统设置了errno, 并执行了perror打印, 最后continue后重新调用read进入下一次阻塞。
3:SA_RESTART标志的适用范围
并不是所有的系统调用都是支援SA_RESTART重启机制的, 所以要注意区分使用范围
支援此标志的系统调用有:
读写IO:read, readv, write, writev, ioctl
不支持的如select
4:总结
对于SA_RESTART还是应该避免使用, 因为首先你要区别哪些系统调用支援哪些不支援, 这是一个比较麻烦的事情, 而且你需要对每个信号都设置此标志, 总的来讲, SA_RESTART标志是一个不建议使用的标志, 最合理的是将系统调用返回-1且errno=EINTR不纳入调用出错来考虑, 这种情况应该重新执行之前的代码来替代SA_RESTART重启系统调用。