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: