You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

协程调度场景下,如何在信号处理程序中原子设置sigmask并执行long jump?

解决协程调度器中swapcontext非原子操作引发的信号重入问题

1. 利用内核默认信号阻塞机制

默认情况下,信号处理程序执行时内核会自动阻塞当前触发的信号(比如你的SIGURG),除非你在sigaction中设置了SA_NODEFER标志。如果没有使用该标志,理论上信号处理程序不会重入,但swapcontext的掩码修改步骤仍有风险:若目标协程的上下文掩码允许SIGURG,那么在swapcontext修改掩码后、跳转前的窗口内,SIGURG会处于未阻塞状态,可能触发新的信号处理。

应对方式:

  • 注册SIGURG时,不要设置SA_NODEFER,依赖内核自动阻塞信号的机制。
  • 所有协程的上下文信号掩码默认阻塞SIGURG,仅当协程处于可被抢占的运行状态时,才由调度器主动解除SIGURG的阻塞。

2. 手动实现原子化的掩码切换与上下文跳转

绕过swapcontext的非原子操作,手动控制信号掩码和上下文切换流程:

  • 在信号处理程序入口,先调用sigprocmask显式阻塞SIGURG(双重保险)。
  • 使用getcontext保存当前线程的上下文。
  • 确保目标上下文的信号掩码包含SIGURG阻塞。
  • 调用setcontext直接切换到目标上下文,避免swapcontext的中间非原子步骤。

注意:setcontext不会保存当前上下文,需提前用getcontext完成保存操作。

3. 将调度逻辑移出信号处理程序

信号处理程序的执行环境受限,尽量减少其中的复杂操作:

  • 在信号处理程序中仅设置一个全局的"需要调度"标志,并用siglongjmp跳转到主线程的调度循环入口。
  • 主线程循环检查该标志,当标志被触发时,再执行协程调度和上下文切换操作。
    这种方式从根本上避免了信号重入风险,代价是调度延迟略高(取决于主线程检查标志的频率)。

核心代码示例

#include <ucontext.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>

ucontext_t main_ctx, coro_ctx;
bool need_schedule = false;

void sigurg_handler(int sig) {
    // 显式阻塞SIGURG,双重保险
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGURG);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    // 保存当前上下文
    getcontext(&main_ctx);

    need_schedule = true;
    // 跳转到主线程调度循环
    siglongjmp(main_ctx.uc_mcontext, 1);
}

void coroutine_func() {
    while (1) {
        // 协程业务逻辑示例
        for (int i = 0; i < 1000000; i++);
        
        // 主动让出CPU时,解除SIGURG阻塞允许下次抢占
        sigset_t mask;
        sigemptyset(&mask);
        sigaddset(&mask, SIGURG);
        sigprocmask(SIG_UNBLOCK, &mask, NULL);

        // 切换回主线程调度
        setcontext(&main_ctx);
    }
}

int main() {
    // 初始化协程上下文,默认阻塞SIGURG
    getcontext(&coro_ctx);
    coro_ctx.uc_stack.ss_sp = malloc(8192);
    coro_ctx.uc_stack.ss_size = 8192;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGURG);
    coro_ctx.uc_sigmask = mask;
    makecontext(&coro_ctx, coroutine_func, 0);

    // 注册SIGURG信号,不使用SA_NODEFER
    struct sigaction sa;
    sa.sa_handler = sigurg_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGURG, &sa, NULL);

    // 主线程调度循环
    while (1) {
        if (sigsetjmp(main_ctx.uc_mcontext, 1) == 1) {
            need_schedule = false;
            // 切换到协程
            setcontext(&coro_ctx);
        }
        // 主线程其他逻辑
    }
}

内容的提问来源于stack exchange,提问作者Markity

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 17:39:24