当前位置: 首页 > news >正文

【linux kernel 常用数据结构和设计模式】【数据结构 2】【通过一个案例属性list、hlist、rbtree、xarray数据结构使用】

一、背景

我在前面的例子中讲解了 内核常用的数据结构, 本机我们就拿 上节中 讲解到的 list、hlist、rbtree、xarray 来写一个 有意义的测试案例。

  • 在汽车电子里,MCU(比如车身域控制器里的 STM32)通过 SPI 向车机 SOC 定期上传各种信号和事件

  • 这些信号类型包括:

    1. 传感器数据(温度、车速、油门位置等)

    2. 诊断消息(MCU 自检、故障码上报)

    3. 控制命令(开关灯、空调风速等)

    4. 异步事件(按钮按下、CAN 报文转发)

  • 驱动需要缓存、分类、调度这些消息,以便车机上层应用(HMI 或中间件)读取。

1. 该如何设计我们的测试demo

  • 驱动模拟:车内 MCU 通过 SPI 向车机发送消息(本 demo 不直接与真实 SPI 硬件交互,而是通过 /proc/spi_demo 写入模拟消息)。

  • 写入格式(ASCII):SEQ:MSGID:DELAY_MS:PAYLOAD,示例:42:7:100:HELLO

    • SEQ:消息序列号 —— 存储 payload 的索引(用于 xarray)。

    • MSGID:消息类型 ID —— 在哈希表(hlist)中查找 handler。

    • DELAY_MS:延迟毫秒 —— 若 >0 则用 rbtree 排序后延迟处理。

    • PAYLOAD:消息的文本数据(不包含 :)。

  • 读取 /proc/spi_demo 可以查看内部状态:已注册设备(list),handlers(hlist/hash),已排期事件(rbtree),已存储消息(xarray)。


  • list_head:MCU 数量不多,但需要遍历,双向链表 O(1) 插删最合适。

  • hlist/hash:消息类型很多(几十/上百种),但分发必须快,hash 平均 O(1) 查找是最佳选择。

  • xarray:MCU 上报数据可能有几万条 seq,稀疏且需要随机访问,xarray 比数组节省内存,比链表查找更快。

  • rbtree:事件调度必须严格时间有序,红黑树保证插入 O(log n),且 rb_first() 总能拿到最近到期任务,适合 timer 驱动。

2. 如何映射到四种数据结构

  1. list_head:SPI 设备列表

    • 车上可能有多个 SPI MCU(例如 MCU-A 管理车身、MCU-B 管理空调),驱动维护一个链表存放当前挂接的 SPI 从设备。

    • 一对多关系:总线 → 多个 SPI 设备。

  2. hlist(哈希表):消息处理器表

    • 每个消息类型(msg_id)绑定一个 handler。

    • 例如 msg_id=1 → 温度数据解析函数,msg_id=2 → 车速解析函数。

    • 查找快,适合 MCU 发来的消息要立刻分发。

  3. xarray:按序号存储原始消息

    • 每条 MCU 上报的消息都有一个 seq 序号。

    • 存入 xarray 后,可以快速随机访问(比如 HMI 想读取最近 1000 条中第 500 条)。

    • 稀疏友好(如果 MCU 序号不是连续的,也没问题)。

  4. rbtree:延迟处理的事件队列

    • 某些 MCU 消息需要在未来某个时间点处理(例如“延迟 2 秒开灯”,“500ms 后触发蜂鸣器”)。

    • 驱动把这些事件按到期时间插入红黑树,timer 定时扫描最近要到期的节点,保证事件按时间顺序触发。

3. 场景

MCU-A 通过 SPI 发来以下报文:

  • 报文 1seq=101, msg_id=1, delay=0, payload=TEMP=25.5
    → 立即处理,handler=温度解析,结果更新到车机的传感器缓存。

  • 报文 2seq=102, msg_id=8, delay=500ms, payload=BUZZER_ON
    → 插入 rbtree,定时器 500ms 后执行蜂鸣器打开动作。

  • 报文 3seq=103, msg_id=7, delay=0, payload=DIAG:0xE101
    → handler 解析诊断信息,存入日志。

最终:

  • /proc/spi_demo 能看到:

    • 已注册设备列表(list_head)

    • handler 哈希表(hlist)

    • 已存消息(xarray)

    • 等待执行的定时任务(rbtree)

二、demo

1. code

/** spi_ds_demo.c - demo kernel module combining list_head, hlist, rbtree, xarray** Scenario: 車载 MCUs 通过 SPI 发送消息到车机的 SPI 驱动,驱动负责接收消息,* 存储原始 payload(按 sequence 使用 xarray),根据消息 ID 查找 handler(使用 hlist/hash),* 可为消息安排延迟处理(使用 rbtree 以 expires 排序),并维护已注册的 SPI 设备列表(list_head)。** 交互接口:*  - /proc/spi_demo      : 读出当前内部状态(devices, handlers, scheduled events, stored messages)*  - 写入 /proc/spi_demo:以 ASCII 写入模拟 MCU 消息,格式为:*        SEQ:MSGID:DELAY_MS:PAYLOAD*    例如: 42:7:100:HELLO*    - SEQ: 序列号(整数,用于存储 payload 到 xarray)*    - MSGID: 消息 ID(用于哈希查找 handler)*    - DELAY_MS: 延迟毫秒(0 表示立即处理)*    - PAYLOAD: 不包含 ':' 的字符串负载(最大 200 字节)** 数据结构展示映射:*  - list_head: 管理已注册的 spi 设备实例(one-to-many: bus -> devices)*  - hlist/hashtable: map msg_id -> handler (hash 桶)*  - rbtree: 按过期时间排序的延迟事件调度(支持按时间先后顺序处理)*  - xarray: 按序列号存储 payload,支持大稀疏序列空间** 适用于学习/测试数据结构的驱动 demo(不与真实 SPI 硬件通信)。*/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/hashtable.h>
#include <linux/rbtree.h>
#include <linux/xarray.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>
#include <linux/timer.h>
#include <linux/spinlock.h>#define PROC_NAME "spi_demo"
#define MAX_PAYLOAD 200
#define HANDLER_BITS 4 /* 16 buckets */MODULE_LICENSE("GPL");
MODULE_AUTHOR("leo");
MODULE_DESCRIPTION("Demo SPI driver showing list_head, hlist, rbtree, xarray");/* ---------- list_head: simulated SPI device instances ---------- */
struct spi_dev_entry {int id; /* device id */char name[32];struct list_head list; /* list_head 用于把设备挂到全局 device_list */
};
static LIST_HEAD(device_list);
static spinlock_t device_lock;/* helper to create/register a fake device */
static struct spi_dev_entry *spi_dev_create(int id, const char *name)
{struct spi_dev_entry *e;e = kzalloc(sizeof(*e), GFP_KERNEL);if (!e)return NULL;e->id = id;strlcpy(e->name, name, sizeof(e->name));INIT_LIST_HEAD(&e->list);spin_lock(&device_lock);list_add_tail(&e->list, &device_list);spin_unlock(&device_lock);return e;
}static void spi_dev_destroy_all(void)
{struct spi_dev_entry *pos, *tmp;spin_lock(&device_lock);list_for_each_entry_safe(pos, tmp, &device_list, list) {list_del(&pos->list);kfree(pos);}spin_unlock(&device_lock);
}/* ---------- hlist/hashtable: message id -> handler mapping ---------- */
struct msg_handler {u32 msg_id;char name[32];struct hlist_node hnode;
};
static DEFINE_HASHTABLE(handler_ht, HANDLER_BITS);
static spinlock_t handler_lock;static void handler_register(u32 id, const char *name)
{struct msg_handler *h;h = kzalloc(sizeof(*h), GFP_KERNEL);if (!h)return;h->msg_id = id;strlcpy(h->name, name, sizeof(h->name));spin_lock(&handler_lock);hash_add(handler_ht, &h->hnode, h->msg_id);spin_unlock(&handler_lock);
}static void handler_unregister_all(void)
{int b;struct msg_handler *h;struct hlist_node *tmp;spin_lock(&handler_lock);hash_for_each_safe(handler_ht, b, tmp, h, hnode) {hash_del(&h->hnode);kfree(h);}spin_unlock(&handler_lock);
}static struct msg_handler *handler_find(u32 id)
{struct msg_handler *h;spin_lock(&handler_lock);hash_for_each_possible(handler_ht, h, hnode, id) {if (h->msg_id == id) {spin_unlock(&handler_lock);return h;}}spin_unlock(&handler_lock);return NULL;
}/* ---------- xarray: store raw payloads by sequence number ---------- */
static DEFINE_XARRAY(msg_xa); /* seq -> *struct stored_msg */struct stored_msg {u32 seq;u32 msg_id;size_t len;char *data; /* allocated payload */
};static void stored_msg_free(struct stored_msg *m)
{if (!m) return;kfree(m->data);kfree(m);
}/* ---------- rbtree: scheduled events sorted by expires (jiffies) ---------- */
struct ev_node {unsigned long expires; /* jiffies */u32 seq; /* sequence number associated */struct rb_node node;
};
static struct rb_root ev_tree = RB_ROOT;
static spinlock_t ev_lock; /* protect ev_tree */
static struct timer_list ev_timer;
static unsigned long timer_interval_jiffies = HZ / 10; /* 100ms *//* insert event into rbtree (sorted by expires then seq) */
static void ev_insert(struct ev_node *en)
{struct rb_node **link = &ev_tree.rb_node, *parent = NULL;struct ev_node *entry;spin_lock(&ev_lock);while (*link) {parent = *link;entry = rb_entry(parent, struct ev_node, node);if (en->expires < entry->expires ||(en->expires == entry->expires && en->seq < entry->seq))link = &(*link)->rb_left;elselink = &(*link)->rb_right;}rb_link_node(&en->node, parent, link);rb_insert_color(&en->node, &ev_tree);spin_unlock(&ev_lock);
}static struct ev_node *ev_first_locked(void)
{struct rb_node *n = rb_first(&ev_tree);if (!n) return NULL;return rb_entry(n, struct ev_node, node);
}static void ev_remove_node_locked(struct ev_node *en)
{rb_erase(&en->node, &ev_tree);
}/* process expired events: pop all with expires <= jiffies */
static void process_expired_events(void)
{struct rb_node *n;struct ev_node *en;unsigned long now = jiffies;for (;;) {spin_lock(&ev_lock);n = rb_first(&ev_tree);if (!n) {spin_unlock(&ev_lock);break;}en = rb_entry(n, struct ev_node, node);if (time_after(en->expires, now)) {spin_unlock(&ev_lock);break;}/* remove then process */rb_erase(&en->node, &ev_tree);spin_unlock(&ev_lock);/* handle event: in demo just print and free */pr_info("[ev] processing seq=%u (expires at %lu, now=%lu)\n", en->seq, en->expires, now);kfree(en);}
}static void ev_timer_fn(struct timer_list *t)
{process_expired_events();/* re-arm timer if tree still has nodes */spin_lock(&ev_lock);if (ev_tree.rb_node)mod_timer(&ev_timer, jiffies + timer_interval_jiffies);spin_unlock(&ev_lock);
}/* schedule an event for seq after delay_ms */
static void schedule_event(u32 seq, unsigned int delay_ms)
{struct ev_node *en;en = kzalloc(sizeof(*en), GFP_KERNEL);if (!en) return;en->seq = seq;en->expires = jiffies + msecs_to_jiffies(delay_ms);ev_insert(en);/* ensure timer running */mod_timer(&ev_timer, jiffies + timer_interval_jiffies);
}/* ---------- procfs interface: read status, write to inject messages ---------- */
static int spi_proc_show(struct seq_file *m, void *v)
{struct spi_dev_entry *d;int b;struct msg_handler *h;struct stored_msg *sm;unsigned long idx = 0;struct rb_node *n;struct ev_node *en;seq_puts(m, "=== spi_demo status ===\n");seq_puts(m, "-- devices (list_head) --\n");spin_lock(&device_lock);list_for_each_entry(d, &device_list, list) {seq_printf(m, "dev id=%d name=%s\n", d->id, d->name);}spin_unlock(&device_lock);seq_puts(m, "-- handlers (hashtable/hlist) --\n");spin_lock(&handler_lock);hash_for_each(handler_ht, b, h, hnode)seq_printf(m, "handler msg_id=%u name=%s\n", h->msg_id, h->name);spin_unlock(&handler_lock);seq_puts(m, "-- scheduled events (rbtree) --\n");spin_lock(&ev_lock);for (n = rb_first(&ev_tree); n; n = rb_next(n)) {en = rb_entry(n, struct ev_node, node);seq_printf(m, "seq=%u expires(in jiffies)=%lu now=%lu\n", en->seq, en->expires, jiffies);}spin_unlock(&ev_lock);seq_puts(m, "-- stored messages (xarray) --\n");xa_for_each(&msg_xa, idx, sm) {seq_printf(m, "seq=%lu msg_id=%u len=%zu data=%s\n", idx, sm->msg_id, sm->len, sm->data ? sm->data : "<null>");}return 0;
}static int spi_proc_open(struct inode *inode, struct file *file)
{return single_open(file, spi_proc_show, NULL);
}/* parse a line like: SEQ:MSGID:DELAY_MS:PAYLOAD */
static ssize_t spi_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{char buf[256];char *bufp = buf;unsigned int seq, msgid, delay;char payload[MAX_PAYLOAD + 1];char *p1, *p2, *p3;int ret;void *stored;struct stored_msg *sm;struct msg_handler *h;if (count == 0 || count >= sizeof(buf))return -EINVAL;if (copy_from_user(buf, ubuf, count))return -EFAULT;buf[count] = '\0';/* simple parse by tokens */p1 = strsep(&bufp, ":");p2 = strsep(&bufp, ":");p3 = strsep(&bufp, ":");if (!p1 || !p2 || !p3)return -EINVAL;/* the rest is payload (trim newline) */strncpy(payload, buf ? buf : "", MAX_PAYLOAD);payload[MAX_PAYLOAD] = '\0';/* strip trailing newline */ret = strlen(payload);if (ret && payload[ret-1] == '\n') payload[ret-1] = '\0';if (kstrtouint(p1, 10, &seq) || kstrtouint(p2, 10, &msgid) || kstrtouint(p3, 10, &delay))return -EINVAL;pr_info("[write] got seq=%u msgid=%u delay=%u payload=%s\n", seq, msgid, delay, payload);/* store payload in xarray */sm = kzalloc(sizeof(*sm), GFP_KERNEL);if (!sm) return -ENOMEM;sm->seq = seq;sm->msg_id = msgid;sm->len = strlen(payload) + 1;sm->data = kzalloc(sm->len, GFP_KERNEL);if (!sm->data) { kfree(sm); return -ENOMEM; }strncpy(sm->data, payload, sm->len);stored = xa_store(&msg_xa, seq, sm, GFP_KERNEL);if (stored) {/* existing entry replaced: free old */stored_msg_free(stored);}/* find handler, if none just log; if delay then schedule */h = handler_find(msgid);if (!h) {pr_info("[write] no handler for msgid=%u\n", msgid);} else {pr_info("[write] found handler %s for msgid=%u\n", h->name, msgid);}if (delay == 0) {pr_info("[write] immediate process seq=%u\n", seq);/* immediate processing: in demo just print */pr_info("[process] seq=%u payload=%s handler=%s\n", seq, sm->data, h ? h->name : "<none>");} else {schedule_event(seq, delay);}return count;
}static const struct proc_ops spi_proc_fops = {.proc_open = spi_proc_open,.proc_read = seq_read,.proc_lseek = seq_lseek,.proc_release = single_release,.proc_write = spi_proc_write,
};/* ---------- module init/exit ---------- */
static struct proc_dir_entry *proc_entry;static int __init spi_demo_init(void)
{pr_info("spi_ds_demo init\n");spin_lock_init(&device_lock);spin_lock_init(&handler_lock);spin_lock_init(&ev_lock);/* create some fake spi devices (list_head usage) */spi_dev_create(1, "spi_mcu_a");spi_dev_create(2, "spi_mcu_b");/* register some handlers (hashtable/hlist usage) */handler_register(1, "power_btn");handler_register(2, "sensor_update");handler_register(7, "diag_msg");/* create proc entry for interaction */proc_entry = proc_create(PROC_NAME, 0666, NULL, &spi_proc_fops);if (!proc_entry) {pr_err("failed to create /proc/%s\n", PROC_NAME);handler_unregister_all();spi_dev_destroy_all();return -ENOMEM;}/* setup timer for rbtree event polling */timer_setup(&ev_timer, ev_timer_fn, 0);pr_info("spi_ds_demo ready. write to /proc/%s lines like SEQ:MSGID:DELAY_MS:PAYLOAD\n", PROC_NAME);return 0;
}static void __exit spi_demo_exit(void)
{unsigned long idx = 0;struct stored_msg *sm;struct rb_node *n;struct ev_node *en;pr_info("spi_ds_demo exit\n");/* remove proc entry */if (proc_entry)proc_remove(proc_entry);/* cancel timer and free rbtree events */del_timer_sync(&ev_timer);spin_lock(&ev_lock);for (n = rb_first(&ev_tree); n;) {en = rb_entry(n, struct ev_node, node);n = rb_next(n);rb_erase(&en->node, &ev_tree);kfree(en);}spin_unlock(&ev_lock);/* free xarray stored messages */xa_for_each(&msg_xa, idx, sm) {xa_erase(&msg_xa, idx);stored_msg_free(sm);}/* unregister handlers and fake devices */handler_unregister_all();spi_dev_destroy_all();pr_info("spi_ds_demo cleaned up\n");
}module_init(spi_demo_init);
module_exit(spi_demo_exit);

2.测试

# write immediate
echo -n "1:2:0:hello_from_mcu" > /proc/spi_demo# write delayed by 500 ms
echo -n "2:7:500:diag_message" > /proc/spi_democat /proc/spi_demo

3. 代码要点说明(更详细)

  • list_head (device_list):

    • 初始化:LIST_HEAD(device_list),并在 init 中调用 spi_dev_create(1, "spi_mcu_a") 等模拟注册。

    • 读取 proc 时遍历:list_for_each_entry(...) 打印设备信息。

    • 清理时使用 list_for_each_entry_safe 并调用 kfree

  • hlist/hashtable (handler_ht):

    • 使用 DEFINE_HASHTABLE(handler_ht, HANDLER_BITS) 创建哈希表。

    • handler_register(id, name) 添加 handler(hash_add),查找时用 hash_for_each_possible

    • 适合 msg_id -> handler 的映射,查找平均 O(1)。

  • xarray (msg_xa):

    • 存储 struct stored_msg *,通过 xa_store(&msg_xa, seq, sm, GFP_KERNEL) 存放。

    • 遍历使用 xa_for_each(&msg_xa, idx, sm) 来在 proc 中列出。

    • 适合按 seq 随机访问与稀疏序号空间。

  • rbtree (ev_tree):

    • 用于存储延迟事件节点 struct ev_node { expires, seq, rb_node },插入通过比较 expires(若相同再比较 seq)。

    • 采用 rb_link_node/rb_insert_color 插入,rb_first/rb_next 顺序遍历。

    • Timer 周期性调用 process_expired_events(),将已过期事件弹出并处理。

    • 适合需要按时间顺序调度与弹出的场景(例如延迟命令、timeout 管理)。

  • 同步:

    • 每个容器(device_list, handler_ht, ev_tree)都由各自的 spinlock 保护。

    • xarray 内部有自己的同步约束;写时用 xa_store(带锁),读时用 xa_for_each(用于 safe 遍历)。

  • 内存管理:

    • 所有 kmalloc 的对象在模块卸载时清理(xarray 元素、rbtree 节点、handler、devices)。

4. 一对多关系的表达方式(在 demo 中体现)

  • bus (implicit) -> 多个 spi devices:用 list_head 把多个 spi_dev_entry 挂在 device_list 上(父对象通过维护 list_head 表示它拥有多个孩子)。

  • msg_id -> 多个消息:哈希桶 hashtable(每个桶是 hlist)里可能包含相同 msg_id 的多个 handler(在本 demo 中一对一,但数据结构支持一对多)。

  • seq -> payloads:xarray 用索引 seq 映射到单个 payload;若需要每个 seq 对应多个子项,可以把 xarray 的 v


文章转载自:

http://8PheGgBq.rgksz.cn
http://N7uFQyz0.rgksz.cn
http://OGNqIQKP.rgksz.cn
http://zyqc8hdK.rgksz.cn
http://w8UlwuN7.rgksz.cn
http://OzTD5zwr.rgksz.cn
http://JolXMH3E.rgksz.cn
http://MQ3Ljux7.rgksz.cn
http://Q7JxzGIH.rgksz.cn
http://URPYHDiY.rgksz.cn
http://b2MeB5JF.rgksz.cn
http://XhYNh3oM.rgksz.cn
http://ppuRGuZZ.rgksz.cn
http://1SMkvVQh.rgksz.cn
http://xcnm1fvm.rgksz.cn
http://ntTnWYAU.rgksz.cn
http://8tiWleVW.rgksz.cn
http://n4oOphLY.rgksz.cn
http://i7Z2uBjI.rgksz.cn
http://QIdueMdb.rgksz.cn
http://meNRdDJm.rgksz.cn
http://OsZTMMeV.rgksz.cn
http://CPKbAxBM.rgksz.cn
http://rqDhRkoa.rgksz.cn
http://oJpLNANa.rgksz.cn
http://6M3aOTZb.rgksz.cn
http://VVULEz5q.rgksz.cn
http://1EjfpEzU.rgksz.cn
http://r49aKyoH.rgksz.cn
http://G7Q469uR.rgksz.cn
http://www.dtcms.com/a/371609.html

相关文章:

  • 论文阅读:DMD | Improved Distribution Matching Distillation for Fast Image Synthesis
  • 深入解析三色标记算法
  • Python struct模块 | 使用pack函数进行字节序打包
  • 二叉树的前中后序遍历(迭代法)
  • Camx-系统默认创建camxoverridesettings.txt
  • SQL面试题及详细答案150道(101-115) --- 数据操纵与定义篇
  • Adobe Premiere Pro(Pr)2022视频编辑软件安装教程与下载地址
  • 18.4 查看订单
  • 【考研C语言编程题】数组元素批量插入实现(含图示+三部曲拆解)
  • 九.弗洛伊德(Floyd)算法
  • pytorch非线性回归
  • Java 大视界 -- Java 大数据机器学习模型在金融市场风险评估与投资组合优化中的应用(407)
  • Python快速入门专业版(十一):布尔值与None:Python中的“真假”与“空值”(附逻辑判断案例)
  • 鸿蒙NEXT应用数据持久化全面解析:从用户首选项到分布式数据库
  • Linux笔记---封装套接字
  • 轻松Linux-8.动静态库的制作及原理
  • LeetCode 面试经典 150 题:移除元素(双指针思想优化解法详解)
  • 【TypeScript】闭包
  • 后端(fastAPI)学习笔记(CLASS 1):扩展基础
  • Spring Boot @RestController 注解详解
  • 腾讯云语音接口实现会议系统
  • ESP32与SUI-101A实现用电器识别
  • Wan2.2-S2V - 音频驱动图像生成电影级质量的数字人视频 ComfyUI工作流 支持50系显卡 一键整合包下载
  • 开始 ComfyUI 的 AI 绘图之旅-图生图(二)
  • VS2017安装Qt插件
  • ZYNQ FLASH读写
  • 容器元素的滚动条回到顶部
  • 【音频字幕】构建一个离线视频字幕生成系统:使用 WhisperX 和 Faster-Whisper 的 Python 实现
  • ncnn-Android-mediapipe_hand 踩坑部署实录
  • java面试中经常会问到的mysql问题有哪些(基础版)