当前位置:网站首页 > 更多 > 玩电脑 > 正文

[系统技巧] 如何理解Linux内核IS_ERR、ERR_PTR、PTR_ERR

作者:CC下载站 日期:2023-10-11 23:53:50 浏览:29 分类:玩电脑

[系统技巧] 如何理解Linux内核IS_ERR、ERR_PTR、PTR_ERR

一、如何理解一些特殊的符号

1)inline:内联函数。内联函数的代码会直接嵌入到调用它的位置,调用几次就复制几次。

2)__must_check:调用函数时一定要处理函数的返回值,否则编译器会给出警告。

3)unlikely:告诉编译器括号内的值发生的概率很低,编译器就根据这个提示信息去做一些分支预测的编译优化。

4)(unsigned long)-MAX_ERRNO:用补码的方式表示-4095,32位系统为0xfffff001。

二、内核空间的指针类型 

1)有效指针;
2)NULL,空指针;
3)错误指针,或者说无效指针。

三、如何理解内核空间最大的错误码

1. 对于32位的系统来说,内核空间占用 3~4G(0xc0000000~0xffffffff)的虚拟地址。其中Linux采用了分页机制来管理内存,而CPU访问的是线性地址需要通过页表来转化成物理地址。所以,内核就约定了留出最后一个page(假设4k一个page)0xfffff000~0xffffffff,专门用来记录内核空间的错误指针。

2. 在 <linux/err.h> 中定义了 #define MAX_ERRNO  4095 最大错误码为 4095。至于这里为什么是 4095,而不是其它值?
我们知道负数在计算机中以二进制补码形式存储。因此,(unsigned long)-MAX_ERRNO 的值为0xfffff001,也就是大于等于 0xfffff001 的指针为非法指针。

注意 (unsigned long)-MAX_ERRNO 并不是 (unsigned long)减去MAX_ERRNO,而是对 -MAX_ERRNO 进行强制类型转化,其应该等价于  (unsigned long)(-MAX_ERRNO),-4095转换为无符号long型是0xfffff001,即:

#define IS_ERR_VALUE(x) unlikely((x) >= 0xfffff001)

四、解读内核相关内联函数的含义

1)IS_ERR() 调用了 IS_ERR_VALUE(),并将指针强转为 unsigned long 类型的错误码:IS_ERR() 判断指针是否非法,如果指针 < 0xfffff001 是有效的;如果指针 >= 0xfffff001 是无效的。即在 (0xfffff001,0xffffffff) 之间的就是错误指针。因此,可以用IS_ERR()来判断内核的指针是否有效。所以在内核中能经常看到返回负数的错误码如:-ENODEV。 参考内核对该函数的使用:

static struct sp_node *sp_alloc(unsigned long start, unsigned long end,
				struct mempolicy *pol)
{
	struct sp_node *n;
	struct mempolicy *newpol;

	n = kmem_cache_alloc(sn_cache, GFP_KERNEL);
	if (!n)
		return NULL;

	newpol = mpol_dup(pol);
	if (IS_ERR(newpol)) {
		kmem_cache_free(sn_cache, n);
		return NULL;
	}
	newpol->flags |= MPOL_F_SHARED;
	sp_node_init(n, start, end, newpol);

	return n;
}

2)ERR_PTR() 将一个错误码强转为一个错误指针,并作为函数的返回值使用。 参考内核对该函数的使用:

struct net_device *tc515_probe(int unit)
{
	struct net_device *dev = corkscrew_scan(unit);
	static int printed;

	if (!dev)
		return ERR_PTR(-ENODEV);

	if (corkscrew_debug > 0 && !printed) {
		printed = 1;
		pr_debug("%s", version);
	}

	return dev;
}

3)PTR_ERR() 将一个错误指针强转为一个错误码,并作为函数的返回值使用。 参考内核对该函数的使用:

static int vma_replace_policy(struct vm_area_struct *vma,
						struct mempolicy *pol)
{
	int err;
	struct mempolicy *old;
	struct mempolicy *new;

	pr_debug("vma %lx-%lx/%lx vm_ops %p vm_file %p set_policy %p\n",
		 vma->vm_start, vma->vm_end, vma->vm_pgoff,
		 vma->vm_ops, vma->vm_file,
		 vma->vm_ops ? vma->vm_ops->set_policy : NULL);

	new = mpol_dup(pol);
	if (IS_ERR(new))
		return PTR_ERR(new);

	if (vma->vm_ops && vma->vm_ops->set_policy) {
		err = vma->vm_ops->set_policy(vma, new);
		if (err)
			goto err_out;
	}

	old = vma->vm_policy;
	vma->vm_policy = new; /* protected by mmap_sem */
	mpol_put(old);

	return 0;
 err_out:
	mpol_put(new);
	return err;
}

五、附上内核源码(include/linux/err.h)

#include <linux/compiler.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 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

#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);
}


您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯