1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
## 问题与思路
问题:
- 读者写者问题, 共享内存, eventfd+epoll 通知
- 多核 CPU, 读写进程可能在不同核心.上下文切换成本更高.
使用 uintr 解决问题的思路:
思路1
- uintr 无法直接干涉 读者进程的调度,但借鉴 `uintr_receiver_wait` 的思路,可以节省一些 写者发通知的成本,缓解这个问题.
- 写者调用 `_senduipi(uipi_index);` 后, 硬件会比较中断向量与寄存器(IA32_UINTR_MISC 的 39:32 bit),是否一致,不一致则正常触发内核中断. 进入内核中断处理后, 可以对接到 eventfd 等机制, 完成通知传递.
- 在上述过程中,写者发通知成本由硬件执行, 因此有可能比写者进程直接通过系统调用发通知更快.
思路2
- 完全放弃 eventfd + epoll 的形式,改为使用 中断形式处理通知. uintr 有可能带来数倍的性能提升.
- 现有实现中,当读者进程挂起时, 用户中断会触发一次系统中断,进入系统中断,记录未被执行的用户中断(称为虚假用户中断).
- 针对读者进程挂起问题, 有可能在 虚假用户中断中,类似 思路 1,直接唤醒读者.
- [poc_v2](https://github.com/intel/uintr-linux-kernel/blob/uintr-next/arch/x86/kernel/irq.c#L381) 有类似的处理.
思路2 改造已有业务涉及范围太大, 先实现并评估 思路 1.
## 设计与实现
要求: 按照思路 1,实现基于 uintr 的通知机制
- 适配 epoll 管理,方便使用.
- 用户中断不应干扰读者进程其他处理流程.
参考 `uintr_receiver_wait` 的思路,使用 eventfd 作为通知机制.
- 增加新的系统中断 及 中断处理函数. 在中断处理函数中写入 eventfd 通知.
- 增加新的系统调用, 创建应用层和内核的关联.
- 调整默认用户中断行为,防止干扰读者进程其他事务.
### 代码实现
源码在 [epoll_waitlist](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/tree/epoll_waitlist?ref_type=heads) 分支.
[add UINTR_EVENT_VECTOR](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/commit/d1528589b422729a8f9b10fce153425bc8ed5949): 增加新的系统调用 和 中断处理函数, 声明 `UINTR_EVENT_VECTOR` 地方比较多,参考 `UINTR_KERNEL_VECTOR` 小心添加.
```c
#define UINTR_EVENT_VECTOR 0xe9
DEFINE_IDTENTRY_SYSVEC(sysvec_uintr_event_notification)
{
ack_APIC_irq();
inc_irq_stat(uintr_event_notifications);
/* do event things */
uintr_event_write();
}
```
[add system call uintr_event(int fd)](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/commit/c6c83e45719d3774bf723536f323e317eb9a384b): 增加新的系统调用, 读者进程首先创建 eventfd 的文件描述符,再通过 `uintr_event` 调用传递给内核.
最终内核维护 [eventfd_ctx 和 等待 list](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/blob/epoll_waitlist/arch/x86/kernel/uintr_core.c#L62)
```c
/* for uint_event*/
static DEFINE_SPINLOCK(uintr_event_lock);
static struct eventfd_ctx *uintr_event_ctx = NULL;
static struct list_head uintr_event_list = LIST_HEAD_INIT(uintr_event_list);
```
- 通过 uintr_event_ctx 关联到应用层通知.
- 收到 uintr-evet 用户中断时,中断处理程序调用 `uintr_event_write`,写入一个字节,通知读者进程.
- `uintr_event_write` 还有一个重要流程是通过 `uintr_event_list` 置位对应 UPID 的 ON 位置, 在标准流程中 ON 置位由硬件完成,这里必须软件置位.
[调整默认用户中断行为](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/commit/aadb3fd0fd2c1b4dab3e68fc9bee8a98f0afb12c): 注意,这个提交使 uintr 无法再接收用户中断了.
- 读者进程挂起时, 不再置位 SN, [`switch_uintr_prepare`](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/blob/epoll_waitlist/arch/x86/kernel/uintr_core.c#L903);
- 读者进程回到前台时, 不再更换 upid 的通知向量,不再发起 `send_IPI_self` 触发用户中断处理. [`switch_uintr_return`](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/blob/epoll_waitlist/arch/x86/kernel/uintr_core.c#L979)
#### qemu bug 修正
可能与 qemu 实现有关,已有代码中不得不处理寄存器丢失置位等情况, 基于硬件测试时请去除相关代码,防止干扰.
ON 置位:
- 问题: 理论上 ON 仅在写者发送时,置位一次, 当在测试中,依然存在无故置位情况, 导致 硬件无法发送用户中断.特别是当读者进程挂起后,发生频率更高.
- 解决: 可能的地方,多次重置 ON,目前是在 进程切换到后台时,增加一次重置 ON;
- [代码](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/blob/epoll_waitlist/arch/x86/kernel/uintr_core.c#L904)
UINTR_TT 中 uitt 地址无故清空
- 问题: 当写者进程因调度切换运行核心时, UINTR_TT 寄存器重置为全 0.导致 用户中断发送到错误地址.
- 解决: 用户进程回到前台之前,比较 UINTR_TT 寄存器内 uitt 地址与实际 uitt 地址是否相同,不相同则重写.
- [代码](https://git.mesalab.cn/zhangyang/uintr-linux-kernel/-/blob/epoll_waitlist/arch/x86/kernel/uintr_core.c#L918)
写者执行 `_senduipi` 但无输出, 过一点时间又恢复正常.
- 问题: 写者进程因调度出现了核心切换,其他核心没有置位 UIF,导致写者在这个核心运行时无法发送用户中断.
- 解决: 限制 qemu 模拟双核 cpu,在读者 写者进程分别都执行一次 `_stui()`.这个问题应该是 qemu 对 uintr 实现并不完整导致.
- 在测试代码中,与内核无关.
## 性能测试
测试程序在 [uintr-event](https://git.mesalab.cn/zhangyang/uintr-event) 下的 `/test` 文件夹
- 测试部分源码来自 ipc-bench
比较 uintr-event 和 eventfd 在相同场景下的性能
- 共享内存 epoll 等
- 取时间,多次复测 重启 qemu; 保证编译命令相同;
- 写者进程 write 前时间,写入共享内存, 读者唤醒后取结束时间.
- 单独测量了 读者挂起,从写者 write 到 读者唤醒,这个场景下时间..
- 写者发通知前增加了 100ms 的延迟,以等待读者挂起.(100ms 大于任何已测得的数据)
- 跳过失败的,只统计成功记录.
编译命令如下
```c
gcc-11 -O2 -Wall -static -muintr -minline-all-stringops -std=c11 ./shm-eventfd-bi.c -lpthread -o ./shm-eventfd-bi -lm
gcc-11 -O2 -Wall -static -muintr -minline-all-stringops -std=c11 ./shm-uintr-event-bi.c -lpthread -o ./shm-uintr-event-bi -lm
```
非阻塞测试 100000,阻塞测试 10000;
| method | Average(us) | min(us) | max(us) | rate(msg/s) | Standard deviation |
| ---------------- | ----------- | ------- | --------- | ----------- | ------------------ |
| eventfd | 93.542 | 20.790 | 34906.550 | 10690 | 298.206 |
| uintr_event | 89.837 | 31.820 | 7010.990 | 11131 | 60.103 |
| evetfd_wait | 523.533 | 129.080 | 19615.600 | 1910 | 865.047 |
| uintr_event_wait | 266.885 | 97.140 | 10011.420 | 3746 | 261.965 |
原始记录
```c
============ RESULTS ================
Message size: 1
Message count: 99575
Total duration: 9314.517 ms
Average duration: 93.542 us
Minimum duration: 20.790 us
Maximum duration: 34906.550 us
Standard deviation: 298.206 us
Message rate: 10690 msg/s
=====================================
============ RESULTS ================
Message size: 1
Message count: 99999
Total duration: 8983.693 ms
Average duration: 89.837 us
Minimum duration: 31.820 us
Maximum duration: 7010.990 us
Standard deviation: 60.103 us
Message rate: 11131 msg/s
=====================================
============ RESULTS ================
Message size: 1
Message count: 9792
Total duration: 5126.437 ms
Average duration: 523.533 us
Minimum duration: 129.080 us
Maximum duration: 19615.600 us
Standard deviation: 865.047 us
Message rate: 1910 msg/s
=====================================
============ RESULTS ================
Message size: 1
Message count: 9999
Total duration: 2668.588 ms
Average duration: 266.885 us
Minimum duration: 97.140 us
Maximum duration: 10011.420 us
Standard deviation: 261.965 us
Message rate: 3746 msg/s
=====================================
```
## 结论
基于 qemu 模拟器的数据:
- 接收者非挂起时, uintr-event 与 evenfd 速度相同或稍快一点点.
- 接收者挂起时, uintr-event 唤醒接收者的速度要快 50% 左右.
- 我对这个结论在物理机能否复现存疑, 幅度应该没有这么夸张.
- 另外测试程序也需要复核以确保测试结果的准确性.
uintr 结合 eventfd 应当可以作为一个解决 eventfd+epoll 瓶颈的一个思路, 提升效果明显.
- 用户中断 rfc 尚未被通过, 源代码库开发并不活跃 (最后 [commit](https://github.com/intel/uintr-linux-kernel/commit/f20fa04c4553c6140dc8dc49e55fa282d2360ece) 时间是 Sep 13, 2021), 尚不能在生产环境使用.
- 以上测试结果均基于 qemu 模拟器测得, qemu 的实现尚有一些 bug,无法 100%保证 物理机可以复现相同的结果.
- uintr_event 基于 [rfc-v1](https://github.com/intel/uintr-linux-kernel/tree/rfc-v1) 修改, 最新的 [poc_v2](https://github.com/intel/uintr-linux-kernel/tree/poc-v2%3E) 跑 ipc-bench 中有 bug (不确定是 qemu 还是 kernel 的问题).
- 已有 qemu 要运行,还需要基于这个 [commit](https://github.com/OS-F-4/uintr-linux-kernel/commit/9f6a41b01ad529801838f1bd8175ed76c1509221) 修改 kernel 代码.
|