Linux内核中的IS_ERR()实现

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, 0), 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

原文地址:https://www.cnblogs.com/Cqlismy/p/11458211.html