【发布时间】:2018-01-27 14:18:56
【问题描述】:
在 Linux 中存在 futex 系统调用之前,pthreads 等线程库使用哪些底层系统调用来阻塞/休眠线程并随后从用户空间唤醒这些线程?
例如,如果一个线程试图获取一个互斥锁,用户态实现将阻塞该线程(可能在短暂的旋转间隔之后),但我找不到用于此的系统调用(futex 除外这是一个相对较新的创作)。
【问题讨论】:
标签: linux multithreading futex
在 Linux 中存在 futex 系统调用之前,pthreads 等线程库使用哪些底层系统调用来阻塞/休眠线程并随后从用户空间唤醒这些线程?
例如,如果一个线程试图获取一个互斥锁,用户态实现将阻塞该线程(可能在短暂的旋转间隔之后),但我找不到用于此的系统调用(futex 除外这是一个相对较新的创作)。
【问题讨论】:
标签: linux multithreading futex
Futex 代表“快速用户空间互斥锁”。它只是对互斥体的抽象,被认为比传统的互斥体机制更快、更方便,因为它为您实现了等待系统。在 futex() 之前和之后,线程通过改变它们的进程状态进入睡眠和唤醒。进程状态是:
当一个线程被挂起时,它会进入(可中断的)“睡眠”状态。稍后,它可以通过 wake_up() 函数唤醒,该函数在内核中对其任务结构进行操作。据我所知,wake_up 是一个内核函数,而不是系统调用。内核不需要系统调用来唤醒或休眠任务;它(或流程)只是更改任务结构以反映流程的状态。当 Linux 调度程序接下来处理该进程时,它会根据其状态来处理它(同样,上面列出了这些状态)。
简短的故事:futex() 为您实现了一个等待系统。没有它,您需要一个可从主线程和睡眠线程访问的数据结构,以唤醒睡眠线程。所有这些都是通过用户态代码完成的。您可能需要内核唯一的东西是互斥锁 - 其细节确实包括锁定机制和互斥锁数据结构,但不会固有地唤醒或休眠线程。您要查找的系统调用不存在。本质上,您所说的大部分内容都可以从用户空间实现,无需系统调用,通过手动跟踪确定是否以及何时休眠或唤醒线程的数据条件。
在 futex 和当前为 Linux 实现 pthreads 之前,NPTL(需要内核 2.6 和更高版本),还有另外两个带有 POSIX Thread API for Linux 的线程库:linuxthreads 和 NGPT(was based on Gnu Pth . LinuxThreads 是多年来唯一广泛使用的 libpthread(它可以still 用于一些奇怪且未维护的 micro-libc 到 work on 2.4;其他 micro-libc 变体可能有自己的 builtin 实现类似 pthread 的 API futex+clone 的顶部)。而且 Gnu Pth 不是线程库,它是具有用户级“线程”切换的单进程线程。
当我们检查内核是否知道部分或全部用户线程时,您应该知道有几个Threading Models(向程序中添加线程可以使用多少个CPU内核;拥有线程的成本是多少? / 可以启动多少个线程)。模型命名为M:N,其中 M 是用户空间线程号,N 是 OS 内核可调度的线程号:
对于 1:1 模型,Unix 中有许多经典的睡眠机制/API,例如选择/轮询和信号以及IPC API 的其他变体。我记得,Linuxthreads 为每个线程使用单独的进程(具有完全共享的内存),并且有特殊的管理器“线程”(进程)来模拟一些 POSIX 线程特性。 Wikipedia 表示 SIGUSR1/SIGUSR2 在 Linuxthreads 中用于线程之间的一些内部通信,同样says 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 or SIGVTALRM magic);或者它可能会调用一些(非常)特殊的内部线程函数,该函数将通过重写机器状态寄存器来进行用户空间线程切换(如linux内核中的switch_to,保存IP / SP和其他regs,恢复IP / SP和其他线程的regs) .因此,内核不会直接从用户态唤醒任何用户线程,它只是调度整个进程;和用户空间调度器实现线程同步逻辑(或者只是调用sched_yield或者在没有线程工作时选择)。
用M:N模型事情很复杂...不太了解NGPT...在POSIX Threads and the Linux Kernel, Dave McCracken, OLS2002,330第5页有一段关于NGPT
有一个新的 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]。基本原理很简单,但 强大到足以适应各种用途。调用者可以在内核中阻塞 并在中断或超时后显式唤醒。
【讨论】: