双链表是uClinux2.6内核中使用非常多的一种数据结构,它将双链表的数据和操作独立出来,使得双链表的一些操作不涉及到具体的数据。
本文所举的例子来自于drivers/mtd/mtdpart.c和include/linux/list.h。
1 定义
双链表的定义及其基本操作的实现都在include/linux/list.h中,下面就是它的定义:
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
struct list_head {
struct list_head *next, *prev;
};
uClinux还提供了一个宏来定义一个双链表的表头:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) /
struct list_head name = LIST_HEAD_INIT(name)
从上述定义可以看出初始化时表头的next和prev指针都是指向自身的。
下面是mtdpart.c中的例子:
/* Our partition linked list */
static LIST_HEAD(mtd_partitions);
2 链表与数据的结合
从链表的定义中看不出具体的数据,那么这个双链表是如何使用的呢?还是看看mtdpart.c的使用吧。
/* Our partition node structure */
struct mtd_part {
…
struct list_head list;
…
};
这就是要用双链表链接起来的数据结构,在此结构体中声明了一个list_head的list,这个成员将用于此结构的链接。
int add_mtd_partitions(struct mtd_info *master,
const struct mtd_partition *parts,
int nbparts)
{
struct mtd_part *slave;
int i;
for (i = 0; i < nbparts; i++) {
/* allocate the partition structure */
slave = kmalloc (sizeof(*slave), GFP_KERNEL);
…
list_add(&slave->list, &mtd_partitions);
…
}
return 0;
}
正是通过list_add这个函数就将具体的数据结构与双链表的表头链接起来了。以下就是list_add的实现:
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
#ifndef CONFIG_DEBUG_LIST
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
#else
extern void list_add(struct list_head *new, struct list_head *head);
#endif
3 双链表成员的访问
在有了双链表的表头后,应该如何访问其成员呢?下面是mtdpart.c的例子:
/*
* This function unregisters and destroy all slave MTD objects which are
* attached to the given master MTD object.
*/
int del_mtd_partitions(struct mtd_info *master)
{
struct list_head *node;
struct mtd_part *slave;
for (node = mtd_partitions.next;
node != &mtd_partitions;
node = node->next) {
slave = list_entry(node, struct mtd_part, list);
…
}
return 0;
}
秘密就在list_entry这个宏定义:
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) /
container_of(ptr, type, member)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );})
此处需要注意的是typeof这个关键字的使用,在Visual DSP的文档中有一个说明:
The typeof keyword is an extension to C originally implemented in the GCC compiler. It should be used with caution because it is not compatible with other dialects of C or C++ and has not been adopted by the more recent C99 standard.
offsetof的定义就比较好理解了:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
在上述例子中,直接使用了一个for循环来访问链表的成员,一种更通用的办法是使用下面的宏:
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) /
for (pos = (head)->next; prefetch(pos->next), pos != (head); /
pos = pos->next)
使用示例如下:
static LIST_HEAD(part_parsers);
static struct mtd_part_parser *get_partition_parser(const char *name)
{
struct list_head *this;
…
list_for_each(this, &part_parsers) {
struct mtd_part_parser *p = list_entry(this, struct mtd_part_parser, list);
…
}
…
}
4 优点
uclinux这种双链表的实现方式的优点在于用很少的空间代价(8个字节的链表头)取得了代码的很大简化。因为如果只用一个指针来存储双链表的表头,那么在每次进行插入或者删除操作的时候都必须判断此头指针是否为空!