1. 慢系統調用
UNP 5.9提到, 慢系統調用(slow system call)指不會立即返回的系統調用, 可能永遠阻塞而無法返回. 諸如多數網絡支持函數, 包括read/write, connect, accept等, 都屬於這一類.
慢系統調用, 主要分為以下類別:
-
讀寫"慢"設備
包括pipe, fifo, 終端設備, 網絡連接等. 讀時, 數據不存在, 需要等待緩沖區有數據輸入; 寫時, 緩沖區滿, 需要等待緩沖區有空閑位置.
注意: 讀寫磁盤文件一般不會阻塞. -
打開某些特殊文件時, 需要等待某些條件才能打開
如打開終端設備, 需要等待連接設備的modern響應, 才能打開 -
pause和wait系統調用
pause阻塞進程, 直到收到信號喚醒;
wait等待任意子進程終止; -
某些ioctl操作
-
某些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);