Operating system 内核和用户线程之间的关系

Operating system 内核和用户线程之间的关系,operating-system,kernel,Operating System,Kernel,内核和用户线程之间是否存在关系 一些操作系统教科书说“将一(多)个用户线程映射到一(多)个内核线程”。map在这里是什么意思?当他们说map时,他们的意思是每个内核线程被分配给一定数量的用户模式线程 内核线程用于向应用程序提供特权服务(如系统调用)。内核还使用它们来跟踪系统上运行的所有内容、分配给哪个进程的资源量,并对它们进行调度 如果您的应用程序大量使用系统调用,那么每个内核线程会有更多的用户线程,并且您的应用程序会运行得更慢。这是因为内核线程将成为瓶颈,因为所有系统调用都将通过它 但另一方面

内核和用户线程之间是否存在关系


一些操作系统教科书说“将一(多)个用户线程映射到一(多)个内核线程”。map在这里是什么意思?

当他们说map时,他们的意思是每个内核线程被分配给一定数量的用户模式线程

内核线程用于向应用程序提供特权服务(如系统调用)。内核还使用它们来跟踪系统上运行的所有内容、分配给哪个进程的资源量,并对它们进行调度

如果您的应用程序大量使用系统调用,那么每个内核线程会有更多的用户线程,并且您的应用程序会运行得更慢。这是因为内核线程将成为瓶颈,因为所有系统调用都将通过它

但另一方面,如果您的程序很少使用系统调用(或其他内核服务),则可以将大量用户线程分配给内核线程,而不会造成除开销以外的性能损失

您可以增加内核线程的数量,但这通常会增加内核的开销,因此,虽然单个线程对系统调用的响应会更好,但整个系统会变得更慢


这就是为什么在内核线程数量和每个内核线程的用户线程数量之间找到一个良好的平衡非常重要。

用户线程是在用户空间中管理的-这意味着调度、切换等不是来自内核

因为,最终,操作系统内核负责“执行单元”之间的上下文切换-您的用户线程必须关联(即,“映射”)到内核可调度对象-内核线程†1

因此,给定N个用户线程,您可以使用N个内核线程(1:1映射)。这使您能够利用内核的硬件多处理(在多个CPU上运行)并成为一个非常简单的库—基本上只是将大部分工作推迟到内核。但是,它确实可以使你的应用程序在操作系统之间可移植,因为你不直接调用内核线程函数。我相信POSIX线程()是首选的*nix实现,它遵循1:1映射(使它实际上相当于内核线程)。然而,这并不能保证,因为它依赖于实现(使用PThreads的一个主要原因是内核之间的可移植性)

或者,您只能使用1个内核线程。这将允许您在非多任务操作系统上运行,或者完全负责调度。Windows’就是这个N:1映射的一个例子

或者,您可以映射到任意数量的内核线程—一个N:M映射。Windows有,这将允许您将N个光纤映射到M个内核线程,并协同调度它们。线程池也可以是M线程的-N工作项的一个示例

†1:一个进程至少有一个内核线程,这是实际的执行单元。此外,进程中必须包含内核线程。操作系统必须安排线程运行,而不是进程运行。

在用户空间中实现线程

实现线程包有两种主要方式:在用户空间和内核中。这个选择有一定的争议,混合实现也是可能的。现在我们将描述这些方法,以及它们的优缺点

第一种方法是将threads包完全放在用户空间中。内核对它们一无所知。就内核而言,它是在管理普通的单线程进程。第一个也是最明显的优点是,用户级线程包可以在不支持线程的操作系统上实现。所有的操作系统过去都属于这一类,甚至现在仍有一些属于这一类

所有这些实现具有相同的总体结构,如图2-8(a)所示。线程在运行时系统上运行,运行时系统是管理线程的过程的集合。我们已经看到了其中的四个:线程创建、线程退出、线程等待和线程产出,但通常还有更多

在用户空间中管理线程时,每个进程都需要自己的私有线程表来跟踪该进程中的线程。该表类似于内核的进程表,只是它只跟踪每个线程的属性,如每个线程的程序计数器、堆栈指针、寄存器、状态等。线程表由运行时系统管理。当线程移动到就绪状态或阻塞状态时,重新启动它所需的信息存储在线程表中,与内核在进程表中存储进程信息的方式完全相同

当一个线程做了一些可能导致它在本地阻塞的事情时,例如,等待其进程中的另一个线程完成某些工作,它将调用运行时系统过程。此过程检查线程是否必须处于阻塞状态。如果是这样,它将线程的寄存器(即它自己的)存储在线程表中,在表中查找准备运行的线程,并用新线程保存的值重新加载机器寄存器。一旦堆栈指针和程序计数器被切换,新线程就会自动重新启动。如果机器有一条指令存储所有寄存器,另一条指令加载所有寄存器,那么整个线程切换可以在少量指令中完成。这样做线程切换至少比捕获到内核快一个数量级,这是支持用户级线程包的有力论据

然而,流程有一个关键区别。当一个线程完成运行时,例如,当它调用thread\u yield时,thread\u yield的代码可以将该线程的信息保存在thread表本身中。此外,它可以这样做