假设您没有在 Linux 上使用 C 运行时,而是针对其系统调用 API 进行编程。毕竟,它是长期稳定的。内存管理和缓冲 I/O很容易解决,但很多软件都受益于并发性。如果还能具有线程生成功能就好了。本文将演示一种简单、实用且可靠的方法,仅使用原始系统调用来生成和管理线程。它只需要大约十几行 C 代码,包括一些内联汇编指令。
问题是无法避免使用一些汇编。clone和clone3系统调用都没有与 C 兼容的线程语义,因此您需要使用每个体系结构的一些内联汇编来掩盖它。本文将重点介绍 x86-64,但基本概念应该适用于 Linux 支持的所有体系结构。glibcclone(2) 包装器在原始系统调用之上安装了一个与 C 兼容的接口,但我们不会在这里使用它。
在深入研究之前,先来看一下完整的、可运行的演示:stack_head.c
克隆系统调用
在 Linux 上,线程是使用clone具有类似经典 unix 语义的系统调用生成的fork(2)。一个进程进入,两个进程以几乎相同的状态退出。对于线程,这些进程几乎共享所有内容,并且仅在两个寄存器上有所不同:返回值(新线程中的零)和堆栈指针。与典型的线程生成 API 不同,应用程序不提供入口点。它仅为新线程提供堆栈。原始克隆 API 的简单形式如下所示:
long clone(long flags, void *stack);
听起来很优雅,但它有一个恼人的问题:新线程在函数中间开始运行,没有任何已建立的堆栈框架。它的堆栈是一片空白。除了跳转到将设置堆栈框架的函数序言外,它还无法执行任何操作。因此,除了系统调用本身的汇编之外,它还需要更多的汇编来使线程进入与 C 兼容的状态。换句话说,通用系统调用包装器无法可靠地生成线程。
void brokenclone(void (*threadentry)(void *), void *arg)
{
// ...
long r = syscall(SYS_clone, flags, stack);
// DANGER: new thread may access non-existant stack frame here
if (!r) {
threadentry(arg);
}
}
由于奇怪的历史原因,每个架构的clone接口略有不同。较新的版本clone3统一了这些差异,但它也存在上述相同的线程生成问题,因此在这里没有帮助。

最低0.47元/天 解锁文章
4407

被折叠的 条评论
为什么被折叠?



