被信号中断的系统调用

被信号中断的系统调用

1:EINTR

  一般来说, 一个阻塞的慢系统调用(ex: read, write)被信号中断后, 有以下几种情况.

  1.1 按照信号默认的处理方式, 如本进程直接退出

  1.2 如果有信号处理函数, 系统调用返回-1(一般的错误返回), 且errno的值被系统置为EINTR(数值为4), 这时并不表示系统调用失败, 应该要重启系统调用

2:重启系统调用

  所谓重启系统调用, 就是某个系统调用执行期间被某个信号中断, 但系统调用不立即返回错误, 而是重启, 重启系统调用就是设置某个信号的SA_RESTART标志, 被此信号中断的系统调用将重启而不是返回错误, 但SA_RESTART标志并不是对所有的系统调用都有效。

  2.1 设置SA_RESTART

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

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 signal
q
stone@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 signal
read interrupt by sigint: Interrupted system call
^CGot SIGINT signal
read interrupt by sigint: Interrupted system call
^CGot SIGINT signal
read interrupt by sigint: Interrupted system call
^CGot SIGINT signal
read interrupt by sigint: Interrupted system call
q
stone@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重启系统调用。

posted @ 2017-10-23 13:53  会飞的小丑  阅读(1489)  评论(0编辑  收藏  举报