线程:为啥必须将所有用户线程映射到内核线程?

Posted

技术标签:

【中文标题】线程:为啥必须将所有用户线程映射到内核线程?【英文标题】:Threads: Why must all user threads be mapped to a kernel thread?线程:为什么必须将所有用户线程映射到内核线程? 【发布时间】:2013-01-25 07:45:26 【问题描述】:

所以这里真的有两个问题。首先,(是的,我已经搜索过了,但想要澄清一下),用户线程和内核线程有什么区别?仅仅是一个由用户程序生成,另一个由操作系统生成,后者可以访问特权指令吗?它们在概念上是相同的还是线程本身存在实际差异?

其次,我的问题的真正问题是:我正在使用的书说“用户线程和内核线程之间必须存在关系”,然后列出了这种关系的不同模型。但是该书未能清楚地解释为什么用户线程必须始终映射到特定的内核线程。这是为什么呢?

【问题讨论】:

好问题。而这本书是Abraham Silberschatz 等人的《操作系统概念》。 【参考方案1】:

我认为一个真实世界的例子会消除困惑,所以让我们看看在 Linux 中是如何完成的。

首先Linux不区分进程和线程,可以调度的实体在Linux中称为任务,用task_struct表示。因此,每当您执行fork() 系统调用时,都会创建一个新的task_struct,其中包含与新任务关联的数据(或指针)。

所以在 Linux 世界中,内核线程意味着 task_struct 对象。

因为调度程序只知道可以分配给不同 CPU(逻辑或物理)的这些实体。换句话说,如果您希望 Linux 调度程序安排您的进程,您必须创建一个 task_struct

用户线程是由一些执行环境(从现在开始的EE)(例如JVM)在内核之外支持和管理的东西。这些 EE 将为您提供一些创建新线程的函数。

但为什么用户线程必须始终映射到特定的内核线程。

假设您使用 EE 创建了一些线程。最终它们必须由 CPU 执行,从上面的解释我们知道线程必须有一个 task_struct 才能分配给某个 CPU。这就是映射必须存在的原因。创建task_structs 是您的 EE 的职责。

如果您的 EE 使用多对一模型,那么它将只为所有线程创建一个 task_struct,并将所有这些线程调度到该 task_struct 上。可以将其想象为有一个 CPU (task_struct) 和许多进程(在 EE 中创建的线程),您的操作系统(EE)将在单个 CPU 上多路复用这些进程。

如果它使用一对一模型,那么在 EE 中创建的每个线程都会有一个 task_struct。因此,当您在 EE 中创建新线程时,相应的 task_struct 会在内核中创建。

Windows 做事不同(进程和线程不同),但总体思路保持不变,内核线程是 CPU 调度程序考虑分配的实体,因此用户线程必须映射到相应的内核线程(如果您希望 CPU 执行他们)。

【讨论】:

【参考方案2】:

内核线程是操作系统维护的线程对象。它是一个能够被处理器调度和执行的实际线程。通常,内核线程是具有权限设置、优先级等的重量级对象。内核线程调度程序负责调度内核线程。

用户程序也可以创建自己的线程调度程序。他们可以创建自己的“线程”并模拟上下文切换以在它们之间切换。但是,这些线程不是内核线程。每个用户线程实际上不能独立运行,用户线程运行的唯一方法是内核线程被实际告知执行用户线程中包含的代码。也就是说,用户线程比内核线程有很大的优势。它们可以更轻量级,因为它们不一定需要有自己的优先级,可以由单个进程管理(可能有更好的关于何时需要运行哪些线程的信息),并且不会创建很多出于安全和锁定目的的内核对象。

用户线程必须与内核线程相关联的原因是用户线程本身只是用户程序中的一堆数据。内核线程是系统中的真正线程,因此用户线程要取得进展,用户程序必须让其调度程序获取用户线程,然后在内核线程上运行它。用户线程和内核线程之间的映射不必是一对一的(1:1);您可以让多个用户线程共享同一个内核线程(一次只运行其中一个用户线程),并且您可以拥有一个在 1 : n 映射中跨不同内核线程轮换的用户线程。

【讨论】:

以上是关于线程:为啥必须将所有用户线程映射到内核线程?的主要内容,如果未能解决你的问题,请参考以下文章

JVM:线程的实现

JVM线程与Linux内核线程的映射(关系)

Erlang - 将每个“erlang 进程”映射到新的内核线程

为啥使用任务集在一组独立的内核上运行多线程 Linux 程序会导致所有线程在一个内核上运行?

是否有一个单独的内核级线程来处理用户进程的系统调用?

多线程 - 多线程基础