博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux2.6.32内核笔记(4)内核链表使用与分析【转】
阅读量:2185 次
发布时间:2019-05-02

本文共 5869 字,大约阅读时间需要 19 分钟。

(转自:)

摘要:描述了普通链表、内核链表以及他们之间的区别,介绍了对链表进行创建,插入,遍历和删除的操作,使用内核链表对足球队球员信息进行操作,详细对内核链表中的各个函数进行了分析。

    一、链表的概念与种类

    链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。链表由一系列节点组成,节点可以运行时动态生成。每个节点包括两个部分:一是存储数据元素的数据域,另一个是存储其它节点地址的指针域。建立链表无须预知数据总量,可以随机分配空间,可以高效的在任意位置实时的插入和删除节点,链表的开销主要是访问的顺序性和组织节点的空间损失。

    链表分为单向链表、双向链表和循环双向链表。单向链表只有一个指向后继节点的指针,链表头代表了整个链表的基地址,最后一个节点的指针域为NULL。双向链表有两个指针,一个是后继节点的指针,一个是前一个节点的指针,这样就可以实现向前和向后两个方向的操作,但是头指针只有向后的指针,末尾指针只有向前的指针。双向循环链表的头指针指向末尾,末尾的指针指向头,这样就变成了一个循环。在我们Linux内核当中,使用的是双向循环链表。

 

    二、内核链表和普通链表的区别

    最大的区别就是内核链表中的链表元素不与特定的类型相关,具有通用性,为什么呢?看下面的图:

 

    上面kernel list是内核链表,下面normal list是普通链表。可以看出其指针域都是指向前一个或者下一个节点的指针域,这样就避免了数据域的不同结构类型之间的冲突,任何结构体都可以通过内核的添加成为链表中的节点。而普通链表则指向其他节点的数据域,这样每次插入操作不同的数据类型都要修改前向和后继指针的类型,操作比较繁琐。

    三、内核链表的结构与函数

    structlist_head

    {

       structlist_head *prev,*next;

    }

    这是一个基本的内核链表结构体,next和prev分别是指向前驱和后继list_head的指针,是一个双向循环链表。

    1.初始化链表头

    staticinline void INIT_LIST_HEAD(struct list_head *list)

    {

        list->next = list;

        list->prev = list;

    }

    将后继指针和前驱指针都指向自己list,这样后面进行插入就可以了。

    2.插入

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

    list_add(struct list_head *list, struct list_head*head)

    list_add_tail(struct list_head *list, struct list_head*head)

    3.删除

    list_del(struct list_head *entry);

    4.遍历

    list_for_each(pos, head)

    5.链表节点到数据项

    list_entry(ptr,type, member) 

    container_of(ptr,type, member)

    list_entry宏是用来根据list_head指针查找链表所嵌入的结构体的地址,具体实现是依赖container_of。主要是求出内层结构体字段到外层的偏移量,计算的方式是,将外层结构体的地址设置为0,然后就有该字段的地址 = 

该字段到外层结构体的偏移量。这些函数具体实现我们后面分析。

    四、使用内核链表操作足球队球员信息

    首先,要创建一个链表,就需要创建一个链表头,然后再将各个节点加进去,这里我打算链表结构中存放球员的号码,进球数和助攻数。单个节点如下图:

   

    切到工作目录,创建两个文件,一个mylist.c一个Makefile。

    1.Makefile内容如下

    obj-m:= mylist.o

    KDIR:= /home/passionbird/project/test/linux-2.6.32.2

    all:

        make -C $(KDIR) M=$(PWD) modulesCROSS_COMPILE=arm-linux- ARCH=arm

    clean:

        rm -rf $(TARGET) *.o *.order *.symvers *.mod

    红色字体部分添加你编译下载到开发板上的内核,不要添加错了。

    2.mylist.c初步内容如下

    初步先这样写,然后往里填

    

 
  1. #include<linux/module.h>

  2. #include<linux/init.h>

  3. #include<linux/list.h>

  4.  
  5. struct member

  6. {

  7. num;

  8. score;

  9. assists;

  10. structlist_head list;

  11. };

  12. int mylist_init()

  13. {

  14.  
  15. return 0;

  16. }

  17.  
  18. void mylist_exit()

  19. {

  20.  
  21. }

  22.  
  23. module_init(mylist_init);

  24. module_exit(mylist_exit);

 

 

首先添加头文件,然后末尾两个宏,现在mylist_init和mylist_exit什么都不做。然后在开头定义一个结构体

structmember

    {

       num;

       score;

       assists;

       structlist_head list;

        };

其中包含了号码,得分,助攻和一个list_head结构体,命名为list,这其实就对应我们刚才画的图。

   创建链表:

    #include<linux/list.h>

    structlist_head member_head;

    INIT_LIST_HEAD(&member_head);

    staticinline void INIT_LIST_HEAD(struct list_head *list)

    {

    list->next = list;

    list->prev = list;

    }

    这个函数需要头文件/list.h的支持,同时这里给进去的参数是一个list_head结构体的指针,我们这里给了member_head的地址,在内部这个指针前向和后继都指向自己,这样我们就完成了链表头的创建。

    插入节点:

    插入之前给成员变量赋值:

    structmember memb1,memb2,memb3,memb4;

    memb1.num= 9;

    memb1.score= 1;

    memb1.assists=2;

   

    memb2.num= 66;

    memb2.score= 0;

    memb2.assists=0;

   

    memb3.num= 21;

    memb3.score= 1;

    memb3.assists=1;

   

    memb4.num= 10;

    memb4.score= 2;

    memb4.assists=0;

   

    节点插入函数如下:

    staticinline void list_add_tail(struct list_head *new, struct list_head *head)

{

    __list_add(new, head->prev, head);

}

   这里的第一个参数要填进去的是你的新节点的的list_head,后面这里是刚才创建的头节点的list_head,两个参数都需要是指针变量,调用方式如下:

    list_add_tail(&(memb1.list),&member_head);

    list_add_tail(&(memb2.list),&member_head);

    list_add_tail(&(memb3.list),&member_head);

    list_add_tail(&(memb4.list),&member_head);

    这样我们就将其全部节点插入进去了。

   

    遍历链表

    便利链表的函数如下;

    #definelist_for_each(pos, head) \

    for(pos = (head)->next; prefetch(pos->next), pos != (head); \

          pos = pos->next)

    list_for_each其实是一个for循环,这里给进去的第二个参数head使我们需要遍历的链表头,也就是memnber_head,第一个参数pos是一个struct head_list类型的指针,会不断地去指向每一个节点的指针域,这里需要去定义:

    structlist_head *pos;

    因为pos指向的是指针域的,但是我们的数据域和指针域具有偏移(不同于普通链表,普通链表是指向数据域的)。所以这里需要使用list_entry将节点取出来,函数如下:

    #definelist_entry(ptr, type, member) \

    container_of(ptr,type, member)

    也就是它依赖于container_of这个函数:

函数的思想如下:

 第一步,首先定义一个临时的数据类型(通过typeof(((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值。

    第二步,用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址(整个宏的返回值就是这个首地址)。

 

点看参数说明:

    /**

 *list_entry - get the struct for this entry

 *@ptr:   the &struct list_head pointer.

 *@type:  the type of the struct this isembedded in.

 *@member:   the name of the list_struct withinthe struct.

 */

    ptr是在结构里面指向list_head的指针,这里指的是pos。

    type指的是要获取的数据域的数据的类型。

    member指的就是结构体list指针域中成员的名称。

调用如下:

    temp=list_entry(pos,structmember,list);

    这里temp还需要提前定义为struct member类型的,

    structmember *temp;

printk("number%d,score  %d, assists %d\n",temp->num,temp->score,temp->assists);

    删除节点:

    函数如下:

    staticinline void list_del(struct list_head *entry)

{

    __list_del(entry->prev,entry->next);

    entry->next= LIST_POISON1;

    entry->prev= LIST_POISON2;

}

可以看出这里传进去的参数只有一个,也就是要删除的节点的指针域的地址。

    这里函数如下:

    voidmylist_exit(void)

{

    list_del(&(memb1.list));

    list_del(&(memb3.list));

   

}

    mylist.c完整代码如下:

 

 
  1. <span style="font-size:14px;">#include<linux/module.h>

  2. #include<linux/init.h>

  3. #include<linux/list.h>

  4.  
  5. MODULE_LICENSE("GPL");

  6. MODULE_AUTHOR("Deep_l_zh");

  7.  
  8. struct member

  9. {

  10. int num;

  11. int score;

  12. int assists;

  13. struct list_head list;

  14. };

  15.  
  16. struct list_head member_head;

  17. struct member memb1,memb2,memb3,memb4;

  18. struct list_head *pos;

  19. struct member *temp;

  20.  
  21.  
  22. int mylist_init(void)

  23. {

  24.  
  25. INIT_LIST_HEAD(&member_head);

  26.  
  27. memb1.num = 9;

  28. memb1.score = 1;

  29. memb1.assists =2;

  30.  
  31. list_add_tail(&(memb1.list),&member_head);

  32.  
  33. memb2.num = 66;

  34. memb2.score = 0;

  35. memb2.assists =0;

  36. list_add_tail(&(memb2.list),&member_head);

  37.  
  38.  
  39. memb3.num = 21;

  40. memb3.score = 1;

  41. memb3.assists =1;

  42. list_add_tail(&(memb3.list),&member_head);

  43.  
  44.  
  45. memb4.num = 10;

  46. memb4.score = 2;

  47. memb4.assists =0;

  48. list_add_tail(&(memb4.list),&member_head);

  49.  
  50. list_for_each(pos,&member_head)

  51. {

  52. temp = list_entry(pos,struct member,list);

  53. printk("number %d,score %d, assists %d\n",temp->num,temp->score,temp->assists);

  54. }

  55.  
  56. return 0;

  57. }

  58.  
  59. void mylist_exit(void)

  60. {

  61. list_del(&(memb1.list));

  62. list_del(&(memb3.list));

  63.  
  64. }

  65.  
  66. module_init(mylist_init);

  67. module_exit(mylist_exit);</span>

 

 

    这时候编译下载到开发板,可以看到输出如下信息:

    /# insmod mylist.ko

    number9,score  1, assists 2

    number66,score  0, assists 0

    number21,score  1, assists 1

    number10,score  2, assists 0

    / #

    表明我们的链表建立,插入和遍历都成功了。

    下一篇帖子打算在应用程序中使用内核链表进行一整套的操作,这篇帖子就到这里吧,如有不正确的地方,还请指出,大家共同进步。

你可能感兴趣的文章
Leetcode C++《热题 Hot 100-14》283.移动零
查看>>
Leetcode C++《热题 Hot 100-15》437.路径总和III
查看>>
Leetcode C++《热题 Hot 100-17》461.汉明距离
查看>>
Leetcode C++《热题 Hot 100-18》538.把二叉搜索树转换为累加树
查看>>
Leetcode C++《热题 Hot 100-19》543.二叉树的直径
查看>>
Leetcode C++《热题 Hot 100-21》581.最短无序连续子数组
查看>>
Leetcode C++《热题 Hot 100-22》2.两数相加
查看>>
Leetcode C++《热题 Hot 100-23》3.无重复字符的最长子串
查看>>
Leetcode C++《热题 Hot 100-24》5.最长回文子串
查看>>
Leetcode C++《热题 Hot 100-26》15.三数之和
查看>>
Leetcode C++《热题 Hot 100-28》19.删除链表的倒数第N个节点
查看>>
Leetcode C++《热题 Hot 100-29》22.括号生成
查看>>
Leetcode C++《热题 Hot 100-44》102.二叉树的层次遍历
查看>>
Leetcode C++《热题 Hot 100-47》236.二叉树的最近公共祖先
查看>>
Leetcode C++《热题 Hot 100-48》406.根据身高重建队列
查看>>
《kubernetes权威指南·第四版》第二章:kubernetes安装配置指南
查看>>
Leetcode C++《热题 Hot 100-49》399.除法求值
查看>>
Leetcode C++《热题 Hot 100-51》152. 乘积最大子序列
查看>>
Leetcode C++ 《第181场周赛-1》 5364. 按既定顺序创建目标数组
查看>>
Leetcode C++ 《第181场周赛-2》 1390. 四因数
查看>>