Page 472 - 《软件学报》2025年第5期
P. 472
2372 软件学报 2025 年第 36 卷第 5 期
为内核预留了一个寄存器组 (包括两个寄存器窗口) 用于减少进出内核的开销, 那么这个冗余的陷阱处理窗口所
需要完成的任务就非常简单, 仅需保存必要的寄存器 (如全局寄存器等) 的值然后切换到内核寄存器组即可. 这一
过程并不需要占用大量的寄存器, 甚至经过优化, 可以完全避免对这一陷阱处理窗口内 in 寄存器的影响, 仅使用
8 个 local 寄存器即可完成. 于是陷阱处理窗口内的 8 个 in 寄存器就可以用来保存 IPC 传递的信息. 这样, 对于消
息长度较短的 IPC, 其信息可以通过参数的方式使用寄存器进行传递, 这一过程中可以使用当前寄存器组的陷阱
处理窗口内的 in 寄存器来暂时保存这些参数. 具体流程如图 6 所示, 当 IPC 发起线程发起 IPC 调用时, 它可以将
需要传递的信息作为 IPC 调用函数的参数, 根据 SPARC 函数调用惯例, 这些参数会被保存在当前寄存器窗口的
out 寄存器中. IPC 调用会导致陷阱, 陷入内核后处理器会自动滑动至下一个相邻窗口即陷阱处理窗口, 该窗口内
的 in 寄存器保存了传递的参数. 之后将使用空闲的 local 寄存器完成必要寄存器的保存并跳转到内核窗口, 然后
切换至被 IPC 调用的线程. 接下来内核将切换到之前 IPC 调用线程的陷阱处理窗口, 将暂存在 in 寄存器中的 IPC
信息转移到全局寄存器中, 然后再切换到 IPC 被调用线程的陷阱处理窗口, 将全局寄存器中保存的信息转移到被
调用线程的 in 寄存器中, 最后返回用户态执行被调用线程 IPC 处理函数. 返回用户态时, 窗口会自动滑动, 传递的
IPC 信息会位于当前窗口 out 寄存器中, 根据 SPARC 调用惯例, 对于 IPC 处理函数来说, 传递的 IPC 信息已经位
于该函数的参数中了, 从而实现了 IPC 信息的寄存器传递. 这种方案既能够有效利用寄存器组设计中不得不预留
出的陷阱处理窗口, 又能够在避免影响程序正常运行的情况下扩展寄存器 IPC 能够利用的寄存器数量, 从而优化
短 IPC 的性能.
④ 返回 IPC 处理函数,
IPC 信息即为函数参数
① 将参数传递至陷阱
处理窗口的 in 寄存器中
SPARC 寄存器
IPC 调用线程上下文
IPC 处理函数线程上下文
内核上下文
② 将源窗口 in 寄存器
③ 将 global 寄存器转移 转移到 global 寄存器中
到目标窗口 in 寄存器中 global 寄存器
图 6 寄存器 IPC 流程
3.2 FlexIPC
在多核处理器上, 一些应用程序可能无法完全利用多个核心的资源, 此时就可以将一些系统服务部署到单独
的核心上运行, 而应用程序在向这样的系统服务发起请求时, 就会导致跨核 IPC. 许多微内核都采用了基于线程迁
移的 IPC 实现方案, 这种实现方式可以确保 IPC 请求能够被及时响应. 然而对于跨核 IPC 来说, 跨核线程迁移需
要 IPI 提供支持, 此时 IPI 就成为跨核 IPC 的性能瓶颈之一. 我们在 S698PM 上的测试表明, SPARC 架构上 IPI 性
能并不理想, 制约了跨核 IPC 的性能, 因此我们设计实现了 FlexIPC 来规避 IPI 带来的较高开销.
FlexIPC 基于轮询共享内存的方式实现 IPC 服务端和客户端的控制流转移. 发起 IPC 的客户端线程需要在
IPC 两端进程的共享内存中设置相关标志, 而服务端线程则会对共享内存中的对应区域进行轮询, 当发现客户端
的消息已经准备好时, 就立即开始对 IPC 消息进行处理. FlexIPC 在自研微内核 ChCore 上的具体实现方法如算法 2
所示. 在 FlexIPC 调用发起时, 会将共享内存中的 start 标志位置为 1, 之后发起线程会一直轮询 finish 标志位的内
存直至其变为 1. 而 IPC 服务端线程则会轮询其与所有客户端线程的共享内存, 在发现 start 标志位为 1 后就可以
开始对此 IPC 消息进行处理, 处理完毕后可以在共享内存中写入返回值并将 finish 标志位置为 1, 这样 IPC 发起线
程就可以得知 IPC 已经完成. 每个客户端线程在建立 FlexIPC 连接前, 首先需要发起一个正常的 IPC 调用, 目的是
让服务端线程得知自己的存在以及共享内存的地址, 这样其之后发起的 FlexIPC 请求才能在服务端轮询的过程中
被发现并处理. 对于跨核 IPC 来说, FlexIPC 请求会比较快地被其他核心上正在运行的服务端 IPC 处理线程发现,