寫給初學者的Linux errno 錯誤碼機制

kovogo發表於2021-11-10

不同於Java的異常處理機制, 當你使用C更多的接觸到是基於錯誤碼的異常機制, 簡單來說就是當呼叫的函式發生異常時, 程式不會跳轉到一個統一處理異常的地方, 取而代之的是返回一個整型錯誤碼。

可能會有小夥伴有疑問了, 以開啟檔案為例該函式定義如下所示

int open(const char *pathname, int flags);

如果開啟檔案成功, open函式會返回一個檔案描述符(該值大於0), 如果失敗則返回-1。對於開發者來說, 只知道檔案開啟失敗了, 而卻不知道具體原因, 實際上的原因可能是多種多樣的, 如:

  • 檔案不存在
  • 當前程式沒有該檔案的讀寫許可權

那麼該如何知道具體錯誤呢? 這就需要藉助系統為我們提供的errno機制了。

errno

errno是一個定義在errno.h標頭檔案的全域性整型變數,表示當前發生的最後一個錯誤, 只需在程式碼中引用errno.h這個標頭檔案邊可以獲取到這個值。

如以下demo所示, 我們嘗試開啟一個不存在的檔案

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>

int main() {
    int fd;
    fd = open("/test.log", O_RDONLY);
    if (fd == -1) {
        printf("open failed, errno: %d\n", errno);
    }
    return 0;
}

執行該程式, 輸出如下所示:

open failed, errno: 2

可以看出,此時錯誤碼為2, 怎麼知道這個錯誤碼代表什麼意思呢?

有以下方式

moreutils

  1. 安裝moreutils
apt install moreutils
  1. 執行errno 錯誤碼檢視具體的錯誤資訊

可以驗證, 錯誤碼2表示當前檔案或目錄不存在,與我們的預期一致

perror

使用定義在stdio.h中的perror函式可以直接在標準輸出上列印錯誤資訊

該函式定義如下所示, 我們可以在錯誤資訊前附加自己定義的錯誤資訊。

void perror(const char *s);

Demo:

int main() {
    int fd;
    fd = open("/test.log", O_RDONLY);
    if (fd == -1) {
        perror("open failed");
    }
    return 0;
}

輸出:

strerr

如果我們只需要獲取錯誤碼對應的文字以記錄日誌, 可以使用strerr函式, 該函式定義在string.h

Demo:

int main() {
    int fd;
    fd = open("/test.log", O_RDONLY);
    if (fd == -1) {
      char* err_msg = strerror(errno);
      printf("%s\n", err_msg);
    }
    return 0;
}

輸出

執行緒安全的嗎?

相信對於併發問題比較敏感的同學已經意識到了一個問題:這errno是一個整型的全域性變數, 那如果多個執行緒同時執行系統呼叫, 並且都因為不同的原因失敗了, 會不會導致其他執行緒的錯誤資訊全部被最後一個產生錯誤的執行緒給覆蓋掉了? 以及會不會有執行緒安全問題呢?

實際上errno被定義為了執行緒區域性變數, 概念同Java中的ThreadLocal, 即每個執行緒都會有自己的errno變數, 不同執行緒之間不會互相影響。

相關文章