Linux内核中链表的实现与应用

///////////////////////////////////////////// //Linux内核中链表的实现与应用 // //参考资料:Linux操作系统原理与应用(第2版) 陈莉君、康华 编著 /////////////////////////////////////////////

链表(循环双向链表)是Linux内核中最简单、最常用的一种数据结构。

   1、链表的定义
        struct list_head {
            struct list_head *next, *prev;
        }
       这个不含数据域的链表,可以嵌入到任何数据结构中,例如可按如下方式定义含有数据域的链表:
        struct my_list {
            void  * mydata;
            struct list_head  list;
        } ;

   2、链表的声明和初始化宏
        struct list_head 只定义了链表结点,并没有专门定义链表头.那么一个链表结点是如何建立起来的?

内核代码 list.h 中定义了两个宏: #defind LIST_HEAD_INIT(name) { &(name), &(name) } //仅初始化 #defind LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) //声明并初始化

        如果要声明并初始化链表头mylist_head,则直接调用:LIST_HEAD(mylist_head),之后,

mylist_head的next、prev指针都初始化为指向自己。这样,就有了一个带头结点的空链表。

         判断链表是否为空的函数:
         static inline int list_empty(const struct list_head  * head) {
              return head->next  ==  head;
          }    //返回1表示链表为空,0表示不空

  3、在链表中增加一个结点
      (内核代码中,函数名前加两个下划线表示内部函数)
        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 ;
        }  
      
        list.h 中增加结点的两个函数为:
       (链表是循环的,可以将任何结点传递给head,调用这个内部函数以分别在链表头和尾增加结点)
        static inline void list_add(struct list_head *new, struct llist_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) ;
        }
        附:给head传递第一个结点,可以用来实现一个队列,传递最后一个结点,可以实现一个栈。
        static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限
       于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表
       示编译程序在调用这个函数时,立即将该函数展开。
        
4、 遍历链表
       list.h 中定义了如下遍历链表的宏:
       #define   list_for_each(pos, head)    for(pos = (head)-> next ;  pos != (head) ;  pos = pos -> next)  
       这种遍历仅仅是找到一个个结点的当前位置,那如何通过pos获得起始结点的地址,从而可以引用结

点的域?list.h 中定义了 list_entry 宏: #define list_entry( ptr, type, member )
( (type *) ( (char *) (ptr) - (unsigned long) ( &( (type *)0 ) -> member ) ) ) 分析:(unsigned long) ( &( (type *)0 ) -> member ) 把 0 地址转化为 type 结构的指针,然后获取该 结构中 member 域的指针,也就是获得了 member 在type 结构中的偏移量。其中 (char *) (ptr) 求 出的是 ptr 的绝对地址,二者相减,于是得到 type 类型结构体的起始地址,即起始结点的地址。

5、链表的应用 一个用以创建、增加、删除和遍历一个双向链表的Linux内核模块

#include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/list.h>

#define N 10 struct numlist { int num; struct list_head list; };

struct numlist numhead;

static int __init doublelist_init(void) { //初始化头结点 struct numlist * listnode; //每次申请链表结点时所用的指针 struct list_head * pos; struct numlist * p; int i;

printk("doublelist is starting...\n");
INIT_LIST_HEAD(&numhead.list);
/* 
 * static inline void INIT_LIST_HEAD(struct list_head *list)
 * {
 * list->next = list;
 * list->prev = list;
 * }
 */

//建立N个结点,依次加入到链表当中
for (i=0; i<N; i++) {
    listnode = (struct numlist *)kmalloc(sizeof(struct numlist), GFP_KERNEL);
    //void *kmalloc(size_t size, int flages) 
    //分配内存,size 要分配内存大小,flags 内存类型
    listnode->num = i+1;
    list_add_tail(&listnode->list, &numhead.list);
    printk("Node %d has added to the doublelist...\n", i+1);
}
//遍历链表
i = 1;
list_for_each(pos, &numhead.list) {
    p = list_entry(pos, struct numlist, list);
    printk("Node %d's data: %d\n", i, p->num);
    i++;
}
return 0;

}

static void __exit doublelist_exit(void) { struct list_head *pos, *n; struct numlist *p; int i;

//依次删除N个结点
i = 1;
list_for_each_safe(pos, n, &numhead.list) {
    //为了安全删除结点而进行的遍历
    list_del(pos); //从链表中删除当前结点
    p = list_entry(pos, struct numlist, list); 
    //得到当前数据结点的首地址,即指针
    kfree(p); //释放该数据结点所占空间
    printk("Node %d has removed from the doublelist...\n", i++);
}
printk("doublelist is exiting...\n");

}

module_init(doublelist_init); module_exit(doublelist_exit); MODULE_LICENCE("GPL"); MODULE_AUTHOR("shigh1005@gmail.com");

全部评论

相关推荐

最近群里有很多同学找我看简历,问问题,主要就是集中在明年三月份的暑期,我暑期还能进大厂嘛?我接下来该怎么做?对于我来说,我对于双非找实习的一个暴论就是title永远大于业务,你在大厂随随便便做点慢SQL治理加个索引,可能就能影响几千人,在小厂你从零到一搭建的系统可能只有几十个人在使用,量级是不一样的。对双非来说,最难的就是约面,怎么才能被大厂约面试?首先这需要一点运气,另外你也需要好的实习带给你的背书。有很多双非的同学在一些外包小厂待了四五个月,这样的产出有什么用呢?工厂的可视化大屏业务很广泛?产出无疑是重要的,但是得当你的实习公司到了一定的档次之后,比如你想走后端,那么中厂后端和大厂测开的选择,你可以选择中厂后端(注意,这里的中厂也得是一些人都知道的,比如哈啰,得物,b站之类,不是说人数超过500就叫中厂),只有这个时候你再去好好关注你的产出,要不就无脑大厂就完了。很多双非同学的误区就在这里,找到一份实习之后,就认为自己达到了阶段性的任务,根本不再投递简历,也不再提升自己,玩了几个月之后,美其名曰沉淀产出,真正的好产出能有多少呢?而实际上双非同学的第一份实习大部分都是工厂外包和政府外包!根本无产出可写😡😡😡!到了最后才发现晚了,所以对双非同学来说,不要放过任何一个从小到中,从中到大的机会,你得先有好的平台与title之后再考虑你的产出!因为那样你才将将能过了HR初筛!我认识一个双非同学,从浪潮到海康,每一段都呆不久,因为他在不断的投递和提升自己,最后去了美团,这才是双非应该做的,而我相信大部分的双非同学,在找到浪潮的那一刻就再也不会看八股,写算法,也不会打开ssob了,这才是你跟别人的差距。
迷茫的大四🐶:我也这样认为,title永远第一,只有名气大,才有人愿意了解你的简历
双非本科求职如何逆袭
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务