在futex
Linux 中存在系统调用之前,线程库使用了哪些底层系统调用pthreads
来阻塞/休眠线程并随后从用户空间唤醒这些线程?
例如,如果一个线程试图获取一个互斥锁,用户态实现将阻塞该线程(可能在短暂的旋转间隔之后),但我找不到用于此的系统调用(除了futex
相对较新的创建)。
在futex
Linux 中存在系统调用之前,线程库使用了哪些底层系统调用pthreads
来阻塞/休眠线程并随后从用户空间唤醒这些线程?
例如,如果一个线程试图获取一个互斥锁,用户态实现将阻塞该线程(可能在短暂的旋转间隔之后),但我找不到用于此的系统调用(除了futex
相对较新的创建)。
在 futex 和当前为 Linux 实现 pthreads 之前,NPTL(需要内核 2.6 和更高版本),还有另外两个带有 POSIX Thread API for Linux 的线程库: linuxthreads和 NGPT(基于Gnu Pth。LinuxThreads是唯一被广泛使用的libpthread 多年(它仍然可以在一些奇怪且未维护的 micro-libc 中使用以在 2.4 上工作;其他 micro-libc 变体可能在futex +clone之上具有自己的 pthread 类 API的内置实现)。而 Gnu Pth 是不是线程库,它是具有用户级“线程”切换的单进程线程。
当我们检查内核是否知道部分或全部用户线程时,您应该知道有几个线程模型(向程序中添加线程可以使用多少个 CPU 内核;拥有线程的成本是多少/多少线程可能会启动)。模型被命名为M:N
其中 M 是用户空间线程号,N 是 OS 内核可调度的线程号:
对于 1:1 模型,Unix 中有许多经典的睡眠机制/API,例如选择/轮询和信号以及IPC API 的其他变体。我记得,Linuxthreads为每个线程使用单独的进程(具有完全共享的内存),并且有特殊的管理器“线程”(进程)来模拟一些 POSIX 线程特性。维基百科说 SIGUSR1/SIGUSR2 在 Linuxthreads 中用于线程之间的一些内部通信,IBM 也说“原语的同步是通过信号实现的。例如,线程阻塞直到被信号唤醒。”。检查项目常见问题解答http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html#H.4“使用 LinuxThreads,我不能再在我的程序中使用信号 SIGUSR1 和 SIGUSR2!为什么?”
LinuxThreads 的内部操作需要两个信号。一种用于挂起和重新启动因互斥锁、条件或信号量操作而阻塞的线程。另一个用于线程取消。 在“旧”内核(2.0 和早期 2.1 内核)上,只有 32 个可用信号,内核保留所有信号,但只有两个:SIGUSR1 和 SIGUSR2。因此,LinuxThreads 只能使用这两个信号。
使用“N:1”模型线程可能会调用一些阻塞系统调用并阻塞一切(一些库可能会将一些阻塞系统调用转换为异步,或使用一些SIGALRM 或 SIGVTALRM 魔法);或者它可能会调用一些(非常)特殊的内部线程函数,该函数将通过重写机器状态寄存器来进行用户空间线程切换(如linux内核中的switch_to,保存IP / SP和其他regs,恢复IP / SP和其他线程的regs) . 因此,内核不会直接从用户态唤醒任何用户线程,它只是调度整个进程;和用户空间调度器实现线程同步逻辑(或者sched_yield
在没有线程工作时调用或选择)。
有了M:N
模型,事情就很复杂了……对 NGPT 了解不多……在 POSIX Threads and the Linux Kernel 中有一段关于 NGPT,Dave McCracken,OLS2002,330第 5 页
有一个新的 pthread 库正在开发中,称为 NGPT。这个库基于 GNU Pth 库,它是一个 M:1 库。NGPT 通过使用多个 Linux 任务扩展 Pth,从而创建了一个 M:N 库。它试图保持 Pth 的 pthread 兼容性,同时还使用多个 Linux 任务进行并发,但这种努力受到 Linux 线程模型的根本差异的阻碍。目前 NGPT 库在阻塞系统调用周围使用非阻塞包装器来避免内核中的阻塞。
一些论文和帖子:POSIX Threads and the Linux Kernel, Dave McCracken, OLS2002,330 , LWN post about NPTL 0.1
futex 系统调用广泛用于所有同步原语和其他需要某种同步的地方。futex 机制足够通用,可以毫不费力地支持标准的 POSIX 同步机制。... Futexes 还允许实现进程间同步原语,这是旧 LinuxThreads 实现中严重遗漏的功能(嗨 jbj!)。
5.5 同步原语 同步原语(如互斥锁、读写锁、条件变量、信号量和屏障)的实现需要某种形式的内核支持。忙等待不是一种选择,因为线程可以有不同的优先级(除了浪费 CPU 周期)。相同的论点排除了排他性使用 sched yield。信号是旧实现的唯一可行解决方案。线程将在内核中阻塞,直到被信号唤醒。这种方法在速度和可靠性方面存在严重缺陷,这是由虚假唤醒和应用程序中信号处理质量的降低引起的。幸运的是,内核中添加了一些新功能来实现各种同步原语:futexes [Futex]。基本原理简单但功能强大,足以适应各种用途。调用者可以在内核中阻塞并被显式唤醒,作为中断的结果,或者在超时之后。
Futex 代表“快速用户空间互斥锁”。它只是对互斥体的抽象,被认为比传统的互斥体机制更快、更方便,因为它为您实现了等待系统。在 futex() 之前和之后,线程通过改变它们的进程状态进入睡眠和唤醒。进程状态为:
当一个线程被挂起时,它会进入(可中断的)“睡眠”状态。稍后,它可以通过 wake_up() 函数唤醒,该函数在内核中对其任务结构进行操作。据我所知,wake_up 是一个内核函数,而不是系统调用。内核不需要系统调用来唤醒或休眠任务;它(或流程)只是更改任务结构以反映流程的状态。当 Linux 调度程序接下来处理该进程时,它会根据其状态来处理它(同样,上面列出了这些状态)。
短篇小说:futex() 为您实现了一个等待系统。没有它,您需要一个可从主线程和睡眠线程访问的数据结构,以唤醒睡眠线程。所有这些都是通过用户态代码完成的。您可能需要内核唯一的东西是互斥锁——其细节确实包括锁定机制和互斥锁数据结构,但不会固有地唤醒或休眠线程。您要查找的系统调用不存在。本质上,您所说的大部分内容都可以从用户空间实现,无需系统调用,通过手动跟踪确定是否以及何时休眠或唤醒线程的数据条件。