Mailbox 与门铃:核间通信 IPC 的寄存器、门铃与共享内存
更新时间:2026-09-19。前置:RISC-V PLIC、RISC-V CLINT/MSIP、硬件自旋锁、事件路由。多个 CPU 核、或核与一个智能加速器(DSP/NPU/管理核)之间要交换命令和数据,靠的是 mailbox。
本文要回答的问题
- 核间为什么不能直接调函数,mailbox 由哪两部分组成?
- doorbell 寄存器怎么「敲门」并在对端产生中断?
- 双向信箱、消息队列和满/空如何设计?
- mailbox 与 spinlock、共享内存、事件路由如何分工?
一、基本模型:共享内存 + doorbell
核间通信的标准组合是「大块数据走共享内存,通知走 doorbell」:
核 A 核 B
准备消息到共享内存(约定地址/环)
写 doorbell 寄存器 ──(中断线/MSI)──► B 收到 mailbox IRQ
B 读共享内存、处理
B 写应答 doorbell ◄─ A 收到完成中断- 共享内存:一块所有相关核都能访问的 RAM(经一致性互连或显式 cache 维护,见 DMA 与 cache、CHI)。消息体、命令块、环形队列放这里。只传指针/长度能省拷贝(零拷贝)。
- doorbell 寄存器:一个对写入敏感的 MMIO 寄存器。A 对它执行一次 store,硬件产生到 B 的中断(连到 PLIC 的某外部线,或在 RISC-V 上触发 B 的 MSIP 核间中断,见 CLINT;也可实现成 MSI 内存写)。B 在中断里查信箱状态。
之所以不让接收核轮询共享内存:轮询费电费核、延迟不可控;doorbell 提供事件驱动的及时通知,且接收核可在空闲时睡眠/进低功耗(见 低功耗)。
二、mailbox 外设的典型结构
每个通道/方向一组寄存器:
DATA/CMD 寄存器或指向共享内存的指针+长度
STATUS: full/empty、新消息标志
DOORBELL/SET: 写则对端中断
CLEAR/ACK: 对端读后清
IRQ enable/mask
双向通道: A→B 一套, B→A 一套(或 N 个 channel)两种粒度:
- 单字 mailbox(寄存器信箱):消息直接放几个硬件寄存器,适合小命令/状态字。写满置 full、对端读走置 empty,full 时再写要丢弃/等待。
- doorbell + 共享队列:硬件只负责门铃/标志,真正消息在共享内存的 ring buffer(生产者/消费者索引),适合高吞吐命令流。队列的 head/tail 更新与一致性是软件重点。
一个 mailbox IP 通常提供多通道(A↔B、A↔C…)或广播,每通道独立中断和使能,可接到中断聚合器统一分发(见 事件路由)。
三、一次请求-应答的握手
A: ① 在共享内存放消息(获取队列槽/锁)
② 做必要的 cache clean(保证 B 看到最新数据)
③ 写 doorbell(带通道/事件位)
B: IRQ → 读状态/队列 → cache invalidate → 处理
⑥ 收到完成 doorbell ◄─ ⑤ 写回结果+clean → 写应答 doorbell
A: ④ 可阻塞等待/挂回调,收到后继续关键点:doorbell 相当于「消息已就绪」的内存屏障/事件——发门铃之前的共享内存写必须对 B 可见(顺序上用 内存屏障 + cache 维护保证),否则 B 可能看到门铃响但数据是旧的。
四、与 spinlock、中断的分工
- mailbox 解决「通知」:告诉对端有消息/完成了。
- spinlock 解决「互斥」:多个核同时往同一个队列放消息时保护 head/tail 和槽位(见 硬件自旋锁)。单生产者单消费者的环形队列可设计成无锁(SPSC),多生产者则需锁或原子 RMW。
- 中断控制器解决「路由/优先级」:doorbell 事件经 PLIC/GIC 送到指定核,可绑核、设优先级。
- 区分于 IPI(核间中断):广义上 doorbell 就是一种 IPI;调度器 IPI 常直接打 MSIP/软中断(见 CLINT),带数据通道的专用 mailbox 则承载更丰富的命令协议(如 RPMsg、SCU 消息)。
五、加速器/管理核场景
典型如应用核与电源管理核(PMU/系统控制器)通信:AP 写命令 mailbox 敲门,管理核改时钟/电源后回 doorbell(见 时钟控制器、低功耗)。再如 CPU↔DSP/NPU 提交任务:门铃通知任务队列有新描述符(思想同 DMA 描述符环)。这种 mailbox 常带安全权限:只有授权核能写某通道(见 总线防火墙)。
六、常见的坑
- 门铃先于数据可见:没做 cache clean/屏障就敲门,对端读到旧数据(经典竞态)。
- 门铃是边沿/写一次事件,被合并:连续两次敲门若对端没及时清,可能只看到一次;要靠共享内存里的序列号/队列计数承载真实消息数,不能假设「一次门铃=一条消息」。
- 丢门铃:对端还没使能中断/没清上一次标志时敲门,事件丢失;握手要能在使能后根据队列非空补发或用电平语义。
- full 时覆盖:单字信箱满了再写,覆盖未读消息;要有流控/状态检查。
- 中断风暴:每放一个小消息敲一次门,高频时把对端打断打爆;攒批敲门或用阈值(见 中断聚合)。
- 不清中断/ACK 时序错:处理前清可能丢新事件,处理后忘清则反复进 ISR。
- cache 非一致:异构核(如 CPU 与不经一致性总线的加速器)共享内存没做 clean/invalidate,数据不一致。
- 多生产者无锁:多个核同时更新同一 ring head 导致错乱,需 spinlock/原子操作。
- 应答超时无处理:对端挂死时本核永久等 doorbell,需超时与看门狗(见 复位控制器)。
- 通道/中断绑错核:门铃送到正在睡眠/非负责的核,延迟大或无响应。
- 安全越权:非可信核能敲安全核通道/写命令寄存器,需要总线防火墙过滤。
- 复位竞态:一核复位时另一核正敲门或等待应答,状态机/队列要能复位恢复。
相关与延伸
中断路由见 PLIC、事件路由聚合器;核间软中断见 CLINT MSIP;互斥见 硬件自旋锁;顺序见 内存屏障。
总结
Mailbox 用「共享内存传消息体(命令、指针或环形队列)+ doorbell 寄存器发通知」实现核间 IPC:发送核把消息放进一致性或经 cache 维护的共享内存,做必要的 clean 和内存屏障保证顺序后对 doorbell 做一次写,硬件把它转成到接收核的外部中断或 MSIP/IPI,接收核在中断里读队列/寄存器、处理后再经反向 doorbell 应答;硬件信箱靠 full/empty 状态防覆盖,高吞吐走共享 ring buffer 并用 spinlock 保护多生产者、序列号承载真实消息数——门铃是可被合并的事件而非计数,必须保证数据先于门铃可见、对端能根据队列非空补发,并配合缓存维护、应答超时和通道权限,否则会出现读旧数据、丢门铃、覆盖和中断风暴。