文章目录
一、如何理解一些特殊的符号
二、内核空间的指针类型
三、如何理解内核空间最大的错误码
四、解读内核相关内联函数的含义
五、附上内核源码(include/linux/err.h)
1)inline:内联函数。内联函数的代码会直接嵌入到调用它的位置,调用几次就复制几次。
2)__must_check:调用函数时一定要处理函数的返回值,否则编译器会给出警告。
3)unlikely:告诉编译器括号内的值发生的概率很低,编译器就根据这个提示信息去做一些分支预测的编译优化。
4)(unsigned long)-MAX_ERRNO:用补码的方式表示-4095,32位系统为0xfffff001。
1)有效指针;
2)NULL,空指针;
3)错误指针,或者说无效指针。
1. 对于32位的系统来说,内核空间占用 3~4G(0xc0000000~0xffffffff)的虚拟地址。其中Linux采用了分页机制来管理内存,而CPU访问的是线性地址需要通过页表来转化成物理地址。所以,内核就约定了留出最后一个page(假设4k一个page)0xfffff000~0xffffffff,专门用来记录内核空间的错误指针。
2. 在
我们知道负数在计算机中以二进制补码形式存储。因此,(unsigned long)-MAX_ERRNO 的值为0xfffff001,也就是大于等于 0xfffff001 的指针为非法指针。
注意 (unsigned long)-MAX_ERRNO 并不是 (unsigned long)减去MAX_ERRNO,而是对 -MAX_ERRNO 进行强制类型转化,其应该等价于 (unsigned long)(-MAX_ERRNO),-4095转换为无符号long型是0xfffff001,即:
#define IS_ERR_VALUE(x) unlikely((x) >= 0xfffff001)
1)IS_ERR() 调用了 IS_ERR_VALUE(),并将指针强转为 unsigned long 类型的错误码:IS_ERR() 判断指针是否非法,如果指针 < 0xfffff001 是有效的;如果指针 >= 0xfffff001 是无效的。即在 (0xfffff001,0xffffffff) 之间的就是错误指针。因此,可以用IS_ERR()来判断内核的指针是否有效。所以在内核中能经常看到返回负数的错误码如:-ENODEV。 参考内核对该函数的使用:
static struct sp_node *sp_alloc(unsigned long start, unsigned long end,
struct mempolicy *pol)
{
struct sp_node *n;
struct mempolicy *newpol;
n = kmem_cache_alloc(sn_cache, GFP_KERNEL);
if (!n)
return NULL;
newpol = mpol_dup(pol);
if (IS_ERR(newpol)) {
kmem_cache_free(sn_cache, n);
return NULL;
}
newpol->flags |= MPOL_F_SHARED;
sp_node_init(n, start, end, newpol);
return n;
}
2)ERR_PTR() 将一个错误码强转为一个错误指针,并作为函数的返回值使用。 参考内核对该函数的使用:
struct net_device *tc515_probe(int unit)
{
struct net_device *dev = corkscrew_scan(unit);
static int printed;
if (!dev)
return ERR_PTR(-ENODEV);
if (corkscrew_debug > 0 && !printed) {
printed = 1;
pr_debug("%s", version);
}
return dev;
}
3)PTR_ERR() 将一个错误指针强转为一个错误码,并作为函数的返回值使用。 参考内核对该函数的使用:
static int vma_replace_policy(struct vm_area_struct *vma,
struct mempolicy *pol)
{
int err;
struct mempolicy *old;
struct mempolicy *new;
pr_debug("vma %lx-%lx/%lx vm_ops %p vm_file %p set_policy %p\n",
vma->vm_start, vma->vm_end, vma->vm_pgoff,
vma->vm_ops, vma->vm_file,
vma->vm_ops ? vma->vm_ops->set_policy : NULL);
new = mpol_dup(pol);
if (IS_ERR(new))
return PTR_ERR(new);
if (vma->vm_ops && vma->vm_ops->set_policy) {
err = vma->vm_ops->set_policy(vma, new);
if (err)
goto err_out;
}
old = vma->vm_policy;
vma->vm_policy = new; /* protected by mmap_sem */
mpol_put(old);
return 0;
err_out:
mpol_put(new);
return err;
}
#include
#include
/*
* 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)
static inline void * __must_check ERR_PTR(long error)
{
return (void *) error;
}
static inline long __must_check PTR_ERR(const void *ptr)
{
return (long) ptr;
}
static inline long __must_check IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
static inline long __must_check IS_ERR_OR_NULL(const void *ptr)
{
return !ptr || IS_ERR_VALUE((unsigned long)ptr);
}