Linux内核list链表初识

早就听说linux内核中有个非常牛X的list万能链表,今天正好看到,初次使用,简单的学习了一下:

 

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的使用,有什么问题,还请多多提出~

转载于:https://www.cnblogs.com/evangwt/archive/2012/10/30/2745693.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值