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_SETFL
和O_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).
我想知道是否可以在 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_SETFL
和O_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).