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

在Linux中存在futex系统调用之前,像pthreads这样的线程库使用底层的系统调用来阻塞/hibernate一个线程,并随后将这些线程从userland中唤醒?

例如,如果一个线程试图获得一个互斥体,那么这个用户空间的实现将阻塞这个线程(也许在一个短暂的空转间隔之后),但是我找不到用于这个的系统调用(除了futex ,创build)。

Solutions Collecting From Web of "在futex之前,线程/进程如何在Linux中停留和唤醒?"

在futex和当前pthreads for Linux的实现之前,NPTL(需要内核2.6和更新版本),还有另外两个线程库,包括Linux线程API: linuxthreads和NGPT( 基于 Gnu Pth。LinuxThreads是唯一被广泛使用的libpthread好几年了(它仍然可以用在一些奇怪的和没有维护的micro-libc上工作在2.4上 ;其他的micro-libc变种可能有自己内建的pthread-like API在futex + clone上实现),Gnu Pth不是线程库,它是单用户级“线程”切换的进程线程。

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

  • “1:1”“内核级线程” – 每个用户空间线程都可由OS内核进行调度。 这是在Linuxthreads,NPTL和许多现代操作系统中实现的。
  • “N:1”用户级线程“ – 用户空间线程是由用户空间计划的,它们全部对内核是不可见的,它只调度一个进程(可能只使用一个CPU内核)。 Gnu Pth( GNU可移植线程 )就是这样的例子,对于一些计算机体系结构还有许多其他的实现。
  • “M:N”“混合线程” – 有些实体可以被OS内核看到和调度,但是可能有更多的用户空间线程。 有时用户空间线程将在内核可见线程之间迁移。

在1:1模式下,Unix中有许多经典的睡眠机制/ API,如选择/轮询和信号以及IPC API的其他变体。 我记得, Linuxthreads为每个线程(具有完全共享的内存)使用不同的进程,并且有特殊的管理器“线程”(进程)来模拟一些POSIX线程特性。 维基百科说SIGUSR1 / SIGUSR2被用在Linuxthreads中用于线程之间的一些内部通信,同样的说IBM “原语的同步是通过信号来实现的,例如,线程阻塞,直到被信号唤醒。 检查项目FAQ 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和其他寄存器,恢复其他线程的IP / SP和寄存器)进行用户空间线程切换。 。 所以内核不直接唤醒用户线程,只是调度整个进程; 和用户空间调度器实现线程同步逻辑(或者只是调用sched_yield或者选择什么时候没有线程工作)。

M:N模型非常复杂…对NGPT不太了解…在POSIX线程和Linux内核中有一段关于NGPT的文章,Dave McCracken,OLS2002,330页5

正在开发的新的pthread库叫做NGPT。 这个库基于GNU Pth库,它是一个M:1库。 NGPT通过使用多个Linux任务来扩展Pth,从而创建一个M:N库。 它试图保持Pth的pthread兼容性,同时也使用多个Linux任务进行并发,但这种努力受到了Linux线程模型的根本差异的阻碍。 NGPT库目前使用非阻塞封装来阻塞系统调用,以避免内核阻塞。

一些论文和帖子: POSIX Threads和Linux coreel,Dave McCracken,OLS2002,330 , LWN post关于NPTL 0.1

futex系统调用广泛用于所有同步原语和其他需要某种同步的地方。 futex机制通用性足以支持标准的POSIX同步机制。 …互斥体还允许执行进程间同步原语,这是旧的LinuxThreads实现(Hi jbj!)中一个非常遗漏的特性。

NPTL设计pdf :

5.5同步原语同步原语(如互斥锁,读写锁,条件变量,信号量和障碍)的实现需要某种形式的内核支持。 忙于等待不是一个选项,因为线程可以有不同的优先级(除了浪费CPU周期)。 同样的说法排除了排污量的排他性。 信号是旧实施的唯一可行解决方案。 线程会在内核中被阻塞,直到被一个信号唤醒。 这种方法在由虚假唤醒引起的速度和可靠性方面具有严重的缺点,并且在应用中信号处理的质量受到损害。 幸运的是,一些新的功能被添加到内核来实现各种同步原语:futexes [Futex]。 基本的原则很简单,但是足够强大以适应各种用途。 调用者可以在内核中进行阻塞,并可以通过中断或超时之后明确唤醒。

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

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

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

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