struct name {
int num;
...;
struct name *next;
struct name *prev;
}
但是linux内核中的实现确有点特殊他是通过独立定义一个链表结构,通过结构体中内嵌这个结构来完成的,这样就实现了链表的定义与结构体的分离。linux内核中广泛的应用了这种链表,可以这么说,如果没有linux list_head链表就不会有现在linux的强大。本来我就以为只有linux内核用这种链表,但是当我分析完lsusb的代码后,发现这个程序也是用内核链表来组织数据结构的,我想Kroah-Hartman不愧为内核的维护者,连应用程序都带着内核的影子。我就试着在应用程序中使用这种链表,发现他的非常的好用,只要包含list.h,然后在结构体重嵌入这种链表,就能方便的实现数据结构的线性链接。下面我就简单的介绍一下这种链表的实现原理。
struct list_head {
struct list_head *next, *prev;
};
我们使用的时候,在自己的结构体中内嵌这个结构就行了,如下;
struct my_struct {
int a;
int b;
...
struct list_head list;
}
这个链表链接起来的不是结构体本身,而是list_head结构。需要一个链表头,这个链表头是list_head结构,不需要内嵌在任何结构中,在使用链表的时候要定义以及初始化这样一个结构。list.h中已经定义了宏用于定义与初始化链表头,如下:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
LIST_HEAD(name)宏用来定义一个链表头,使得他的两个指针都指向自己。我们可以直接在程序的变量声明处,直接调用LIST_HEAD(name)宏,来定义并初始化一个名为name的链表,也可以先声明一个链表,调用INIT_LIST_HEAD来初始化这个链表。从宏定义山看不能直接使用LIST_HEAD_INIT,它只适合声明初始化。
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_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
向链表添加元素只知道链表的头与新的元素地址就可以了,因为链表是双向循环链表,链表头的前一个元素就是这个链表的最后一个元素。list_add是将新元素添加到链表头的后面,而list_add_tail是将心元素添加到链表的尾部,这两个操作如下图所示:
图 1 list_add操作
图 2 list_add_tail操作
说完了添加元素,下面说一下如何删除链表元素,如下:
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
删除链表元素也非常简单,只需要找到前一个元素与后一个元素,将他们链接在一起就可以了。
for ( mylist = mylist_head->next; mylist != mylist_head; mylist = mylist->next);
但是这样的程序没有什么作用,我们需要的是结构体。所以需要一种技术使得由list_head的地址找到内嵌他的结构提的地址,这个技术就是内核中顶顶大名的container_of宏,这个宏就是list_head链表的精髓,它的初衷是为了实现由结构体的元素的地址而找到结构体首地址。借鉴这种技术就可以实现我们的目的。如下:
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
这个宏实现非常的简单,但是往往伟大的思想就蕴藏在简单的代码中,我们来分析一下这个宏。首先他是一个带参数的宏,有三个参数第一个参数是一个地址,他是结构体中元素的地址,第二个参数是结构体类型,第三个参数是,元素在结构体中的名字。我们以list_head链表为例来说明。例如有如下结构:
struct my_struct {
int a;
char b;
...
strcut list_head list;
}
我们知道了里面的list_head元素的地址为plist,需要找到类型问struct my_struct结构变量的地址。就需要这样调用宏