慢系統呼叫,指的是可能永遠無法回傳,從而使行程永遠阻塞的系統呼叫,比如無客戶連接時的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真正的讀錯誤
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/163238.html
標籤:Linux
