以open函数为例,该系统API原型为:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
在c语言中,是不可能存在相同函数名的两个函数的,那么可以知道open函数是一个可变参函数,即
open(const char *pathname, int flags, ...)
当flags取值含O_CREAT(文件不存在则创建)值时,可变参用来指定新建的该文件的属性。
根据man手册介绍,open的返回值及容错机制都有说到
open() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
即open()和create()函数如果执行成功就返回该文件的文件描述符,失败则返回-1,失败的原因会被记录在errno中。再往手册下看,可以看到errno的取值,有EACCES,EEXIST,EFAULT,EFBIG等等。可以推测,这些取值是一些宏定义。该API都退出了,还可以打印errno的值来得到错误标识码,所以这些量肯定是全局变量。既然如此,那么这个errno肯定是不可重入的,也就是说它记录的是最近一次API调用的错误码。
这些宏的定义在/usr/include/asm-generic/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 */
测试代码: 可读可写打开当前目录的errno_test文件,但是当前目录并不存在该文件或者目录
int main(void)
{
int fd;
fd = open("./errno_test", O_RDWR);
if (fd < 0)
{
printf("errno: %d\n", errno);
}
close(fd);
return 0;
}
运行结果:
错误码2对应到宏是ENOENT,上面注释也已说明: “No such file or directory”。
对于API使用者来说,一个错误标识码表达错误信息显然还不够对明朗,每次拿到标注码后还要去对照宏定义(的注释内容)显然十分不方便。既然这些标识码有与之对应的错误信息,何不将他们封装成一个指针数组,指针指向错误信息字符串?这样每次拿到错误标注码后就可以作为下标索引得到数组中的错误信息了。
当然,这么简单是事情,标准c库的perror函数已经为我们实现了:
int main(void)
{
int fd;
fd = open("./errno_test", O_RDWR);
if (fd < 0)
{
//printf("errno: %d\n", errno);
perror("open");
}
close(fd);
return 0;
}
运行结果:
perror的用法,也十分巧妙。
Linux API的这种设计方法,值得我们在项目开发中借鉴。