Linux内核链表

 2.4内核中的链表结构核2.6并没有太大区别。二者不同之处在于2.6扩充了两种链表数据结构:链表的读拷贝更新(rcu)和HASH链表(hlist)。这两种扩展都基于最基本的list结构。

 

链表数据结构定义:

struct list_head { struct list_head *next, prev; };

这里的list_head没有数据域。在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。

 

Linux内核链表接口

(1)声明和初始化

实际上Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?这里是使用LIST_HEAD()这个宏来构建的。

#define LIST_HEAD_INIT(name) (&name, &name) #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) 

这样,当需要用LIST_HEAD(nf_sockopts)声明一个名为nf_sockopts的链表头时,它的next、prev指针都初始化为指向自己。这样就构建了一个空链表,因为Linux用头指针的next是否指向自己来判断链表是否为空。

static inline int list_empty(const struct list_head *head) { return head->next == head; } 

除了用LIST_HEAD()宏在声明的时候初始化一个链表以外,Linux还提供了一个INIT_LIST_HEAD宏用于运行时初始化链表:

#define INIT_LIST_HEAD(ptr) do { / (ptr)->next = (ptr); / (ptr)->prev = (ptr); / while (0) 

 

(2)插入

对链表的插入操作有两种:在表头插入和在表尾插入。Linux为此提供了两个接口:

static inline void list_add(struct list_head *new, struct list_head *head); static inline void list_add_tail(struct list_head *new, struct list_head *head); 

因为Linux链表是循环表,且表头的next、prev分别指向链表中的第一个和最后一个节点,所以,list_add和list_add_tail的区别并不大,实际上,Linux分别用以下两个函数来实现接口。

static struct 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_tail(struct list_head *new, struct list_head *next) { __list_add(new, head->prev, head); } 

 

(3)删除

static struct void __list_del(struct list_head *prev, struct list_head *next) { next->prev = prev; prev->next = next; } static struct void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; }  

从接口函数中可以看到,被删除下来的prev、next指针分别被设为LIST_POISON1和LIST_POISON2两个特殊值,这样设置是为了保证不在链表中的节点项不可访问,对LIST_POISON1和LIST_POISON2的访问都将引起页故障。与之相对应,list_del_init()函数将节点从链表中解下来之后,调用LIST_INIT_HEAD()将节点置为空链状态。

你可能感兴趣的:(DataStructure)