一、一些概念
1.Linux内核链表是内嵌式双向循环链表,核心是struct list_head,只存指针、不存数据,通过嵌入宿主结构体实现类型无关、高效统一的链表操作;
2.核心结构:
//在include/linux/types.h中定义struct list_head{ struct list_head *next, *prev;};1)只有两个指针:next(下一个)、prev(上一个);2)无数据域,嵌入到业务结构体中使用;3)双向循环:空链表时 next/prev 都指向自己。
二、常用操作(在include/linux/list.h中定义)
1.链表初始化
1)静态初始化 //定义并初始化一个链表头 LIST_HEAD(my_list_head);2)动态初始化 struct list_head my_list_head; INIT_LIST_HEAD(&my_list_head);
2.插入节点
1)头插(栈):list_add(new, head);2)尾插(队列):list_add_tail(new, head);
3.删除节点
//删除指定节点 list_del(&node->list); //删除并初始化节点(安全) list_del_init(&node->list);
4.链表判断
//判断是否为空list_empty(&my_list_head);//判断是否只有一个节点list_is_singular(&my_list_head);
5.链表合并/切割
//合并链表(头接)list_splice(&list1, &my_list_head);//合并链表(尾接)list_splice_tail(&list1, &my_list_head);
6.遍历链表
1)基础遍历(指针级)struct list_head *pos;list_for_each(pos, &my_list_head) { // pos 是 struct list_head* struct my_data *data = list_entry(pos, struct my_data, list); // 访问 data->id, data->name} 2)直接遍历宿主(推荐)struct my_data *pos;list_for_each_entry(pos, &my_list_head, list) { // pos 直接是 struct my_data* printk("id: %d, name: %s\n", pos->id, pos->name);} 3)安全遍历(遍历中可删除节点)struct my_data *pos, *n;list_for_each_entry_safe(pos, n, &my_list_head, list) { // 可安全删除 pos list_del(&pos->list); kfree(pos);} 4)反向遍历list_for_each_entry_reverse(pos, &my_list_head, list);list_for_each_entry_safe_reverse(pos, n, &my_list_head, list);
三、测试
1.源码
listTestDrv.c
#include<linux/init.h>#include<linux/module.h>#include<linux/list.h>#include<linux/slab.h>// 1.定义宿主结构体struct my_data { int id; char name[32]; struct list_head list;};// 2.定义链表头staticLIST_HEAD(my_data_list);staticint __init list_demo_init(void){ int i; struct my_data *data;struct my_data *n; //3.创建并添加节点 for (i = 0; i < 3; i++) { data = kmalloc(sizeof(*data), GFP_KERNEL); data->id = i; snprintf(data->name, sizeof(data->name), "node-%d", i); list_add_tail(&data->list, &my_data_list); // 尾插 } //4.遍历并打印 printk("=== Traverse list ===\n"); list_for_each_entry(data, &my_data_list, list) { printk("id: %d, name: %s\n", data->id, data->name); } //5.安全删除所有节点 printk("=== Delete all nodes ===\n"); list_for_each_entry_safe(data, n, &my_data_list, list) { list_del(&data->list); kfree(data); } return 0;}staticvoid __exit list_demo_exit(void){ printk("Module exit\n");}module_init(list_demo_init);module_exit(list_demo_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("AXUN");MODULE_DESCRIPTION("list test");
2.Makefile文件
CC = gcc-12 #指定编译器为gcc-12obj-m += listTestDrv.o # 要编译的模块名(对应listTestDrv.c)KERNELDIR ?= /lib/modules/$(shell uname -r)/build #内核源码路径PWD := $(shell pwd) #当前目录all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules #编译模块clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean #清理编译产物
3.编译模块
执行编译命令make,编译成功后会生成listTestDrv.ko。
4.加载/卸载模块
#加载模块(需要root权限)
sudo insmod listTestDrv.ko
#查看已加载的模块(.ko文件)
sudo lsmod
#卸载模块
sudo rmmod listTestDrv