1.实现
- 以下定义在 include/linux/list.h
struct list_head {
struct list_head *next,*prev;
};
- 容易发现list_head没有数据域,在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。两个指针指向的是另一个list_head字段的地址,而不是包含list_head结构体的整个数据结构的地址。
- 这样的话一个链表的数据域也可以不同
- 在include/linux/netfilter.h中有用到上述链表
struct nf_hook_ops {
struct list_head list;
nf_hookfn *hook;
struct module *owner;
u_int8_t pf;
unsigned int hooknum;
int priority;
};
2.操作接口
2.1 声明和定义
- Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
- 为了说明这个while(0)的必要性我再粘贴一段代码
static inline void list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
if (list_empty(head))
return;
if (list_is_singular(head) &&
(head->next != entry && head != entry))
return;
if (entry == head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list, head, entry);
}
2.2 插入/删除/合并
- (a) 插入
#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
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);
}
- (b) 删除
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
#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
这里有个小细节:使用内联函数而不使用宏
- 那为什么不用NULL??我也不知道确切答案,反正用LIST_POISON1/2就对了
- LIST_POISON1/2的定义在poison.h
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
- 当我们需要删除nf_sockopts链表中添加的new_sockopt项时,我们这么操作:
list_del(&new_sockopt.list);
- © 搬移
- Linux提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类:
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list, head);
}
- 例如list_move(&new_sockopt.list,&nf_sockopts)会把new_sockopt从它所在的链表上删除,并将其再链入nf_sockopts的表头
- (d) 合并
static inline void __list_splice(const struct list_head *list,
struct list_head *prev,
struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
- 因为表头不含数据,且合并后list的表头不再被遍历到,但表头指针list的next、prev仍然指向原来的节点,为了避免引起混乱,个人认为还是用list_splice_init()好一点。
2.3 遍历
- (a) 由链表节点到数据项变量
#define list_entry(ptr, type, member) container_of(ptr, type, member)
#define hlist_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) );})
- 先对第一句话做一点说明
- (type *)0 把从数据段基地址开始的一段空间强转为(type *)类型,没错这里是空指针,但是只引用了它的成员而没有访问地址的具体内容,所以编译器不会报错
- typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型
- 所以第一句话的作用就是先用typeof获取结构体成员member的类型,然后定义一个这个类型的临时变量__mptr,并将结构体变量中的成员的地址赋给临时变量__mptr
- 在看第二句话前先看一下/include/linux/stddef.h中offsetof()宏的实现,size_t最终定义为unsigned int(i386)
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
- 所以第二句话作用就是先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址
- 例如,我们要访问nf_sockopts链表中首个nf_sockopt_ops变量,则如此调用:
list_entry(nf_sockopts->next, struct nf_sockopt_ops, list);
- (b) 遍历宏
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
#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))
#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))
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
3.示例
- 下面是一个用户态的例子,一开始编译不了,其实把头文件拿出来去掉依赖就行了,并不难
- 编译成内核模块是最简单的方法,下面是源文件mlist.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
static int __init hello_init(void)
{
int i=0,count=0;
struct list_head *p;
LIST_HEAD(list);
char *string="123456789ABCDEF";
struct file_store {
char c;
struct list_head node;
} *pstore;
for(i=0;i<15;i++){
if(!(pstore=(struct file_store *)vmalloc(sizeof(struct file_store))))
break;
pstore->c=*(string+i);
list_add_tail(&pstore->node,&list);
}
list_for_each(p,&list){
count++;
}
printk(KERN_INFO "%s has altogether %d character(s)\n",string,count);
if(1){
struct list_head *p;
list_for_each_entry_reverse(pstore,&list,node){
p=pstore->node.next;
list_del(&pstore->node);
printk(KERN_INFO "%c\t",pstore->c);
vfree(pstore);
pstore=list_entry(p,struct file_store,node);
}
}else{
struct file_store *p;
list_for_each_entry_safe(pstore,p,&list,node){
list_del(&pstore->node);
printk(KERN_INFO "%c\t",pstore->c);
vfree(pstore);
}
}
printk(KERN_INFO "init ended\n");
return 0;
}
module_init(hello_init);
static void __exit mlist_exit(void)
{
printk(KERN_INFO "module exit\n");
}
module_exit(mlist_exit);
MODULE_AUTHOR("hzq");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple kernel list module");
MODULE_ALIAS("a simplest module");
KVERS = $(shell uname -r)
obj-m +=mlist.o
build:kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean