linux内核中的IS_ERR

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

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

[plain]  view plain copy print ?
  1. asmlinkage int sys_execve(char __user *filenamei, char __user * __user *argv,  
  2.               char __user * __user *envp, struct pt_regs *regs)  
  3. {  
  4.     int error;  
  5.     char * filename;  
  6.   
  7.   
  8.     filename = getname(filenamei);  
  9.     error = PTR_ERR(filename);  
  10.     if (IS_ERR(filename))  
  11.         goto out;  
  12.     error = do_execve(filename, argv, envp, regs);  
  13.     putname(filename);  
  14. out:  
  15.     return error;  
  16. }  
IS_ERR宏定义在include/linux/err.h,如下所示:

[plain]  view plain copy print ?
  1. #ifndef _LINUX_ERR_H  
  2. #define _LINUX_ERR_H  
  3.   
  4. #include <linux/compiler.h>  
  5.   
  6. #include <asm/errno.h>  
  7.   
  8. /*  
  9.  * Kernel pointers have redundant information, so we can use a  
  10.  * scheme where we can return either an error code or a dentry  
  11.  * pointer with the same return value.  
  12.  *  
  13.  * This should be a per-architecture thing, to allow different  
  14.  * error and pointer decisions.  
  15.  */  
  16. #define IS_ERR_VALUE(x) unlikely((x) > (unsigned long)-1000L)  
  17.   
  18. static inline void *ERR_PTR(long error)  
  19. {  
  20.     return (void *) error;  
  21. }  
  22.   
  23. static inline long PTR_ERR(const void *ptr)  
  24. {  
  25.     return (long) ptr;  
  26. }  
  27.   
  28. static inline long IS_ERR(const void *ptr)  
  29. {  
  30.     return IS_ERR_VALUE((unsigned long)ptr);  
  31. }  
  32.   
  33. #endif /* _LINUX_ERR_H */  
下面我们就来具体分析一下这段代码,看看内核中的巧妙设计思路。

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

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

而所谓的错误指针就是指其已经到达了最后一个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的内核中是这样定义的:

[plain]  view plain copy print ?
  1. #define MAX_ERRNO   4095  
  2. #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):

[plain]  view plain copy print ?
  1. ......  
  2. #define EPERM        1  /* Operation not permitted */  
  3. #define ENOENT       2  /* No such file or directory */  
  4. #define ESRCH        3  /* No such process */  
  5. #define EINTR        4  /* Interrupted system call */  
  6. #define EIO      5  /* I/O error */  
  7. #define ENXIO        6  /* No such device or address */  
  8. #define E2BIG        7  /* Argument list too long */  
  9. #define ENOEXEC      8  /* Exec format error */  
  10. #define EBADF        9  /* Bad file number */  
  11. #define ECHILD      10  /* No child processes */  
  12. #define EAGAIN      11  /* Try again */  
  13. #define ENOMEM      12  /* Out of memory */  
  14. #define EACCES      13  /* Permission denied */  
  15. #define EFAULT      14  /* Bad address */  
  16. #define ENOTBLK     15  /* Block device required */  
  17. #define EBUSY       16  /* Device or resource busy */  
  18. #define EEXIST      17  /* File exists */  
  19. #define EXDEV       18  /* Cross-device link */  
  20. #define ENODEV      19  /* No such device */  
  21. #define ENOTDIR     20  /* Not a directory */  
  22. #define EISDIR      21  /* Is a directory */  
  23. #define EINVAL      22  /* Invalid argument */  
  24. #define ENFILE      23  /* File table overflow */  
  25. #define EMFILE      24  /* Too many open files */  
  26. #define ENOTTY      25  /* Not a typewriter */  
  27. #define ETXTBSY     26  /* Text file busy */  
  28. #define EFBIG       27  /* File too large */  
  29. #define ENOSPC      28  /* No space left on device */  
  30. #define ESPIPE      29  /* Illegal seek */  
  31. #define EROFS       30  /* Read-only file system */  
  32. #define EMLINK      31  /* Too many links */  
  33. #define EPIPE       32  /* Broken pipe */  
  34. #define EDOM        33  /* Math argument out of domain of func */  
  35. #define ERANGE      34  /* Math result not representable */  
  36. ........  
如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针。这个指针里保存的实际上是一种错误代码。而通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码。

你可能感兴趣的:(linux内核中的IS_ERR)