09、Linux 错误处理实验
就算是最简单的字符设备注册也可能会失败,所以之前我们都是通过检查函数返回值来确认操作是否成功。现在我们要用goto语句来实现Linux系统更有效的错误处理方式。
一、goto 语句简介
在编写驱动程序时,必须确保函数执行失败后能正确清理。如果某个步骤失败,必须撤销之前所有已注册的内容。否则,内核会因保留无效的指针而处于不稳定状态。
处理这类错误时,建议使用goto语句。它的结构更清晰:先执行注册步骤,失败时直接跳转到清理代码,按相反顺序撤销所有已注册的内容。
::: 例如: 注册A → 注册B失败 → goto 错误处理 → 先撤销A → 最后退出函数
这种写法能确保每个步骤失败时,都能回退到初始状态,避免系统残留无效配置。 :::
二、IS_ERR()简介
Linux内核中的指针有三种情况:
- 正常指针:指向有效内存地址的正常数据
- 空指针:明确表示"无地址"的NULL值
- 错误指针:表示错误的特殊指针,指向预留的无效地址区域
在64位系统中,内核把错误指针都指向内存最后一页(地址范围从0xfffffffffffff000到0xffffffffffffffff)。当指针位于这个区域时,就说明它是一个错误标记。
内核提供了三个简单函数来处理这类错误指针:
- IS_ERR():检查指针是否属于错误指针
- PTR_ERR():把错误指针转换成具体的错误代码(比如-ENOMEM)
- ERR_PTR():把错误代码转换成对应的错误指针
这些函数都在内核的include/linux/err.h文件里定义,帮助开发者统一管理和识别内存操作中的错误情况。
C
static inline void * __must_check ERR_PTR(long error)
{
return (void *) error;
}
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);
}
static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr)
{
return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
当程序发现指针异常时,可以用PTR_ERR()函数将异常指针转化为具体的错误代码(例如内存不足的-ENOMEM或参数无效的-EINVAL)。这些错误代码都在内核的./uapi/asm-generic/errno-base.h文件里预先定义好了,每个错误号都对应特定的问题类型。
C
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _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 */
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
那么如何判断函数返回的指针是有效地址还是错误码呢? 对于 IS_ERR()的使用, 内核使用案例代码如下所示:
目录:usb/core/phy.c
C
static int usb_phy_roothub_add_phy(struct device *dev, int index,
struct list_head *list)
{
struct usb_phy_roothub *roothub_entry;
struct phy *phy;
phy = devm_of_phy_get_by_index(dev, dev->of_node, index);
if (IS_ERR(phy)) {
if (PTR_ERR(phy) == -ENODEV)
return 0;
else
return PTR_ERR(phy);
}
roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
if (!roothub_entry)
return -ENOMEM;
INIT_LIST_HEAD(&roothub_entry->list);
roothub_entry->phy = phy;
list_add_tail(&roothub_entry->list, list);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26