本文以linux内核3.10为例
单向链表和双向链表在实际使用中有一些局限性,如数据区必须是固定数据,而实际需求是多种多样的。为此,Linux内核把所有链表操作方法的共同部分提取出来,把不同的部分留给代码编程者自己去处理。Linux内核实现了一套纯链表的封装,链表节点数据结构只有指针区而没有数据区,另外还封装了各种操作函数,如创建节点函数、插入节点函数、删除节点函数、遍历节点函数等。
一、链表定义
linux内核实现的链表在<include/linux/types.h>中,定义如下:
struct list_head {
struct list_head *next, *prev;
};
二、链表在内核中的应用举例
struct list_head数据结构不包含链表节点的数据区,通常是嵌入其他数据结构,如文件系统<include/linux/fs.h>中的struct block_device。
struct block_device {
dev_t bd_dev; /* not a kdev_t - it's a search key */
int bd_openers;
struct inode * bd_inode; /* will die */
struct super_block * bd_super;
struct mutex bd_mutex; /* open/close mutex */
struct list_head bd_inodes;
void * bd_claiming;
void * bd_holder;
int bd_holders;
bool bd_write_holder;
#ifdef CONFIG_SYSFS
struct list_head bd_holder_disks;
#endif
struct block_device * bd_contains;
unsigned bd_block_size;
struct hd_struct * bd_part;
/* number of times partitions within this device have been opened. */
unsigned bd_part_count;
int bd_invalidated;
struct gendisk * bd_disk;
struct request_queue * bd_queue;
struct list_head bd_list;
/*
* Private data. You must have bd_claim'ed the block_device
* to use this. NOTE: bd_claim allows an owner to claim
* the same device multiple times, the owner must take special
* care to not mess up bd_private for that case.
*/
unsigned long bd_private;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
};
三、链表初始化
链表头的初始化有两种方法,一种是静态初始化,另一种动态初始化。把next和prev指针都初始化并指向自己,这样便初始化了一个带头节点的空链表。相关定义在<include/linux/list.h>中。
1. 静态初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
2.动态初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
四、链表插入
添加节点到一个链表中,内核提供了几个接口函数,如list_add()是把一个节点添加到head与head->next中间,list_add_tail()是把一个节点插入head->prev与head中间。
/*
* 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.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
五、链表删除
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
#else
extern void __list_del_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
从上面的源码可以看出来,list_del将entry的prev指向的对象与next指向的对象连起来,然后将自己的prev与next指针设为LIST_POISON1与LIST_POISON2,这两个值可以理解为NULL;而list_del_init也是将entry的prev指向的对象与next指向的对象连起来,最终将自己的prev与next指针指向自身
六、链表更新
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
七、链表遍历
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
我们发现这两个宏函数内部调用list_entry()宏获取一个指针pos,然后调用for循环,最后会更新pos指针,按照for循环遍历的习惯,我们大致能猜到pos为结构体指针类型,是否是我们猜想的这样,接着看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()宏的定义在<include/linux/kernel.h>头文件中,offsetof()宏是标准C提供的,通过把0地址转换为type类型的指针,然后去获取该结构体中member成员的指针,也就是获取了member在type结构体中的偏移量。最后用指针ptr减去offset,就得到type结构体的真实地址了。
/**
* 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) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
阅读过上面的代码后,我们明显看出来list_for_each_entry沿着next指针方向遍历,而list_for_each_entry_reverse沿着prev指针方向遍历
八、实践
#include <stdio.h>
#include "list.h"
typedef struct worker {
struct list_head list;
int id;
}worker;
int main() {
printf("test kernel list!\n");
//---------------------------------初始化---------------------------------
worker per1;
INIT_LIST_HEAD(&per1.list);
per1.id = 1;
//--------------------------------- 插入 ---------------------------------
worker per2;
per2.id = 2;
list_add(&per2.list, &per1.list);
worker per3;
per3.id = 3;
list_add(&per3.list, &per1.list);
worker per4;
per4.id = 4;
list_add(&per4.list, &per1.list);
//--------------------------------- 遍历 ---------------------------------
printf("遍历\n");
worker * pos;
list_for_each_entry(pos, &(per1.list), list)
{
printf("pos->id is:%d\n",pos->id);
}
printf("**************************\n");
list_for_each_entry_reverse(pos, &(per1.list), list)
{
printf("pos->id is:%d\n",pos->id);
}
//--------------------------------- 删除 ---------------------------------
printf("删除\n");
list_del_init(&per4.list);
list_for_each_entry(pos, &(per1.list), list)
{
printf("pos->id is:%d\n",pos->id);
}
return 0;
}
运行结果如下:
更多Linux核心API可查看以下网址学习