【linux kernel 常用数据结构和设计模式】【数据结构 2】【通过一个案例属性list、hlist、rbtree、xarray数据结构使用】
一、背景
我在前面的例子中讲解了 内核常用的数据结构, 本机我们就拿 上节中 讲解到的 list、hlist、rbtree、xarray
来写一个 有意义的测试案例。
-
在汽车电子里,MCU(比如车身域控制器里的 STM32)通过 SPI 向车机 SOC 定期上传各种信号和事件。
-
这些信号类型包括:
-
传感器数据(温度、车速、油门位置等)
-
诊断消息(MCU 自检、故障码上报)
-
控制命令(开关灯、空调风速等)
-
异步事件(按钮按下、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. 如何映射到四种数据结构
-
list_head
:SPI 设备列表-
车上可能有多个 SPI MCU(例如 MCU-A 管理车身、MCU-B 管理空调),驱动维护一个链表存放当前挂接的 SPI 从设备。
-
一对多关系:总线 → 多个 SPI 设备。
-
-
hlist
(哈希表):消息处理器表-
每个消息类型(msg_id)绑定一个 handler。
-
例如
msg_id=1
→ 温度数据解析函数,msg_id=2
→ 车速解析函数。 -
查找快,适合 MCU 发来的消息要立刻分发。
-
-
xarray
:按序号存储原始消息-
每条 MCU 上报的消息都有一个 seq 序号。
-
存入
xarray
后,可以快速随机访问(比如 HMI 想读取最近 1000 条中第 500 条)。 -
稀疏友好(如果 MCU 序号不是连续的,也没问题)。
-
-
rbtree
:延迟处理的事件队列-
某些 MCU 消息需要在未来某个时间点处理(例如“延迟 2 秒开灯”,“500ms 后触发蜂鸣器”)。
-
驱动把这些事件按到期时间插入红黑树,timer 定时扫描最近要到期的节点,保证事件按时间顺序触发。
-
3. 场景
MCU-A 通过 SPI 发来以下报文:
-
报文 1:
seq=101, msg_id=1, delay=0, payload=TEMP=25.5
→ 立即处理,handler=温度解析,结果更新到车机的传感器缓存。 -
报文 2:
seq=102, msg_id=8, delay=500ms, payload=BUZZER_ON
→ 插入 rbtree,定时器 500ms 后执行蜂鸣器打开动作。 -
报文 3:
seq=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