SimpleKernel 1.17.0
Loading...
Searching...
No Matches
cfs_scheduler_test.cpp
Go to the documentation of this file.
1
5#include "cfs_scheduler.hpp"
6
7#include <gtest/gtest.h>
8
9#include "kstd_vector"
11
12// 测试 CFS 调度器的基本入队出队功能
13TEST(CfsSchedulerTest, BasicEnqueueDequeue) {
14 CfsScheduler scheduler;
15
16 // 创建测试任务
17 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
18 task1.status = TaskStatus::kReady;
20 task1.sched_data.cfs.vruntime = 0;
21
22 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
23 task2.status = TaskStatus::kReady;
25 task2.sched_data.cfs.vruntime = 0;
26
27 // 测试空队列
28 EXPECT_TRUE(scheduler.IsEmpty());
29 EXPECT_EQ(scheduler.GetQueueSize(), 0);
30 EXPECT_EQ(scheduler.PickNext(), nullptr);
31
32 // 加入任务
33 scheduler.Enqueue(&task1);
34 EXPECT_FALSE(scheduler.IsEmpty());
35 EXPECT_EQ(scheduler.GetQueueSize(), 1);
36
37 scheduler.Enqueue(&task2);
38 EXPECT_EQ(scheduler.GetQueueSize(), 2);
39
40 // 选择任务(vruntime 相同时,按入队顺序)
41 auto* next1 = scheduler.PickNext();
42 EXPECT_NE(next1, nullptr);
43 EXPECT_EQ(scheduler.GetQueueSize(), 1);
44
45 auto* next2 = scheduler.PickNext();
46 EXPECT_NE(next2, nullptr);
47 EXPECT_EQ(scheduler.GetQueueSize(), 0);
48
49 EXPECT_EQ(scheduler.PickNext(), nullptr);
50 EXPECT_TRUE(scheduler.IsEmpty());
51}
52
53// 测试 vruntime 排序
54TEST(CfsSchedulerTest, VruntimeOrdering) {
55 CfsScheduler scheduler;
56
57 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
59 task1.sched_data.cfs.vruntime = 1000;
60
61 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
63 task2.sched_data.cfs.vruntime = 500; // 最小 vruntime
64
65 TaskControlBlock task3("Task3", 3, nullptr, nullptr);
67 task3.sched_data.cfs.vruntime = 750;
68
69 // 按任意顺序加入任务
70 scheduler.Enqueue(&task1);
71 scheduler.Enqueue(&task2);
72 scheduler.Enqueue(&task3);
73
74 EXPECT_EQ(scheduler.GetQueueSize(), 3);
75
76 // 应该按 vruntime 从小到大选择
77 EXPECT_EQ(scheduler.PickNext(), &task2); // vruntime = 500
78 EXPECT_EQ(scheduler.PickNext(), &task3); // vruntime = 750
79 EXPECT_EQ(scheduler.PickNext(), &task1); // vruntime = 1000
80 EXPECT_EQ(scheduler.PickNext(), nullptr);
81}
82
83// 测试新任务的 vruntime 初始化
84TEST(CfsSchedulerTest, NewTaskVruntimeInitialization) {
85 CfsScheduler scheduler;
86
87 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
89 task1.sched_data.cfs.vruntime = 1000;
90
91 // 加入第一个任务
92 scheduler.Enqueue(&task1);
93 auto* picked1 = scheduler.PickNext();
94 EXPECT_EQ(picked1, &task1);
95
96 // 第二个任务 vruntime = 0 (新任务)
97 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
99 task2.sched_data.cfs.vruntime = 0;
100
101 scheduler.Enqueue(&task2);
102 // 新任务的 vruntime 应该被设置为 min_vruntime (1000)
103 EXPECT_EQ(task2.sched_data.cfs.vruntime, 1000);
104}
105
106// 测试权重对 vruntime 的影响
107TEST(CfsSchedulerTest, WeightImpactOnVruntime) {
108 CfsScheduler scheduler;
109
110 TaskControlBlock task1("HighPriorityTask", 1, nullptr, nullptr);
111 task1.sched_data.cfs.weight = CfsScheduler::kDefaultWeight * 2; // 高优先级
112 task1.sched_data.cfs.vruntime = 0;
113
114 TaskControlBlock task2("LowPriorityTask", 2, nullptr, nullptr);
115 task2.sched_data.cfs.weight = CfsScheduler::kDefaultWeight / 2; // 低优先级
116 task2.sched_data.cfs.vruntime = 0;
117
118 scheduler.Enqueue(&task1);
119 scheduler.Enqueue(&task2);
120
121 auto* first = scheduler.PickNext();
122 EXPECT_NE(first, nullptr);
123
124 // 模拟 OnTick 更新 vruntime
125 uint64_t initial_vruntime = first->sched_data.cfs.vruntime;
126 scheduler.OnTick(first);
127
128 // 权重大的任务 vruntime 增长慢
129 if (first == &task1) {
130 // task1 权重是 2048,delta = 1024 * 1000 / 2048 = 500
131 EXPECT_EQ(first->sched_data.cfs.vruntime, initial_vruntime + 500);
132 } else {
133 // task2 权重是 512,delta = 1024 * 1000 / 512 = 2000
134 EXPECT_EQ(first->sched_data.cfs.vruntime, initial_vruntime + 2000);
135 }
136}
137
138// 测试 OnTick 抢占逻辑
139TEST(CfsSchedulerTest, OnTickPreemption) {
140 CfsScheduler scheduler;
141
142 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
144 task1.sched_data.cfs.vruntime = 100;
145
146 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
148 task2.sched_data.cfs.vruntime = 0; // 更小的 vruntime
149
150 scheduler.Enqueue(&task2);
151
152 // task1 运行,vruntime 不断增长
153 task1.sched_data.cfs.vruntime = 100;
154 bool should_preempt = scheduler.OnTick(&task1);
155
156 // task2 的 vruntime (0) 比 task1 小很多,应该抢占
157 EXPECT_TRUE(should_preempt);
158}
159
160// 测试 OnTick 不抢占情况
161TEST(CfsSchedulerTest, OnTickNoPreemption) {
162 CfsScheduler scheduler;
163
164 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
166 task1.sched_data.cfs.vruntime = 1000;
167
168 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
170 // OnTick 会让 task1 增加 1000,所以 task2 应该设置为 1000 + 1000 - 5 = 1995
171 // 这样 OnTick 后:task1 = 2000, task2 = 1995,差距 5 < 10
172 task2.sched_data.cfs.vruntime = 1995;
173
174 scheduler.Enqueue(&task2);
175
176 bool should_preempt = scheduler.OnTick(&task1);
177
178 // OnTick 后差距为 5,小于 kMinGranularity (10),不应该抢占
179 EXPECT_FALSE(should_preempt);
180}
181
182// 测试 Dequeue 功能
183TEST(CfsSchedulerTest, DequeueSpecificTask) {
184 CfsScheduler scheduler;
185
186 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
188 task1.sched_data.cfs.vruntime = 100;
189
190 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
192 task2.sched_data.cfs.vruntime = 200;
193
194 TaskControlBlock task3("Task3", 3, nullptr, nullptr);
196 task3.sched_data.cfs.vruntime = 300;
197
198 scheduler.Enqueue(&task1);
199 scheduler.Enqueue(&task2);
200 scheduler.Enqueue(&task3);
201
202 EXPECT_EQ(scheduler.GetQueueSize(), 3);
203
204 // 移除中间的任务
205 scheduler.Dequeue(&task2);
206 EXPECT_EQ(scheduler.GetQueueSize(), 2);
207
208 // 验证只剩下 task1 和 task3
209 EXPECT_EQ(scheduler.PickNext(), &task1);
210 EXPECT_EQ(scheduler.PickNext(), &task3);
211 EXPECT_EQ(scheduler.PickNext(), nullptr);
212}
213
214// 测试空指针处理
215TEST(CfsSchedulerTest, NullPointerHandling) {
216 CfsScheduler scheduler;
217
218 // Enqueue 空指针应该不崩溃
219 scheduler.Enqueue(nullptr);
220 EXPECT_EQ(scheduler.PickNext(), nullptr);
221
222 // Dequeue 空指针应该不崩溃
223 scheduler.Dequeue(nullptr);
224
225 // OnTick 空指针应该不崩溃
226 EXPECT_FALSE(scheduler.OnTick(nullptr));
227
228 // OnPreempted 空指针应该不崩溃
229 scheduler.OnPreempted(nullptr);
230
231 // OnScheduled 空指针应该不崩溃
232 scheduler.OnScheduled(nullptr);
233}
234
235// 测试默认权重设置
236TEST(CfsSchedulerTest, DefaultWeightAssignment) {
237 CfsScheduler scheduler;
238
239 TaskControlBlock task("Task", 1, nullptr, nullptr);
240 task.sched_data.cfs.weight = 0; // 权重未设置
241 task.sched_data.cfs.vruntime = 0;
242
243 scheduler.Enqueue(&task);
244
245 // 权重应该被设置为默认值
247}
248
249// 测试统计信息
250TEST(CfsSchedulerTest, Statistics) {
251 CfsScheduler scheduler;
252
253 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
255 task1.sched_data.cfs.vruntime = 0;
256
257 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
259 task2.sched_data.cfs.vruntime = 0;
260
261 // 测试 enqueue 计数
262 scheduler.Enqueue(&task1);
263 scheduler.Enqueue(&task2);
264 auto stats = scheduler.GetStats();
265 EXPECT_EQ(stats.total_enqueues, 2);
266
267 // 测试 pick 计数
268 scheduler.PickNext();
269 stats = scheduler.GetStats();
270 EXPECT_EQ(stats.total_picks, 1);
271
272 scheduler.PickNext();
273 stats = scheduler.GetStats();
274 EXPECT_EQ(stats.total_picks, 2);
275
276 // 测试 dequeue 计数
277 scheduler.Enqueue(&task1);
278 scheduler.Enqueue(&task2);
279 scheduler.Dequeue(&task1);
280 stats = scheduler.GetStats();
281 EXPECT_EQ(stats.total_dequeues, 1);
282
283 // 测试重置统计
284 scheduler.ResetStats();
285 stats = scheduler.GetStats();
286 EXPECT_EQ(stats.total_enqueues, 0);
287 EXPECT_EQ(stats.total_picks, 0);
288 EXPECT_EQ(stats.total_dequeues, 0);
289 EXPECT_EQ(stats.total_preemptions, 0);
290}
291
292// 测试 min_vruntime 更新
293TEST(CfsSchedulerTest, MinVruntimeUpdate) {
294 CfsScheduler scheduler;
295
296 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
298 task1.sched_data.cfs.vruntime = 1000;
299
300 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
302 task2.sched_data.cfs.vruntime = 500;
303
304 TaskControlBlock task3("Task3", 3, nullptr, nullptr);
306 task3.sched_data.cfs.vruntime = 750;
307
308 scheduler.Enqueue(&task1);
309 scheduler.Enqueue(&task2);
310 scheduler.Enqueue(&task3);
311
312 // 初始 min_vruntime 应该是 0
313 EXPECT_EQ(scheduler.GetMinVruntime(), 0);
314
315 // 选择 task2 (vruntime = 500)
316 scheduler.PickNext();
317
318 // min_vruntime 应该更新为队列中最小的 (750)
319 EXPECT_EQ(scheduler.GetMinVruntime(), 750);
320
321 // 选择 task3 (vruntime = 750)
322 scheduler.PickNext();
323
324 // min_vruntime 应该更新为 1000
325 EXPECT_EQ(scheduler.GetMinVruntime(), 1000);
326
327 // 选择 task1 (vruntime = 1000)
328 scheduler.PickNext();
329
330 // 队列为空,min_vruntime 保持为 1000
331 EXPECT_EQ(scheduler.GetMinVruntime(), 1000);
332}
333
334// 测试多次 OnTick 的 vruntime 累积
335TEST(CfsSchedulerTest, MultipleTicksVruntimeAccumulation) {
336 CfsScheduler scheduler;
337
338 TaskControlBlock task("Task", 1, nullptr, nullptr);
340 task.sched_data.cfs.vruntime = 0;
341
342 // 模拟多次 tick
343 constexpr int kTickCount = 10;
344 uint64_t expected_delta = (CfsScheduler::kDefaultWeight * 1000) /
345 task.sched_data.cfs.weight; // = 1000
346
347 for (int i = 0; i < kTickCount; ++i) {
348 scheduler.OnTick(&task);
349 }
350
351 EXPECT_EQ(task.sched_data.cfs.vruntime, expected_delta * kTickCount);
352}
353
354// 测试不同权重下的公平性
355TEST(CfsSchedulerTest, FairnessWithDifferentWeights) {
356 CfsScheduler scheduler;
357
358 TaskControlBlock high_priority("HighPriority", 1, nullptr, nullptr);
359 high_priority.sched_data.cfs.weight =
360 CfsScheduler::kDefaultWeight * 2; // 2倍权重
361 high_priority.sched_data.cfs.vruntime = 0;
362
363 TaskControlBlock low_priority("LowPriority", 2, nullptr, nullptr);
364 low_priority.sched_data.cfs.weight = CfsScheduler::kDefaultWeight; // 1倍权重
365 low_priority.sched_data.cfs.vruntime = 0;
366
367 // 模拟相同次数的 tick
368 constexpr int kTickCount = 10;
369
370 for (int i = 0; i < kTickCount; ++i) {
371 scheduler.OnTick(&high_priority);
372 scheduler.OnTick(&low_priority);
373 }
374
375 // 高优先级任务的 vruntime 增长应该是低优先级的一半
376 // high: delta = 1024 * 1000 / 2048 = 500 per tick
377 // low: delta = 1024 * 1000 / 1024 = 1000 per tick
378 EXPECT_EQ(high_priority.sched_data.cfs.vruntime, 500 * kTickCount);
379 EXPECT_EQ(low_priority.sched_data.cfs.vruntime, 1000 * kTickCount);
380 EXPECT_EQ(low_priority.sched_data.cfs.vruntime,
381 high_priority.sched_data.cfs.vruntime * 2);
382}
383
384// 测试极端权重值
385TEST(CfsSchedulerTest, ExtremeWeightValues) {
386 CfsScheduler scheduler;
387
388 TaskControlBlock task("Task", 1, nullptr, nullptr);
389 task.sched_data.cfs.vruntime = 0;
390
391 // 测试极小权重 (避免除零)
392 task.sched_data.cfs.weight = 1;
393 scheduler.OnTick(&task);
395
396 // 测试极大权重
397 task.sched_data.cfs.vruntime = 0;
399 scheduler.OnTick(&task);
401 EXPECT_LT(task.sched_data.cfs.vruntime, 10); // 应该很小
402}
403
404// 测试队列大小的一致性
405TEST(CfsSchedulerTest, QueueSizeConsistency) {
406 CfsScheduler scheduler;
407
408 kstd::vector<TaskControlBlock*> tasks;
409 for (int i = 0; i < 5; ++i) {
410 auto* task = new TaskControlBlock("Task", 10, nullptr, nullptr);
411 task->sched_data.cfs.weight = CfsScheduler::kDefaultWeight;
412 task->sched_data.cfs.vruntime = i * 100;
413 tasks.push_back(task);
414 }
415
416 // 加入 5 个任务
417 for (auto* task : tasks) {
418 scheduler.Enqueue(task);
419 }
420 EXPECT_EQ(scheduler.GetQueueSize(), 5);
421
422 // 移除 3 个任务
423 scheduler.PickNext();
424 scheduler.PickNext();
425 scheduler.PickNext();
426 EXPECT_EQ(scheduler.GetQueueSize(), 2);
427
428 // 加入 2 个任务
429 scheduler.Enqueue(tasks[0]);
430 scheduler.Enqueue(tasks[1]);
431 EXPECT_EQ(scheduler.GetQueueSize(), 4);
432
433 // 清空队列
434 while (!scheduler.IsEmpty()) {
435 scheduler.PickNext();
436 }
437 EXPECT_EQ(scheduler.GetQueueSize(), 0);
438 EXPECT_TRUE(scheduler.IsEmpty());
439
440 // 清理内存
441 for (auto* task : tasks) {
442 delete task;
443 }
444}
445
446// 测试抢占统计
447TEST(CfsSchedulerTest, PreemptionStatistics) {
448 CfsScheduler scheduler;
449
450 TaskControlBlock task1("Task1", 1, nullptr, nullptr);
452 task1.sched_data.cfs.vruntime = 1000;
453
454 TaskControlBlock task2("Task2", 2, nullptr, nullptr);
456 task2.sched_data.cfs.vruntime = 0;
457
458 scheduler.Enqueue(&task2);
459
460 // 触发抢占
461 scheduler.OnTick(&task1);
462 auto stats = scheduler.GetStats();
463 EXPECT_EQ(stats.total_preemptions, 1);
464
465 // OnPreempted 也会增加抢占计数
466 scheduler.OnPreempted(&task1);
467 stats = scheduler.GetStats();
468 EXPECT_EQ(stats.total_preemptions, 2);
469}
CFS (Completely Fair Scheduler) 调度器
auto Dequeue(TaskControlBlock *task) -> void override
从就绪队列中移除指定任务
auto OnTick(TaskControlBlock *current) -> bool override
每个 tick 更新任务的 vruntime
auto PickNext() -> TaskControlBlock *override
选择下一个要运行的任务
auto Enqueue(TaskControlBlock *task) -> void override
将任务加入就绪队列
auto OnPreempted(TaskControlBlock *task) -> void override
任务被抢占时调用
auto IsEmpty() const -> bool override
判断队列是否为空
static constexpr uint32_t kDefaultWeight
默认权重 (对应 nice 值为 0)
auto GetQueueSize() const -> size_t override
获取就绪队列大小
auto GetMinVruntime() const -> uint64_t
获取当前 min_vruntime
virtual auto GetStats() const -> const Stats &
获取调度器统计信息
virtual auto OnScheduled(TaskControlBlock *task) -> void
任务开始运行时调用 (从 Ready 变为 Running)
virtual auto ResetStats() -> void
重置统计信息
constexpr etl::fsm_state_id_t kReady
Definition task_fsm.hpp:15
任务控制块,管理进程/线程的核心数据结构
union TaskControlBlock::SchedData sched_data
#define EXPECT_TRUE(cond, msg)
#define EXPECT_NE(val1, val2, msg)
#define EXPECT_LT(val1, val2, msg)
#define EXPECT_FALSE(cond, msg)
#define EXPECT_GT(val1, val2, msg)
#define EXPECT_EQ(val1, val2, msg)
uint32_t weight
任务权重 (1024 为默认)
struct TaskControlBlock::SchedData::@3 cfs
CFS 调度器数据
uint64_t vruntime
虚拟运行时间
TEST(CfsSchedulerTest, BasicEnqueueDequeue)