在学习数据结构中的队列(双链队列)时,我们一般是在数据结构的类型定义中加入指针,以维持队列结构,例如,我们有一中双链数据结构foo:
typedef struct foo
{
struct foo *prev;
struct foo *next;
......
} foo;
然后我们会为foo写一套用于各种队列操作的子程序,由于用来维持队列的两个指针类型是固定的(都指向foo数据结构),可想而知,这些子程序只能用于foo类型的数据结构。如果需要使用大量的队列,上面的做法就需要为每一种数据结构的队列定制一套队列操作子程序。为避免这种情况的发生,Linux内核采用了一套通用的、一般的、可以用到各种不同数据结构的队列操作。为此,代码的作者们把指针prev和next从具体的“宿主”数据结构中抽象出来成为一种数据结构list_head,这种数据结构即可以“寄宿”在具体的宿主数据结构内部,成为该数据结构的一个“连接件”;也可以独立存在而成为一个队列的头(定义在include/linux/list.h中)。定义如下:
struct list_head {
struct list_head *next, *prev;
};
以内存页框数据结构page为例:
typedef struct page {
struct list_head list;
......
struct page *next_hash;
......
struct list_head lru;
......
} mem_map_t;
page数据结构中寄宿了两个list_head结构,即page有两个队列操作的连接件,所以page结构可以同时存在于两个双链队列中。内核中将队列操作定义成一系列宏,下面列举几个操作加以说明:
初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) } /** * 创建一个新的链表。是新链表头的占位符,并且是一个哑元素。 * 同时初始化prev和next字段,让它们指向list_name变量本身。 */ #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) /* * do-while(0)是宏定义的常用形式,避免了分号和花括号等的干扰 */ #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0)
链入队列
/** * 把元素插入特定元素之后 */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; }
删除
/** * 删除特定元素 */ static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; }
通过上面几个操作可以看出,队列操作都是通过list_head进行的,但是那不过是个连接件,list_head中并没有指向宿主结构的指针,那么能不能通过list_head找到它的宿主结构呢,答案当然是肯定的,内核中通过list_entry宏得到list_head的宿主结构,代码如下:
/*
* ptr为list_head指针类型,type为宿主结构类型,member为宿主结构中的list_head成员变量名
* 通过ptr减掉其本身到宿主结构的偏移量即可求得宿主的地址
* 如何求得这个偏移量呢?假设宿主结构地址为0,则member成员变量的地址就是偏移量
*/
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
/*举个例子:*/
#define memlist_entry list_entry
/*
* memlist_entry()将一个list_head指针curr换算成其宿主结构的起始地址,也就是取得指向其宿主page结构的指针,page定义见上面
*/
page = memlist_entry(curr, struct page, list);
/*展开之后变为:*/
page=((struct page*)((char*)(curr)-(unsigned long)(&((struct page*)0)->list)));
Linux2.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) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)