Skip to content

Commit 9855e93

Browse files
committed
fix: 调整协程信号屏蔽处理位置和简化抢占信号处理器
- 将信号屏蔽的恢复操作移到关键区结束后执行 - 简化抢占信号处理器的实现,使用Resume/Suspend替代手动上下文切换 - 调整测试代码以更好地验证时间片抢占效果
1 parent 87425ee commit 9855e93

File tree

4 files changed

+63
-88
lines changed

4 files changed

+63
-88
lines changed

.vscode/launch.json

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
{
2-
// Use IntelliSense to learn about possible attributes.
3-
// Hover to view descriptions of existing attributes.
4-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5-
"version": "0.2.0",
62
"configurations": [
7-
{
8-
"name": "Debug",
9-
"type": "gdb",
10-
"request": "launch",
11-
"target": "./zig-out/bin/zco",
12-
"cwd": "${workspaceRoot}",
13-
"valuesFormatting": "parseText"
14-
}
3+
{
4+
"name": "C/C++ Debug (gdb Launch)",
5+
"type": "cppdbg",
6+
"request": "launch",
7+
"program": "${workspaceFolder}/zig-out/bin/zco",
8+
"args": [],
9+
"stopAtEntry": true,
10+
"cwd": "${workspaceFolder}",
11+
"environment": [],
12+
"MIMode": "gdb",
13+
"setupCommands": [
14+
{
15+
"description": "Enable pretty-printing for gdb",
16+
"text": "-enable-pretty-printing",
17+
"ignoreFailures": true
18+
}
19+
]
20+
}
1521
]
1622
}

src/co.zig

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub fn Resume(self: *Co) !void {
4343

4444
// 增加切换计数(在关中断状态下安全)
4545
schedule.total_switches.raw += 1;
46+
// 恢复信号
47+
_ = c.pthread_sigmask(c.SIG_SETMASK, &oldset, null);
48+
// === 关键区结束 ===
4649

4750
// 启动定时器(在协程开始运行前,重置计时)
4851
schedule.startTimer() catch |e| {
@@ -71,6 +74,9 @@ pub fn Resume(self: *Co) !void {
7174

7275
// 增加切换计数(在关中断状态下安全)
7376
schedule.total_switches.raw += 1;
77+
// 恢复信号
78+
_ = c.pthread_sigmask(c.SIG_SETMASK, &oldset, null);
79+
// === 关键区结束 ===
7480

7581
// 启动定时器(在协程开始运行前,重置计时)
7682
schedule.startTimer() catch |e| {
@@ -138,13 +144,12 @@ pub const Co = struct {
138144
// 停止定时器(协程挂起时)
139145
self.schedule.stopTimer();
140146
self.schedule.runningCo = null;
141-
142-
const swap_result = c.swapcontext(&co.ctx, &schedule.ctx);
143-
144147
// 恢复信号
145148
_ = c.pthread_sigmask(c.SIG_SETMASK, &oldset, null);
146149
// === 关键区结束 ===
147150

151+
const swap_result = c.swapcontext(&co.ctx, &schedule.ctx);
152+
148153
if (swap_result != 0) return error.swapcontext;
149154

150155
if (self.schedule.exit) {

src/main.zig

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ pub fn main() !void {
2323
// const t5 = try std.Thread.spawn(.{}, testDataChan, .{});
2424
// t5.join();
2525

26-
const t6 = try std.Thread.spawn(.{}, testTimerLifecycle, .{});
27-
t6.join();
26+
// const t6 = try std.Thread.spawn(.{}, testTimerLifecycle, .{});
27+
// t6.join();
28+
try testPreemption();
2829
}
2930

3031
pub fn testDataChan() !void {
@@ -268,18 +269,18 @@ pub fn testPreemption() !void {
268269
}.run, .{&counter2});
269270

270271
// 暂时禁用协程3,只测试协程1和协程2的抢占
271-
// _ = try s.go(struct {
272-
// fn run() !void {
273-
// const schedule = try zco.getSchedule();
274-
// const co = try schedule.getCurrentCo();
275-
// var i: usize = 0;
276-
// while (i < 20) : (i += 1) {
277-
// try co.Sleep(100 * std.time.ns_per_ms); // 睡眠100ms
278-
// std.log.info("状态检查协程运行中... {} (观察抢占效果)", .{i});
279-
// }
280-
// std.log.info("状态检查协程完成", .{});
281-
// }
282-
// }.run, .{});
272+
_ = try s.go(struct {
273+
fn run() !void {
274+
const schedule = try zco.getSchedule();
275+
const co = try schedule.getCurrentCo();
276+
var i: usize = 0;
277+
while (i < 20) : (i += 1) {
278+
try co.Sleep(100 * std.time.ns_per_ms); // 睡眠100ms
279+
std.log.info("状态检查协程运行中... {} (观察抢占效果)", .{i});
280+
}
281+
std.log.info("状态检查协程完成", .{});
282+
}
283+
}.run, .{});
283284

284285
std.log.info("开始运行调度器,测试时间片抢占...", .{});
285286
std.log.info("如果时间片抢占正常工作,应该看到协程1和协程2交替输出进度", .{});
@@ -341,29 +342,31 @@ pub fn testTimerLifecycle() !void {
341342
}
342343
}.run, .{&counter2});
343344

344-
// 协程3:短时间运行,用于观察调度效果
345-
_ = try s.go(struct {
346-
fn run() !void {
347-
const schedule = try zco.getSchedule();
348-
const co = try schedule.getCurrentCo();
349-
std.log.info("协程3开始运行(短时间,会主动让出CPU)", .{});
345+
// // 协程3:短时间运行,用于观察调度效果
346+
// _ = try s.go(struct {
347+
// fn run() !void {
348+
// const schedule = try zco.getSchedule();
349+
// const co = try schedule.getCurrentCo();
350+
// std.log.info("协程3开始运行(短时间,会主动让出CPU)", .{});
350351

351-
for (0..10) |i| {
352-
std.log.info("协程3: 步骤 {}", .{i});
353-
try co.Suspend(); // 主动挂起
354-
try co.Sleep(50 * std.time.ns_per_ms); // 睡眠50ms
355-
}
352+
// for (0..10) |i| {
353+
// std.log.info("协程3: 步骤 {}", .{i});
354+
// try co.Suspend(); // 主动挂起
355+
// try co.Sleep(50 * std.time.ns_per_ms); // 睡眠50ms
356+
// }
356357

357-
std.log.info("协程3完成", .{});
358-
}
359-
}.run, .{});
358+
// std.log.info("协程3完成", .{});
359+
// }
360+
// }.run, .{});
360361

361362
std.log.info("开始运行调度器,测试时间片抢占...", .{});
362363
std.log.info("如果时间片抢占正常工作,应该看到协程1和协程2交替输出进度", .{});
363364

364365
// 主协程等待一段时间
365-
const mainCo = try s.getCurrentCo();
366-
try mainCo.Sleep(5 * std.time.ns_per_s);
366+
// const mainCo = try s.getCurrentCo();
367+
// try mainCo.Sleep(5 * std.time.ns_per_s);
368+
369+
std.time.sleep(10 * std.time.ns_per_s);
367370

368371
std.log.info("测试完成!", .{});
369372
std.log.info("协程1最终计数: {}", .{counter1});

src/schedule.zig

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -47,59 +47,20 @@ pub const Schedule = struct {
4747

4848
// 抢占信号处理器 - 使用关中断方式,彻底解决竞态条件
4949
fn preemptSigHandler(_: c_int, _: [*c]c.siginfo_t, uctx_ptr: ?*anyopaque) callconv(.C) void {
50+
_ = uctx_ptr;
5051
const schedule = localSchedule orelse return;
5152

52-
// 立即屏蔽SIGALRM信号,防止嵌套调用
53-
var sigset: c.sigset_t = undefined;
54-
var oldset: c.sigset_t = undefined;
55-
_ = c.sigemptyset(&sigset);
56-
_ = c.sigaddset(&sigset, c.SIGALRM);
57-
_ = c.sigprocmask(c.SIG_BLOCK, &sigset, &oldset);
58-
5953
// 在关中断状态下安全地获取runningCo
6054
const co = schedule.runningCo orelse {
61-
_ = c.sigprocmask(c.SIG_SETMASK, &oldset, null);
55+
// _ = c.sigprocmask(c.SIG_SETMASK, &oldset, null);
6256
return;
6357
};
6458

6559
// 增加抢占计数(在关中断状态下安全)
6660
schedule.preemption_count.raw += 1;
6761

68-
// 检查协程状态,确保它是正在运行的
69-
if (co.state != .RUNNING) {
70-
_ = c.sigprocmask(c.SIG_SETMASK, &oldset, null);
71-
return;
72-
}
73-
74-
const interrupted_ctx: *c.ucontext_t = @ptrCast(@alignCast(uctx_ptr.?));
75-
76-
// 完整保存被中断的上下文到协程
77-
co.ctx = interrupted_ctx.*;
78-
79-
// 为signalHandler配置新的栈
80-
const new_rip = @intFromPtr(&signalHandler);
81-
82-
// 使用协程的栈作为signalHandler的栈,确保16字节对齐
83-
const stack_top = co.stack[co.stack.len - 1 ..].ptr;
84-
const aligned_stack = @as(*u8, @ptrFromInt(@intFromPtr(stack_top) & ~@as(usize, 15)));
85-
86-
// 设置正确的调用栈
87-
// 1. 将返回地址压入栈中(这里我们设置一个假的返回地址,因为signalHandler不会返回)
88-
const fake_return_addr = @intFromPtr(&signalHandler) + 1000; // 假的返回地址
89-
90-
// 2. 为返回地址预留空间,并确保16字节对齐
91-
const stack_ptr = @as(*u8, @ptrFromInt(@intFromPtr(aligned_stack) - 16)); // 预留16字节空间
92-
const return_addr_ptr = @as(*usize, @ptrFromInt(@intFromPtr(stack_ptr) + 8)); // 在栈顶+8的位置放置返回地址
93-
return_addr_ptr.* = fake_return_addr;
94-
95-
// 3. 设置栈指针和指令指针
96-
const rsp_ptr = @as(*u8, @ptrFromInt(@intFromPtr(stack_ptr) + 8)); // RSP指向返回地址
97-
interrupted_ctx.uc_mcontext.gregs[c.REG_RSP] = @intCast(@intFromPtr(rsp_ptr));
98-
interrupted_ctx.uc_mcontext.gregs[c.REG_RIP] = @intCast(new_rip);
99-
100-
// 恢复信号屏蔽并返回
101-
// 内核恢复执行时会跳转到 signalHandler
102-
_ = c.sigprocmask(c.SIG_SETMASK, &oldset, null);
62+
co.Resume() catch return;
63+
co.Suspend() catch return;
10364
}
10465

10566
const CoMap = std.AutoArrayHashMap(usize, *Co);

0 commit comments

Comments
 (0)