博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IS_ERR、PTR_ERR、ERR_PTR
阅读量:6591 次
发布时间:2019-06-24

本文共 4663 字,大约阅读时间需要 15 分钟。

最近在使用filp_open打开文件时遇到到一个问题,当打开一个并不存在的文件时,filp_open返回值值为0xfffffffe,而并不是0(NULL),这是因为内核对返回指针的函数做了特殊处理。内核中的函数常常返回指针,通常如果调用出错,会返回NULL空指针,但linux做了更精妙的处理,能够通过返回的指针体现出来。

对任何一个指针,必然有三种情况:一种是有效指针,一种是NULL,空指针,一种是错误指针,或者说无效指针。而所谓的错误指针就是指其已经到达了最后一个page,比如对于32bit的系统来说,内核空间最高地址0xffffffff,那么最后一个page就是指的0xfffff000~0xffffffff(以4K大小页为例)。这段地址是被保留的,如果超过这个地址,则肯定是错误的。

 

linux/err.h中包含了这一机制的处理,主要通过IS_ERR, PTR_ERR, ERR_PTR几个宏。

 

/* * 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)

 

 ----

/* 将错误号转化为指针,由于错误号在-1000~0间,返回的指针会落在最后一页  */static inline void *ERR_PTR(long error){         return (void *) error;}/* 将指针转化为错误号  */static inline long PTR_ERR(const void *ptr){         return (long) ptr;}/* 判断返回的指针是错误信息还是实际地址,即指针是否落在最后一页 是实际地址:落在最后一页,返回‘0’ 不是实际地址:没有落在最后一页,返回‘1’ */static inline long IS_ERR(const void *ptr)   //☆☆{         return IS_ERR_VALUE((unsigned long)ptr);}

所以对于内核中返回的指针,检查错误的方式不是if(!retptr),而是if( IS_ERR(retptr) 或

If( IS_ERR_VALUE(retptr) )。

 

 下面是本人对于IS_ERR函数的理解,不完全是正确的,如果理解有错误,请告之我.

 

    在IS_ERR()函数中(unsigned long)-1000L实际上表示的是0x FFFF F000(因为负数在计算机中是原码的补码加一),在linux中虚拟内存空间的分配,0~3G是给用 户空间的,而3G~4G是给linux内核的,而0xFFFFF000就位于linux内核的虚拟内存空间范围内,从0xFFFFF000到4G间的大小 只有4KB,这实际上也就是一个PAGE_SIZE的大小,这时如果一个指针位于这块4KB的区域,则这个指针也就不可能是一个页面的首地址了,因为这已 经不足以分配一个页面了。

 

    这内核虚拟空间的top 4KB一般是不作为分配空间来使用的。(我没有找到确切的证据是这样的,只是根据后面的分析觉得这块空间保留,其地址范围用来进行错误判断).

 

    如果传递给IS_ERR()函数的参数是一个页面的首地址指针,那么必然是一个错误指针

    IS_ERR()也可以用来检测一个错误码,这就是与ERR_PTR()配合使用了,看下面一小段代码:(kernel/fs/namespace.c/sys_mount())

asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type,unsigned long flags, void * data){    int retval;    ....    char *dir_page;    ....    dir_page = getname(dir_name);     retval = PTR_ERR(dir_page);     if (IS_ERR(dir_page))          goto out1;    ....}

 

 

    getname()返回有可能是一个分配的页面的首地址,也有可能因为内存不足返回ERR_PTR(-ENOMEM);先看返回是页面首地址的情况,接着 通过PTR_ERR()将这个指针类型的地址转化成为一个整型,再通过IS_ERR()来判断是否是一个有效的页面首地址,这跟前面分析的一样.

    再接下来看一下,如果返回的是错误码的情况,ENOMEM在kernel/include/asm-*/error.h中定义的值是12,经过 ERR_PTR(-ENOMEM)返回则成了指针类型,指向0xFFFFFFF4,就指针而言它是指向虚拟内核空间的top4KB空间,再通过 IS_ERR()判断返回的是false。

 

    在linux中我们看到错误码ERRCODE的值从1~??,这个??不太可能大于4KB的,所以通过ERR_PTR(-ERRCODE),则映射到了虚 拟内核空间的top4KB(0xFFFFF000~4G)去了,再通过IS_ERR()即可检测出"is error"!

 

    综上述,IS_ERR()可以检测页面首地址是否有效,也可以检测出错误码.

 

IS_ERR()有一些妙处。
内核中的函数常常返回指针,问题是如果出错,也希望能够通过返回的指针体现出来。
所幸的是,内核返回的指针一般是指向页面的边界(4K边界),即
ptr & 0xfff == 0
这样ptr的值不可能落在(0xfffff000,0xffffffff)之间,
而一般内核的出错代码也是一个小负数,在-1000到0之间,转变成unsigned long,
正好在(0xfffff000,0xffffffff)之间。因此可以用
(unsigned long)ptr > (unsigned long)-1000L
来判断内核函数的返回值是一个有效的指针,还是一个出错代码。
涉 及到的任何一个指针,必然有三种情况,一种是有效指针,一种是NULL,空指针,一种是错误指针,或者说无效指针.而所谓的错误指针就是指其已经到达了最 后一个page.比如对于32bit的系统来说,内核空间最高地址0xffffffff,那么最后一个page就是指的 0xfffff000~0xffffffff(假设4k一个page).这段地址是被保留的,如果超过这个地址,则肯定是错误的。
Linux内核中,出错有多种可能:
include/asm-generic/errno-base.h文件:
#define EPERM            1      /* Operation not permitted */
#define ENOENT           2      /* No such file or directory */
#define ESRCH            3      /* No such process */
#define EINTR            4      /* Interrupted system call */
#define EIO              5      /* I/O error */
#define ENXIO            6      /* No such device or address */
#define E2BIG            7      /* Argument list too long */
#define ENOEXEC          8      /* Exec format error */
#define EBADF            9      /* Bad file number */
#define ECHILD          10      /* No child processes */
#define EAGAIN          11      /* Try again */
#define ENOMEM          12      /* Out of memory */
#define EACCES          13      /* Permission denied */
#define EFAULT          14      /* Bad address */
#define ENOTBLK         15      /* Block device required */
#define EBUSY           16      /* Device or resource busy */
#define EEXIST          17      /* File exists */
#define EXDEV           18      /* Cross-device link */
#define ENODEV          19      /* No such device */
#define ENOTDIR         20      /* Not a directory */
#define EISDIR          21      /* Is a directory */
#define EINVAL          22      /* Invalid argument */
#define ENFILE          23      /* File table overflow */
#define EMFILE          24      /* Too many open files */
#define ENOTTY          25      /* Not a typewriter */
#define ETXTBSY         26      /* Text file busy */
#define EFBIG           27      /* File too large */
#define ENOSPC          28      /* No space left on device */
#define ESPIPE          29      /* Illegal seek */
#define EROFS           30      /* Read-only file system */
#define EMLINK          31      /* Too many links */
#define EPIPE           32      /* Broken pipe */
#define EDOM            33      /* Math argument out of domain of func */
#define ERANGE          34      /* Math result not representable */
而出错时,往往返回的是-EBUSY,-EINVAL,-ENODEV,-EPIPE,-EAGAIN,-ENOMEM等等,可以看到,这个值实际上是在-1000~0之间的。
对于一个返回指针的函数,我们通常返回NULL表示失败,但是这不能指出那种失败(内存不足?硬件错误还是网络不可达?)
所以返回的时候用ERR_PTR(-ENOME) 等就可以判断,因为这个指针显然不合法
参考 include/iinux/err.h

 

转载地址:http://yldio.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
有关在linux 下跑asp.net文章博客
查看>>
vue填坑之引入iconfont字体图标
查看>>
C# DES
查看>>
Linux/Unix的精巧约定两例及其简析:目录权限和文本行数
查看>>
查找被占用端口进程
查看>>
虚拟化技术正从传统的基于虚拟机管理程序的服务器虚拟化,扩展到网络虚拟化。...
查看>>
Boost asio Boost::asio — UnregisterWaitEx' has not been declared
查看>>
Java学习之Iterator(迭代器)的一般用法 (转)
查看>>
【Interface&navigation】复选框(30)
查看>>
Linux学习实例,请各位知道---对用户的管理配额
查看>>
4月20日作业
查看>>
导入mysql文件
查看>>
linux服务器DNS配置
查看>>
Mysql cluster数据备份和恢复
查看>>
我的友情链接
查看>>
查看nginx/apache/php/mysql编译参数
查看>>
怎样将lib设为源文件夹
查看>>
分布式文件系统之MooseFS----部署
查看>>
Linux下用cronolog切割Tomcat日志并删除指定天数前的日志记录
查看>>