9

我想知道是否可以在 Linux 用户空间的单个进程中实现本机代码的抢先式多任务处理。(也就是说,在外部暂停一些正在运行的本机代码,保存上下文,在不同的上下文中交换,然后恢复执行,所有这些都由用户空间编排,但使用可能进入内核的调用。)我认为这可以使用信号来完成SIGALRM和家庭的处理程序,*context()但事实证明整个*context()家庭都是异步信号不安全的,因此不能保证这种方法有效。我确实找到了一个实现这个想法的要点,所以显然它确实可以在 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 是否提供任何保证使这种方法正确?有没有办法让这个正确?有完全不同的方法可以正确地做到这一点吗?

(通过“在用户空间实现”我并不是说我们永远不会进入内核。我的意思是与内核实现的抢占式多任务对比。)

4

1 回答 1

4

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

您可以在信号处理程序(参见signal(7)signal-safety(7)sigreturn(2) ...)中设置一些volatile sig_atomic_t标志(请阅读)并定期检查该标志(例如,至少每隔几毫秒一次)你的代码,例如在大多数调用之前,或者如果你有一个,或者在你的事件循环中,等等......所以它变成了协作的用户土地调度。sig_atomic_t

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

您可能希望禁止阻塞系统调用并用非阻塞变体或包装器替换它们。另请参阅poll(2)fcntl(2)F_SETFLandO_NONBLOCK等...

您可能希望代码生成器避免大的调用堆栈,例如像 GCC 的-fsplit-stack 检测选项那样(阅读GCC 中的splitstacks)。

如果您生成(或编写一些)汇编程序,您可以使用这些技巧。AFAIK Go 编译器对其 goroutine 使用类似的东西。研究您的ABI,例如从这里开始

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

PS。如果您对使用类似技巧的垃圾收集技术感兴趣,请查看MTA 上的MPSCheney(例如Chicken Scheme)。

于 2018-01-07T20:20:28.623 回答