ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

kernel中的IS_ERR函数和PTR_ERR函数的使用

kernel中的IS_ERR函数和PTR_ERR函数的使用

Linux内核中常用的错误处理机制,处理返回指针的函数,其中指针值可能表示错误码

先show一段代码:

iff --git a/drivers/opt_ide1/opt-ide1-dio.c b/drivers/opt_ide1/opt-ide1-dio.c index 0a8bddcc7f6b..709418a4f69a 100755 --- a/drivers/opt_ide1/opt-ide1-dio.c +++ b/drivers/opt_ide1/opt-ide1-dio.c @@ -261,8 +261,7 @@ static irqreturn_t HW_TRI_handler(int irq, void *dev_id) } input_sync(opt_ide1_do->di_inputdev); } - if (!IS_ERR(&opt_ide1_do->gpio_out[OPT_TRIGGER_LED])) - gpio_set_value(opt_ide1_do->gpio_out[OPT_TRIGGER_LED], di0_state_tmp); + gpio_set_value(opt_ide1_do->gpio_out[OPT_TRIGGER_LED], di0_state_tmp); spin_unlock(&opt_ide1_do->spi_lock); return IRQ_HANDLED; @@ -1030,16 +1029,14 @@ static int opt_ide1_do_probe(struct platform_device *pdev) for(i = 0; i < OPT_DO_MAX; i++){ //printk("[hezhensheng] %s OPT_DO_MAX = %d, line = %d \n", __FUNCTION__, OPT_DO_MAX, __LINE__); - if(!IS_ERR(&opt_ide1_do.gpio_out[i])) - gpio_direction_output(opt_ide1_do.gpio_out[i], 0); + gpio_direction_output(opt_ide1_do.gpio_out[i], 0); } gpio_set_value(opt_ide1_do.gpio_out[OPT_HARD_TRIGGER_EN], 0); opt_ide1_do.soft_trig_en = 0; opt_ide1_do.external_light_source_mode = 0; for(i = 0; i < OPT_DI_MAX; i++){ - if(!IS_ERR(&opt_ide1_do.gpio_in[i])) - gpio_direction_input(opt_ide1_do.gpio_in[i]); + gpio_direction_input(opt_ide1_do.gpio_in[i]); }

同事优化了IS_ERR()函数,日常上这份驱动也都没反馈什么问题回来,所以对此函数没还真放多少心思留意。

先来了解一下,基本都是搭配着PTR_ERR函数使用

接着去kernel代码查阅一下是否一样,发现确实如此和PTR_ERR搭配着使用;

在 Linux 内核编程中,很多函数返回的是指针类型(如struct file *,void *),但失败时不能简单地返回NULL,因为NULL可能也是一个合法的返回值(例如表示“没有对象”)。为了复用指针返回值来传递错误码,内核提供了一套编码机制,其中IS_ERR()和PTR_ERR()是最常用的辅助函数。


📖 基本用法

1. 错误码的编码与解码
  • ERR_PTR(err):将错误码err(负值)转换为一个特殊的指针值(通常是将错误码加上一个固定的偏移量)。

  • IS_ERR(ptr):判断一个指针是否代表了一个错误码(即是否是通过ERR_PTR编码的)。

  • PTR_ERR(ptr):从错误指针中提取出原始的错误码(负值)。

通常,我们不需要手动调用ERR_PTR,因为内核函数内部会使用它。我们作为调用者主要使用IS_ERR和PTR_ERR。

2. 典型使用模式
struct my_device *dev = some_kernel_function(...); if (IS_ERR(dev)) { int err = PTR_ERR(dev); pr_err("Failed to get device: %d\n", err); return err; // 或者进行其他错误处理 } // 正常使用 dev

⚠️ 重要注意事项

①必须先检查IS_ERR(),再解引用或调用PTR_ERR()
  • 如果ptr不是错误指针(即是一个有效的指针),对其调用PTR_ERR会得到一个“伪造”的错误码,这个值没有意义且可能引起误导。

  • 必须像上面示例那样,先if (IS_ERR(ptr)),在分支内再使用PTR_ERR。

②区分NULL与错误指针
  • NULL是一个有效的指针值(表示空),它不是错误指针,IS_ERR(NULL)返回false。

  • 许多内核函数用NULL表示“没有对象”或“未找到”,而用错误码表示“发生了严重错误”。

  • 因此检查时通常需要:

    if (!ptr) {/* 表示没有对象,不是错误 */} else if (IS_ERR(ptr)) {/* 表示真正的错误 */} else { /* 有效指针 */ }
③错误码的取值范围有限
  • 内核规定,错误码(负值)必须落在[-4095, -1]范围内(MAX_ERRNO定义为 4095)。

  • 所以ERR_PTR编码后的指针值总是落在(void *)(-4095 ... -1)这个特殊范围内。

  • 超出此范围的负值不会被识别为错误指针。

④类型转换的困惑
  • IS_ERR接受const void *,任何指针类型都可以直接传入,无需强制转换。

  • PTR_ERR返回long类型,通常需要转换为int来使用(内核错误码都是int)。

  • 如果需要将错误指针转换为另一种指针类型(例如从void *转为struct foo *),可以使用ERR_CAST(ptr),它只是做类型转换,等价于(struct foo *)ptr,但能更清晰地表达意图。

⑤不要在中断上下文或原子上下文中长期持有错误指针
  • 错误指针本身只是一个数值,但占用了一个指针槽位。虽然它不是一个真实的内存地址,但在某些锁或引用计数场景下,错误处理路径需要格外小心。


🧪 示例:常见错误处理模式

struct dentry *dentry = lookup_one_len(name, parent, len); if (IS_ERR(dentry)) { /* 真正的错误,比如 -ENOENT, -EACCES 等 */ return PTR_ERR(dentry); } if (!dentry) { /* 理论上 lookup_one_len 不会返回 NULL,但某些函数会 */ return -ENOENT; } /* 正常使用 dentry */

🔧 内核实现简析(可选了解)

  • IS_ERR(ptr)实际上检查(unsigned long)ptr >= (unsigned long)-MAX_ERRNO,即指针值是否落在内核保留的错误区间。

  • PTR_ERR(ptr)直接返回(long)ptr,因为错误指针的数值就是负错误码。


💎 总结

函数作用
ERR_PTR(err)将错误码转为错误指针
IS_ERR(ptr)判断指针是否为错误指针
PTR_ERR(ptr)从错误指针中提取错误码

记住两件事:

  1. 永远先IS_ERR,后PTR_ERR。

  2. NULL不是错误指针,需要单独处理。

掌握这些规则,就能正确、安全地处理内核中返回指针的接口,避免因错误处理不当而导致的崩溃或逻辑混乱。

转载一下:Linux系统调试:深入解析IS_ERR函数的使用_dev is err-CSDN博客

一、为什么需要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()定位调用路径

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进