UINTC 跨核中断控制器设计
本节描述用于直接发送用户态跨核软件中断的中断控制器。
若不加说明,下文中中断均默认指用户态跨核软件中断。
动机与设计思路
用户态跨核中断有这样的特点:
- 收发用户态中断的用户进程数量不固定,可能远多于硬件线程数量
- 用户进程不能保证占有某个硬件线程,不能保证时刻在运行
- 用户进程间的中断收发不能任意进行,需要预先取得许可
因此区别于传统的中断控制器,中断的来源和目的由硬件固定,用户态跨核中断控制器只能承载发送方和接收方之间连接的功能,发送方、接收方在中断控制器上的硬件槽位和用户进程之间的绑定需要能够由操作系统动态控制。
完成绑定后,发送跨核中断时,发送方进程仅需在运行时提出请求即可;但接收中断需要中断能够在接收方运行时正确到达接收方运行的硬件线程,接收方所在的硬件线程不固定,所以可以将发送跨核中断涉及的主体分为发送方、接收方和硬件线程三部分。
- 发送方和接收方够成二分图,一对发送方和接收方之间连边,表示此发送方可以向此接收方发送中断。此二分图会由操作系统映射到中断控制器上,实现不需经过内核的快速收发。如果图超出了中断控制器的容量,操作系统可以只映射一个子图,未映射部分可能需要操作系统软件模拟。这是此中断控制器的核心部分。
- 每个硬件线程则取决于正在执行的用户进程,同时会监听至多一个接收方,发给该接收方的中断会到达该硬件线程。暂时没有支持一个硬件线程监听多个接收方的考虑,所以此部分相对较简单。
图有邻接矩阵和邻接表等常见的表示方式。本节描述的中断控制器设计采用邻接矩阵,用发送方数 × 接收方数的二进制位矩阵 enable
表示每对发送方和接收方之间是否连边。每对连边的发送方和接收方之间可以发送中断。同时,另一个二进制位矩阵 pending
表示每对发送方和接收方之间是否有已发送而待处理的中断。如果某接收方对应的矩阵一行有 pending
的中断,而某硬件线程在监听此接收方,该硬件线程的 uip.USIP
会被置 1
,从而达到当发送方用户进程和接收方用户进程都在运行时,中断可以直接由发送方在用户态发送给接收方的效果;当接收方用户进程不在运行时,已发送中断会在矩阵中等待。
中断控制器采用内存映射的接口。通过将暴露给用户进程的寄存器地址分页,操作系统可以将每个发送方和接收方槽位独立映射给拥有权限的用户进程。
下面具体介绍中断控制器 UINTC 的设计方案。
简介
UINTC 提供若干数量的发送方和接收方槽位,以及若干上下文。
发送方和接收方可动态对应用户进程,从 1 开始依次编号,编号 0 保留。在下文中,我们设 S 为包括保留的 0 号在内的发送方数量,R 为包括保留的 0 号在内的接收方数量,这样发送方的编号为 1 到 S-1,接收方的编号为 1 到 R-1。每个发送方和接收方记录一个用户态中断 ID,称为 UIID(User Interrupt ID)。UIID 用来标识使用此发送方或接收方的用户进程。UIID 和操作系统内的进程 ID 不必相同,发送方和接收方的 UIID 可以分别分配。
每个上下文对应一个硬件线程,从 0 开始依次编号。上下文的编号可以与对应硬件线程的 Hart ID(mhartid
)相同或不同。在下文中,我们设 N 为上下文数量,这样上下文编号的范围为 0 到 N-1。上下文和硬件线程的对应由实现决定。
规定 R、S 不超过 4096,N 不超过 2048。
下面简要介绍 UINTC 提供的寄存器。若不加特殊说明,下文所有寄存器均为 32 位,在内存中占 4 字节。不同寄存器读写时实际有意义的位数可能各异。
对于每对发送方和接收方,UINTC 提供以下寄存器用于中断整体管理:
-
使能寄存器 enable
一位,记录发送方是否可以向接收方发送中断。
此寄存器意指由操作系统读写。
下用
enable[s][r]
表示发送方s
发送中断给接收方r
的使能,1
表示允许发送,0
表示禁止发送。为了操作便捷,使能寄存器在内存映射中压位,并可以分别根据发送方或接收方编号连续访问。对于每个发送方,连续 32 个接收方的使能会占一个 4 对齐的内存地址,此地址的每一位分别表示一个接收方的使能寄存器值。对于每个接收方,连续 32 个发送方给的使能会占一个 4 对齐的内存地址,此地址的每一位分别表示一个发送方的使能寄存器值。
可以行列随机访问的二维矩阵对 FPGA 不太友好。如果发送方和接收方更替不太频繁,可以考虑只允许一维随机访问,那样更改用户进程和硬件槽位绑定时时间可能相对较长。
-
等待寄存器 pending
一位,记录发送方是否已向接收方发送中断,此中断尚未被接收方领取。
下用
pending[s][r]
表示是否有s
向r
发送的正在等待的中断,1
表示有0
表示没有。等待寄存器也以类似使能寄存器的方式压位访问。
对于每个发送方,UINTC 提供以下寄存器用于发送和发送管理:
-
发送方 UIID 寄存器 sender_uiid
用于记录发送方对应的 UIID。
此寄存器意指由操作系统维护。
下用
sender_uiid[s]
表示发送方s
对应的发送方 UIID 寄存器。 -
发送寄存器 send
向此寄存器写入接收方的 UIID 以请求向后者发送用户态中断。
下用
send[s]
表示发送方s
对应的发送寄存器。 -
发送状态寄存器 status
从此寄存器读取检查上一次发送是否成功。
下用
status[s]
表示发送方s
对应的发送状态寄存器。此寄存器主要用于在发送方、接收方较多时,如果一个中断接收方未绑定硬件槽位,发送方可以知情。
对于每个接收方,UINTC 提供以下寄存器用于接收和接收管理:
-
接收方 UIID 寄存器 receiver_uiid
用于记录接收方对应的 UIID。
此寄存器意指由操作系统维护。
下用
receiver_uiid[r]
表示接收方r
对应的接收方 UIID 寄存器。 -
领取寄存器 claim
读取此寄存器可以得到发送给此硬件线程的中断发送方 UIID 之一。读取到的发送方硬件线程发送的中断被领取。
RISC-V 中,用户态软件中断仅有一个信号,所以需要这样的机制来得知中断的具体来源。此机制来源于 PLIC。
对于每个上下文,UINTC 提供以下寄存器用于监听中断:
-
监听寄存器 listen
用于记录此上下文监听的接收方编号(非 UIID)。
此寄存器意指由操作系统在用户进程调度时维护,从而让运行用户进程的硬件线程接收到对应的中断。
下用
listen[c]
表时上下文c
对应的监听寄存器。
上述寄存器读写的详细效果见下文。
内存映射
中断控制器采用内存映射,其基址由实现定义。各寄存器相对于基址的偏移见下。
0x0000000 - 0x0001FFC 的前两个 4 KiB 页是每个上下文的监听寄存器,意指由操作系统管理。 0x0002000 - 0x1FFFFFC 的部分每连续两个 4 KiB 页属于一个发送方。其中第一页包括发送寄存器 send 和发送状态寄存器 status,意指让操作系统映射给拥有此发送方的用户进程。第二页包括发送方 UIID 寄存器 sender_uiid 等其余寄存器,意指由操作系统管理。 0x2002000 - 0x3FFFFFC 的部分每连续两个 4 KiB 页属于一个接收方。其中第一页包括领取寄存器 claim,意指让操作系统映射给拥有此接收方的用户进程。第二页包括接收方 UIID 寄存器 receiver_uiid 等其余寄存器,意指由操作系统管理。
监听寄存器部分:
偏移 | 寄存器描述 |
---|---|
0x0000000 | 监听寄存器 listen,上下文 0 |
0x0000004 | 监听寄存器 listen,上下文 1 |
... | ... |
0x0001FFC | 监听寄存器 listen,上下文 2047 |
发送方部分:
偏移 | 寄存器描述 |
---|---|
0x0002000 | 1 号发送方的发送寄存器 send / 发送状态寄存器 status |
0x0002004 - 0x0002FFC | 保留 |
0x0003000 | 1 号发送方的发送方 UIID 寄存器 sender_uiid |
0x0003004 - 0x00037FC | 保留 |
0x0003800 | 1 号发送方的使能寄存器 enable,接收方 0 - 31 |
0x0003804 | 1 号发送方的使能寄存器 enable,接收方 32 - 63 |
... | ... |
0x00039FC | 1 号发送方的等待寄存器 pending,接收方 4064 - 4095 |
0x0003A00 | 1 号发送方的等待寄存器 pending,接收方 0 - 31 |
0x0003A04 | 1 号发送方的等待寄存器 pending,接收方 32 - 63 |
... | ... |
0x0003BFC | 1 号发送方的等待寄存器 pending,接收方 4064 - 4095 |
0x0004000 | 2 号发送方的发送寄存器 send / 发送状态寄存器 status |
0x0004004 - 0x0004FFC | 保留 |
0x0005000 | 2 号发送方的发送方 UIID 寄存器 sender_uiid |
0x0005004 - 0x00057FC | 保留 |
0x0005800 | 2 号发送方的使能寄存器 enable,接收方 0 - 31 |
0x0005804 | 2 号发送方的使能寄存器 enable,接收方 32 - 63 |
... | ... |
0x00059FC | 2 号发送方的等待寄存器 pending,接收方 4064 - 4095 |
0x0005A00 | 2 号发送方的等待寄存器 pending,接收方 0 - 31 |
0x0005A04 | 2 号发送方的等待寄存器 pending,接收方 32 - 63 |
... | ... |
0x0005BFC | 2 号发送方的等待寄存器 pending,接收方 4064 - 4095 |
... | ... |
... | ... |
0x1FFE000 | 4095 号发送方的发送寄存器 send / 发送状态寄存器 status |
0x1FFE004 - 0x1FFEFFC | 保留 |
0x1FFF000 | 4095 号发送方的发送方 UIID 寄存器 sender_uiid |
0x1FFF004 - 0x1FFF7FC | 保留 |
0x1FFF800 | 4095 号发送方的使能寄存器 enable,接收方 0 - 31 |
0x1FFF804 | 4095 号发送方的使能寄存器 enable,接收方 32 - 63 |
... | ... |
0x1FFF9FC | 4095 号发送方的等待寄存器 pending,接收方 4064 - 4095 |
0x1FFFA00 | 4095 号发送方的等待寄存器 pending,接收方 0 - 31 |
0x1FFFA04 | 4095 号发送方的等待寄存器 pending,接收方 32 - 63 |
... | ... |
0x1FFFBFC | 4095 号发送方的等待寄存器 pending,接收方 4064 - 4095 |
中间保留部分:
偏移 | 寄存器描述 |
---|---|
0x2000000 - 0x2001FFC | 保留 |
接收方部分:
偏移 | 寄存器描述 |
---|---|
0x2002000 | 1 号接收方的领取寄存器 claim |
0x2002004 - 0x2002FFC | 保留 |
0x2003000 | 1 号接收方的接收方 UIID 寄存器 receiver_uiid |
0x2003004 - 0x20037FC | 保留 |
0x2003800 | 1 号接收方的使能寄存器 enable,发送方 0 - 31 |
0x2003804 | 1 号接收方的使能寄存器 enable,发送方 32 - 63 |
... | ... |
0x20039FC | 1 号接收方的等待寄存器 pending,发送方 4064 - 4095 |
0x2003A00 | 1 号接收方的等待寄存器 pending,发送方 0 - 31 |
0x2003A04 | 1 号接收方的等待寄存器 pending,发送方 32 - 63 |
... | ... |
0x2003BFC | 1 号接收方的等待寄存器 pending,发送方 4064 - 4095 |
0x2004000 | 2 号接收方的领取寄存器 claim |
0x2004004 - 0x2004FFC | 保留 |
0x2005000 | 2 号接收方的接收方 UIID 寄存器 receiver_uiid |
0x2005004 - 0x20057FC | 保留 |
0x2005800 | 2 号接收方的使能寄存器 enable,发送方 0 - 31 |
0x2005804 | 2 号接收方的使能寄存器 enable,发送方 32 - 63 |
... | ... |
0x20059FC | 2 号接收方的等待寄存器 pending,发送方 4064 - 4095 |
0x2005A00 | 2 号接收方的等待寄存器 pending,发送方 0 - 31 |
0x2005A04 | 2 号接收方的等待寄存器 pending,发送方 32 - 63 |
... | ... |
0x2005BFC | 2 号接收方的等待寄存器 pending,发送方 4064 - 4095 |
... | ... |
... | ... |
0x3FFE000 | 4095 号接收方的领取寄存器 claim |
0x3FFE004 - 0x3FFEFFC | 保留 |
0x3FFF000 | 4095 号接收方的接收方 UIID 寄存器 receiver_uiid |
0x3FFF004 - 0x3FFF7FC | 保留 |
0x3FFF800 | 4095 号接收方的使能寄存器 enable,发送方 0 - 31 |
0x3FFF804 | 4095 号接收方的使能寄存器 enable,发送方 32 - 63 |
... | ... |
0x3FFF9FC | 4095 号接收方的等待寄存器 pending,发送方 4064 - 4095 |
0x3FFFA00 | 4095 号接收方的等待寄存器 pending,发送方 0 - 31 |
0x3FFFA04 | 4095 号接收方的等待寄存器 pending,发送方 32 - 63 |
... | ... |
0x3FFFBFC | 4095 号接收方的等待寄存器 pending,发送方 4064 - 4095 |
寄存器读写细节
使能寄存器 enable
使能寄存器分以同一发送方访问和以同一接收方访问的两部分。通过两部分读写,实际读写的硬件值相同,即在两部分读同一对发送方和接收方对应的位读出的值相同,写入同一对发送方和接收方对应的位效果相同。
对于以同一发送方访问的部分,考虑发送方 s
的第 i
个地址的 32 位 enable
寄存器,即地址偏移为 0x0000000 + s × 0x2000 + 0x1800 + i × 4 的 32 位寄存器。若不考虑不在 1 到 R-1 间的情况,此寄存器覆盖的接收方编号范围为 32 × i
到 32 × i + 31
。
写入时,对于写入数据的第 j
位,对应的接收方编号为 r = 32 × i + j
。
- 如果
r
在1
到R-1
间,则置enable[s][r]
为写入的第j
位。 - 否则,第
j
位被忽略。
读取时,对于读出数据的第 j
位,对应的接收方编号同样为 r = 32 × i + j
。
- 如果
r
在0
到n - 1
间,读出的第j
位为enable[s][r]
。 - 否则,第
j
位为0
。
对于以同一接收方访问的部分,考虑接收方 r
的第 i
个地址的 32 位 enable
寄存器,即地址偏移为 0x2000000 + r × 0x2000 + 0x1800 + i × 4
的 32 位寄存器。若不考虑不在 1
到 S - 1
间的情况,此寄存器覆盖的发送方编号范围为 32 × i
到 32 × i + 31
。
写入时,对于写入数据的第 j
位,对应的发送方编号为 s = 32 × i + j
。
- 如果
s
在1
到S - 1
间,则置enable[s][r]
为写入的第j
位。 - 否则,第
j
位被忽略。
读取时,对于读出数据的第 j
位,对应的接收方编号同样为 s = 32 × i + j
。
- 如果
s
在1
到S - 1
间,读出的第j
位为enable[s][r]
。 - 否则,第
j
位为0
。
等待寄存器 pending
等待寄存器的读写方式与使能寄存器相同,不过地址偏移中的 0x1800
改为 0x1A00
。
发送方 UIID 寄存器 sender_uiid
考虑发送方 s
的发送方 UIID 寄存器。读写此寄存器时直接从 sender_uiid[i]
读出或写入 sender_uiid[i]
。
发送寄存器 send
和发送状态寄存器 status共用地址,读取此寄存器所在地址的效果见该寄存器描述。
考虑发送方 s
的发送寄存器。
写入此寄存器时,设写入的值为 u
。如果对于 0 到 R-1 中的接收方编号 r
,存在任意一个 r
使得 receiver_uiid[r]
等于 u
,则说明 u
对应的接收方进程在硬件线程 r
上运行。此时
- 如果
enable[s][r]
为0
,发送失败,置status[s]
为0
。 - 否则
enable[s][r]
为1
,置pending[s][r]
为1
,发送成功,置status[s]
为1
。
如果没有找到这样的 r
,则说明 u
对应的接收方进程在在中断控制器上不存在,发送失败,此时
- 置
status[s]
为0
。
发送状态寄存器 status
和发送寄存器 send共用地址,写入此寄存器所在地址的效果见该寄存器描述。
考虑发送方 s
的发送状态寄存器。
读取此寄存器时,返回结果的第 0
位为 status[s]
,其余位为 0
。
接收方 UIID 寄存器 receiver_uiid
考虑接收方 s
的接收方 UIID 寄存器。读写此寄存器时直接从 receiver_uiid[i]
读出或写入 receiver_uiid[i]
。
领取寄存器 claim
考虑接收方 r
的领取寄存器。
读取此寄存器值时:
- 如果对于 1 到 S-1,存在
s
使得pending[s][r]
且enable[s][r]
为1
,则读出的值为任意这样的s
对应的sender_uiid[s]
。对于这个s
,置pending[s][r]
为0
,领取此中断。 - 否则不存在这样的
s
,读出的值为0
。
写入此寄存器的值会被忽略。
监听寄存器 listen
考虑上下文 c
的监听寄存器。读写此寄存器时直接从 listen[i]
读出或写入 listen[i]
。
CSR 中 USIP 的设置
CSR xip
中的 USIP 位应为一可读可写位。USIP 可被软件写入,除此之外,对于编号为 c
的上下文对应的硬件线程,令 r = listen[c]
,如果 r
在 1 到 R-1 内,且存在 1 到 S-1 的 s
使得 pending[s][r]
和 enable[s][r]
均为 1
,则中断控制器发出软件中断,置 USIP 为 1
。USIP 的真实值为软件写入的值和中断控制器生成值之或(OR)。因此,如果中断控制器不发出软件中断,可以通过向 USIP 写入 0
清除软件中断。否则如果中断控制器在发出软件中断,向 USIP 写入 0
将不会清除软件中断,读出的值仍为 1
。只有领取中断才能清除中断控制器给出的值。
软件流程
槽位申请和配置
用户进程可以向操作系统申请发送方和接收方槽位。操作系统会将为新分配的槽位分配 UIID,写入对应槽位的 sender_uiid
或 receiver_uiid
,并将用户进程可以操作的 4 KiB 页映射给用户进程。
槽位可以作为类似 fd 的资源,或直接用 fd 实现。
用户进程可以申请监听拥有的接收方槽位。此时操作系统将所在硬件线程对应的上下文的 listen
设为此槽位编号。
用户进程可以申请连接发送方和接收方。操作系统如果决定连接,可以将对应的 enable
置 1
。切断类似。
槽位释放
释放发送或接收方槽位时,
- 解除对应 4 KiB 页映射。
- 将对应槽位的
enable
和pending
全部置0
,可以将释放的发送方槽位已发送的中断以其他方式提供给接收方进程。需要先清空enable
再清除pending
,避免清空途中新的中断到来。 sender_uiid
或receiver_uiid
设为无效值。特别是receiver_uiid
影响中断控制器控制中断发送,必须设为无效值。- 如果是发送方,
status
应清空,避免信息泄露。
中断发送
- 用户进程尝试向自己拥有的发送方槽位的
send
写入接收方 UIID。 - 如果用户进程关心发送是否成功,读取
status
。如果为1
(非0
),发送成功,否则可以发起系统调用等,要求操作系统立即响应。
中断处理
- 用户进程确认收到的中断包括用户态跨核软件中断。
- 读取此进程设置监听的接收方槽位对应的
claim
获取中断来源。获取一个中断来源后,该来源的中断从中断控制器中清除。 - 重复此过程读取其余正在等待的中断,直到返回值为
0
,表示已无更多正在等待的中断。此时中断控制器不再置 USIP 为1
。
操作系统时间片管理
时间片结束时,(可以)设置当前硬件线程的对应上下文的 listen
为无效值。
时间片开始时,如果即将运行的用户进程监听一个接收方槽位,设置当前硬件线程的对应上下文的 listen
为即将监听的槽位编号。
另外,操作系统需要管理 USIP
位软件读写的部分等其他用户态中断相关状态。
操作系统动态配置硬件槽位绑定
当软件所需收发槽位较多,操作系统允许软件申请的槽位不能全部映射到硬件槽位时,可以考虑在不通知用户进程的情况下,解除槽位绑定或将已解除绑定的槽位重新绑定。
类似槽位释放时,解除绑定需要
- 解除对应 4 KiB 页映射。
- 将对应槽位的
enable
和pending
全部置0
。 sender_uiid
或receiver_uiid
设为无效值。特别是receiver_uiid
影响中断控制器控制中断发送,必须设为无效值。- 如果是发送方,
status
应清空,避免信息泄露。
不同于槽位释放,解除绑定需要记录原有信息,包括 enable
、pending
、status
原有的值等,并记录已发送或将要接收的中断,以其他形式分发。
此时,操作系统需要处理被解除绑定的进程试图访问槽位造成的 Page Fault、接收方未绑定而发送方绑定导致发送失败后用户进程的请求等清空。
类似分配槽位时,重新绑定需要
- 将对应槽位的
enable
置为正确的值。修改pending
可作为一种重新分配解除绑定时记录的中断的方式。 - 写入
sender_uiid
或receiver_uiid
。 - 写入
status
。 - 添加 4 KiB 页映射。