2.2.5 在内核中实现线程

现在我们研究内核了解和管理线程的情形。如图2-16b所示,此时不再需要运行时系统了。另外,每个进程中也没有线程表。相反,在内核中有用来记录系统中所有线程的线程表。当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新完成线程创建或撤销工作。

内核的线程表保存了每个线程的寄存器、状态和其他信息。这些信息和在用户空间中(在运行时系统中)的线程是一样的,但是现在保存在内核中。这些信息是传统内核所维护的每个单线程进程信息(即进程状态)的子集。另外,内核还维护了传统的进程表,以便跟踪进程的状态。

所有能够阻塞线程的调用都以系统调用的形式实现,这与运行时系统过程相比,代价是相当可观的。当一个线程阻塞时,内核根据其选择,可以运行同一个进程中的另一个线程(若有一个就绪线程)或者运行另一个进程中的线程。而在用户级线程中,运行时系统始终运行自己进程中的线程,直到内核剥夺它的CPU(或者没有可运行的线程存在了)为止。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

由于在内核中创建或撤销线程的代价比较大,某些系统采取“环保”的处理方式,回收其线程。当某个线程被撤销时,就把它标志为不可运行的,但是其内核数据结构没有受到影响。稍后,在必须创建一个新线程时,就重新启动某个旧线程,从而节省了一些开销。在用户级线程中线程回收也是可能的,但是由于其线程管理的代价很小,所以没有必要进行这项工作。

内核线程不需要任何新的、非阻塞系统调用。另外,如果某个进程中的线程引起了页面故障,内核可以很方便地检查该进程是否有任何其他可运行的线程,如果有,在等待所需要的页面从磁盘读入时,就选择一个可运行的线程运行。这样做的主要缺点是系统调用的代价比较大,所以如果线程的操作(创建、终止等)比较多,就会带来很大的开销。

虽然使用内核线程可以解决很多问题,但是不会解决所有的问题。例如,当一个多线程进程创建新的进程时,会发生什么?新进程是拥有与原进程相同数量的线程,还是只有一个线程?在很多情况下,最好的选择取决于进程计划下一步做什么。如果它要调用exec来启动一个新的程序,或许一个线程是正确的选择;但是如果它继续执行,则应该复制所有的线程。

另一个话题是信号。回忆一下,信号是发给进程而不是线程的,至少在经典模型中是这样的。当一个信号到达时,应该由哪一个线程处理它?线程可以“注册”它们感兴趣的某些信号,因此当一个信号到达的时候,可把它交给需要它的线程。但是如果两个或更多的线程注册了相同的信号,会发生什么?这只是线程引起的问题中的两个,但是还有更多的问题。