信号中断 与 慢系统调用
1. 術語
1.1. 慢系統調用(Slow system call)
該術語適用于那些可能永遠阻塞的系統調用。永遠阻塞的系統調用是指調用永遠無法返回,多數網絡支持函數都屬于這一類。如:若沒有客戶連接到服務器上,那么服務器的accept調用就會一直阻塞。
慢系統調用可以被永久阻塞,包括以下幾個類別:
(1)讀寫‘慢’設備(包括pipe,終端設備,網絡連接等)。讀時,數據不存在,需要等待;寫時,緩沖區滿或其他原因,需要等待。讀寫磁盤文件一般不會阻塞。
(2)當打開某些特殊文件時,需要等待某些條件,才能打開。例如:打開中斷設備時,需要等到連接設備的modem響應才能完成。
(3)pause和wait函數。pause函數使調用進程睡眠,直到捕獲到一個信號。wait等待子進程終止。
(4)某些ioctl操作。
(5)某些IPC操作。
2. EINTR介紹
2.1. EINTR錯誤產生的原因
早期的Unix系統,如果進程在一個慢系統調用(slow system call)中阻塞時,當捕獲到某個信號且相應信號處理函數返回時,這個系統調用被中斷,調用返回錯誤,設置errno為EINTR(相應的錯誤描述為“Interrupted system call”)。
怎么看哪些系統條用會產生EINTR錯誤呢?用man啊!
如下表所示的系統調用就會產生EINTR錯誤,當然不同的函數意義也不同。
?
| 系統調用函數 | errno為EINTR表征的意義 |
| write | 由于信號中斷,沒寫成功任何數據。 The call was interrupted by a signal before any data was written. |
| open | 由于信號中斷,沒讀到任何數據。 The call was interrupted by a signal before any data was read. |
| recv | 由于信號中斷返回,沒有任何數據可用。 The receive was interrupted by delivery of a signal before any data were available. |
| sem_wait | 函數調用被信號處理函數中斷。 The call was interrupted by a signal handler. |
?
2.2. 如何處理被中斷的系統調用
既然系統調用會被中斷,那么別忘了要處理被中斷的系統調用。有三種處理方式:
◆ 人為重啟被中斷的系統調用
◆ 安裝信號時設置 SA_RESTART屬性(該方法對有的系統調用無效)
◆? 忽略信號(讓系統不產生信號中斷)
2.2.1. 人為重啟被中斷的系統調用
人為當碰到EINTR錯誤的時候,有一些可以重啟的系統調用要進行重啟,而對于有一些系統調用是不能夠重啟的。例如:accept、read、write、select、和open之類的函數來說,是可以進行重啟的。不過對于套接字編程中的connect函數我們是不能重啟的,若connect函數返回一個EINTR錯誤的時候,我們不能再次調用它,否則將立即返回一個錯誤。針對connect不能重啟的處理方法是,必須調用select來等待連接完成。
這里的“重啟”怎么理解?
一些IO系統調用執行時,如 read 等待輸入期間,如果收到一個信號,系統將中斷read, 轉而執行信號處理函數. 當信號處理返回后, 系統遇到了一個問題: 是重新開始這個系統調用, 還是讓系統調用失敗?早期UNIX系統的做法是, 中斷系統調用,并讓系統調用失敗, 比如read返回 -1, 同時設置 errno 為EINTR中斷了的系統調用是沒有完成的調用,它的失敗是臨時性的,如果再次調用則可能成功,這并不是真正的失敗,所以要對這種情況進行處理, 典型的方式為:
[cpp]?view plaincopy
可以去github上看看別人怎么處理EINTR錯誤的。在github上搜索“==EINTR”關鍵字就有一大堆了。摘取幾個看看:
[cpp]?view plaincopy
[cpp]?view plaincopy
2.2.2. 安裝信號時設置 SA_RESTART屬性
?我們還可以從信號的角度來解決這個問題,? 安裝信號的時候, 設置 SA_RESTART屬性,那么當信號處理函數返回后, 不會讓系統調用返回失敗,而是讓被該信號中斷的系統調用將自動恢復。
[cpp]?view plaincopy但注意,并不是所有的系統調用都可以自動恢復。如msgsnd喝msgrcv就是典型的例子,msgsnd/msgrcv以block方式發送/接收消息時,會因為進程收到了信號而中斷。此時msgsnd/msgrcv將返回-1,errno被設置為EINTR。且即使在插入信號時設置了SA_RESTART,也無效。在man msgrcv中就有提到這點:
| msgsnd and msgrcv are never automatically restarted after being interrupted by a signal handler, regardless of the setting? of the SA_RESTART flag when establishing a signal? handler. |
?
2.2.3. 忽略信號
當然最簡單的方法是忽略信號,在安裝信號時,明確告訴系統不會產生該信號的中斷。
[cpp]?view plaincopy
3. 測試代碼
為了方便大家測試,這里附上兩段測試代碼。
3.1. 測試代碼一
鬧鐘信號SIGALRM中斷read系統調用。安裝SIGALRM信號時如果不設置SA_RESTART屬性,信號會中斷read系統過調用。如果設置了SA_RESTART屬性,read就能夠自己恢復系統調用,不會產生EINTR錯誤。
[cpp]?view plaincopy
3.2. 測試代碼二
鬧鐘信號SIGALRM中斷msgrcv系統調用。即使在插入信號時設置了SA_RESTART,也無效。
[cpp]?view plaincopy
4. 總結
慢系統調用(slow system call)會被信號中斷,系統調用函數返回失敗,并且errno被置為EINTR(錯誤描述為“Interrupted system call”)。
處理方法有以下三種:①人為重啟被中斷的系統調用;②安裝信號時設置 SA_RESTART屬性;③忽略信號(讓系統不產生信號中斷)。
有時我們需要捕獲信號,但又考慮到第②種方法的局限性(設置 SA_RESTART屬性對有的系統無效,如msgrcv),所以在編寫代碼時,一定要“人為重啟被中斷的系統調用”。
?
總結
以上是生活随笔為你收集整理的信号中断 与 慢系统调用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iPhone 14全系详细配置出炉 标准
- 下一篇: lsb_release -a 查询系统版