# POSIX,第 1 部分:錯誤處理
> 原文:<https://github.com/angrave/SystemProgramming/wiki/POSIX%2C-Part-1%3A-Error-handling>
## 什么是 POSIX 錯誤處理?
在其他語言中,您可能會看到使用異常實現的錯誤處理。雖然你在技術上可以在 c 中使用它們(你保留一堆非常 try / catch 塊并使用`setjmp`和`longjmp`分別轉到這些塊),但是 C 中的錯誤處理通常是通過 posix 錯誤處理代碼來完成的看起來像這樣。
```c
int ret = some_system_call()
if(ret == ERROR_CODE){
switch(errno){
// Do different stuff based on the errno number.
}
}
```
在內核中,`goto`的使用被大量用于清理應用程序的不同部分。 **你不應該使用 gotos** 因為它們使代碼更難閱讀。內核中的 getos 是不必要的,所以不要上課。
## 什么是`errno`以及何時設定?
POSIX 定義了一個特殊的整數`errno`,它在系統調用失敗時設置。 `errno`的初始值為零(即沒有錯誤)。當系統調用失敗時,它通常會返回-1 表示錯誤并設置`errno`
## 多線程怎么樣?
每個線程都有自己的`errno`副本。這非常有用;否則一個線程中的錯誤會干擾另一個線程的錯誤狀態。
## 什么時候`errno`重置為零?
除非你專門將它重置為零,否則它不會!當系統調用成功時,他們執行 _ 而不是 _ 重置`errno`的值。
這意味著如果您知道系統調用失敗(例如,它返回-1),您應該只依賴于 errno 的值。
## 使用`errno`有什么問題和最佳做法?
當復雜的錯誤處理使用庫調用或系統調用可能會改變`errno`的值時要小心。實際上,將 errno 的值復制到 int 變量更安全:
```c
// Unsafe - the first fprintf may change the value of errno before we use it!
if (-1 == sem_wait(&s)) {
fprintf(stderr, "An error occurred!");
fprintf(stderr, "The error value is %d\n", errno);
}
// Better, copy the value before making more system and library calls
if (-1 == sem_wait(&s)) {
int errno_saved = errno;
fprintf(stderr, "An error occurred!");
fprintf(stderr, "The error value is %d\n", errno_saved);
}
```
同樣,如果您的信號處理程序進行任何系統或庫調用,那么最好保存 errno 的原始值并在返回之前恢復該值:
```c
void handler(int signal) {
int errno_saved = errno;
// make system calls that might change errno
errno = errno_saved;
}
```
## 如何打印出與特定錯誤號相關聯的字符串消息?
使用`strerror`獲取錯誤值的簡短(英文)描述
```c
char *mesg = strerror(errno);
fprintf(stderr, "An error occurred (errno=%d): %s", errno, mesg);
```
## perror 和 strerror 有什么關系?
在之前的頁面中,我們使用 perror 將錯誤打印到標準錯誤。使用`strerror`,我們現在可以編寫`perror`的簡單實現:
```c
void perror(char *what) {
fprintf(stderr, "%s: %s\n", what, strerror(errno));
}
```
## 使用 strerror 有什么問題?
不幸的是`strerror`不是線程安全的。換句話說,兩個線程不能同時調用它!
有兩種解決方法:首先,我們可以使用互斥鎖定義臨界區和本地緩沖區。所有調用`strerror`的地方的所有線程都應該使用相同的互斥鎖
```c
pthread_mutex_lock(&m);
char *result = strerror(errno);
char *message = malloc(strlen(result) + 1);
strcpy(message, result);
pthread_mutex_unlock(&m);
fprintf(stderr, "An error occurred (errno=%d): %s", errno, message);
free(message);
```
或者使用較不便攜但線程安全的`strerror_r`
## 什么是 EINTR? sem_wait 是什么意思?讀?寫?
當信號(例如 SIGCHLD,SIGPIPE,...)傳遞給過程時,某些系統調用可能會中斷。此時系統調用可能會返回而不執行任何操作!例如,字節可能未被讀/寫,信號量等待可能沒有等待。
可以通過檢查返回值以及`errno`是否為 EINTR 來檢測此中斷。在這種情況下,應重試系統調用。通常會看到包含系統調用的以下類型的循環(例如 sem_wait)。
```c
while ((-1 == systemcall(...)) && (errno == EINTR)) { /* repeat! */}
```
小心寫`== EINTR`,而不是`= EINTR`。
或者,如果結果值需要稍后使用...
```c
while ((-1 == (result = systemcall(...))) && (errno == EINTR)) { /* repeat! */}
```
在 Linux 上,將`read`和`write`調用到本地磁盤通常不會返回 EINTR(而是自動為您重新啟動該功能)。但是,在對應于網絡流 _ 的文件描述符上調用`read`和`write`可以 _ 返回 EINTR。
## 哪些系統調用可能被中斷并需要包裝?
使用 man 頁面!手冊頁包括可由系統調用設置的錯誤列表(即錯誤值)。經驗法則是“慢”(阻塞)調用(例如,寫入套接字)可能會被中斷,但快速非阻塞調用(例如 pthread_mutex_lock)則不會。
來自 linux 信號 7 手冊頁。
“如果在阻止系統調用或庫函數調用時調用信號處理程序,則:
* 信號處理程序返回后,調用自動重啟;要么
* 調用失敗并顯示錯誤 EINTR。發生這兩種行為中的哪一種取決于接口以及是否使用 SA_RESTART 標志建立了信號處理程序(請參閱 sigaction(2))。 UNIX 系統的細節各不相同;下面是 Linux 的詳細信息。
如果對信號處理程序中斷對以下某個接口的阻塞調用,則在使用 SA_RESTART 標志后,如果信號處理程序返回,則將自動重新啟動該調用。否則呼叫將失敗并顯示錯誤 EINTR:
* read(2),readv(2),write(2),writev(2)和 ioctl(2)調用“慢”設備。 “慢”設備是 I / O 調用可能無限期阻塞的設備,例如終端,管道或套接字。 (根據此定義,磁盤不是慢速設備。)如果慢速設備上的 I / O 調用在信號處理程序中斷時已經傳輸了某些數據,則該調用將返回成功狀態(通常,傳輸的字節數)。 “
注意,很容易相信設置'SA_RESTART'標志足以使整個問題消失。不幸的是,這不是真的:仍有系統調用可能提前返回并設置`EINTR`!有關詳細信息,請參見[信號(7)](https://cs-education.github.io/sysassets/man_pages/html/man7/signal.7.html)。
## Errno 例外嗎?
有些 POSIX 實用程序每個人都有自己的錯誤。一種是當你調用`getaddrinfo`時檢查錯誤并轉換為字符串的函數是 [gai_strerr](https://linux.die.net/man/3/gai_strerror) 。不要混淆他們!
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話