第一百七十六章:无常
大乘期涉及内核源码:
一
安全修炼圆满后,林小源踏入了一片从未见过的领域。
眼前的景象让他屏住呼吸——无数透明的光影在虚空中穿梭,每一道光影都带着柔和的光芒,它们彼此交错却从不碰撞。一位白发老者盘坐在这片光影之海的中央,周身环绕着淡淡的金光。
"晚辈林小源,拜见前辈。"林小源躬身行礼。
老者睁开双眼,目光如古井般深邃。"我乃RCU,Read-Copy-Update,人们叫我'无常'。"
"无常?"林小源疑惑。
"你看那些光影。"RCU抬手一指,"每一道都是一位读取者。他们在我这里读取数据,无需任何锁链束缚,无需等待任何人。"
林小源看到那些光影自由地穿行,确实没有任何阻拦。"那写入者呢?如果读取者正在读,写入者同时修改怎么办?"
RCU微微一笑,从袖中取出一卷古旧的书简。"这就是Copy-Update的智慧。写入者不直接修改原物——他先复制一份,在副本上修改,然后将指针瞬间切换到新副本。旧数据不会立刻消失,要等所有正在读取的修士都离开之后,才会被释放。"
他将书简递给林小源。"记住,无常之道的核心有三:读取者无需加锁;写入者先复制再更新;旧数据在宽限期后释放。"
林小源接过书简,感受到其中蕴含的道韵——这是一种他从未接触过的同步哲学。
二
"前辈,既然读取者无需加锁,那怎么知道什么时候所有读取者都离开了?"林小源追问。
RCU指了指脚下。"每个读取者进入时,会调用rcu_read_lock()标记自己在读取;离开时,调用rcu_read_unlock()。写入者调用synchronize_rcu(),阻塞等待,直到确认所有在更新前进入临界区的读取者都已离开。"
"那岂不是很慢?"
"恰恰相反。"RCU摇头,"读取几乎零开销——不需要原子操作,不需要内存屏障,不需要任何同步指令。代价全部由写入者承担。所以RCU最适合读多写少的场景:路由表、文件系统目录缓存、模块列表、进程列表……"
林小源心中一动。内核中确实有大量这样的数据结构——被无数进程频繁读取,却很少被修改。
三
"前辈,无常之道虽妙,但写入者岂不是很辛苦?"林小源又问。
RCU哈哈大笑。"世间安得两全法?读取者轻松,写入者就要多付出。但这正是无常的哲学——万物皆流,无物常驻。旧数据不会被销毁,直到确认安全。这不是浪费,这是敬畏。"
他顿了顿,语气变得郑重:"记住,无常之道有三不:不能在持有RCU读锁时睡眠;不能调用可能睡眠的函数;不能持有导致睡眠的锁。临界区之内,时间必须短暂。"
林小源将这些告诫铭记于心。无常之道,看似轻描淡写,实则暗藏玄机。
/*
* RCU (Read-Copy-Update):
*
* 核心思想:
* 读取者无需加锁
* 写入者先复制,再更新指针
* 旧数据在宽限期后释放
*
* 优势:
* 读取无开销
* 无死锁
* 适合读多写少场景
*
* 使用场景:
* 路由表
* 文件系统目录缓存
* 模块列表
* 进程列表
*
* RCU 操作:
* rcu_read_lock() — 进入读取侧临界区
* rcu_read_unlock() — 离开临界区
* rcu_dereference() — 读取 RCU 保护的指针
* rcu_assign_pointer() — 更新 RCU 保护的指针
* synchronize_rcu() — 等待宽限期
* call_rcu() — 注册回调
*/
/* 模拟 RCU 保护的数据 */
struct list_node {
int data;
struct list_node *next;
};
/* 模拟 RCU 指针 */
struct list_node *rcu_head = NULL;
/* 模拟 rcu_read_lock/unlock */
int rcu_read_depth = 0;
void rcu_read_lock(void) {
rcu_read_depth++;
}
void rcu_read_unlock(void) {
rcu_read_depth--;
}
/* 模拟 rcu_dereference */
struct list_node *rcu_dereference(struct list_node **p) {
return *p;
}
/* 模拟 rcu_assign_pointer */
void rcu_assign_pointer(struct list_node **p, struct list_node *val) {
/* 内存屏障确保写入顺序 */
*p = val;
}
/* 添加节点 (Copy-Update) */
void rcu_add_node(int data) {
struct list_node *new = malloc(sizeof(struct list_node));
new->data = data;
new->next = rcu_head;
/* 更新指针 (旧数据仍然存在) */
rcu_assign_pointer(&rcu_head, new);
}
/* 遍历 (无需加锁) */
void rcu_traverse(void) {
rcu_read_lock();
struct list_node *p = rcu_dereference(&rcu_head);
while (p) {
printf(" %d", p->data);
p = p->next;
}
printf("\n");
rcu_read_unlock();
}
printf("=== 无常 — RCU 入门 ===\n\n");
printf("RCU: 读取无需加锁\n\n");
/* 添加节点 */
rcu_add_node(3);
rcu_add_node(2);
rcu_add_node(1);
printf("--- 遍历 ---\n");
rcu_traverse();
printf("\n--- RCU 原理 ---\n");
printf("读取者:\n");
printf(" rcu_read_lock()\n");
printf(" p = rcu_dereference(head)\n");
printf(" // 使用 p\n");
printf(" rcu_read_unlock()\n\n");
printf("写入者:\n");
printf(" new = copy(old)\n");
printf(" modify(new)\n");
printf(" rcu_assign_pointer(head, new)\n");
printf(" synchronize_rcu()\n");
printf(" free(old)\n\n");
printf("--- 优势 ---\n");
printf("1. 读取无开销\n");
printf(" 不需要原子操作\n");
printf(" 不需要内存屏障\n\n");
printf("2. 无死锁\n");
printf(" 读取者不持锁\n\n");
printf("3. 读多写少\n");
printf(" 路由表、缓存\n");
/* 清理 */
struct list_node *p = rcu_head;
while (p) {
struct list_node *next = p->next;
free(p);
p = next;
}#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* RCU (Read-Copy-Update):
*
* 核心思想:
* 读取者无需加锁
* 写入者先复制,再更新指针
* 旧数据在宽限期后释放
*
* 优势:
* 读取无开销
* 无死锁
* 适合读多写少场景
*
* 使用场景:
* 路由表
* 文件系统目录缓存
* 模块列表
* 进程列表
*
* RCU 操作:
* rcu_read_lock() — 进入读取侧临界区
* rcu_read_unlock() — 离开临界区
* rcu_dereference() — 读取 RCU 保护的指针
* rcu_assign_pointer() — 更新 RCU 保护的指针
* synchronize_rcu() — 等待宽限期
* call_rcu() — 注册回调
*/
/* 模拟 RCU 保护的数据 */
struct list_node {
int data;
struct list_node *next;
};
/* 模拟 RCU 指针 */
struct list_node *rcu_head = NULL;
/* 模拟 rcu_read_lock/unlock */
int rcu_read_depth = 0;
void rcu_read_lock(void) {
rcu_read_depth++;
}
void rcu_read_unlock(void) {
rcu_read_depth--;
}
/* 模拟 rcu_dereference */
struct list_node *rcu_dereference(struct list_node **p) {
return *p;
}
/* 模拟 rcu_assign_pointer */
void rcu_assign_pointer(struct list_node **p, struct list_node *val) {
/* 内存屏障确保写入顺序 */
*p = val;
}
/* 添加节点 (Copy-Update) */
void rcu_add_node(int data) {
struct list_node *new = malloc(sizeof(struct list_node));
new->data = data;
new->next = rcu_head;
/* 更新指针 (旧数据仍然存在) */
rcu_assign_pointer(&rcu_head, new);
}
/* 遍历 (无需加锁) */
void rcu_traverse(void) {
rcu_read_lock();
struct list_node *p = rcu_dereference(&rcu_head);
while (p) {
printf(" %d", p->data);
p = p->next;
}
printf("\n");
rcu_read_unlock();
}
int main() {
printf("=== 无常 — RCU 入门 ===\n\n");
printf("RCU: 读取无需加锁\n\n");
/* 添加节点 */
rcu_add_node(3);
rcu_add_node(2);
rcu_add_node(1);
printf("--- 遍历 ---\n");
rcu_traverse();
printf("\n--- RCU 原理 ---\n");
printf("读取者:\n");
printf(" rcu_read_lock()\n");
printf(" p = rcu_dereference(head)\n");
printf(" // 使用 p\n");
printf(" rcu_read_unlock()\n\n");
printf("写入者:\n");
printf(" new = copy(old)\n");
printf(" modify(new)\n");
printf(" rcu_assign_pointer(head, new)\n");
printf(" synchronize_rcu()\n");
printf(" free(old)\n\n");
printf("--- 优势 ---\n");
printf("1. 读取无开销\n");
printf(" 不需要原子操作\n");
printf(" 不需要内存屏障\n\n");
printf("2. 无死锁\n");
printf(" 读取者不持锁\n\n");
printf("3. 读多写少\n");
printf(" 路由表、缓存\n");
/* 清理 */
struct list_node *p = rcu_head;
while (p) {
struct list_node *next = p->next;
free(p);
p = next;
}
return 0;
}道藏笔记
内核启示
RCU 的核心思想很精妙:读取者完全不用加锁,代价全让写入者扛。写入者不直接改原数据——先复制一份副本,在副本上改好,然后用 rcu_assign_pointer() 原子切换指针。旧数据不急着释放,得等所有正在读的读者都离开(这个等待期叫宽限期)。
读取侧用 rcu_read_lock()/rcu_read_unlock() 标记进出,rcu_dereference() 带读屏障读指针。synchronize_rcu() 同步等宽限期结束,call_rcu() 异步注册回调。最适合读多写少的场景:路由表、文件系统目录缓存、模块列表、进程列表——这些数据被无数进程频繁读取,却很少被修改。
RCU 是无常——万物皆可观察,无需阻塞。
RCU 之试
无常一章中,读者无锁读取、写者复制后更新并等待宽限期的机制是什么?