1、前言
对于任何一个指针来说,必然有三种情况:一种是有效指针,一种是NULL,也就是空指针,一种是错误指针,也就是无效指针,在Linux内核中,所谓的错误指针就是指其已经到达了内核空间的最后一个page,例如,对于32bit的系统来说,内核空间最后地址为0xFFFF FFFF,那么最后一个page就是指地址0xFFFF F000~0xFFFF FFFF(4K大小页面),这段地址是被保留的,如果指针落在这段地址之内,说明是错误的无效的指针。
2、内核如何实现IS_ERR()
在Linux内核源码中实现了指针错误的处理机制,相关的函数接口主要有IS_ERR()、PTR_ERR()、ERR_PTR()等,其函数的源码在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 normal
* 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((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO)
static inline void * __must_check ERR_PTR(long error)
{
return (void *) error;
}
static inline long __must_check PTR_ERR(__force const void *ptr)
{
return (long) ptr;
}
static inline bool __must_check IS_ERR(__force const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr)
{
return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr);
}
在Linux的源码中IS_ERR()函数其实就是判断指针是否出错,如果指针指向了内核空间的最后一个page,那么说明它就是一个无效的指针,如果指针并不是落在内核空间的最后一个page,那么说明这指针是有效的,内核中,无效的指针能表示成一种负数的错误号。
内核空间为什么要保留出最后一个page呢?首先,驱动程序都是运行在内核空间的,内核空间虽然很大,但总是有限的,在这有限的空间内,最后一个page是专门保留的,一般人并不可能会用到内核空间最后一个page的指针,其次,内核空间中返回的指针一般是指向页面的边界(4K边界),也就是ptr & 0xFFF == 0,当发现指针指向了最后一个page,哪说明该指针是无效的。
接下来,对IS_ERR()函数进行分析,其函数如下所示:
static inline bool __must_check IS_ERR(__force const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
在该函数的内部,调用了IS_ERR_VALUE(),这是一个宏定义,其代码如下:
#define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO)
在源码中可以知道MAX_ERRNO的值时4095,也就是0xFFF,在上面的宏定义中(unsigned long)-MAX_ERRNO,是对负数-MAX_ERRNO进行强制类型转换,-0xFFF强制转换为unsigned long类型为0xFFFF F000,所以该宏等价于下面:
#define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (0xFFFFF000))
也就是判断传入的指针值是否落在区间0xFFFF F000~0xFFFF FFFF之内,如果落在这个区间之内的话,就是无效的指针,因此,可以使用IS_ERR()函数去判断内核函数中返回的指针值是否是有效的指针,另外,平时在内核中看见的错误号码都是在前面加个负号,也就是这个原因。
ERR_PTR()和PTR_ERR()函数只是对错误进行强制转换而已,PTR_ERR()函数将无效指针转换为错误号,ERR_PTR()函数将错误号转换为无效指针,而IS_ERR_OR_NULL()函数则用来判断传入的指针是无效指针还是空指针。
3、使用示例
对于IS_ERR()的使用,可以参考下面的代码:
myclass = class_create(THIS_MODULE, "myclass");
if (IS_ERR(myclass)) {
ret = PTR_ERR(myclass);
goto fail;
}
mydevice = device_create(myclass, NULL, MKDEV(major, ), NULL, "simple-device");
if (IS_ERR(mydevice)) {
class_destroy(myclass);
ret = PTR_ERR(mydevice);
goto fail;
}
在代码中,调用class_create()和device_create()函数,必须使用IS_ERR()函数判断返回的指针是否是有效的,如果是无效的,需要调用PTR_ERR()函数将无效的指针转换为错误号,并进行错误号返回。
4、小节
本文主要简单介绍了Linux内核中无效指针的处理机制,包括IS_ERR()、PTR_ERR()等函数的实现。
参考:
https://blog.csdn.net/xxu0123456789/article/details/6339625
https://blog.csdn.net/ljk0922/article/details/47911203
https://www.cnblogs.com/Ph-one/p/4414540.html
https://blog.csdn.net/u014470361/article/details/81175817
linux内核中的IS_ERR()、PTR_ERR()、ERR_PTR()
IS_ERR宏定义在include/linux/err.h,如下所示: #define MAX_ERRNO 4095 //判断x是不是在(0xfffff000,0xffffffff)之间,注意这里用u ...
Linux内核中的GPIO系统之(3):pin controller driver代码分析
一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...
Linux内核中流量控制
linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...
【转】 Linux内核中读写文件数据的方法--不错
原文网址:http://blog.csdn.net/tommy_wxie/article/details/8193954 Linux内核中读写文件数据的方法 有时候需要在Linuxkernel--大 ...
【转】在linux内核中读写文件 -- 不错
原文网址:http://blog.csdn.net/tommy_wxie/article/details/8194276 1. 序曲 在用户态,读写文件可以通过read和write这两个系统调用来完成 ...
Linux内核中的GPIO系统之(3):pin controller driver代码分析--devm_kzalloc使用【转】
转自:http://www.wowotech.net/linux_kenrel/pin-controller-driver.html 一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道 ...
Linux 内核中的 Device Mapper 机制
本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...
向linux内核中添加外部中断驱动模块
本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...
Linux内核中双向链表的经典实现
概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...
随机推荐
JavaScript Math和Number对象
目录 1. Math 对象:数学对象,提供对数据的数学计算.如:获取绝对值.向上取整等.无构造函数,无法被初始化,只提供静态属性和方法. 2. Number 对象 :Js中提供数字的对象.包含整数.浮 ...
[NHibernate]N+1 Select查询问题分析
目录 写在前面 文档与系列文章 N+1 Select查询问题分析 总结 写在前面 在前面的文章(延迟加载,立即加载)中都提到了N+1 Select的问题,总觉得理解的很不到位,也请大家原谅,这也是为什 ...
Redis 配置文件详解
# Redis 配置文件 # 当配置中需要配置内存大小时,可以使用 1k, 5GB, 4M 等类似的格式,其转换方式如下(不区分大小写)## 1k => 1000 bytes# 1kb => ...
java基础回顾(八)——Queue
今天回顾了下关于Queue的一些相关知识 我们可以看到,Deque也是一个接口,它继承了Queue的接口规范.其中LinkedList和ArrayDeque都是实现Deque接口,所以,可以说他们俩都 ...
html5新增标签兼容性
很多低版本的浏览器是不识html5新增的标签的,所以为了解决浏览器兼容性问题,主要有两种方法: js可以创建我们自定义的标签,例如,我们可以用js语句 document.createElement(' ...
php中实现精确设置session过期时间的方法
http://www.jb51.net/article/52309.htm 大多数据情况下我们对于session过期时间使用的是默认设置的时间,而对于一些有特殊要求的情况下我们可以设置一下sessio ...
Java父类子类的对象初始化过程
摘要 Java基本的对象初始化过程,子类的初始化,以及涉及到父类和子类的转化时可能引起混乱的情况. 1. 基本初始化过程: 对于一个简单类的初始化过程是: static 修饰的模块(static变量和 ...
LODOP获取打印成功,是否加入队列
之前博文介绍过获取打印机状态码 LODOP获取打印机状态码和状态码含义测试,但是打印机种类千差万别,状态码不一定准确,特别是打印成功的状态码,获取任务不在队列,可以判断打印成功,删除任务也是任务不在队 ...
springboot+mybatis+pagehelper
springboot+mybatis+pagehelper整合 springboot 版本2.1.2.RELEASE mybatis 版本3.5 pagehelper 版本5.18 支持在map ...
[加密]openssl之数字证书签名,CA认证原理及详细操作
转自:http://blog.sina.com.cn/s/blog_cfee55a70102wn3h.html 1 公钥密码体系(Public-key Cryptography) 公钥密码体系,又称非 ...