• Linux内核源码—list_entry(2.6.26.1)


    list_head

    在 Linux 内核中是通过链表的形式来管理进程的,其定义非常简单(/include/linux/list.h):

    struct list_head {
    	struct list_head *next, *prev;
    };
    

    只有两个指针,不包含其他数据,那怎么通过 list_head 将进程连接起来呢?

    Linux 的做法是将 list_head 放入结构体中,比如下面的sched_rt_entity(/include/linux/sched.h):

    struct sched_rt_entity {
    	struct list_head run_list;
    	unsigned int time_slice;
    	unsigned long timeout;
    	int nr_cpus_allowed;
    
    	struct sched_rt_entity *back;
    #ifdef CONFIG_RT_GROUP_SCHED
    	struct sched_rt_entity	*parent;
    	/* rq on which this entity is (to be) queued: */
    	struct rt_rq		*rt_rq;
    	/* rq "owned" by this entity/group: */
    	struct rt_rq		*my_q;
    #endif
    };
    

    如此一来,链表其实就是将结构体中的 list_head 域进行了链接

    地址计算

    接下来的问题是,Linux 只将 list_head 进行了链接,怎么访问结构体中的其他成员呢?

    方法也很简单,用 list_head 的地址减去其相对首地址的偏移量,从而得到结构体的首地址

    在内核中的实现为:

    #define list_entry(ptr, type, member) \
    	container_of(ptr, type, member)
    
    #define container_of(ptr, type, member) ({			\
    	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
    	(type *)( (char *)__mptr - offsetof(type,member) );})
    

    将其展开,其实就是:

    #define list_entry(ptr, type, member) /
            ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
    

    list_entry 通过结构体中某个成员变量的地址计算出结构体的首地址,ptr 是某个成员变量的地址, type 是结构体类型, member是成员变量。

    计算 member 偏移量的方法很巧妙,如果假设结构体的首地址是 0 ,那么成员变量 member 的地址不就是相对于首地址的偏移量嘛。&((type *)0)->member)) 将地址 0 转换为 type 类型的指针,通过该指针去访问 member 就可以取出其地址。前半部分 (char *)(ptr) 使得指针的加减为 1 字节,最后将地址转换为 type 类型的指针。

    References

    1. 理解 linux 中的 container_of 和 offsetof 宏
    2. linux内核链表以及list_entry--linux内核数据结构(一)
    3. list_entry() 源代码解析
  • 相关阅读:
    PTA甲级—树
    PTA甲级—链表
    PTA刷题记录
    PTA甲级—数学
    PTA甲级—常用技巧与算法
    PAT甲级—暴力搜索
    Qt连连看(三)非核心功能实现
    常见数据类型取值范围与10的数量级对照
    PTA甲级—STL使用
    Qt连连看(二)界面制作
  • 原文地址:https://www.cnblogs.com/zyb993963526/p/15971283.html
Copyright © 2020-2023  润新知