【问题标题】:How are threads/processes parked and woken in Linux, prior to futex?在 futex 之前,如何在 Linux 中停止和唤醒线程/进程?
【发布时间】:2018-01-27 14:18:56
【问题描述】:

在 Linux 中存在 futex 系统调用之前,pthreads 等线程库使用哪些底层系统调用来阻塞/休眠线程并随后从用户空间唤醒这些线程?

例如,如果一个线程试图获取一个互斥锁,用户态实现将阻塞该线程(可能在短暂的旋转间隔之后),但我找不到用于此的系统调用(futex 除外这是一个相对较新的创作)。

【问题讨论】:

    标签: linux multithreading futex


    【解决方案1】:

    Futex 代表“快速用户空间互斥锁”。它只是对互斥体的抽象,被认为比传统的互斥体机制更快、更方便,因为它为您实现了等待系统。在 futex() 之前和之后,线程通过改变它们的进程状态进入睡眠和唤醒。进程状态是:

    • 运行状态
    • 睡眠状态
    • 不可中断的睡眠状态(即阻塞诸如 read() 或 write() 之类的系统调用
    • 已失效/僵尸状态

    当一个线程被挂起时,它会进入(可中断的)“睡眠”状态。稍后,它可以通过 wake_up() 函数唤醒,该函数在内核中对其任务结构进行操作。据我所知,wake_up 是一个内核函数,而不是系统调用。内核不需要系统调用来唤醒或休眠任务;它(或流程)只是更改任务结构以反映流程的状态。当 Linux 调度程序接下来处理该进程时,它会根据其状态来处理它(同样,上面列出了这些状态)。

    简短的故事:futex() 为您实现了一个等待系统。没有它,您需要一个可从主线程和睡眠线程访问的数据结构,以唤醒睡眠线程。所有这些都是通过用户态代码完成的。您可能需要内核唯一的东西是互斥锁 - 其细节确实包括锁定机制和互斥锁数据结构,但不会固有地唤醒或休眠线程。您要查找的系统调用不存在。本质上,您所说的大部分内容都可以从用户空间实现,无需系统调用,通过手动跟踪确定是否以及何时休眠或唤醒线程的数据条件。

    【讨论】:

    • 而对于 pthread_* 函数,它们在很大程度上也是在不依赖很多系统调用的情况下实现的。另请参阅this 答案。
    • 他们必须依赖系统调用来阻塞和唤醒线程。如果没有内核和调度程序的帮助,用户空间就无法做到这一点。我想问在futex() 被引入之前这是怎么发生的。
    • 哦,还有this一个
    • 内核已经有某种互斥体很长时间了。其中大部分是在用户空间中完成的。
    • 在唤醒和睡眠方面,它是“只有在检测到争用时才会发生系统调用(称为 futex)并且上下文切换到内核中,才会使调用进程进入睡眠状态,直到互斥锁被释放。 "
    【解决方案2】:

    在 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" ''内核级线程'' - 每个用户空间线程都可由操作系统内核调度。这是在 Linuxthreads、NPTL 和许多现代操作系统中实现的。
    • "N:1" ''user-level threading'' - 用户空间线程由用户空间规划,它们对内核都是不可见的,它只调度一个进程(它可能只使用1个CPU内核)。 Gnu Pth (GNU Portable Threads) 就是一个例子,对于一些计算机架构还有很多其他的实现。
    • "M:N" ''hybrid threading'' - 有一些实体可见并可被操作系统内核调度,但其中可能有更多的用户空间线程。有时用户空间线程会在内核可见线程之间迁移。

    对于 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,330LWN post about NPTL 0.1

    futex 系统调用广泛用于所有同步 原语和其他需要某种东西的地方 同步。 futex 机制足够通用,可以支持 标准 POSIX 同步机制很少 努力。 ... Futexes 还允许进程间的实现 同步原语,旧版本中非常遗漏的功能 LinuxThreads 实现(嗨 jbj!)。

    NPTL design pdf:

    5.5 同步原语 同步原语的实现,例如互斥锁、读写 锁、条件变量、信号量和屏障需要某种形式的内核 支持。忙等待不是一种选择,因为线程可以有不同的优先级(除了浪费 CPU 周期)。相同的论点排除了排他性使用 sched yield。 信号是旧实现的唯一可行解决方案。线程会在内核中阻塞,直到被信号唤醒。这种方法在速度和可靠性方面存在严重缺陷,这是由虚假唤醒和应用程序中信号处理质量的降低引起的。 幸运的是,内核中添加了一些新功能来实现各种 同步原语:futexes [Futex]。基本原理很简单,但 强大到足以适应各种用途。调用者可以在内核中阻塞 并在中断或超时后显式唤醒。

    【讨论】:

    • 我几乎找不到更好的答案了。
    • 您好,请查看 Xi Yang 2015 的这篇论文“Computer Performance Microscopy with SHIM”(github.com/ShimProfiler/SHIMmicrosoft.com/en-us/research/publication/…),它可能会给您下一个问题的一些想法 45556708(想法:一些PMU 计数器对于超线程是私有的,而其他计数器是为两个超线程共享的。通过添加第二个超线程,我们可以对第一个超线程进行分析:“一个连续分析器,它以 15 个周期的分辨率进行采样;......以 61% 的开销”)
    • 我已经阅读了这篇论文。他们对我正在考虑的方法采取了相反的方法——他们实际上是从助手中对主线程进行采样,而我希望主线程在助手增加的计数器的帮助下记录“准确”的跟踪。问题似乎是内存推测失败,以这种方式跨超线程紧密共享计数器。
    猜你喜欢
    • 1970-01-01
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 2015-07-31
    • 1970-01-01
    • 2011-08-05
    相关资源
    最近更新 更多