Linux错误处理
1. Linux下编号前50错误码
内核定义了许多常见的错误码,如EPERM(操作不允许)、ENOENT(无此文件或目录)、EINTR系统调用被中断)等。
位于文件linux\include\uapi\asm-generic\errno-base.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 */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Invalid system call number */#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
.......................................
...... ...............................
2. linux错误处理函数
Linux 下错误处理函数位于 linux\include\linux\err.h
中
/* SPDX-License-Identifier: GPL-2.0 */
/* 声明遵循GPL-2.0许可证 */#ifndef _LINUX_ERR_H
#define _LINUX_ERR_H/* 包含必要的编译器和类型定义的头文件 */
#include <linux/compiler.h>
#include <linux/types.h>/* 包含架构特定的errno定义 */
#include <asm/errno.h>/** 内核指针包含冗余信息,因此我们可以使用一种方案,通过该方案可以返回错误码或普通指针,* 并且返回值相同。这应该是按架构划分的事情,以允许不同的错误和指针决策。*/
#define MAX_ERRNO 4095
/* 定义最大errno值,用于确定错误指针的范围 */#ifndef __ASSEMBLY__
/* 如果不是汇编代码,则定义以下宏和函数 *//* 检查给定的值是否是一个错误指针的值 */
#define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO)/** 将一个错误码转换为一个错误指针。* 注意:这里直接将错误码(一个long类型)转换为void*类型,* 利用了错误码通常为负数的特性,以及void*指针无法直接表示负地址的特性。*/
static inline void * __must_check ERR_PTR(long error)
{return (void *) error;
}/** 将一个错误指针转换回其对应的错误码。* 注意:这里直接将void*类型的错误指针转换回long类型。*/
static inline long __must_check PTR_ERR(__force const void *ptr)
{return (long) ptr;
}/* 检查给定的指针是否是一个错误指针 */
static inline bool __must_check IS_ERR(__force const void *ptr)
{return IS_ERR_VALUE((unsigned long)ptr);
}/* 检查给定的指针是否为NULL或错误指针 */
static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr)
{return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr);
}/*** ERR_CAST - 显式地将一个错误值指针转换为另一种指针类型* @ptr: 要转换的指针。** 显式地将一个错误值指针转换为另一种指针类型,以便清楚地表明正在执行的操作。* 注意:这个宏实际上并没有进行特殊的转换,只是简单地返回了输入指针。* 它主要用于在代码中清晰地表明正在进行的操作是类型转换。*/
static inline void * __must_check ERR_CAST(__force const void *ptr)
{/* 去除const限定符 */return (void *) ptr;
}/** 如果给定的指针是一个错误指针,则返回其错误码;否则返回0。* 这在需要处理可能返回错误指针的函数调用时非常有用。*/
static inline int __must_check PTR_ERR_OR_ZERO(__force const void *ptr)
{if (IS_ERR(ptr))return PTR_ERR(ptr);elsereturn 0;
}/* 已弃用:建议使用PTR_ERR_OR_ZERO代替 */
#define PTR_RET(p) PTR_ERR_OR_ZERO(p)#endif /* __ASSEMBLY__ */#endif /* _LINUX_ERR_H */
这段代码是Linux内核中用于处理错误指针的宏和函数定义,它们被包含在linux/err.h
头文件中。这个机制允许内核函数在返回指针时,如果发生错误,可以返回一个特殊的“错误指针”,这个指针实际上是一个错误码的负值(通过ERR_PTR
宏创建)。调用者可以使用IS_ERR
、PTR_ERR
等宏和函数来检查和处理这些错误指针。
下面是对这些宏和函数的解释:
-
MAX_ERRNO
:定义了最大错误码值,为4095。这是因为在Linux中,错误码通常是正整数,并且使用-errno
来表示错误指针(通过取负值来避免与有效的内存地址冲突)。 -
IS_ERR_VALUE(x)
:检查给定的值x
是否是一个错误指针的值。它通过比较x
是否大于等于-MAX_ERRNO
来判断。 -
ERR_PTR(error)
:将一个错误码error
转换为一个错误指针。这个宏简单地将错误码(一个long
类型的值)强制转换为void*
类型。 -
PTR_ERR(ptr)
:将一个错误指针ptr
转换回它表示的错误码。这个宏通过强制转换ptr
为long
类型来实现。 -
IS_ERR(ptr)
:检查给定的指针ptr
是否是一个错误指针。它通过调用IS_ERR_VALUE
宏来判断。 -
IS_ERR_OR_NULL(ptr)
:检查给定的指针ptr
是否为NULL
或是一个错误指针。 -
ERR_CAST(ptr)
:将给定的指针ptr
(可能是一个错误指针)显式地转换为另一个指针类型,同时去除其const
属性。这个宏主要用于类型安全的转换,但实际上它并没有改变指针的值或类型(除了去除const
)。 -
PTR_ERR_OR_ZERO(ptr)
:如果ptr
是一个错误指针,则返回它表示的错误码;否则返回0。这个宏通常用于处理可能返回错误指针的函数调用,如果调用成功则返回0。 -
PTR_RET(p)
:这是一个已废弃的宏,其功能与PTR_ERR_OR_ZERO
相同。建议使用PTR_ERR_OR_ZERO
代替PTR_RET
。
这个头文件和其中的宏、函数是Linux内核中处理错误指针的标准方式,它们使得内核代码更加健壮和易于维护。通过检查返回值是否为错误指针,调用者可以适当地处理错误情况,而不会导致未定义的行为或安全漏洞。
3 .举例
一般情况下,返回错误的方式: return -ERROR
例如 #define EPERM 1 /* Operation not permitted */
操作不允许 return -EPERM
实际案例
在Linux内核空间编写驱动程序时,处理“操作不允许”(EPERM
)错误通常涉及检查调用者的权限,并在权限不足时返回适当的错误码。
一个字符设备驱动程序举例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>#define DEVICE_NAME "my_privileged_device"static int major;
static struct class *my_class;
static struct cdev my_cdev;static int my_open(struct inode *inode, struct file *file) {// 在这里检查权限,例如检查调用者是否是root用户if (!capable(CAP_SYS_ADMIN)) { // 检查是否具有系统管理员权限printk(KERN_WARNING "Operation not permitted for %s\n", current->comm);return -EPERM; // 返回EPERM错误码}// 如果权限检查通过,则执行其他打开逻辑(如果有的话)// ...return 0; // 返回0表示成功
}static int my_release(struct inode *inode, struct file *file) {// 释放资源(如果有的话)// ...return 0; // 返回0表示成功
}// 其他文件操作函数,如read、write等,可以根据需要实现
// ...static const struct file_operations my_fops = {.owner = THIS_MODULE,.open = my_open,.release = my_release,// .read = my_read,// .write = my_write,// 其他文件操作可以按需添加
};static int __init my_driver_init(void) {int ret;// 分配主设备号if ((major = register_chrdev(0, DEVICE_NAME, &my_fops)) < 0) {printk(KERN_ALERT "Failed to register character device\n");return major; // 返回错误码}// 创建类my_class = class_create(THIS_MODULE, DEVICE_NAME);if (IS_ERR(my_class)) {unregister_chrdev(major, DEVICE_NAME);printk(KERN_ALERT "Failed to create class\n");return PTR_ERR(my_class); // 返回错误码}// 创建设备节点device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);// 初始化cdev结构并添加到内核cdev_init(&my_cdev, &my_fops);ret = cdev_add(&my_cdev, MKDEV(major, 0), 1);if (ret < 0) {device_destroy(my_class, MKDEV(major, 0));class_destroy(my_class);unregister_chrdev(major, DEVICE_NAME);printk(KERN_ALERT "Failed to add cdev\n");return ret; // 返回错误码}printk(KERN_INFO "Driver initialized successfully\n");return 0; // 返回0表示成功
}static void __exit my_driver_exit(void) {// 清理资源cdev_del(&my_cdev);device_destroy(my_class, MKDEV(major, 0));class_destroy(my_class);unregister_chrdev(major, DEVICE_NAME);printk(KERN_INFO "Driver exited successfully\n");
}module_init(my_driver_init);
module_exit(my_driver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux driver example with permission check");
在这个示例中,my_open
函数检查调用者是否具有系统管理员权限(CAP_SYS_ADMIN
)。如果没有,它将打印一条警告消息并返回-EPERM
错误码。这表示调用者没有足够的权限来打开设备。
对返回错误码的处理举例
IS_ERR_VALUE
, ERR_PTR
, PTR_ERR
, 和 IS_ERR
是一组用于错误处理的宏和函数。它们允许内核函数返回指针类型的值时,能够区分有效的指针地址和错误码(通常是通过负整数表示的)。
#include <linux/errno.h>
#include <linux/types.h>// 假设这是一个可能返回错误的内核函数
void *my_function_that_might_fail(void) {// 模拟一个错误情况return ERR_PTR(-EPERM); // 返回EPERM错误
}// 使用这些宏和函数来处理错误的示例函数
int process_my_function(void) {void *result = my_function_that_might_fail();if (IS_ERR(result)) {// 处理错误,将指针转成错误码long error = PTR_ERR(result);//打印错误码printk(KERN_ERR "my_function_that_might_fail failed with error: %ld\n", error);// 根据错误码执行相应的错误处理逻辑// ...return error; // 将错误码返回给调用者} else if (IS_ERR_OR_NULL(result)) {// 处理NULL指针的情况(虽然在这个例子中不太可能,因为ERR_PTR不会返回NULL)printk(KERN_ERR "my_function_that_might_fail returned NULL\n");// 执行相应的错误处理逻辑// ...return -EINVAL; // 或者其他适当的错误码} else {// 成功情况:处理有效的指针// ...printk(KERN_INFO "my_function_that_might_fail succeeded\n");// 执行成功逻辑// ...return 0; // 返回0表示成功}
}
在这个示例中,my_function_that_might_fail
函数模拟了一个错误情况,并返回了 ERR_PTR(-EPERM)
,表示操作不允许。调用者 process_my_function
使用 IS_ERR
宏来检查返回值是否是一个错误指针。如果是,它使用 PTR_ERR
宏将错误指针转换回错误码,并打印出错误信息。