Linux内核源码—list_entry(2.6.26.1)
list_head
在 Linux 内核中是通过链表的形式来管理进程的,其定义非常简单(/include/linux/list.h):
1 2 3 | struct list_head { struct list_head *next, *prev; }; |
只有两个指针,不包含其他数据,那怎么通过 list_head 将进程连接起来呢?
Linux 的做法是将 list_head 放入结构体中,比如下面的sched_rt_entity(/include/linux/sched.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 的地址减去其相对首地址的偏移量,从而得到结构体的首地址。
在内核中的实现为:
1 2 3 4 5 6 | #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) );}) |
将其展开,其实就是:
1 2 | #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:
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
2019-03-06 剑指offer:顺时针打印矩阵
2017-03-06 UVa 10801 电梯换乘