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

不同於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變數, 不同執行緒之間不會互相影響。

Tags: