Skip to content

第一百七十九章:回调与屏障

大乘期

涉及内核源码:

穿过宽限期的荒原,林小源来到一处幽暗的地下宫殿。宫殿的墙壁上镶嵌着无数细小的水晶,每颗水晶中都封存着一段函数代码。

"这些是……回调?"林小源低声问道。

一位佝偻的老者从阴影中走出,手中拄着一根刻满符文的拐杖。"不错。我是回调链的守护者。每当你调用call_rcu()注册一个回调,它就会被送到这里,挂到我的链表上。"

老者用拐杖敲了敲地面,墙上的水晶齐齐亮了起来。"宽限期结束后,我会依次执行这些回调。批量处理,一个接一个,不会遗漏任何一个。"

林小源注意到宫殿的入口处矗立着三道透明的光墙,每道光墙的厚度不同。

"那三道光墙是什么?"

老者眯起眼睛。"内存屏障。第一道是写屏障smp_wmb(),确保屏障前的写入在屏障后的写入之前完成。第二道是读屏障smp_rmb(),确保屏障前的读取在屏障后的读取之前完成。第三道是全屏障smp_mb(),确保所有操作都按顺序执行。"

"前辈,内存屏障和RCU有什么关系?"林小源追问道。

老者走到第一道光墙前,手掌贴上去。光墙内部浮现出流动的符文。"rcu_assign_pointer()内部包含写屏障。当写入者更新指针时,写屏障确保数据的写入在指针更新之前完成。否则,读取者可能看到新的指针,但指向的数据还没写完。"

他又走到第二道光墙前。"rcu_dereference()内部包含读屏障。当读取者读取指针时,读屏障确保指针的读取在数据访问之前完成。否则,你可能先读到了数据,再读到指针——顺序反了,数据就是脏的。"

林小源心中一凛。他想起在读取侧临界区时,那位中年修士特别强调过rcu_dereference()的重要性。

"屏障是看不见的守护者。"老者拄着拐杖缓缓踱步,"你感觉不到它的存在,但没有它,整个RCU机制就会崩溃。数据的顺序性,是并发编程的根基。"

"还有一种屏障——rcu_barrier()。"老者从怀中取出一块漆黑的令牌,"它等待的不是宽限期,而是所有已注册的回调都执行完毕。当你卸载一个内核模块时,必须调用它,确保所有RCU回调都已完成,否则回调函数可能访问已经卸载的代码,导致内核崩溃。"

林小源接过令牌,感受到其中沉甸甸的分量。每一块令牌、每一道屏障,都是为了防止那些看不见的并发错误。

"记住,"老者最后说道,"回调是异步的智慧——你不用亲自等,让内核替你等。屏障是顺序的保障——在混乱的并发世界中,守住先后的秩序。"

c
/*
 * RCU 回调:
 *
 * call_rcu(head, func):
 *   注册回调函数
 *   宽限期结束后调用 func(head)
 *   不阻塞当前线程
 *
 * rcu_barrier():
 *   等待所有回调完成
 *   类似 synchronize_rcu
 *   但等待的是回调
 *
 * 回调链:
 *   内核维护回调链表
 *   宽限期结束后批量执行
 *   避免频繁的上下文切换
 *
 * 内存屏障:
 *   smp_wmb() — 写屏障
 *   smp_rmb() — 读屏障
 *   smp_mb() — 全屏障
 *
 * RCU 使用的屏障:
 *   rcu_assign_pointer — 包含写屏障
 *   rcu_dereference — 包含读屏障
 */

/* 模拟 RCU 回调 */
struct rcu_head {
    void (*func)(struct rcu_head *);
    struct rcu_head *next;
};

struct rcu_callback_list {
    struct rcu_head *head;
    struct rcu_head **tail;
    int count;
};

void callback_list_init(struct rcu_callback_list *list) {
    list->head = NULL;
    list->tail = &list->head;
    list->count = 0;
}

void call_rcu(struct rcu_callback_list *list, struct rcu_head *head,
              void (*func)(struct rcu_head *)) {
    head->func = func;
    head->next = NULL;
    *list->tail = head;
    list->tail = &head->next;
    list->count++;
}

void process_callbacks(struct rcu_callback_list *list) {
    printf("处理 %d 个回调:\n", list->count);
    struct rcu_head *p = list->head;
    while (p) {
        struct rcu_head *next = p->next;
        p->func(p);
        p = next;
    }
    list->head = NULL;
    list->tail = &list->head;
    list->count = 0;
}

/* 示例回调函数 */
void free_callback(struct rcu_head *head) {
    printf("  回调: 释放内存\n");
}

printf("=== 回调与屏障 — RCU 的高级机制 ===\n\n");

/* RCU 回调 */
struct rcu_callback_list callbacks;
callback_list_init(&callbacks);

struct rcu_head head1, head2;
call_rcu(&callbacks, &head1, free_callback);
call_rcu(&callbacks, &head2, free_callback);

printf("--- RCU 回调 ---\n");
printf("注册了 %d 个回调\n", callbacks.count);
printf("宽限期结束后自动调用\n\n");

printf("--- 模拟宽限期结束 ---\n");
process_callbacks(&callbacks);

printf("\n--- call_rcu vs synchronize_rcu ---\n");
printf("call_rcu:\n");
printf("  异步\n");
printf("  注册回调\n");
printf("  不阻塞\n\n");
printf("synchronize_rcu:\n");
printf("  同步\n");
printf("  阻塞等待\n");

printf("\n--- rcu_barrier ---\n");
printf("作用:\n");
printf("  等待所有回调完成\n\n");
printf("使用场景:\n");
printf("  模块卸载时\n");
printf("  确保所有 RCU 操作完成\n\n");

printf("\n--- 内存屏障 ---\n");
printf("smp_wmb():\n");
printf("  写屏障\n");
printf("  确保写入顺序\n\n");
printf("smp_rmb():\n");
printf("  读屏障\n");
printf("  确保读取顺序\n\n");
printf("smp_mb():\n");
printf("  全屏障\n");
printf("  确保所有操作顺序\n\n");

printf("--- RCU 中的屏障 ---\n");
printf("rcu_assign_pointer:\n");
printf("  包含写屏障\n");
printf("  确保数据写入在指针更新前\n\n");
printf("rcu_dereference:\n");
printf("  包含读屏障\n");
printf("  确保指针读取在数据访问前\n");

道藏笔记

内核启示

call_rcu() 注册回调函数后就不管了,宽限期结束后内核批量执行,不用你亲自等。rcu_barrier() 等的不是宽限期,而是所有已注册回调都执行完——卸载内核模块时必须调它,不然回调访问已卸载的代码就崩了。

内存屏障是 RCU 能正确工作的隐形守护者。rcu_assign_pointer() 内部有写屏障,确保数据写完再更新指针;rcu_dereference() 内部有读屏障,确保先读指针再读数据。没有屏障,编译器和 CPU 的重排可能让你读到半写完的脏数据。smp_wmb() 管写顺序,smp_rmb() 管读顺序,smp_mb() 全管。

回调是异步的智慧,屏障是顺序的保障。


破关试炼

回调与屏障之试

回调与屏障一章讲 call_rcu、rcu_barrier、rcu_assign_pointer 都属于哪套机制?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道