Linux 上的用户 space 可以实现本机代码的抢占式多任务处理吗?

Can preemptive multitasking of native code be implemented in user space on Linux?

我想知道是否可以在 Linux 上的用户 space 的单个进程中实现本机代码的抢占式多任务处理。 (也就是说,在外部暂停一些 运行 本机代码,保存上下文,在不同的上下文中交换,然后恢复执行,所有这些都由用户 space 精心策划,但使用可能进入内核的调用。)认为这可以使用 SIGALRM*context() 系列的信号处理程序来完成,但事实证明整个 *context() 系列都是 async-signal-unsafe so that approach isn't guaranteed to work. I did find a gist 实现了这个想法,所以很明显确实碰巧在 Linux 上工作,至少有时是这样,即使到 POSIX 它不需要工作。要点将其安装为 SIGALRM 上的信号处理程序,这会进行多次 *context() 调用:

void
timer_interrupt(int j, siginfo_t *si, void *old_context)
{
    /* Create new scheduler context */
    getcontext(&signal_context);
    signal_context.uc_stack.ss_sp = signal_stack;
    signal_context.uc_stack.ss_size = STACKSIZE;
    signal_context.uc_stack.ss_flags = 0;
    sigemptyset(&signal_context.uc_sigmask);
    makecontext(&signal_context, scheduler, 1);

    /* save running thread, jump to scheduler */
    swapcontext(cur_context,&signal_context);
}

Linux 是否提供任何保证使此方法正确?有没有办法使这个正确?有没有完全不同的方法可以正确地做到这一点?

("implement in user space"我不是说我们永远不进入内核,我的意思是对比内核实现的抢占式多任务。)

您无法可靠地更改信号处理程序中的上下文。 (如果您从某个信号处理程序执行此操作,它通常 通常 在实践中工作,但并非总是如此,因此它是 undefined behavior)。

你可以设置一些 volatile sig_atomic_t 标志(阅读 sig_atomic_t) in a signal handler (see signal(7), signal-safety(7), sigreturn(2) ...) and check that flag regularly (e.g. at least once every few milliseconds) in your code, for example before most calls, or inside your event loop 如果你有,等等......所以它变成 cooperative user-land scheduling .

如果您可以更改代码,则更容易做到,例如当您设计一些发出 C 代码(common practice)的编译器时,或者如果您破解您的 C 编译器以发出此类测试。然后您将更改您的代码生成器,有时会在生成的代码中发出这样的测试。

您可能想要禁止阻塞系统调用,并将它们替换为non-blocking variants or wrappers. See also poll(2), fcntl(2)F_SETFLO_NONBLOCK等...

您可能希望代码生成器避免大的调用堆栈,例如就像 GCC 中的 -fsplit-stack instrumentation option does (read about splitstacks

如果你生成(或编写一些)汇编程序,你可以使用这样的技巧。据我所知,Go 编译器为其 goroutines 使用了类似的东西。研究你的 ABI, e.g. from here.

但是,内核启动的抢占式调度更可取(在 Linux 上仍会在进程或内核任务之间发生,请参阅 clone(2))。

PS。如果您对使用类似技巧的垃圾收集技术感兴趣,请查看 MPS and Cheney on the MTA (e.g. into Chicken Scheme).