Linux内核链表使用方法

news2024/10/6 2:52:38

简介:

        链表是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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中国星坤连接器:定制化服务,精准选型!

在当今快速发展的电子行业中&#xff0c;连接器作为电子设备中不可或缺的组成部分&#xff0c;其性能和品质直接影响到整个系统的性能表现。中国星坤连接器以其卓越的产品选型系统和质量保证&#xff0c;为全球客户提供了一站式的解决方案。 精准选型&#xff0c;快速定位 中国…

模板进阶:非类型模板参数,类模板特化,模板的编译分离

1. 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将该参数当成常…

【python】python母婴数据分析模型预测可视化(数据集+论文+PPT+源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

vue-cli 脚手架详细介绍

4 vue-cli 脚手架 1 脚手架介绍 vue-cli也叫vue脚手架,vue-cli是vue官方提供的一个全局命令工具&#xff0c;这个命令可以帮助我们快速的创建一个vue项目的基础架子。 脚手架&#xff1a;搭建好的一个架子&#xff0c;我们在架子上进行开发 开箱即用零配置基于webpack、webpac…

13 学习总结:指针 · 其一

目录 一、内存和地址 &#xff08;一&#xff09;内存 &#xff08;二&#xff09;内存单元 &#xff08;三&#xff09;地址 &#xff08;四&#xff09;拓展&#xff1a;CPU与内存的联系 二、指针变量和地址 &#xff08;一&#xff09;创建变量的本质 &#xff08;二…

【MySQL】逻辑架构与存储引擎

一、逻辑架构 1、MySQL逻辑架构 我们可以根据上图来对sql的执行过程进行分析 第一步&#xff1a;客户端与服务器建立一个连接&#xff0c;从连接池中分配一个线程处理SQL语句第二步&#xff1a;SQL接口接受SQL指令第三步&#xff1a;如果是5.7版本&#xff0c;就会先去缓存中…

SpringMVC(2)——controller方法参数与html表单对应

controller方法参数与html表单对应 0. User实体类 import org.springframework.format.annotation.DateTimeFormat;import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.Map;public class User implements Serializable {private …

期末考试结束,老师该如何私发成绩?

随着期末考试的落幕&#xff0c;校园里又恢复了往日的宁静。然而&#xff0c;对于老师们来说&#xff0c;这并不意味着工作的结束&#xff0c;相反&#xff0c;一系列繁琐的任务才刚刚开始。 成绩单的发放&#xff0c;就是其中一项让人头疼的工作。家长们焦急地等待着孩子的考试…

【全面讲解如何安装Jupyter Notebook!】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

springboot三层架构详细讲解

目录 springBoot三层架构0.简介1.各层架构1.1 Controller层1.2 Service层1.3 ServiceImpl1.4 Mapper1.5 Entity1.6 Mapper.xml 2.各层之间的联系2.1 Controller 与 Service2.2 Service 与 ServiceImpl2.3 Service 与 Mapper2.4 Mapper 与 Mapper.xml2.5 Service 与 Entity2.6 C…

【Spring Boot】关系映射开发(三):多对多映射

关系映射开发&#xff08;三&#xff09;&#xff1a;多对多映射 1.创建实体1.1 创建 Student 实体1.2 创建 Teacher 实体 2.创建测试 在 多对多 关联关系中&#xff0c;只能通过 中间表 的方式进行映射&#xff0c;不能通过增加外键来实现。 注解 ManyToMany 用于关系的发出端…

【React Native优质开源项目】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Nacos架构设计

Nacos1.X架构设计 Nacos2.X架构修改

Gitlab代码管理工具安装配置

前言&#xff1a; 没有真正的证书与域名建议使用httpip的方式在内网使用&#xff0c;不建议使用假的域名地址 一、安装前配置 #更改主机域名 hostnamectl set-hostname gitlab.dome.com bash #配置hosts 底部添加下面内容 vim /etc/hosts ############################ ip gi…

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

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

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

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

人工智能在病理组学虚拟染色中的应用|文献精析·24-07-07

小罗碎碎念 本期文献精析&#xff0c;分享的是一篇关于深度学习在虚拟染色技术中应用于组织学研究的综述。 角色姓名单位&#xff08;中文&#xff09;第一作者Leena Latonen东芬兰大学&#xff08;QS-552&#xff09;生物医学研究所通讯作者Pekka Ruusuvuori图尔库大学&#…

Raw Socket(一)实现TCP三次握手

实验环境&#xff1a; Windows物理机&#xff1a;192.168.1.4 WSL Ubuntu 20.04.6 LTS&#xff1a;172.19.32.196 Windows下的一个http服务器&#xff1a;HFS&#xff0c;大概长这个样子&#xff1a; 客户端就是Ubuntu&#xff0c;服务端就是这个…

2024年【危险化学品生产单位安全生产管理人员】考试总结及危险化学品生产单位安全生产管理人员考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 危险化学品生产单位安全生产管理人员考试总结是安全生产模拟考试一点通总题库中生成的一套危险化学品生产单位安全生产管理人员考试试题&#xff0c;安全生产模拟考试一点通上危险化学品生产单位安全生产管理人员作业…

6、Redis系统-数据结构-05-整数

五、整数集合&#xff08;Intset&#xff09; 整数集合是 Redis 中 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素&#xff0c;并且元素数量不大时&#xff0c;就会使用整数集合这个数据结构作为底层实现。整数集合通过紧凑的内存布局和升级机制&#xff0c;实现了…