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() 源代码解析
posted @ 2022-03-06 12:04  Kayden_Cheung  阅读(110)  评论(0编辑  收藏  举报
//目录