📜  用户级线程与内核级线程的关系

📅  最后修改于: 2021-09-28 09:58:07             🧑  作者: Mango

一个任务是在一个程序的执行上完成的,这导致了一个进程。每个任务都包含一个或多个子任务,而这些子任务由线程作为程序内的功能执行。操作系统(内核)不知道用户空间中的线程。

有两种类型的线程,用户级线程 (ULT) 和内核级线程 (KLT)。

  1. 用户级线程:
    应用程序开发人员在用户空间设计的线程使用线程库来执行唯一的子任务。
  2. 内核级线程:
    操作系统开发人员设计的内核空间中的线程,用于执行操作系统的独特功能。类似于中断处理程序。

用户级线程和内核级线程之间存在很强的关系。

ULT 和 KLT 之间的依赖关系:

  1. 线程库的使用:
    线程库充当应用程序开发人员创建线程数(根据子任务数)并管理这些线程的接口。进程的这个 API 可以在内核空间或用户空间中实现。在实时应用中,必要的线程库是在用户空间实现的。每当应用程序需要线程创建、调度或线程管理活动时,这就减少了对内核的系统调用。因此,线程创建速度更快,因为它只需要进程内的函数调用。每个线程的用户地址空间在运行时分配。总的来说,它减少了各种接口和架构开销,因为所有这些功能都独立于内核支持。
  2. 同步:
    每个任务(进程)内的子任务(功能)可以根据应用程序并发或并行执行。在那种情况下,单线程进程是不合适的。那里唤起了多线程进程。一个唯一的子任务被分配给进程内的每个线程。这些线程可能使用相同的数据段或不同的数据段。通常,同一进程内的线程将共享代码段、数据段、地址空间、打开的文件等。

当子任务通过共享代码段并发执行时,可能会导致数据不一致。最终,需要合适的同步技术来维护访问共享数据(临界区)的控制流。

在多线程进程中,同步采用四种不同的模型:

  1. 互斥锁——一次只允许一个线程访问共享资源。
  2. 读/写锁——这允许共享资源的独占写入和并发读取。
  3. 计数信号量——这个计数是指一次可以同时访问的共享资源的数量。一旦达到计数限制,剩余的线程就会被阻塞。
  4. 条件变量——这会阻塞线程直到条件满足(忙等待)。
    所有这些同步模型都使用线程库在每个进程中执行。锁定变量的内存空间分配在用户地址空间中。因此,不需要内核干预。

1. 时间安排:
应用程序开发人员在线程创建期间使用线程库设置每个 ULT 线程的优先级和调度策略。在程序执行时,基于定义的属性由线程库进行调度。在这种情况下,系统调度程序无法控制线程调度,因为内核不知道 ULT 线程。

2. 上下文切换:
在同一进程内从一个 ULT 线程切换到另一个 ULT 线程更快,因为每个线程都有自己独特的线程控制块、寄存器、堆栈。因此,寄存器被保存和恢复。不需要对地址空间进行任何更改。整个切换发生在线程库控制下的用户地址空间内。

3. 异步输入/输出:
在 I/O 请求之后,ULT 线程保持阻塞状态,直到它收到来自接收方的确认(ack)。尽管它遵循异步 I/O,但它为应用程序用户创建了一个同步环境。这是因为线程库本身会调度另一个 ULT 来执行,直到被阻塞的线程将sigpoll作为 ack 发送到进程线程库。只有线程库,重新调度被阻塞的线程。

例如,考虑一个程序从一个文件复制(读取)内容并在另一个文件中粘贴(写入)。此外,还有一个显示进度完成百分比的弹出窗口。

这个过程包含三个子任务,每个子任务分配给一个 ULT,

  • 线程 A –从源文件中读取内容。存储在进程地址空间内的全局变量 X 中。
  • 线程 B –读取全局变量 X。写入目标文件。
  • 线程 C –以图形表示方式显示完成的进度百分比。

在这里,应用程序开发人员将使用线程库在程序内调度多个控制流。

执行顺序:从线程A开始,然后是线程B,然后是线程C。
线程A和线程B共享全局变量X,只有当线程A写到X之后,线程B才能读取X。这种情况下,需要对共享变量进行同步,避免线程B读取旧数据。 上下文切换从线程 A 到线程 B 再到线程 C 发生在进程地址空间内。每个线程在其自己的线程控制块 (TCB) 中保存和恢复寄存器。线程 C 保持阻塞状态,直到线程 B 开始其对目标文件的第一次写操作。这就是背后的原因,尽管过程已完成,但几秒钟后会弹出 100% 的图形指示。

ULT 和 KLT 之间的依赖关系:
当 ULT 需要内核资源时,就会出现 KLT 和 ULT 之间唯一的主要依赖项。每个 ULT 线程都与一个称为轻量级进程的虚拟处理器相关联。这是由线程库根据应用程序需要创建并绑定到 ULT。每当调用系统调用时,系统调度程序都会创建一个内核级线程并将其调度到 LWP。这些 KLT 被不知道 ULT 的系统调度程序调度为访问内核资源。而 KLT 通过 LWP 了解与其关联的每个 ULT。

如果关系不存在怎么办?
如果 KLT 和 ULT 之间没有关联,那么根据内核,每个进程都是单线程进程。在这种情况下,

  1. 系统调度器可以调度具有较低优先级或空闲线程的线程的进程。这会导致高优先级线程的饥饿,进而降低系统的效率。
  2. 当单个线程被阻塞时,整个进程都会被阻塞。那么即使在多核系统中,CPU 利用率也会变得更低。尽管可能存在可执行线程,但内核将每个进程视为一个单线程进程,一次只分配一个内核。
  3. 系统调度程序可以提供单个时间片,而不管进程内的线程数如何。一个单线程进程和一个有 1000 个线程的进程提供相同的时间片会使系统效率低下。