
就算是最简单的字符设备注册也可能会失败,所以之前我们都是通过检查函数返回值来确认操作是否成功。现在我们要用 goto 语句来实现 Linux 系统更有效的错误处理方式。
一、goto 语句简介
在编写驱动程序时,必须确保函数执行失败后能正确清理。如果某个步骤失败,必须撤销之前所有已注册的内容。否则,内核会因保留无效的指针而处于不稳定状态。
处理这类错误时,建议使用 goto 语句。它的结构更清晰:先执行注册步骤,失败时直接跳转到清理代码,按相反顺序撤销所有已注册的内容。
示例
注册 A → 注册 B 失败 → goto 错误处理 → 先撤销 A → 最后退出函数
这种写法能确保每个步骤失败时,都能回退到初始状态,避免系统残留无效配置。
二、IS_ERR() 简介
Linux 内核中的指针有三种情况:
- 正常指针:指向有效内存地址的正常数据
- 空指针:明确表示"无地址"的 NULL 值
- 错误指针:表示错误的特殊指针,指向预留的无效地址区域
在 64 位系统中,内核把错误指针都指向内存最后一页(地址范围从 0xfffffffffffff000 到 0xffffffffffffffff)。当指针位于这个区域时,就说明它是一个错误标记。
内核提供了三个简单函数来处理这类错误指针:
IS_ERR():检查指针是否属于错误指针PTR_ERR():把错误指针转换成具体的错误代码ERR_PTR():把错误代码转换成对应的错误指针
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 文件里预先定义好了。
内核使用案例
文件位置:drivers/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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24