Linux内核数据结构之链表

与经典双向链表比较

??经典双向链表如图。其中有一个pre指针和一个next指针,数据是在链表的节点内。

??内核链表如图。每一个链表节点内只有一个pre指针和一个next指针,整个链表节点嵌入到了一个需要使用链表的结构体内。

内核链表介绍

??内核链表节点结构体定义如图。其中next指针指向下一个链表节点,prev指针指向前一个链表节点。

??前面已经说过,内核链表节点是嵌入到数据节点内的,那么就产生了一个问题,如何访问到链表所在结构体的指针呢?

??内核链表中通过list_entry宏来访问到链表所在结构体的指针,如下图。其中有3个参数ptr、type、member,根据注释可知,ptr是指向链表节点成员的指针变量,type就是链表节点嵌入的结构体,即包含数据成员的结构体,member是type结构体中定义的链表节点成员使用的名称。

??list_entry宏中还包含了2个宏,分别为container_of和container_of中使用的offsetof,分别如下两图。

??在GNU C中,圆括号包围的符合语句可以生成返回值,在container_of中,定义__mptr是为了防止出现ptr++等副作用。

??offsetof宏就是取type结构体中member成员相对于0地址的偏移量,最后通过__mptr减去这个偏移量,就可以获取到链表节点所在结构体的指针了。

常用函数

??INIT_LIST_HEAD:初始化一个链表头节点。

??list_add_tail:添加一个成员到链表尾。

??list_del:删除一个元素。

??如下图,在删除一个元素的时候,next和prev都不是指向null,而是分别指向了LIST_POISON1和LIST_POISON2两个指定的地址。这是为了防止有的节点申请内存错误的时候也是null,所以用了两个特定的地址,LIST_POISON1和LIST_POISON2都是低位地址,在内核空间申请内存时是不会出现的。

??List_empty:检查链表是否为空。

??list_for_each_entry:遍历链表,通过list_entry获取到外结构体指针。

内核链表的使用

??首先定义结构体,数据为ch和grade,ch保存学生姓名,grade保存学生成绩。

??然后定义one、two、three三名学生,给他们的姓名和分数赋值。定义一个链表头。

??调用INIT_LIST_HEAD进行初始化,然后one、two、three三个结点中list_head插入到链表中。

??最后调用list_for_each宏遍历输出链表,list_for_each中通过list_entry获取链表结点所在结构体。

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

struct k_list
{
    struct list_head test_list;
    char ch;
    int grade;
};

static __init int list_op_init(void)
{

    struct k_list *one, *two, *three, *entry;
    struct list_head test_head;
    struct list_head *ptr;

    one = kmalloc(sizeof(struct k_list *), GFP_KERNEL);
    two = kmalloc(sizeof(struct k_list *), GFP_KERNEL);
    three = kmalloc(sizeof(struct k_list *), GFP_KERNEL);

    one->ch = ‘A‘;
    two->ch = ‘B‘;
    three->ch = ‘C‘;
    one->grade = 90;
    two->grade = 85;
    three->grade = 88;

    INIT_LIST_HEAD(&test_head);
    list_add(&one->test_list, &test_head);
    list_add(&two->test_list, &test_head);
    list_add(&three->test_list, &test_head);

    list_for_each(ptr, &test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    printk(KERN_INFO "\n Deleting first entry \n");
    list_del(&one->test_list);
    kfree((void *)one);
    one = NULL;

    list_for_each(ptr,&test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    printk(KERN_INFO "\n Deleting second entry \n");
    list_del(&two->test_list);
    kfree((void *)two);
    two = NULL;

    list_for_each(ptr, &test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    printk(KERN_INFO "\n Deleting third entry \n");
    list_del(&three->test_list);
    kfree((void *)three);

    list_for_each(ptr, &test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    return 0;
}

static __exit void list_op_exit(void) {
    printk(KERN_INFO "k_list module exit successfully! ...\n");
}

module_init(list_op_init);
module_exit(list_op_exit);

Makefile文件内容如下:

obj-m += k_list.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

??以上代码是在Linux内核中编写,首先进行make,然后通过insmod加载内核模块,再通过dmesg可以查看输出结果,最后通过rmmod卸载内核模块。

??输出结果如下:

时间: 2024-10-07 05:25:38

Linux内核数据结构之链表的相关文章

AT&amp;T汇编语言与GCC内嵌汇编,Linux内核数据结构之链表

最近在看<Linux内核源代码情景分析>,作者毛德操.书中刚开始介绍了AT&T汇编语言与GCC内嵌汇编,以及Linux内核数据结构之链表.可惜书中介绍的不够全面.因为推荐大家阅读下面两篇文章.很不错. AT&T汇编语言与GCC内嵌汇编:http://grid.hust.edu.cn/zyshao/Teaching_Material/OSEngineering/Chapter2.pdf. Linux内核数据结构之链表:http://www.cnblogs.com/Anker/p/

Linux内核数据结构——链表

目录 目录 简介 单向链表 双向链表 环形链表 Linux内核中的链表实现 offsetof container_of container_of 第一部分 container_of 第二部分 链表初始化 向链表中增加一个节点 删除节点 移动节点 判断链表是否为空 遍历链表 Demo测试 mlisth mlistc 执行结果 简介 最近在学习Android Binder驱动程序实现的时候,发现里面的数据结构用到了struct list_head.而我google发现struct list_head

linux内核数据结构学习总结(undone)

本文旨在整理内核和应用层分别涉及到的数据结构,从基础数据结构的角度来为内核研究作准备,会在今后的研究中不断补充 目录 1. 进程相关数据结构 1) struct task_struct 2. 内核中的队列/链表对象 3. 内核模块相关数据结构 2) struct module 1. 进程相关数据结构 0x1: task_struct 我们知道,在windows中使用PCB(进程控制块)来对进程的运行状态进行描述,对应的,在linux中使用task_struct结构体存储相关的进程信息,task_

Go语言移植Linux内核数据结构hlist

hlist(哈希链表)可以通过相应的Hash算法,迅速找到相关的链表Head及节点. 在有些应用场景,比Go标准库提供的list(一种双向链表)更合适. 依照list.h中的源码,我实现了一个Go语言版本的hlist例子. 首先说下hlist的构成:             在hlist(哈希链表)中, 头结点使用struct hlist_head来表示,hlist_head仅一个first指针. 普通节点使用struct hlist_node来表示. 源码中有几个特别的地方: 1. 在stru

Linux内核之旅 链表实现

1 #include "stdio.h" 2 #include "stdlib.h" 3 4 struct list_head{ 5 struct list_head *prev; 6 struct list_head *next; 7 }; 8 9 struct task{ 10 int member; 11 struct list_head list; 12 }; 13 14 #define list_entry(ptr,member,type) 15 ((ty

[原理分析]linux内核中的链表原理实践

摘要: 本文根据linux内核的链表定义,尝试自己来完成相关的接口设计,并且构建测试用例,来体会下链表接口的用法. 正文: 首先来看下linux内核提供的链表接口大致如下: struct head_node{ struct head_node* pre; struct head_node* next; }; 即节点中只有两个指针:一个指向前一个元素,一个指向后一个元素:假设我们现在要使用该接口,跟一个int值联合在一起形成数据节点,那么存在两种可能的方式: case1: typedef stru

linux内核系列(一)内核数据结构之链表

双向链表 传统链表与linu内核链表的区别图: 图一 图二 从上图中看出在传统链表中各种不同链表间没有通用性,因为各个数据域不同,而在linux内核中巧妙将链表结构内嵌到数据域结构中使得不同结构之间能连接起来: 链表的常用操作 内核中链表实现文件路径:include/linux/list.h 链表结构定义 struct list_head {     struct list_head *next, *prev; }; 获取结构入口地址(list_entry) #define list_entry

Linux 内核数据结构:双向链表

Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到.我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为广泛的数据结构,具体你可以 查看 这里. 首先让我们看一下主要的结构体: struct list_head { struct list_head *next, *prev; }; 你可以看到其与常见的结构体实现有显著不同,比如 glib 中所使用到的双向链表实现. struct GList { gp

Linux 内核数据结构:Linux 双向链表

Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到.我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为广泛的数据结构,具体你可以 查看 这里. 首先让我们看一下主要的结构体: struct list_head { struct list_head *next, *prev; }; 你可以看到其与常见的结构体实现有显著不同,比如 glib 中所使用到的双向链表实现. struct GList { gp