今天在阅读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);
}