Linux内核中有很多种链表,如果对每一种链表都使用单独的数据结构去表示,那么需要对每个链表实现一组原语操作,包括初始化、插入、删除等。于是,Linux内核定义了一个很有趣的数据结构: list_head
struct list_head * next, * prev;
};
乍一看这定义,似乎很普通,但妙就妙在普通上。
data_type data;
list_node * next, * prev;
}
示意图如下:

data;
list_head;
}
示意图如下(其中增加了一个哑元):
container_of(ptr, type, member)
跟踪到container_of宏:
const typeof ( ((type * ) 0 ) -> member ) * __mptr = (ptr); /
(type * )( ( char * )__mptr - offsetof(type,member) );})
这里面offsetof不需要跟踪,我们也能理解这个宏的意思了。先简单对宏的三个参数说明一下。ptr是指向list_head数据结构的指针,type是容器数据结构的类型,member是list_head在type中的名字。直接看下面的示例代码:
xxx;
list_head list1;
list_head list2;
xxx;
};
struct data vardat = {初始化};
list_head * p = & vardat.list1;
list_head * q = & vardat.list2;
list_entry(p, struct data, list1) == & vardat;
list_entry(q, struct data, list2) == & vardat;
从上面这个例子可以看出,vardat可以同时挂到两个(或更多)链表上,其中list1是第一个链表上的一个节点,list2是第二个链表上的节点,从&list1和&list2都可以得到vardat,上面提出的问题也就解决了。
看过之后恍然大悟,原来这么简单,把一个TYPE类型的指向0的指针,其MEMBER自然就是offset,妙哉!
list_add_tail( new , head); // 额,跟上面的区别就不用解释了,不过这里的head是真正的链表头
list_del(entry); // 删除entry节点
list_empty(head); // 检查是否为空链表
list_entry(ptr, type, member); // 前面解释过了
list_for_each(pos, head); // 遍历列表,每次循环是通过pos返回节点list_head指针
// 下面这个最有用!
list_for_each_entry(pos, head, member); // 同上,但通过pos返回的是container数据结构的地址。
慢!发现一个问题了,list_entry中需要type,为啥list_for_each_entry不需要呢?简单,pos是你给的一个container数据结构的指针,在宏的实现中,用typeof(*pos)就得到type了!
// xxxxxxx
struct hlist_head preempt_notifiers;
struct list_head rcu_node_entry;
struct list_head tasks;
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct list_head ptraced;
struct list_head ptrace_entry;
struct list_head thread_group;
// 还有好多list,不抄了……
}
其中tasks是所有进程组成的链表,因此要遍历所有进程,可以用这个宏:
for (p = & init_task ; (p = next_task(p)) != & init_task ; )
#define next_task(p) /
list_entry((p) -> tasks.next, struct task_struct, tasks)
本文深入解析Linux内核中独特的链表实现方式,介绍list_head结构及其操作宏,并演示如何利用这些宏实现高效的链表操作。

1910

被折叠的 条评论
为什么被折叠?



