一、为什么需要is_err?
在linux内核开发中,内核空间的函数(如内存分配、设备驱动接口)无法像用户空间那样直接返回-enomem
或-einval
等错误码,因为它们的返回值类型通常是指针。为此,内核采用了一种巧妙的方式:将错误码编码到指针值中,而is_err()
正是用来检测这种特殊指针的关键工具。
二、is_err的原理
错误码的编码规则
- 内核将错误码(如
-enomem
)转换为指针的形式,具体实现依赖于体系结构。 - 例如在x86-64中,错误码会被转换为
(void *)(-4095ul ~ -1ul)
区间的虚拟地址(即最高有效位为1的地址)。 - 当指针值在 0xfffff000~0xffffffffffffffff范围时,is_err()返回true。
核心函数解析
// 判断指针是否为错误码 bool is_err(const void *ptr); // 从错误指针中提取原始错误码 long ptr_err(const void *ptr); // 将错误码转换为指针 void *err_ptr(long error);
三、实际使用场景
示例:字符设备驱动中的内存分配
static int mydev_open(struct inode *inode, struct file *filp) { struct my_device *dev = kmalloc(sizeof(*dev), gfp_kernel); if (!dev) return -enomem; // 用户空间可以直接返回错误码 // 内核函数返回指针的错误处理 dev->regs = ioremap(device_base, size); if (is_err(dev->regs)) { int err = ptr_err(dev->regs); kfree(dev); return err; // 将错误传递到用户空间 } return 0; }
四、常见错误及调试技巧
典型错误案例
void *ptr = vmalloc(1024); if (is_err(ptr)) { // 错误!vmalloc失败时返回null,而非错误指针 printk("allocation failed: %ld\n", ptr_err(ptr)); return; }
✅ 正确做法:对可能返回错误指针的函数(如devm_clk_get()
)使用is_err
,对返回null的函数(如kmalloc()
)直接判空。
调试技巧
- 打印错误码:
printk("error code: %ld\n", ptr_err(ptr));
- 使用
dump_stack()
定位调用路径
五、底层实现解析( include/linux/err.h)
/* spdx-license-identifier: gpl-2.0 */ #ifndef _linux_err_h #define _linux_err_h #include <linux/compiler.h> #include <linux/types.h> #include <asm/errno.h> /* * kernel pointers have redundant information, so we can use a * scheme where we can return either an error code or a normal * pointer with the same return value. * * this should be a per-architecture thing, to allow different * error and pointer decisions. */ #define max_errno 4095 #ifndef __assembly__ #define is_err_value(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-max_errno) 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); } /** * err_cast - 显式将带有错误值的指针转换为另一种指针类型 * @ptr: 需要转换的指针。 * * 以一种明确的方式,将带有错误值的指针显式转换为另一种指针类型。 */ static inline void * __must_check err_cast(__force const void *ptr) { // 去除 const 限定符并进行类型转换 return (void *) ptr; } static inline int __must_check ptr_err_or_zero(__force const void *ptr) { if (is_err(ptr)) return ptr_err(ptr); else return 0; } #endif #endif /* _linux_err_h */
当指针值在0xfffff000~0xffffffffffffffff
范围时,is_err()
返回true
。
函数/宏 | 功能描述 | 使用场景 |
---|---|---|
ptr_err_or_zero | 检查指针是否为错误指针,如果是则返回错误码,否则返回 0 | 简化错误处理逻辑,直接获取错误码或成功标志。 |
err_cast | 将带限定符的指针转换为普通指针,保留错误信息 | 需要将错误指针传递给期望不同类型的函数时使用。 |
is_err_or_null | 检查指针是否为 null 或错误指针 | 同时判断指针是否为空或包含错误信息,适用于返回值可能为 null 或错误指针的情况。 |
六、总结
场景 | 正确用法 | 错误用法 |
---|---|---|
内存分配失败 | if (!ptr) | is_err(ptr) |
资源获取类函数 | if (is_err(ptr)) | 直接判空 |
错误传递 | return ptr_err(ptr); | 返回未经转换的指针 |
掌握is_err
系列函数的使用,是linux内核调试的重要基础。它不仅能帮助开发者准确定位资源分配错误,更是理解内核错误处理机制的关键入口。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论