這篇博客介紹了unix錯誤處理中重要的概念:
errno,介紹了它的定義,作用和注意事項
一. 錯誤總是不可避免的...
在計算機中因為各種內(nèi)外部原因,錯誤是不可避免的,異常處理是程序的組成部分。
比如,我們想獲取一個文件的基本信息,可以調(diào)用系統(tǒng)函數(shù)
int stat(const char *restrict pathname, struct stat *restrict buf);
如果成功,文件的信息會通過第二個參數(shù)buf返回;但也可能會失敗,失敗的原因可能是
文件不存在,也可能是沒有訪問權(quán)限或者其他原因。
那么操作系統(tǒng)如何告訴我們發(fā)生了錯誤?更進一步,操作系統(tǒng)如何告知調(diào)用者錯誤的具體
原因呢?
根據(jù)stat函數(shù)的man文檔,我們知道,可以通過返回值來判斷是否成功,返回值等于0表
示成功,返回-1表示失敗
那具體錯誤原因呢?這時errno就登場了,errno相當(dāng)于一個錯誤碼,當(dāng)stat發(fā)生
錯誤時,會設(shè)置errno的值,我們可以通過檢查errno來得到具體的錯誤信息。
一般的代碼片段如下:
struct stat sb;
if (stat("./not_exist.txt", &sb) == -1)
{
printf("errorno is: %d\n", errno);
printf("error msg produced by strerror(): %s\n", strerror(errno));
perror("error msg produced by perror");
}
上述代碼中,strerror和perror是兩個有用的函數(shù)。strerror能夠?qū)?code>errno轉(zhuǎn)化為
對應(yīng)錯誤信息的字符串,perror能夠直接打印出錯誤信息,errno沒有出現(xiàn)在perror
的輸入?yún)?shù)中,這個問題后面在講
二 常見錯誤碼
Linux中可以通過man errno.3指令來查看常見錯誤碼,錯誤碼作為常量定義在errno.h
文件中,常見的錯誤碼定義如下(取自errno-bash.h文件):
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
錯誤碼可以分為兩類:致命錯誤和非致命錯誤。
致命錯誤無法恢復(fù),程序只能立刻終止運行
非致命錯誤大多數(shù)情況下暫時的(比如網(wǎng)絡(luò)短暫異常),程序可以處理這些錯誤,稍后重試,
增強程序健壯性
三 注意事項
errno有兩個性質(zhì):
- 正常的系統(tǒng)調(diào)用不會改變
errno的值,只有錯誤的系統(tǒng)調(diào)用才會修改errno的值
這意味著,errno始終記錄著最近一次調(diào)用錯誤的錯誤碼,我們不能通過檢查errno來
判斷是否發(fā)生錯誤,是否發(fā)生錯誤需要通過系統(tǒng)調(diào)用的返回值來判斷,在確定發(fā)生了錯誤
之后,在去檢查errno獲得具體的錯誤原因。
這個性質(zhì)也是一開始提到的perror不需要errno作為參數(shù)的原因。 -
errno不會等于0
四 errno的內(nèi)部實現(xiàn)
errno是一個整數(shù),可以簡單實現(xiàn)為:
extern int errno
上面的實現(xiàn)方式非常簡單,可惜在多線程的情況下會發(fā)生錯誤,因為當(dāng)多個線程需要同時修改
errno時,前一個線程的值會被后一個線程覆蓋,因此,每一個現(xiàn)成需要自己的errno
Linux中,errno的真實定義是:
extern int *_ _errno_location(void);
#define errno (*_ _errno_location())
例子
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <string.h> //strerror()
/**
* When an error occurs in one of the UNIX System functions, a negative value
* is often returned, and the integer errno is usually set to a value that
* tells why
*/
int
main(int argc, char* argv[])
{
struct stat sb;
if (stat("./not_exist.txt", &sb) == -1)
{
printf("errorno is: %d\n", errno);
printf("error msg produced by strerror(): %s\n", strerror(errno));
perror("error msg produced by perror");
}
if (stat("./errno.c", &sb) == 0)
{
//errno is never cleared by a routine if an error does not occur
perror("last error msg is");
}
}
輸出結(jié)果為:
errorno is: 2
error msg produced by strerror(): No such file or directory
error msg produced by perror: No such file or directory
last error msg is: No such file or directory
參考資料
- Linux系統(tǒng)文件:
/usr/include/asm-generic/errno-base.h - Advanced Programming in the Unix Environment(3rd) P14 Error Handling