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);
else
return 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
宏将错误指针转换回错误码,并打印出错误信息。