早就听说linux内核中有个非常牛X的list万能链表,今天正好看到,初次使用,简单的学习了一下:
1. 初始化头节点
2. 申请空间
3. 初始化每个节点,添加到list链表中
a) 头插法
b) 尾插法
4. 遍历链表
5. 删除链表
马上写代码:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/slab.h> 4 #include <linux/list.h> 5 6 MODULE_LICENSE("GPL"); 7 8 #define EMPLOYEE_NUM 10 9 10 struct employee { 11 char name[20]; 12 int number; 13 int salary; 14 struct list_head list; 15 }; 16 17 /*定义头节点*/ 18 static struct list_head employee_list; 19 static struct employee *employee_p = NULL; 20 static struct list_head *pos = NULL; 21 static struct list_head *next = NULL; 22 static struct employee *employee_tmp = NULL; 23 24 static int __init listtest_init(void) 25 { 26 int i = 0; 27 /*初始化头节点*/ 28 INIT_LIST_HEAD(&employee_list); 29 /*申请空间*/ 30 employee_p = kmalloc(sizeof(struct employee) * EMPLOYEE_NUM, GFP_KERNEL); 31 if (employee_p == NULL) { 32 printk("kmalloc failed!\n"); 33 return -ENOMEM; 34 } 35 memset(employee_p, 0, sizeof(struct employee) * EMPLOYEE_NUM); 36 /*初始化每个结构体*/ 37 for (i = 0; i < EMPLOYEE_NUM; ++i) { 38 sprintf(employee_p[i].name, "Employee%02d", i + 1); 39 employee_p[i].number = 10001 + i; 40 employee_p[i].salary = 10000 + i * 1000; 41 /*添加节点到employee_list所指向的节点*/ 42 #if 0 43 /*头插法*/ 44 list_add(&(employee_p[i].list), &employee_list); 45 #endif 46 /*尾插法*/ 47 list_add_tail(&(employee_p[i].list), &employee_list); 48 } 49 50 /*链表的遍历*/ 51 #if 1 52 list_for_each(pos, &employee_list) { 53 /*每个list_head对应的struct*/ 54 employee_tmp = list_entry(pos, struct employee, list); 55 printk("Employee name:%s\tsalary:%d!\n", 56 employee_tmp->name, employee_tmp->salary); 57 } 58 #endif 59 #if 0 60 list_for_each_entry(employee_tmp, &employee_list, list) { 61 printk("Employee name:%s\tsalary:%d!\n", 62 employee_tmp->name, employee_tmp->salary); 63 } 64 #endif 65 66 return 0; 67 } 68 69 static void __exit listtest_exit(void) 70 { 71 #if 0 72 int i = 0; 73 for (i = 0; i < EMPLOYEE_NUM; ++i) { 74 /*删除节点*/ 75 list_del(&(employee_p[i].list)); 76 } 77 #endif 78 #if 0 79 /*非法操作,段错误*/ 80 list_for_each(pos, &employee_list) { 81 list_del(pos); 82 } 83 #endif 84 list_for_each_safe(pos, next, &employee_list) { 85 list_del(pos); 86 } 87 kfree(employee_p); 88 employee_p = NULL; 89 } 90 91 module_init(listtest_init); 92 module_exit(listtest_exit);
Makefile:
1 # makefile for kernel module 2 3 4 KERNELDIR ?= /opt/kernel 5 6 obj-m += listtest.o 7 8 default: 9 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 10 11 clean: 12 @rm -rf *.o *.ko *.mod.* *.*.* .tmp* module* Module*
下面主要针对代码中红色部分list链表的遍历问题的分析:
情况一:
struct list_head结构体变量位于结构体的中间或最后。
1 struct employee{ 2 char name[20]; 3 int number; 4 int salary; 5 struct list_head list; 6 };
链表遍历:
1 list_for_each(pos, &employee_list) { 2 employee_tmp = list_entry(pos, struct employee, list); 3 printk("Employee name: %s\tsalary:%d!\n", 4 employee_tmp->name, employee_tmp->salary); 5 }
list_entry逐层展开:
list_entry展开:
1 #define list_entry(ptr, type, member) \ 2 container_of(ptr, type, member)
container_of展开:
1 #define container_of(ptr, type, memeber) ({\ 2 const typeof(((type *)0)->member) *__mptr = (ptr); 3 (type *)((char *)__mptr - offsetof(type, member));})
offsetof展开:
1 #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
因此,逐层展开过程如下:
1. list_entry展开:
1 {const typeof(((struct employee *)0)->list) *__mptr = (pos); 2 (stuct employee *)((char *)pos - offsetof(struct employee, list));}
2. offsetof展开
1 {const typeof(((type *)0)->list) *__mptr = (pos); 2 (struct employee *)((char *)pos - (size_t)&((struct employee *)0)->list);}
3. size_t与架构有关,list_entry在arm架构下完整展开:
1 {const typeof(((type *)0)->list) *__mptr = (pos); 2 (struct employee *)((char *)pos - (unsigned int)&((struct employee *)0)->list);}
含义分析:
typeof:获取类型
1 (struct employee *)((char *)__mptr
强制转换成(char *)为了计算地址,以字节为单位
1 (unsigned int)&((struct employee *)0)->list)
获取list在结构体中的偏移地址,用unsigned int类型,可以容纳较长的偏移地址范围,"&"取地址时,偏移地址本身以字节为单位。
块语句的值为最后一个语句的值,因此
1 employee_tmp = list_entry(pos, struct employee, list);
employee_tmp = 当前pos所在的struct employee类型的结构体的起始地址。
这样便通过以struct list_head为节点的链表,获取到了实际为struct employee的完整节点。
遍历过程可以用这个宏:
1 /** 2 *list_for_each_entry - iterate over list of given type 3 *@pos: the type * to use as a loop cursor. 4 *@head:the head for your list. 5 *@member: the name of the list_struct within the struct. 6 */ 7 #define list_for_each_entry(pos, head, member) 8 for (pos = list_entry((head)->next, typeof(*pos), member); 9 prefetch(pos->member.next), &pos->member != (head); 10 pos = list_entry(pos->member.next, typeof(*pos), member))
代码:
1 struct employee *pos = NULL; 2 list_for_each_entry(pos, employee_list, list) { 3 printk("Employee name: %s\tsalary:%d\n", 4 pos->name, pos->salary); 5 }
情况二:
struct list_head结构体变量在结构体的最前面。
1 struct employee { 2 struct list_head list; 3 char name[20]; 4 int number; 5 int salary; 6 };
链表遍历:
1 list_for_each(pos, employee_list) { 2 /*强制类型转换*/ 3 employee_tmp = (struct employee *)pos; 4 printk("Employee name: %s\tsalary:%d!\n", 5 employee_tmp->name, employee_tmp->salary); 6 }
但是,实际情况中,一个结构体中可能含有多个struct list_head变量,也就是说一个结构体可能为不同的链表所使用,所以第一种情况无法避免,是通用的方法。
附:
顺带list插入部分的简单示意图:
list链表的删除操作:
初次分析list的使用,有什么问题,还请多多提出~