线程设计考虑

Two possibilities exist: either we are alone in the Universe or we are not. Both are equally terrifying.

本文为Udacity Introduction to Operation System 课程9总结

线程描述符

概述

我们知道线程可以分为内核和用户空间的线程。对于支持多线程的操作系统来说,它起着线程抽象、调度以及同步等功能。而对于用户态的线程,需要有特定的库执行上述任务。在本节中,我们来看一看为了支持线程的工作,我们需要哪些数据对其进行描述。

注:在讨论线程描述之前,我们先要了解linux是如何支持线程的,实际上,linux下是没有线程的概念的,虽然有着线程库,但是在内核中并没有真正的对线程的支持,而是使用进程实现线程的功能。进程是进行调度的最小单位,而线程是一个与其他进程共享某些资源的进程,每一个线程拥有一个唯一的task_struct进行描述。

单CPU多进程

在只有一个CPU的情况下,用户态线程和内核线程可以是一对一、多对一或多对多的映射,此时我们的用户层面的线程(User-Level Thread, ULT)需要有如下的信息:

  • UL thread ID
  • UL thread 寄存器
  • 线程栈

而内核层面的线程(Kernel-Level Thread, KLT)包含如下信息:

  • 栈指针
  • 寄存器指针

进程描述符则包含虚拟内存的映射。当我们有多个进程在运行时,此时ULT、KLT和PCB会构成一组数据。

多CPU多进程

在这种情况下,我们还需要一个数据结构描述CPU,通过一个指向当前CPU上运行的线程的指针,我们可以在CPU、KLT、PCB和ULT之间建立关联。

PCB改进

单个PCB的缺点

在上文中我们强调了Linux没有线程的概念,每一个线程实际上都是一个轻量化的进程,那么自然而然的我们会考虑到用PCB来描述线程,但是这引出了一个问题:如果我们仅仅只用一个PCB描述线程,那么这个数据结构会有下面的问题:

  • 这个数据结构会很大,包含了许多连续的数据(规模大)
  • 每个线程的PCB应当是私有的(开销大)
  • 线程切换时需要保护和恢复现场(性能问题)
  • 每一次更新状态需要发生许多改变(灵活性不足)

将PCB拆分为多个数据结构

针对单一PCB的缺点,我们将PCB拆分为多个数据结构,这样有如下好处:

  • 每个数据结构规模更小(规模小)
  • 更容易分享(开销小)
  • 线程切换时所需要保护的数据更少(性能好)
  • 只需要改变一部分状态(灵活)

Linux下内核线程结构

在linux内核中,其线程数据结构为kthread_worker,定义如下:

1
2
3
4
5
6
struct kthread_worker {
spinlock_t lock;
struct list_head work_list;
struct task_struct *task;
struct kthread_work *current_work;
};

而用于描述具体任务的是task_struct结构。

SunOS 下的线程描述设计*1

SunOs中的线程和pthread库中的设计比较类似,当创建一个线程后,会返回一个线程ID(tid),这个index指向一个指针表,而这个指针表指向每一个线程具体的数据结构。这个设计结构如下:

图片名称

其中,thread local storage保存了线程函数中在编译期间就获知的局部变量。而stack的空间可能由库或用户决定。通过数据结构的大小作为偏移量,即可寻找到相邻的thread,这里的一个问题是stack的增长可能是很危险的,如果栈过长,可能导致下一个线程中的数据被覆盖,引起莫名其妙的问题。在文章中,定义了一个red zone来解决这个问题,一旦栈增长到了红色区域内,就会引发一个错误。

参考文献

0%