1. 程式人生 > >Linux被中斷的系統呼叫

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;
}

使用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;
}

使用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;
}

由於對被中斷系統呼叫處理方式的差異性,因此對應用程式來說,與被中斷的系統呼叫相關的問題是:

  • 應用程式無法保證總是知道訊號處理函式的註冊方式,以及是否設定了SA_RESTART標誌
  • 可移植的程式碼必須顯式處理關鍵函式的出錯返回,當函數出錯且errno等於EINTR時,可以根據實際需求進行相應處理,比如重啟該函式
int nread = read(fd, buf, 1024);

if (nread < 0)
{
    if (errno == EINTR)
    {
        //read被中斷,其實不應該算作失敗,可以根據實際需求進行處理,比如重寫呼叫read,也可以忽略它
    }
    else
    {
        //read真正的讀錯誤
    }
}