今天在看内核源码时,看到一个判断指针是否是错误指针或无效指针的函数IS_ERR(2.6.11内核,include/linux/err.h中),其源码如下:
static inline long IS_ERR(const void *ptr) { return unlikely((unsigned long)ptr > (unsigned long)-1000L); }
这个函数非常简单,只是将指针的值和-1000L进行比较,如果ptr的值大于(unsigned long)-1000L,则表示ptr是一个无效指针或错误指针。很奇怪,不知道为什么要和-1000L进行比较,所以进行一番搜索和学习,现将自己的理解整理一下,记录下来,跟大家分享一下。
Linux中的指针分为三类:有效指针、空指针(NULL, 通常为0)和无效指针(错误指针)。这里只关心无效指针,什么样的指针式无效指针?根据IS_ERR的判断,如果指针的值大于(unsigned long)-1000L,则这个指针是无效指针。将-1000L通过十六进制输出其值为0xfffffc18,这个值刚好在0xfffff000到0xffffffff之间,而这个区间正好是32位下4G内存空间的最后一页。根据查阅的资料,之前版本的内核不是和-1000L比较,而是和-4095比较,也就是说指针的值只要是在最后一页就认为是无效指针。所以Linux是将最后一页作为来判定是否是错误指针来使用的,当然现在的范围只是最后一页的一部分0xfffffc18到0xffffffff之间,根据include/linux/err.h文件的注释,Linux内核中返回的无效指针中包含更多的信息,包含错误码或者一个目录项指针。
先看下面的一个函数,
struct net_device * __init el1_probe(int unit)
{
struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
static unsigned ports[] = { 0x280, 0x300, 0};
unsigned *port;
int err = 0;
if (!dev) return ERR_PTR(-ENOMEM);
if (unit >= 0) {
sprintf(dev->name, "eth%d", unit);
netdev_boot_setup_check(dev);
io = dev->base_addr;
irq = dev->irq;
mem_start = dev->mem_start & 7;
}
SET_MODULE_OWNER(dev);
if (io > 0x1ff) { /* Check a single specified location. */
err = el1_probe1(dev, io);
} else if (io != 0) {
err = -ENXIO; /* Don't probe at all. */
} else {
for (port = ports; *port && el1_probe1(dev, *port); port++)
;
if (!*port)
err = -ENODEV;
}
if (err)
goto out;
err = register_netdev(dev);
if (err)
goto out1;
return dev;
out1:
release_region(dev->base_addr, EL1_IO_EXTENT);
out:
free_netdev(dev);
return ERR_PTR(err);
}
主要看红色的部分,当dev分配失败时,返回的是ERR_PTR(-ENOMEM),相当于是(void *)(-ENOMEM),而这个返回的指针的值转换成16进制后也落在这个值刚好在0xfffffc18到0xffffffff之间。所以当Linux判断一个指针是一个无效指针时,仅仅通过这个无效的指针就可以拿到错误码,知道错误原因,而不需要用另一个变量来返回错误原因。其实说了那么一大堆废话,就是想引出这个结论。平时在写程序时,如果需要某个函数返回一个指针,发生错误时返回NULL,但仅仅知道一个NULL,是不能判断究竟发生了什么错误,还需要另外一个位置来存储错误,从这点就可以看出内核设计的一些细节非常值得我们学习和借鉴。
如果目录项的指针位于0xfffffc18到0xffffffff之间,也表示这个目录项指针是一个无效指针,因为最后一页是不使用的,用来标示错误。至于内核为什么把范围的起始地址从0xfffff000该为0xfffffc18,现在还不知道,知道的同学可以留个言,共同学习。
另外本人的表达能力不好,如果有什么疑惑也可以留言,交流一下。