Page 469 - 《软件学报》2025年第5期
P. 469
苏浩然 等: SPARC 架构下低时延微内核进程间通信设计 2369
致, 因此这样的设计能够有效提升 IPC 等场景下的性能.
由于每个进程不需要占用全部的寄存器窗口, 因此我们可以同时将多个线程的寄存器组映射在真实寄存器
中, 即寄存器中同时保存了多个线程的上下文. 那么在需要将某个线程的上下文换出到内存中时, 就可能涉及在多
个寄存器组之间进行选择. 我们认为具体的换出策略与寄存器组机制的设计是正交的, 可以选择简单的先进先出
策略, 也可以利用每个进程的调度信息判断是否需要将某个进程挂起并将该进程的寄存器组换出到内存中, 这与
所使用的处理器的寄存器窗口总数以及系统的具体目标和需求有关, 此处不进行详细讨论.
下面将具体说明使用寄存器组机制后的上下文切换流程. 如算法 1 所示, 当内核决定切换到某个线程时, 需要
调用代码中的函数来激活该线程的寄存器组. 每个线程的寄存器组数据结构 (struct reg_bank) 中, active 字段表示
其当前是否已经处于激活状态, 即已经位于某个处理器核心的某个寄存器窗口中; cpu_id 字段则表示如果其已经
被激活, 其上下文所在的处理器核心编号. 如果该寄存器组已处于激活状态并且位于当前处理器核心的某个寄存
器窗口中, 则激活函数可直接返回; 否则说明该线程的上下文被存储在其他处理器核心的某个寄存器窗口上, 因此
需要发送 IPI 来通知该处理器核心, 先将它的上下文保存到内存中, 然后再在当前处理器核心上继续完成激活. 之
后, 通过 reg_bank_assign 函数在当前处理器核心上选择空闲的寄存器窗口作为目标线程的寄存器组. 注意到每个
处理核心都维护了对应的寄存器组元数据 (struct reg_bank_meta), 其 activated_reg_bank 字段记录了当前处理器
核心上的窗口占用情况, 因此可以从中得知是否有寄存器窗口空闲, 在没有任何窗口空闲时, 默认采取先进先出策
略驱除一个寄存器组以释放被占用的窗口. 分配完成后, 被分配的寄存器窗口被记录到 reg_bank 结构体的
start_window 字段. 之后则将目标线程的上下文换入到被选中的窗口中, 此时如果元数据中记录了被选中的窗口
正在作为某个其他线程的寄存器组, 则说明这个线程被驱逐, 需要将其上下文换出到内存中, 上下文的换入/换出
由伪代码中的 reg_bank_swap 函数代表, 在实际代码中这一部分主要由汇编代码实现. 最后则需要更新相关数据
结构并设置系统寄存器的值. 下面将介绍执行对寄存器组进行交换 (即执行 reg_bank_swap 函数) 的实现方法.
算法 1. 激活寄存器组.
1 void reg_bank_activate(struct reg_bank *bank)
2 {
3 struct reg_bank_meta *meta = reg_bank_meta[current_cpu_id];
4 struct reg_bank *bank_out;
5 if (bank->active == true) {
6 if (bank->cpu_id != current_cpu_id) {
7 send_ipi(bank->cpu_id, REG_BANK_REVOKE, bank);
8 } else {
9 return;
10 }
11 }
12 reg_bank_assign_window(meta, bank); // Set bank->start_window.
13 bank_out = meta->activated_reg_bank[bank->start_window];
14 if (bank_out != NULL) {
15 // Swap is implemented with assembly code.
16 reg_bank_swap(bank->start_window, bank->context, bank_out->context);
17 list_del(&bank_out->reg_bank_node);
18 bank_out->active = false;
19 } else {