在futex之前,线程/进程如何在Linux中停放和唤醒?

时间:2017-08-18 20:10:32

标签: linux multithreading futex

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

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

2 个答案:

答案 0 :(得分:3)

在futex和当前实现的pthreads for Linux,NPTL(需要内核2.6和更新版本)之前,还有另外两个带有适用于Linux的POSIX Thread API的线程库:linuxthreads和NGPT(was based on Gnu Pth .LinuxThreads是多年来唯一广泛使用的libpthread(它可以still用于某些奇怪的和未维护的micro-libc到work on 2.4;其他micro-libc变种可能有在builtin + clone之上自己futex实现了类似pthread的API。而且Gnu Pth不是线程库,它是单进程线程,用户级"线程"切换

当我们检查内核是否知道部分或全部用户线程时,你应该知道有几个Threading Models(通过向程序添加线程可以使用多少CPU内核;拥有成本的成本是多少线程/可以启动多少个线程)。模型命名为M:N,其中M是用户空间线程编号,N是OS内核可调度的线程编号:

  • " 1:1" ''内核级线程'' - 每个用户空间线程都可由OS内核调度。这是在Linuxthreads,NPTL和许多现代操作系统中实现的。
  • " N:1" ''用户级线程'' - 用户空间线程由用户空间规划,它们对内核都是不可见的,它只调度一个进程(并且它可能只使用1个CPU核心)。 Gnu Pth(GNU Portable Threads)就是它的例子,并且对于某些计算机体系结构还有许多其他实现。
  • " M:N" ''混合线程'' - OS内核有一些实体可见且可调度,但其中可能有更多的用户空间线程。有时,用户空间线程将在内核可见线程之间进行迁移。

使用1:1模型,Unix中有许多经典的睡眠机制/ API,如select / poll和信号以及IPC API的其他变体。我记得,Linuxthreads为每个线程使用了单独的进程(具有完全共享的内存),并且有特殊的经理"线程" (进程)模拟一些POSIX线程功能。 Wikipedia表示SIGUSR1 / SIGUSR2在Linuxthreads中用于线程之间的一些内部通信,相同says IBM"基元的同步是通过信号实现的。例如,线程阻塞直到被信号唤醒。"。还要检查项目FAQ http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html#H.4"使用LinuxThreads,我不能再在程序中使用SIGUSR1和SIGUSR2信号了!为什么"

  

LinuxThreads需要两个信号用于其内部操作。 一个用于挂起和重新启动在互斥,条件或信号量操作上阻塞的线程。另一个用于线程取消。   关于``old''内核(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的段落

  

正在开发一个名为NGPT的新pthread库。该库基于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]。基本原则很简单但是   功能强大,适用于各种用途。调用者可以在内核中阻塞   并且由于中断或超时后明确地被唤醒。

答案 1 :(得分:-2)

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

  • 正在运行状态
  • 睡眠状态
  • 不可中断的睡眠状态(即阻塞系统调用,如read()或write()
  • 已解散/僵尸状态

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

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