欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Linux被中断的系统调用

程序员文章站 2022-10-29 20:14:47
慢系统调用,指的是可能永远无法返回,从而使进程永远阻塞的系统调用,比如无客户连接时的accept、无输入时的read都属于慢速系统调用。 在Linux中,当阻塞于某个慢系统调用的进程捕获一个信号,则该系统调用就会被中断,转而执行信号处理函数,这就是被中断的系统调用。 然而,当信号处理函数返回时,有可 ......

慢系统调用,指的是可能永远无法返回,从而使进程永远阻塞的系统调用,比如无客户连接时的accept、无输入时的read都属于慢速系统调用。
在linux中,当阻塞于某个慢系统调用的进程捕获一个信号,则该系统调用就会被中断,转而执行信号处理函数,这就是被中断的系统调用。
然而,当信号处理函数返回时,有可能发生以下的情况:

  • 如果信号处理函数是用signal注册的,系统调用会自动重启,函数不会返回
  • 如果信号处理函数是用sigaction注册的
    • 默认情况下,系统调用不会自动重启,函数将返回失败,同时errno被置为eintr
    • 只有中断信号的sa_restart标志有效时,系统调用才会自动重启

下面我们编写代码,分别验证上述几种情形,其中系统调用选择read,中断信号选择sigalrm,中断信号由alarm产生。

使用signal

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

void handler(int s)
{
    printf("read is interrupt by signal handler\n");
    return;
}

int main()
{
    char buf[10];
    int nread = 0;

    signal(sigalrm, handler);
    alarm(2);

    printf("read start\n");
    nread = read(stdin_fileno, buf, sizeof(buf));
    printf("read return\n");

    if ((nread < 0) && (errno == eintr))
    {
        printf("read return failed, errno is eintr\n");
    }

    return 0;
}

Linux被中断的系统调用

使用sigaction + 默认情况

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

void handler(int s)
{
    printf("read is interrupt by signal handler\n");
    return;
}

int main()
{
    char buf[10];
    int nread = 0;
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;
    act.sa_flags = 0;  //不给sigalrm信号设置sa_restart标志,使用sigaction的默认处理方式
    //act.sa_flag |= sa_interrupt;  //sa_interrupt是sigaction的默认处理方式,即不自动重启被中断的系统调用
    //实际上,不管act.sa_flags值为多少,只要不设置sa_restart,sigaction都是按sa_interrupt处理的

    sigaction(sigalrm, &act, null);
    alarm(2);

    printf("read start\n");
    nread = read(stdin_fileno, buf, sizeof(buf));
    printf("read return\n");

    if ((nread < 0) && (errno == eintr))
    {
        printf("read return failed, errno is eintr\n");
    }

    return 0;
}

Linux被中断的系统调用

使用sigaction + 指定sa_restart标志

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

void handler(int s)
{
    printf("read is interrupt by signal handler\n");
    return;
}

int main()
{
    char buf[10];
    int nread = 0;
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;
    act.sa_flags = 0;
    act.sa_flags |= sa_restart;  //给sigalrm信号设置sa_restart标志

    sigaction(sigalrm, &act, null);
    alarm(2);

    printf("read start\n");
    nread = read(stdin_fileno, buf, sizeof(buf));
    printf("read return\n");

    if ((nread < 0) && (errno == eintr))
    {
        printf("read return failed, errno is eintr\n");
    }

    return 0;
}

Linux被中断的系统调用

由于对被中断系统调用处理方式的差异性,因此对应用程序来说,与被中断的系统调用相关的问题是:

  • 应用程序无法保证总是知道信号处理函数的注册方式,以及是否设置了sa_restart标志
  • 可移植的代码必须显式处理关键函数的出错返回,当函数出错且errno等于eintr时,可以根据实际需求进行相应处理,比如重启该函数
int nread = read(fd, buf, 1024);

if (nread < 0)
{
    if (errno == eintr)
    {
        //read被中断,其实不应该算作失败,可以根据实际需求进行处理,比如重写调用read,也可以忽略它
    }
    else
    {
        //read真正的读错误
    }
}