对内核源码中IS_ERR的理解

  今天在看内核源码时,看到一个判断指针是否是错误指针或无效指针的函数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,现在还不知道,知道的同学可以留个言,共同学习。

另外本人的表达能力不好,如果有什么疑惑也可以留言,交流一下。


 

你可能感兴趣的:(linux,struct,IO,Module,null,linux内核)