linux内核中的IS

linux内核中的IS_ERR()、PTR_ERR()和ERR_PTR()

在看内核源码的时候,经常会遇到IS_ERR,比如在linux/arch/arm/kernel/sys_arm.c中

asmlinkage int sys_execve(char __user *filenamei, char __user * __user *argv,  char __user * __user *envp, struct pt_regs *regs){int error;char * filename;filename = getname(filenamei);error = PTR_ERR(filename);if (IS_ERR(filename))goto out;error = do_execve(filename, argv, envp, regs);putname(filename);out:return error;}

IS_ERR宏定义在include/linux/err.h,如下所示:

#ifndef _LINUX_ERR_H#define _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 IS_ERR_VALUE(x) unlikely((x) > (unsigned long)-1000L)static inline void *ERR_PTR(long error){return (void *) error;}static inline long PTR_ERR(const void *ptr){return (long) ptr;}static inline long IS_ERR(const void *ptr){return IS_ERR_VALUE((unsigned long)ptr);}#endif /* _LINUX_ERR_H */

下面我们就来具体分析一下这段代码,看看内核中的巧妙设计思路。

要想明白IS_ERR(),首先理解要内核空间。所有的驱动程序都是运行在内核空间,内核空间虽然很大,但总是有限的,而在这有限的空间中,其最后一个page是专门保留的,也就是说一般人不可能用到内核空间最后一个page的指针。换句话说,你在写设备驱动程序的过程中,涉及到的任何一个指针,必然有三种情况:

    有效指针;NULL,空指针;错误指针,或者说无效指针。

而所谓的错误指针就是指其已经到达了最后一个page,即内核用最后一页捕捉错误。比如对于32bit的系统来说,内核空间最高地址0xffffffff,那么最后一个page就是指的0xfffff000~0xffffffff(假设4k一个page),这段地址是被保留的。内核空间为什么留出最后一个page?我们知道一个page可能是4k,也可能是更多,比如8k,但至少它也是4k,所以留出一个page出来就可以让我们把内核空间的指针来记录错误了。内核返回的指针一般是指向页面的边界(4k边界),即ptr & 0xfff == 0。如果你发现你的一个指针指向这个范围中的某个地址,那么你的代码肯定出错了。IS_ERR()就是判断指针是否有错,如果指针并不是指向最后一个page,那么没有问题;如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针,这个指针里保存的实际上是一种错误代码。而通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码。因此,判断一个指针是不是有效的,可用如下的方式:

#define IS_ERR_VALUE(x) unlikely((x) > (unsigned long)-1000L)

(unsigned long)-1000L 应该为 (unsigned long)-0x1000L!(因为 -0x1000 才是 0xFFFFF000),这应该是内核的一个bug吧!在2.6.30.4的内核中是这样定义的:

#define MAX_ERRNO4095#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)

即判断是不是在(0xfffff000,0xffffffff)之间,因此,可以用IS_ERR()来判断内核函数的返回值是不是一个有效的指针。注意这里用unlikely()的用意!

至于PTR_ERR(), ERR_PTR(),只是强制转换以下而已。现在应该知道为什么我写返回错误码的时候也加个负号如 -ENOSYS这样子了。而PTR_ERR()只是返回错误代码,也就是提供一个信息给调用者,如果你只需要知道是否出错,而不在乎因为什么而出错,那你当然不用调用PTR_ERR()了。

而我们的错误码的值在内存中定义都是这样的(asm-generic/errno-base.h):

......#defineEPERM 1/* Operation not permitted */#defineENOENT 2/* No such file or directory */#defineESRCH 3/* No such process */#defineEINTR 4/* Interrupted system call */#defineEIO 5/* I/O error */#defineENXIO 6/* No such device or address */#defineE2BIG 7/* Argument list too long */#defineENOEXEC 8/* Exec format error */#defineEBADF 9/* Bad file number */#defineECHILD10/* No child processes */#defineEAGAIN11/* Try again */#defineENOMEM12/* Out of memory */#defineEACCES13/* Permission denied */#defineEFAULT14/* Bad address */#defineENOTBLK15/* Block device required */#defineEBUSY16/* Device or resource busy */#defineEEXIST17/* File exists */#defineEXDEV18/* Cross-device link */#defineENODEV19/* No such device */#defineENOTDIR20/* Not a directory */#defineEISDIR21/* Is a directory */#defineEINVAL22/* Invalid argument */#defineENFILE23/* File table overflow */#defineEMFILE24/* Too many open files */#defineENOTTY25/* Not a typewriter */#defineETXTBSY26/* Text file busy */#defineEFBIG27/* File too large */#defineENOSPC28/* No space left on device */#defineESPIPE29/* Illegal seek */#defineEROFS30/* Read-only file system */#defineEMLINK31/* Too many links */#defineEPIPE32/* Broken pipe */#defineEDOM33/* Math argument out of domain of func */#defineERANGE34/* Math result not representable */........

如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针。这个指针里保存的实际上是一种错误代码。而通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码。我想有一天和你去旅行。去那没有去过的地方,

linux内核中的IS

相关文章:

你感兴趣的文章:

标签云: