IS_ERR PTR_ERR ERR_PTR原理小记

96
Jiang阿涵
0.2 2017.10.20 18:59* 字数 550

今天在阅读Linux内核源代码的时候,看到了IS_ERR这个函数,觉得很有意思,深入地了解了一下,现把学习的结果记录一下。

IS_ERR宏的出现,是为了解决函数返回值的问题。由于C语言的函数返回值只能有一个,如果一个函数在正常执行后,返回一个指针,而在出现错误的时候,返回错误码,如何来实现这个需求呢?下面是一段示例代码:

void *p = NULL;

int err = func(&p);

if (err == 0)
{
    // p有效
}
else
{
    // p无效
}

可以看到,函数func返回的表示错误码,而指针则通过一个额外的参数来传递。我们能不能让func直接返回一个指针,如果失败了,让指针信息里包含错误码的信息呢?答案是可以的。

Linux支持的每个体系结构的虚拟地址空间都有从一个虚拟地址0到至少4KiB的区域,该区域没有任何有意义的信息。因此内核可以重用该地址范围来编码错误码。使用这种方法,总体来说,如果内核返回一个指针,那么有三种情况:合法指针、NULL指针和非法指针。

  • 合法指针:内核返回的指针一般是指向页面的边界(4K边界对齐),即ptr $0xfff == 0
  • 非法指针:这样的ptr的值不可能落在(0xfffff000, 0xffffffff)之间(这个区间是内核的高地址区间),而一般内核的出错代码也是一个小负数,在-1000到0之间,转换成unsigned long类型,正好在(0xfffff000, 0xffffffff)之间。因此可以用(unsigned long)ptr > (unsigned long)-MAX_ERRNO来判断内核函数返回值是一个有效的指针还是一个出错码。

与IS_ERR配套使用的宏还有PTR_ERR和ERR_PTR,分别用于将指针转换成错误码,和把错误码转换成指针。下面是从Linux源代码中找到的相关源代码,出自:include/linux/err.h

/*
 * Kernel pointers have redundant information, so we can use a
 * scheme where we can return either an error code or a dentry
 * 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((x) >= (unsigned long)-MAX_ERRNO)

static inline void * __must_check ERR_PTR(long error)
{
    return (void *) error;
}

static inline long __must_check PTR_ERR(const void *ptr)
{
    return (long) ptr;
}

static inline long __must_check IS_ERR(const void *ptr)
{
    return IS_ERR_VALUE((unsigned long)ptr);
}

static inline long __must_check IS_ERR_OR_NULL(const void *ptr)
{
    return !ptr || IS_ERR_VALUE((unsigned long)ptr);
}
操作系统