Linux 慢系统调用与EINTR(被中断)

1. 慢系统调用

UNP 5.9提到, 慢系统调用(slow system call)指不会立即返回的系统调用, 可能永远阻塞而无法返回. 诸如多数网络支持函数, 包括read/write, connect, accept等, 都属于这一类.
慢系统调用, 主要分为以下类别:

  1. 读写"慢"设备
    包括pipe, fifo, 终端设备, 网络连接等. 读时, 数据不存在, 需要等待缓冲区有数据输入; 写时, 缓冲区满, 需要等待缓冲区有空闲位置.
    注意: 读写磁盘文件一般不会阻塞.

  2. 打开某些特殊文件时, 需要等待某些条件才能打开
    如打开终端设备, 需要等待连接设备的modern响应, 才能打开

  3. pause和wait系统调用
    pause阻塞进程, 直到收到信号唤醒;
    wait等待任意子进程终止;

  4. 某些ioctl操作

  5. 某些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);

参考 信号中断 与 慢系统调用 | CSDN

posted @ 2021-06-04 18:33  明明1109  阅读(2128)  评论(0编辑  收藏  举报