Linux 内核常用数据结构
时间轴
2026-05-24
init
概述
Linux 内核中定义了大量的通用数据结构,这些结构贯穿于进程调度、内存管理、文件系统、设备驱动等各个子系统。本文按实际使用频率从高到低梳理这些数据结构,涵盖数据结构定义、核心 API 及典型使用场景。
本文基于 Linux 6.x 内核,部分 API 在不同版本间可能略有差异。
基础容器
list_head — 双向循环链表
使用频率:最高。 这是 Linux 内核中使用最广泛的数据结构,没有之一。几乎每个子系统都会用到它。
Linux 的链表实现将数据与链表节点分离,链表节点嵌入到结构体中,而非结构体包含链表指针。这种设计的精髓在于:一套链表操作适用于所有数据类型。
头文件: <linux/list.h>
数据结构:
1 | struct list_head { |
核心 API:
| API | 说明 |
|---|---|
LIST_HEAD(name) |
静态定义并初始化一个链表头 |
INIT_LIST_HEAD(ptr) |
动态初始化链表头 |
list_add(new, head) |
在 head 之后插入 |
list_add_tail(new, head) |
在 head 之前插入(尾部) |
list_del(entry) |
删除节点 |
list_del_init(entry) |
删除并重新初始化节点 |
list_empty(head) |
判断链表是否为空 |
list_entry(ptr, type, member) |
从 list_head 指针获取包含它的结构体 |
list_for_each(pos, head) |
遍历链表 |
list_for_each_entry(pos, head, member) |
遍历链表并获取宿主结构体 |
list_for_each_entry_safe(pos, n, head, member) |
安全遍历(可在遍历时删除) |
list_move(list, head) |
移动节点到新链表 |
list_splice(list, head) |
合并两个链表 |
使用示例:
1 |
|
运行
1 | ~ # insmod list_test.ko |
list_add 和 list_add_tail:
1 | /** |
list_add_tail是被插入到了head—>prev和head之间。但因为这是一个环,“head 的前面” 在逻辑上就等于 “链表的末尾”。
list_del 和 list_del_init 函数
1 | /** |
list_replace 可以替换一个链表节点
1 | /** |
hlist — 哈希链表
hlist 是专门为哈希表设计的双向链表变体。就是说在对需要存储的数据进行hash时,如果产生了冲突,就使用链表的方式将产生冲突的数据进行存储。通常情况下,哈希表中元素的使用顺序是:数据存储—>数据获取—>数据删除。与 list_head 的区别在于:头节点只用一个 struct hlist_head(单指针),节省哈希表数组的内存。
它的核心设计动机是为了解决标准双向循环链表
list_head在作为哈希桶(Hash Bucket)使用时存在的内存浪费和语义不匹配问题。
| 特性 | list_head (标准链表) |
hlist_head + hlist_node (哈希链表) |
|---|---|---|
| 头节点结构 | 完整的双向指针 (next, prev) |
仅一个单向指针 (first) |
| 数据节点结构 | 双向指针 (next, prev) |
双向指针 (next, pprev) |
| 是否循环 | 是(头尾相连) | 否(以 NULL 结尾) |
| 空链表判断 | head->next == head |
head->first == NULL |
| 内存开销(头节点) | 2个指针 (16字节/64位) | 1个指针 (8字节/64位) |
头文件: <linux/list.h>
数据结构:
1 | struct hlist_head { |
pprev的类型是struct hlist_node **(指向指针的指针)。它存储的不是"前一个节点的地址",而是 “前一个节点中 next 字段的内存地址”。对于hlist链 head -> A -> B
- 对于链表中间的节点 B:
B->pprev == &(A->next)- 对于链表的节点 A:
A->pprev == &(head->first)
这样设计便于删除
1 | static inline void __hlist_del(struct hlist_node *n) |
add相关
1 | /** |
rbtree — 红黑树
内核中使用最多的自平衡二叉搜索树,提供 O(log n) 的查找、插入和删除。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。红黑树的特性:
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL 或 NULL)的叶子节点!]
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。此特性确保没有一条路径会比其他路径长出两倍,因而,红黑树是相对是接近平衡的二叉树。
对红黑树的所有操作都要保持红黑树的特性不变,红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。cfs_rq就是使用红黑树存储任务的。
头文件: <linux/rbtree.h> / <linux/rbtree_augmented.h>
数据结构:
1 | struct rb_node { |
粗略一看,这里似乎没有定义颜色的域,但这就是这里红黑树实现的一个巧妙的地方。
__rb_parent_color这个域其实同时包含了颜色信息以及父亲节点的指针,因为该域是一个long的类型,需要大小为sizeof(long)的对齐,那么在一般的32位机器上,其后两位的数值永远是0,于是可以拿其中的一位来表示颜色。关键在于 内存对齐(Alignment):
struct rb_node被强制要求按sizeof(long)对齐。- 在 32 位系统上,
sizeof(long) == 4,意味着任何合法的rb_node指针地址必然是 4 的倍数,即二进制最低 2 位 永远为00。- 在 64 位系统上,
sizeof(long) == 8,最低 3 位 永远为000。事实上,这里就是使用了最低位来表示颜色信息。以下关于父亲指针和颜色信息的操作其本质上都是对
rb_parent_color的位进行操作。
1
2
3
4
5
6
7
8
9
10
11
12
/* 'empty' nodes are nodes that are known not to be inserted in an rbtree */
Linux的红黑树实现对速度进行了优化,因此比传统的实现少一个间接层(有更好的缓存局部性)。 每个struct rb_node结构体的实例嵌入在它管理的数据结构中,因此不需要靠指针来分离rb_node和它 管理的数据结构。
-
用户应该编写他们自己的树搜索和插入函数,来调用已提供的红黑树函数, 而不是使用一个比较回调函数指针。
-
加锁代码也留给红黑树的用户编写。
示例
1 | // SPDX-License-Identifier: GPL-2.0 |
radix_tree — 基数树
xarray 的前身,用于整数 ID 到指针的映射。虽然 xarray 已逐步取代它,但内核中仍存在大量使用基数树的代码。
如果是新项目,请直接使用 <linux/xarray.h> 中的 XArray API。XArray 修复了 radix_tree 的诸多设计缺陷(如 preload 复杂性、索引偏移问题),且 API 更简洁。radix_tree 在 5.10.x 中仅是 XArray 上的兼容层。
头文件: <linux/radix-tree.h>
1 |
|
可见linux-5.10.x中已经用xarray替代了它
示例
1 | // SPDX-License-Identifier: GPL-2.0 |
xarray — 可扩展数组
xarray 是 Linux 4.20 引入的新一代基数树(radix tree)替代品,提供从整数(unsigned long)到指针的映射,API 更简洁,性能更好。
头文件: <linux/xarray.h>
数据结构:
1 | struct xarray { |
核心 API:
| API | 说明 |
|---|---|
DEFINE_XARRAY(name) |
静态定义 xarray |
xa_init(xa) |
动态初始化 |
xa_store(xa, index, entry, gfp) |
存储条目 |
xa_load(xa, index) |
读取条目 |
xa_erase(xa, index) |
删除条目 |
xa_insert(xa, index, entry, gfp) |
插入(key 不能已存在) |
xa_for_each(xa, index, entry) |
遍历所有条目 |
xa_find(xa, indexp, max, filter) |
查找范围内的条目 |
使用示例:
1 | DEFINE_XARRAY(my_xa); |
plist — 优先级链表
plist 在 list_head 基础上增加了优先级,表头始终指向优先级最高的节点(小的 prio 值代表高优先级),常用于需要"总是先处理最高优先级"的场景。
头文件: <linux/plist.h>
数据结构:
1 | struct plist_node { |
核心 API:
| API | 说明 |
|---|---|
plist_head_init(head) |
初始化 |
plist_node_init(node, prio) |
初始化节点 |
plist_add(node, head) |
按优先级插入 |
plist_del(node, head) |
删除节点 |
plist_first(head) |
获取优先级最高的节点 |
plist_head_empty(head) |
判断是否为空 |
llist — 无锁链表
llist(lock-less list)是一种无锁的单链表,基于 CAS 操作实现,在特定场景(如中断与进程共享数据)下比 spinlock + list_head 性能更好。
头文件: <linux/llist.h>
数据结构:
1 | struct llist_head { |
核心 API:
| API | 说明 |
|---|---|
llist_add(new, head) |
头部插入(无锁) |
llist_del_all(head) |
原子地摘下整个链表 |
llist_del_first(head) |
删除第一个节点 |
llist_empty(head) |
判断是否为空 |
典型模式:
1 | // 生产者(可在中断上下文) |
rhashtable — 可调整大小的哈希表
rhashtable 是一个可自动扩展和收缩的哈希表实现,支持 RCU 查找,适用于需要动态增长的哈希场景。
头文件: <linux/rhashtable.h>
核心 API:
| API | 说明 |
|---|---|
rhashtable_init(ht, params) |
初始化 |
rhashtable_insert_slow(ht, key, obj) |
插入 |
rhashtable_lookup(ht, key, params) |
查找 |
rhashtable_remove(ht, obj, params) |
删除 |
rhashtable_free_and_destroy(ht, fn, data) |
销毁 |
内核中的典型应用:
- 网络命名空间的连接跟踪表
- XFRM 安全策略数据库
- BPF map 的哈希类型实现
maple_tree — 枫树(Maple Tree)
Linux 6.1 引入的新型数据结构,用于替代 VMA 管理中的红黑树+链表组合。maple_tree 是一种 B 树变体,支持范围操作(range operations),对 VMA 的查找、遍历和间隙搜索非常高效。
头文件: <linux/maple_tree.h>
数据结构:
1 | struct maple_tree { |
核心 API:
| API | 说明 |
|---|---|
mt_init(mt) |
初始化 |
mtree_lock(mt) |
获取写锁 |
mtree_unlock(mt) |
释放写锁 |
mas_store(mas, entry) |
存储条目 |
mas_find(mas, max) |
查找 |
mas_erase(mas) |
删除 |
MTREE_INIT(mt, flags) |
静态初始化 |
mtree_destroy(mt) |
销毁 |
内核中的典型应用:
- VMA 管理:Linux 6.1+ 用
maple_tree替代红黑树 + 双向链表管理vm_area_struct - 用户空间程序(用户态 RCU 库 URCU 也实现了 maple tree)
interval_tree — 区间树
区间树是增强型红黑树,用于管理区间([start, last]),支持快速查找与给定区间重叠的所有区间。基于 rbtree_augmented 实现。
头文件: <linux/interval_tree.h>
数据结构:
1 | struct interval_tree_node { |
核心 API:
| API | 说明 |
|---|---|
interval_tree_insert(node, root) |
插入 |
interval_tree_remove(node, root) |
删除 |
interval_tree_iter_first(root, start, last) |
查找第一个重叠区间 |
interval_tree_iter_next(node, start, last) |
查找下一个重叠区间 |
内核中的典型应用:
- VMA 区间查找(查找与某个地址范围重叠的虚拟内存区域)
- DRM GPU 驱动的 GEM 缓冲区管理
klist — 内核对象链表
klist 是对 list_head 的包装,与 kobject 体系配合使用,提供 get/put 引用计数保护:遍历链表时自动获取节点对象的引用,防止遍历过程中节点被释放。
头文件: <linux/klist.h>
数据结构:
1 | struct klist_node { |
核心 API:
| API | 说明 |
|---|---|
klist_add_head(n, k) |
添加到头部 |
klist_add_tail(n, k) |
添加到尾部 |
klist_del(n) |
删除 |
klist_iter_init(k, i) |
初始化迭代器 |
klist_next(i) |
获取下一个节点(自动 get/put) |
klist_iter_exit(i) |
清理迭代器 |
内核中的典型应用:
- 设备驱动模型中
bus_type的设备列表 - 设备驱动模型中
driver的设备列表
ID、位图与 DMA
idr — ID 分配器
idr 提供整数 ID 到指针的映射,自动分配一个唯一的整数 ID 并关联到指针。适合"需要一个整数句柄"的场景。
头文件: <linux/idr.h>
核心 API(现代接口):
| API | 说明 |
|---|---|
idr_alloc(idr, ptr, start, end, gfp) |
分配 ID 并关联指针 |
idr_find(idr, id) |
通过 ID 查找指针 |
idr_remove(idr, id) |
删除 ID 映射 |
idr_for_each(idr, fn, data) |
遍历所有条目 |
idr_destroy(idr) |
销毁 idr |
idr_init(idr) |
初始化 |
使用示例:
1 | DEFINE_IDR(my_idr); |
内核中的典型应用:
- 进程 PID 管理
- 设备 minor 号分配
- GPU DRM 驱动的句柄管理(GEM buffer handle)
ida — IDA 分配器
ida 是 idr 的简化版,只分配整数 ID 而不关联指针(当只需要唯一整数 ID 时使用,内存开销更小)。
头文件: <linux/idr.h>
核心 API:
| API | 说明 |
|---|---|
ida_alloc(ida, gfp) |
分配一个 ID |
ida_free(ida, id) |
释放 ID |
ida_alloc_range(ida, min, max, gfp) |
在范围内分配 ID |
ida_init(ida) |
初始化 |
ida_destroy(ida) |
销毁 |
bitmap / cpumask — 位图
内核用 unsigned long 数组实现位图,提供高效的位操作集合。cpumask 是位图的特殊形式,专门描述 CPU 集合。
头文件: <linux/bitmap.h> / <linux/cpumask.h>
核心 API(bitmap):
| API | 说明 |
|---|---|
bitmap_zero(dst, nbits) |
全部清零 |
bitmap_set(dst, pos, nbits) |
设置位 |
bitmap_clear(dst, pos, nbits) |
清除位 |
bitmap_find_next_zero_area(buf, len, start, n, mask) |
查找连续 0 区域 |
bitmap_and(dst, src1, src2, nbits) |
按位与 |
bitmap_or(dst, src1, src2, nbits) |
按位或 |
核心 API(cpumask):
| API | 说明 |
|---|---|
cpumask_set_cpu(cpu, mask) |
将 CPU 加入掩码 |
cpumask_clear_cpu(cpu, mask) |
从掩码移除 CPU |
cpumask_test_cpu(cpu, mask) |
测试 CPU 是否在掩码中 |
for_each_cpu(cpu, mask) |
遍历掩码中的 CPU |
cpumask_of(cpu) |
获取单个 CPU 的掩码 |
cpu_possible_mask |
系统中所有可能的 CPU |
cpu_online_mask |
当前在线的 CPU |
cpu_present_mask |
当前存在的 CPU |
内核中的典型应用:
- IRQ 亲和性设置(指定中断由哪些 CPU 处理)
- 进程的
cpus_allowed(设置进程能运行在哪些 CPU 上) - 内存节点的 DMA 掩码
scatterlist — 散布表
scatterlist 用于描述不连续的内存区域,在 DMA(直接内存访问)场景中极为常用:将分散的物理内存片段链接成一个整体,供 DMA 引擎一次性处理。
头文件: <linux/scatterlist.h>
数据结构:
1 | struct scatterlist { |
核心 API:
| API | 说明 |
|---|---|
sg_init_one(sg, buf, len) |
初始化单条目 sg |
sg_init_table(sg, nents) |
初始化 sg 表 |
sg_set_buf(sg, buf, len) |
设置条目 |
sg_set_page(sg, page, len, offset) |
设置页条目 |
sg_next(sg) |
获取下一个条目 |
sg_nents(sg) |
计算条目数 |
dma_map_sg(dev, sg, nents, dir) |
为 DMA 映射 sg 表 |
dma_unmap_sg(dev, sg, nents, dir) |
解除 DMA 映射 |
使用示例:
1 | struct scatterlist sg[2]; |
内核中的典型应用:
- 块设备 I/O(bio 中的 scatter-gather 链表)
- 网络驱动(分散-聚集 Tx/Rx)
- 任何涉及 DMA 的数据传输
- 加密/解密子系统的数据缓冲区
并发与同步
atomic_t — 原子变量
内核中用于简单计数、标志位的原子操作,是锁-free 编程的基础。在 32 位平台上 atomic_t 是 32 位,64 位平台上还有 atomic64_t。
头文件: <linux/atomic.h>
数据结构:
1 | typedef struct { |
核心 API:
| API | 说明 |
|---|---|
atomic_read(v) |
读取值 |
atomic_set(v, i) |
设置值 |
atomic_inc(v) |
自增 |
atomic_dec(v) |
自减 |
atomic_add(i, v) |
加 |
atomic_sub(i, v) |
减 |
atomic_inc_return(v) |
自增并返回新值 |
atomic_dec_and_test(v) |
自减并测试是否为 0 |
atomic_cmpxchg(v, old, new) |
CAS 操作 |
atomic_xchg(v, new) |
交换并返回旧值 |
内核中的典型应用:
- 引用计数(驱动程序的打开计数)
- 统计计数器(网络包计数、中断计数)
- 简单的锁-free 标志
kref / refcount_t — 引用计数
kref
封装 refcount_t,提供对象的引用计数管理,配合 release 回调在计数归零时自动释放资源。
头文件: <linux/kref.h>
1 | struct kref { |
refcount_t
refcount_t 是 atomic_t 的加强版,提供溢出保护——到达最大值后不再增加,避免引用计数溢出导致的 use-after-free 漏洞。
头文件: <linux/refcount.h>
1 | typedef struct refcount_struct { |
典型使用模式:
1 | struct my_object { |
内核中的典型应用:
struct kobject的引用计数struct device的生命周期管理- 文件描述符(
struct file) - 几乎所有需要管理生命周期的内核对象
spinlock_t — 自旋锁
Linux 内核最基本的忙等锁,用于 SMP 系统中的短临界区保护。持有者在一个 CPU 上自旋等待时,其他 CPU 上的执行者也在自旋等待。
头文件: <linux/spinlock.h>
数据结构(简化):
1 | typedef struct spinlock { |
核心 API:
| API | 说明 |
|---|---|
spin_lock_init(lock) |
动态初始化 |
DEFINE_SPINLOCK(lock) |
静态定义 + 初始化 |
spin_lock(lock) |
获取锁(禁用内核抢占) |
spin_unlock(lock) |
释放锁 |
spin_lock_irq(lock) |
获取锁并禁用本地中断 |
spin_unlock_irq(lock) |
释放锁并启用本地中断 |
spin_lock_irqsave(lock, flags) |
获取锁,保存中断状态 |
spin_unlock_irqrestore(lock, flags) |
释放锁,恢复中断状态 |
spin_lock_bh(lock) |
获取锁并禁用 bottom half |
spin_trylock(lock) |
尝试获取锁(非阻塞) |
spin_is_locked(lock) |
检查锁状态 |
持有自旋锁期间绝不能睡眠(不能调用 kmalloc(GFP_KERNEL)、copy_from_user 等可能阻塞的操作)。这是内核中最常见的 bug 来源之一。
选择指南:
| 场景 | API |
|---|---|
| 进程上下文之间 | spin_lock / spin_unlock |
| 进程与中断之间 | spin_lock_irqsave / spin_unlock_irqrestore |
| 不同中断之间 | spin_lock_irqsave / spin_unlock_irqrestore |
| 进程与 bottom half 之间 | spin_lock_bh / spin_unlock_bh |
mutex — 互斥锁
与自旋锁不同,mutex 在无法获取锁时会让出 CPU 进入睡眠,适用于可能休眠的临界区。
头文件: <linux/mutex.h>
数据结构:
1 | struct mutex { |
核心 API:
| API | 说明 |
|---|---|
mutex_init(lock) |
动态初始化 |
DEFINE_MUTEX(lock) |
静态定义 |
mutex_lock(lock) |
获取锁(可能睡眠) |
mutex_unlock(lock) |
释放锁 |
mutex_lock_interruptible(lock) |
可被信号中断的获取 |
mutex_trylock(lock) |
尝试获取(非阻塞) |
mutex_is_locked(lock) |
检查状态 |
mutex 上锁者必须负责解锁(不允许在不同上下文中 lock/unlock)。内核会对此进行严格检查。
spinlock 与 mutex 的选择:
| spinlock | mutex | |
|---|---|---|
| 无法获取时 | 自旋等待 | 睡眠让出 CPU |
| 临界区 | 短(纳秒级) | 可较长(毫秒级) |
| 能否睡眠 | 绝对不能 | 可以 |
| 中断上下文 | 可用 | 不可用 |
| 系统开销 | 低 | 较高(涉及调度) |
completion — 完成量
completion 是内核中实现一个线程等待另一个线程完成某项任务的同步机制,比信号量更轻量、语义更清晰。
头文件: <linux/completion.h>
数据结构:
1 | struct completion { |
核心 API:
| API | 说明 |
|---|---|
DECLARE_COMPLETION(comp) |
静态定义 |
init_completion(comp) |
动态初始化 |
wait_for_completion(comp) |
等待完成(不可中断) |
wait_for_completion_interruptible(comp) |
等待完成(可被信号中断) |
wait_for_completion_timeout(comp, timeout) |
带超时的等待 |
complete(comp) |
唤醒一个等待者 |
complete_all(comp) |
唤醒所有等待者 |
try_wait_for_completion(comp) |
非阻塞尝试 |
使用示例:
1 | // 线程 A:等待 |
内核中的典型应用:
- 内核线程创建/销毁等待
- 设备初始化完成通知
- 异步 I/O 完成通知
- 模块卸载等待
RCU (rcu_head) — 读-复制-更新
RCU(Read-Copy-Update)是 Linux 内核中重要的无锁同步机制,适用于读多写少的场景。读者完全无锁,写者先复制再更新,等待所有读者完成后回收旧数据。
头文件: <linux/rcupdate.h> / <linux/srcu.h>
数据结构:
1 | struct rcu_head { |
核心 API:
| API | 说明 |
|---|---|
rcu_read_lock() |
读者进入临界区 |
rcu_read_unlock() |
读者离开临界区 |
call_rcu(head, func) |
注册回收回调 |
synchronize_rcu() |
等待所有读者完成(阻塞) |
rcu_assign_pointer(p, v) |
写者更新指针 |
rcu_dereference(p) |
读者解引用指针 |
kfree_rcu(ptr, rcu_field) |
RCU 安全释放内存 |
典型模式:
1 | // 读者 —— 完全无锁 |
内核中的典型应用:
- 网络路由表查找
- 文件系统 dentry 缓存
radix_tree/xarray的无锁查找fdtable(文件描述符表)的扩展
等待与调度
wait_queue — 等待队列
等待队列是一种更通用的等待机制:进程将自己加入等待队列后进入睡眠,当条件满足时被唤醒。
头文件: <linux/wait.h>
数据结构:
1 | struct wait_queue_head { |
核心 API:
| API | 说明 |
|---|---|
DECLARE_WAIT_QUEUE_HEAD(wq) |
静态定义 |
init_waitqueue_head(wq) |
动态初始化 |
wait_event(wq, condition) |
等待直到条件为真 |
wait_event_interruptible(wq, condition) |
可被信号中断的等待 |
wait_event_timeout(wq, condition, timeout) |
带超时的等待 |
wake_up(wq) |
唤醒所有等待者 |
wake_up_interruptible(wq) |
唤醒 TASK_INTERRUPTIBLE 的等待者 |
wake_up_nr(wq, nr) |
唤醒 nr 个等待者 |
使用示例:
1 | DECLARE_WAIT_QUEUE_HEAD(wq); |
内核中的典型应用:
- 进程状态切换(TASK_INTERRUPTIBLE / TASK_UNINTERRUPTIBLE)
- 设备驱动的阻塞 I/O(read/write 等待数据)
- Pipe、Socket 的读写等待
work_struct / workqueue — 工作队列
工作队列将任务推迟到进程上下文中执行,是中断底半部(bottom half)处理的常用方式之一。
头文件: <linux/workqueue.h>
数据结构:
1 | struct work_struct { |
核心 API:
| API | 说明 |
|---|---|
DECLARE_WORK(work, func) |
静态定义 |
INIT_WORK(work, func) |
动态初始化 |
schedule_work(work) |
调度到系统工作队列 |
schedule_delayed_work(dwork, delay) |
延迟调度 |
queue_work(wq, work) |
调度到指定工作队列 |
cancel_work_sync(work) |
取消并等待完成 |
flush_work(work) |
等待工作完成 |
alloc_ordered_workqueue(name, flags) |
创建有序工作队列 |
使用示例:
1 | static void my_work_handler(struct work_struct *work) |
内核中的典型应用:
- 中断底半部处理
- 设备驱动的延迟初始化
- 网络栈的包处理
- GPU 驱动的 command submission
timer_list — 内核定时器
用于在指定时间后执行回调函数(软中断上下文),精度为 jiffies 级别(通常 1ms~10ms)。需要更高精度应使用 hrtimer。
头文件: <linux/timer.h>
数据结构:
1 | struct timer_list { |
核心 API:
| API | 说明 |
|---|---|
timer_setup(timer, callback, flags) |
初始化定时器 |
mod_timer(timer, expires) |
修改到期时间 |
add_timer(timer) |
添加定时器 |
del_timer(timer) |
删除定时器 |
del_timer_sync(timer) |
同步删除(等待 handler 完成) |
timer_pending(timer) |
检查是否已提交 |
使用示例:
1 | static void my_timer_callback(struct timer_list *t) |
内核中的典型应用:
- TCP 重传定时器、keepalive 定时器
- 看门狗定时器
- 设备驱动的轮询(polling)
- LED 闪烁控制
hrtimer — 高精度定时器
hrtimer 提供纳秒级精度的定时器,底层基于红黑树管理。是现代 Linux 定时器子系统的基础。
头文件: <linux/hrtimer.h>
数据结构:
1 | struct hrtimer { |
核心 API:
| API | 说明 |
|---|---|
hrtimer_init(timer, clock_id, mode) |
初始化 |
hrtimer_start(timer, time, mode) |
启动定时器 |
hrtimer_cancel(timer) |
取消定时器 |
hrtimer_forward_now(timer, interval) |
从当前时间向前推进 |
使用示例:
1 | static enum hrtimer_restart my_hrtimer_cb(struct hrtimer *timer) |
内核中的典型应用:
- 进程调度器的周期 tick
- POSIX 定时器
- 高精度 sleep(
usleep_range) - 网络包的精确延迟控制
设备模型与通知
kobject / kset — 内核对象模型
kobject 是 Linux 设备驱动模型的基石,为所有内核对象提供统一的引用计数、sysfs 表示和热插拔事件支持。
头文件: <linux/kobject.h>
数据结构:
1 | struct kobject { |
核心 API:
| API | 说明 |
|---|---|
kobject_init(obj, ktype) |
初始化 kobject |
kobject_add(obj, parent, fmt, ...) |
添加到 sysfs |
kobject_init_and_add(obj, ktype, parent, fmt, ...) |
初始化 + 添加 |
kobject_put(obj) |
减少引用计数 |
kobject_get(obj) |
增加引用计数 |
kobject_uevent(obj, action) |
发送 uevent 到用户空间 |
kset_register(kset) |
注册 kset |
kobject_create_and_add(name, parent) |
快速创建 kobject |
内核中的典型应用:
/sys文件系统的每个目录和文件struct device、struct driver、struct bus_type等设备模型的基类uevent热插拔事件(如 U 盘插入通知 udev)
notifier_block — 通知链
通知链(Notifier Chain)是内核中发布-订阅模式的实现:某个子系统发布事件,其他感兴趣的模块注册回调来接收通知。
头文件: <linux/notifier.h>
数据结构:
1 | struct notifier_block { |
核心 API:
| API | 说明 |
|---|---|
blocking_notifier_chain_register(head, nb) |
注册通知块 |
blocking_notifier_chain_unregister(head, nb) |
注销 |
blocking_notifier_call_chain(head, val, v) |
发起通知 |
raw_notifier_chain_register(head, nb) |
注册(原子上下文安全) |
atomic_notifier_chain_register(head, nb) |
注册(原子上下文) |
通知链类型:
| 类型 | 回调上下文 | 是否可阻塞 |
|---|---|---|
atomic_notifier_chain |
原子上下文(中断/spinlock) | 否 |
blocking_notifier_chain |
进程上下文 | 是 |
raw_notifier_chain |
任意上下文(调用者负责) | 取决于调用者 |
SRCU_notifier_chain |
进程上下文(SRCU 保护) | 是 |
内核中的典型应用:
- 内核恐慌(panic)通知
- CPU 热插拔事件
- 网络设备事件(netdev 注册/注销)
- 系统重启/挂起通知
- 内存不足(OOM)通知
内存管理与缓存
kfifo — 内核 FIFO 队列
kfifo 是一个无锁的环形缓冲区(circular buffer),提供 producer/consumer 单读者/单写者的无锁通信(多读者/多写者需外部同步)。
头文件: <linux/kfifo.h>
核心 API:
| API | 说明 |
|---|---|
DECLARE_KFIFO(fifo, type, size) |
静态定义 |
kfifo_alloc(fifo, size, gfp) |
动态分配 |
kfifo_put(fifo, val) |
入队一个元素 |
kfifo_get(fifo, val) |
出队一个元素 |
kfifo_in(fifo, buf, n) |
入队多个字节 |
kfifo_out(fifo, buf, n) |
出队多个字节 |
kfifo_is_empty(fifo) |
是否为空 |
kfifo_is_full(fifo) |
是否已满 |
kfifo_len(fifo) |
已使用的元素数 |
kfifo_reset(fifo) |
清空队列 |
kfifo_free(fifo) |
释放内存 |
使用示例:
1 | DECLARE_KFIFO(fifo, int, 32); |
内核中的典型应用:
- 串口驱动的收发缓冲区
- 音频驱动的 PCM 缓冲区
- 内核日志缓冲区(printk ring buffer)
percpu — Per-CPU 变量
Per-CPU 变量为每个 CPU 分配独立的内存副本,CPU 访问自己的副本无需加锁,且缓存利用率极高(变量在 CPU 本地缓存中)。
头文件: <linux/percpu.h>
核心 API:
| API | 说明 |
|---|---|
DEFINE_PER_CPU(type, name) |
静态定义 |
alloc_percpu(type) |
动态分配 |
per_cpu(var, cpu) |
访问指定 CPU 的副本 |
get_cpu_var(var) |
获取本 CPU 副本(禁用抢占) |
put_cpu_var(var) |
配合 get_cpu_var,恢复抢占 |
this_cpu_ptr(ptr) |
获取本 CPU 副本的指针 |
per_cpu_ptr(ptr, cpu) |
获取指定 CPU 副本的指针 |
for_each_possible_cpu(cpu) |
遍历所有 CPU |
使用示例:
1 | static DEFINE_PER_CPU(int, my_counter); |
内核中的典型应用:
- 统计计数器(网络包计数、中断计数)
- 内存分配器的 per-CPU 缓存(slab/slub)
- RCU 的 per-CPU 状态
- 调度器的 per-CPU 运行队列
circ_buf — 环形缓冲区宏
一组简单的宏,用于将普通字符数组作为环形缓冲区使用。常用于实现轻量级的 producer/consumer 队列。
头文件: <linux/circ_buf.h>
数据结构:
1 | struct circ_buf { |
核心宏:
| 宏 | 说明 |
|---|---|
CIRC_SPACE(head, tail, size) |
可用空间 |
CIRC_CNT(head, tail, size) |
已使用字节数 |
CIRC_SPACE_TO_END(head, tail, size) |
到缓冲区末尾的连续空间 |
内核中的典型应用:
- TTY 驱动的行规范(line discipline)缓冲区
- 简单串口驱动
flex_array — 柔性数组
flex_array 允许创建跨多个页面的数组,但每个元素大小固定。相比一次 kmalloc 大块内存,flex_array 对小内存碎片的容忍度更好(因为每个元素分布在不同页面上)。
从 Linux 5.10 开始,flex_array 已被标记为 deprecated,推荐使用普通 kmalloc_array 或 kvmalloc_array 代替。
头文件: <linux/flex_array.h>(已删除)
替代方案: kvmalloc_array(n, size, GFP_KERNEL) 会自动选择 kmalloc 或 vmalloc。
page — 物理页描述符
struct page 是 Linux 内存管理中最重要的数据结构,每个物理内存页都有一个对应的 page 结构体。
头文件: <linux/mm_types.h>
数据结构(大幅简化):
1 | struct page { |
核心 API(部分):
| API | 说明 |
|---|---|
alloc_pages(gfp_mask, order) |
分配 2^order 个页面 |
__free_pages(page, order) |
释放页面 |
get_page(page) |
增加引用计数 |
put_page(page) |
减少引用计数 |
page_to_pfn(page) |
获取页帧号 |
pfn_to_page(pfn) |
页帧号转 page |
kmap(page) |
映射到内核地址空间 |
kunmap(page) |
解除映射 |
内核中的典型应用:
- Page Cache
- SLUB/SLAB 分配器的底层基础
- 用户空间页表映射(缺页异常处理)
mm_struct / vm_area_struct — 进程地址空间
mm_struct — 内存描述符
描述一个进程的完整虚拟地址空间,每个进程有唯一的 mm_struct(线程间可共享)。
头文件: <linux/mm_types.h>
1 | struct mm_struct { |
vm_area_struct — 虚拟内存区域
描述一段连续的虚拟地址区间,每个区间有相同的保护属性和映射类型。
1 | struct vm_area_struct { |
内核中的典型应用:
- 缺页异常处理器
mmap()/munmap()系统调用/proc/<pid>/maps的信息来源
网络
sk_buff — Socket 缓冲区
sk_buff(通常简称为 skb)是 Linux 网络子系统的核心数据结构,表示一个网络数据包。它贯穿于整个网络栈:从网卡驱动接收到应用层发送,都以 sk_buff 为载体。
头文件: <linux/skbuff.h>
数据结构(简化):
1 | struct sk_buff { |
sk_buff 使用四指针模型管理数据空间:
1 | head data tail end |
核心 API:
| API | 说明 |
|---|---|
alloc_skb(size, gfp) |
分配 skb |
skb_put(skb, len) |
在尾部添加数据 |
skb_push(skb, len) |
在头部添加数据(添加协议头) |
skb_pull(skb, len) |
从头部移除数据(剥离协议头) |
skb_clone(skb, gfp) |
克隆 skb(共享数据) |
skb_copy(skb, gfp) |
深拷贝(复制数据) |
kfree_skb(skb) |
释放 skb |
skb_queue_head(list, skb) |
入队到头部 |
skb_dequeue(list) |
出队 |
内核中的典型应用:
- 整个网络栈的包载体
- TUN/TAP 虚拟网卡
- Netfilter / iptables 的包过滤