Linux内核链表使用方法

简介:

        链表是linux内核中最简单,同时也是应用最广泛的数据结构。内核中定义的是双向链表。

        linux的链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中。linux的链表节点只有2个指针(pre和next),这样的话,链表的节点将独立于用户数据之外,便于实现链表的共同操作。

一、链表函数介绍

Linux内核链表源码路径: include/linux/list.h

常用函数、宏介绍:

函数作用备注
初始化LIST_HEAD_INITINTI_LIST_HEAD初始化链表头常用
LIST_HEAD
添加list_add头部添加常用
list_add_tail尾部添加
删除list_del删除节点,指向特定的位置常用
list_del_init删除节点后,反初始化
遍历list_entry根据list倒推宿主结构体的首地址常用
list_for_each正向遍历获取list
list_for_each_entry正向遍历,获取list数组结构
list_for_each_prev反向遍历获取list
list_for_each_entry_reverse反向遍历,获取list数组结构
搬移list_move将链表的某个节点插入到新的链表上
list_move_tail
合并list_splice将2条链表合并
list_splice_init合并后原有的list反初始化
list_splice_tail
list_splice_tail_init
替换list_replace将新节点和链表上某位置的节点替换
list_replace_init将新节点和链表上某位置的节点替换,替换后将旧节点反初始化

定义链表结构体:

struct list_head {
	struct list_head *next, *prev;
};

1、初始化

1.1 创建链表头 并用 INIT_LIST_HEAD 初始化

struct list_head listHead;

static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

1.2 也可以直接用 LIST_HEAD 创建并初始化链表头

/* 初始化 */
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

2、添加

  • list_add

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

功能:将new添加到链表头head的下一个位置

参数:

        new:添加的链表节点

        head:链表头

  • list_add_tail

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

功能:将new添加到链表头head的尾部

参数:

        new:添加的链表节点

        head:链表头

函数原型:

/* 添加 */
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;
}

static inline void list_add(struct list_head *new, struct list_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);
}

3、删除

  • list_del

list_del(struct list_head *entry)

功能:删除某节点

参数:

        entry:entry所在的链表头中将entry节点删除

  • list_del_init

函数原型:

/* 删除 */
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
	next->prev = prev;
	prev->next = next;
}

static inline void __list_del_entry(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = NULL;
	entry->prev = NULL;
}

static inline void list_del_init(struct list_head *entry)
{
	__list_del_entry(entry);
	INIT_LIST_HEAD(entry);
}

4、遍历

4.1 container_of 解析

list.h文件中,最复杂的就是获取用户数据的宏定义list_entry,其功能是根据结构体中已知的list链表成员变量的地址,来倒推宿主结构体的首地址。

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

调用container_of这个宏

#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member)*__mptr = (ptr);    \
             (type *)((char *)__mptr - offsetof(type, member)); })

分析一下container_of宏

// 步骤1:将数字0强制转型为type*,然后取得其中的member元素
((type *)0)->member  // 相当于((struct student *)0)->list

// 步骤2:定义一个临时变量__mptr,并将其也指向ptr所指向的链表节点
const typeof(((type *)0)->member)*__mptr = (ptr);

// 步骤3:计算member字段距离type中第一个字段的距离,也就是type地址和member地址之间的差
// offset(type, member)也是一个宏,定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

// 步骤4:将__mptr的地址 - type地址和member地址之间的差
// 其实也就是获取type的地址

4.2 遍历宏原型

/* 遍历 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)    //根据结构体中的已知的成员变量的地址,来寻求该结构体的首地址

#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))

#define list_for_each_prev(pos, head) \
	for (pos = (head)->prev; pos != (head); pos = pos->prev)

#define list_for_each_entry_reverse(pos, head, member)			\
	for (pos = list_last_entry(head, typeof(*pos), member);		\
	     &pos->member != (head); 					\
	     pos = list_prev_entry(pos, member))

list_for_eachlist_for_each_safe 差异:

        list_for_each_safe 可防止删除链表条目。如:list_for_each执行的for循环中,如果删除条目会导致段错误"Segmentation fault (core dumped)"报错。而 list_for_each_safe就可以解决此问题。

5、判断链表为空

  • list_empty
static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

二、使用方法

链表数据结构在内核态和用户态都能使用,使用方法如下:

1、定义 struct list_head 链表头 head

2、初始化 LIST_HEAD(head)

3、添加entry到链表 list_add_tail(&entry, &head)

4、遍历链表头

struct list_head *cursor, *next;

list_for_each_safe(cursor, next, &tx_req_list) {

    stpHead_Addr = list_entry(cursor, struct ipcl_req, list);        //根据我们结构体中的已知的成员变量的地址,来寻求该结构体的首地址

    ...        //我们自己定义功能

    list_del_init(cursor);                //链表删除cursor,并初始化 cursor

}

示例:

代码下载路径:https://download.csdn.net/download/hinewcc/89522091

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"

struct list_head listHead;      //定义链表头

//LIST_HEAD(listHead);        

/* 含链表的结构体 */
struct list_member {
    char name[32];
    struct list_head entry;
};

#define MEMBER_NUM  5

int main(int argc, char **argv)
{
	int i;
	
	if (argc != 2) {
		printf("usage: ./app name");
		return -1;
	}

    printf("search name: %s\n", argv[1]);

/* 1.初始化listHead链表 */
    INIT_LIST_HEAD(&listHead);                                              

    struct list_member stMember[MEMBER_NUM] = {0};
    struct list_head *cursor, *next;

/* 2.listHead链表添加 */
    for (i = 0; i < MEMBER_NUM; i++) {
        printf("addr[%d]: %p\n", i, &stMember[i]);
        sprintf(stMember[i].name, "name%d", i);
        list_add_tail(&stMember[i].entry, &listHead);          //listHead链表添加成员
    }
       
/* 3.listHead链表轮询并比较 */
    list_for_each_safe(cursor, next, &listHead) {              //轮询listHead链表头
        /*  
            功能:根据结构体中的已知的 entry 成员变量的地址,来寻求该结构体的首地址
            参数1: entry成员指针
            参数2: 结构体类型
            参数3: 结构体中entry的成员名
        */
        struct list_member *member = list_entry(cursor, struct list_member, entry);

        if (strcmp(member->name, argv[1]) == 0) {           //比较
            printf("search OK: addr: %p\n", member);
            break;
        }
    }

/* 4.测试 list_del 删除, list_empty 检测链表空 */
    list_for_each_safe(cursor, next, &listHead) {
        struct list_member *member = list_entry(cursor, struct list_member, entry);
        printf("del %s\n", member->name);
        list_del(cursor);
        
        if (list_empty(&listHead)) {
            printf("list empty!!!\n");
        }
    }

	return -1;
}

编译:$ gcc -o test_app -I ./ main.c

运行:

$ ./test_app name1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/780595.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Linux操作系统使用逻辑卷的快照(snapshot),进行对逻辑卷的数据备份。

作用&#xff1a;结合特定应用程序&#xff0c;方便备份数据。 基于cow&#xff08;copy on write 写时复制&#xff09;机制 在创建逻辑卷快照的时候&#xff0c;如果不去设置逻辑卷快照的权限的话&#xff0c;那么这个逻辑卷的权限就是可读可写&#xff0c; 创建逻辑卷快照…

coco数据集格式计算mAP的python脚本

目录 背景说明COCOeval 计算mAPtxt文件转换为coco json 格式自定义数据集标注 背景说明 在完成YOLOv5模型移植&#xff0c;运行在板端后&#xff0c;通常需要衡量板端运行的mAP。 一般需要两个步骤 步骤一&#xff1a;在板端批量运行得到目标检测结果&#xff0c;可保存为yol…

AI教你如何系统的学习Python

Python学习计划 第一阶段&#xff1a;Python基础&#xff08;1-2个月&#xff09; 目标&#xff1a;掌握Python的基本语法、数据类型、控制结构、函数、模块和包等。 学习Python基本语法&#xff1a;包括变量、数据类型&#xff08;整数、浮点数、字符串、列表、元组、字典、…

STM32基础篇:GPIO

GPIO简介 GPIO&#xff1a;即General Purpose Input/Output&#xff0c;通用目的输入/输出。就是一种片上外设&#xff08;内部模块&#xff09;。 对于STM32的芯片来说&#xff0c;周围有一圈引脚&#xff0c;有时需要对引脚进行读写&#xff08;读&#xff1a;从外部输入一…

【xinference】(15):在compshare上,使用docker-compose运行xinference和chatgpt-web项目,配置成功!!!

视频演示 【xinference】&#xff08;15&#xff09;&#xff1a;在compshare上&#xff0c;使用docker-compose运行xinference和chatgpt-web项目&#xff0c;配置成功&#xff01;&#xff01;&#xff01; 1&#xff0c;安装docker方法&#xff1a; #!/bin/shdistribution$(…

【嵌入式DIY实例-ESP8266篇】-LCD ST7735显示BMP280传感器数据

LCD ST7735显示BMP280传感器数据 文章目录 LCD ST7735显示BMP280传感器数据1、硬件准备与接线2、代码实现本文介绍如何将 ESP8266 NodeMCU 板 (ESP-12E) 与 Bosch Sensortec 的 BMP280 气压和温度传感器连接。 NodeMCU 微控制器 (ESP8266EX) 从 BMP280 传感器读取温度和压力值,…

VUE3初学入门-02-VUE创建项目

创建VUE项目的另一个方法 三种方法通过vue-cli进行创建通过npm进行创建比较 部署到nginx修改配置生成部署文件 三种方法 上一篇是在VSCODE中建立工作区&#xff0c;然后创建&#xff0c;属于命令加鼠标方式。个人感觉&#xff0c;在VSCODE基本上都是这样的操作&#xff0c;不是…

vue3中svg图标的封装与使用

组件封装&#xff1a; <template><svg :class"svgClass" :style"{ width: size px, height: size px, color: color, verticalAlign:deviationem}" aria-hidden"true"><use :xlink:href"#icon-${name}" /></s…

Python编程学习笔记(2)--- 列表简介

1、列表是什么 列表由一系列按特定顺序排列的元素组成。可以创建包含字母表中所有字母、数字、0~9或所有家庭成员姓名的列表&#xff1b;也可以将任何东西加入列表中&#xff0c;其中的元素之间可以没有任何关系。列表通常包含多个元素&#xff0c;因此给列表指定一个表示复数…

基于SSM+JSP的KTV点歌系统(带1w+文档)

基于SSMJSP的KTV点歌系统(带1w文档) 开发一个KTV点歌系统可以解决不利于线下点歌的问题&#xff0c;同时管理员可以利用网络对KTV点歌系统信息进行管理&#xff0c;设计的网站保证信息的完整安全&#xff0c;这样才能提高工作效率&#xff0c;保证系统安全正常的运行。 项目简介…

vim未找到命令,且yum install vim安装vim失败

vim未找到命令&#xff0c;且yum安装vim失败 1、wget更新yum云资源&#xff0c;本次更新为华为云镜像资源 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.huaweicloud.com/repository/conf/CentOS-7-anon.repowget报未找到命令&#xff0c;请查看文章Linux wget…

iOS UITableView自带滑动手势和父视图添加滑动手势冲突响应机制探索

场景 我们有时候会遇到这样的一个交互场景&#xff1a;我们有一个UITableView 放在一个弹窗中&#xff0c;这个弹窗可以通过滑动进行展示和消失&#xff08;跟手滑动的方式&#xff09;&#xff0c;然后这个UITableView放在弹窗中&#xff0c;并且可以滚动&#xff0c;展示一些…

昇思25天学习打卡营第19天|Diffusion扩散模型

学AI还能赢奖品&#xff1f;每天30分钟&#xff0c;25天打通AI任督二脉 (qq.com) Diffusion扩散模型 本文基于Hugging Face&#xff1a;The Annotated Diffusion Model一文翻译迁移而来&#xff0c;同时参考了由浅入深了解Diffusion Model一文。 本教程在Jupyter Notebook上成…

Python数据分析案例50——基于EEMD-LSTM的石油价格预测

案例背景 很久没更新时间序列预测有关的东西了。 之前写了很多CNN-LSTM&#xff0c;GRU-attention&#xff0c;这种神经网络之内的不同模型的缝合&#xff0c;现在写一个模态分解算法和神经网络的缝合。 虽然eemd-lstm已经在学术界被做烂了&#xff0c;但是还是很多新手小白或…

RAG 案框架(Qanything、RAGFlow、FastGPT、智谱RAG)对比

各家的技术方案 有道的QAnything 亮点在&#xff1a;rerank RAGFLow 亮点在&#xff1a;数据处理index 智谱AI 亮点在文档解析、切片、query改写及recall模型的微调 FastGPT 优点&#xff1a;灵活性更高 下面分别按照模块比较各框架的却别 功能模块QAnythingRAGFLowFastG…

MPC学习资料汇总

模型预测控制MPC学习资料汇总 需要的私信我~ 需要的私信我~ 需要的私信我~ 【01】课件内容 包含本号所有MPC课程的课件&#xff0c;以及相关MATLAB文档。 【02】课件源代码 本号所有MPC课程的源代码。 【03】MPC仿真案例 三个MPC大型仿真案例&#xff1a; 1&#xff09;…

力扣爆刷第160天之TOP100五连刷66-70(回溯、旋转图像、技巧题)

力扣爆刷第160天之TOP100五连刷66-70&#xff08;回溯、旋转图像、技巧题&#xff09; 文章目录 力扣爆刷第160天之TOP100五连刷66-70&#xff08;回溯、旋转图像、技巧题&#xff09;一、110. 平衡二叉树二、39. 组合总和三、543. 二叉树的直径四、470. 用 Rand7() 实现 Rand1…

win系统提示VCRUNTIME140_1.dll丢失或找不到的8个处理方法

在使用电脑过程中经常会遇到各种各样的问题&#xff0c;比如vcruntime140_1.dll丢失或找不到vcruntime140_1.dll无法继续执行代码就是其中的一个常见问题!那么遇到vcruntime140_1.dll丢失问题要怎么处理&#xff1f;vcruntime140_1.dll是什么&#xff1f;下面我给大家详细介绍v…

谷粒商城学习笔记-16-人人开源搭建后台管理系统

文章目录 一&#xff0c;克隆前/后端代码1&#xff0c;克隆前端工程renren-fast-value2&#xff0c;克隆后端工程renren-fast 二&#xff0c;集成后台管理系统的后端代码三&#xff0c;启动后台管理系统四&#xff0c;前端系统的安装和运行1&#xff0c;下载安装VSCode2&#x…

Crossformer_Transformer

文章目录 摘要1 引言2 相关工作多变量时间序列预测基于Transformer的MTS预测视觉Transformers 3 方法详细解释 3.1 维度-分段-方式嵌入3.2 两阶段注意力层跨时间阶段跨维度阶段 3.3 分层编码器-解码器编码器解码器 摘要 最近&#xff0c;许多深度模型被提用于多变量时间序列&a…