对进程和线程如何构成软件及如何映射到内存进行探究颇有益处。本节将介绍一些在未来几章常会提到的概念。应用程序包括指令和数据,在其开始运行之前,这些只是一些分布在磁盘上的指令和数据,如图1-21所示。 正在执行的应用程序称为进程,进程不仅仅是指令和数据,它还有状态。状态是保存在处理器寄存器中的一组值,如当前执行指令的地址、保存在内存中的值,以及唯一定义进程在任一时刻任务的所有其他值。进程与应用程序的一个重要的区别在于,进程运行时,进程的状态会发生变化。图1-22显示了在内存上运行的应用程序的布局。 进程是应用程序的基本构建块。同时运行的多个应用程序实际上就是多个进程。要支持多个用户,通常是利用具有不同权限的多个进程实现的。除非一个进程创建时显式与另一进程共享状态,否则其所有状态应是该进程私有的,即对其他进程不可见。举一个更具体的例子,如果运行文本编辑器的两个副本,那么两者可能都有变量current_line,但任一副本都不能读取属于另一副本的current_line变量值。 应用程序状态最关键的部分就是已分配内存。回想一下,内存分配采用虚拟地址,因此上面假想的文本编辑器的两个副本很可能用从0x111000到0x11a000的虚拟地址来存储文档。每个应用程序将维护自身的TLB映射,这样相同的虚拟地址就能映射到不同的物理地址。如果以一个核心运行这两个应用程序,那么每个应用程序预期平均会有一半TLB条目用于其映射,因此多个活动的进程,最终会增加对内部芯片结构(如TLB或缓存)的压力,因此TLB未命中或缓存未命中的次数将会增加。 每个进程可以运行多个线程。线程也像进程一样有一些状态,但线程的状态基本上只是保存在其寄存器中的值以及其栈上的数据。图1-23显示了一个多线程应用程序的内存布局。 线程与同一应用程序中的其他线程共享很多状态。回顾文本编辑器的例子,其另一种实现方式就是单个文本编辑器程序,但有两个窗口,每个窗口显示不同的文档,此时这两个文档不能再保存在相同的虚拟地址中,而是需要不同的虚拟地址。如果编辑器应用程序编得不好,则一个窗口中的活动可能会导致另一个窗口中数据的变化。 但选择编写多线程应用程序有充足的理由,其中一个主要理由是在有多个硬件线程的系统上使用多个线程应该会比单线程更快完成工作获得结果。另一个原因可能是问题自然而然分解成多个线程。例如,Web服务器与远程机器同时会有多个连接,所以当然适合用多个线程编写。使用多个线程优于使用多个进程还有一个优点,即线程共享大部分机器状态,特别是TLB和缓存条目。因此,如果所有线程需要共享一些数据,就可从同一内存地址读取。 本次讨论中应该记得的一点:线程和进程是获得多指令流以协作解决某个问题的途径。进程的优点在于每个进程是孤立的,一个进程死掉对其他正在运行的进程没有任何影响。多进程的缺点是每个进程需要自己的TLB条目,从而增加了TLB和缓存的未命中率。使用多进程的另一个缺点是进程之间共享数据需要显式控制,而这种操作可能开销很大。 多个线程的优点是在线程之间共享数据的成本低,因为某个线程可将数据项存储到内存,且该数据立刻对此进程中的所有其他线程可见。共享的其他优点是所有线程共享相同的TLB和缓存条目,所以多线程应用程序的缓存未命中率较低。其缺点是一个线程失败就很可能会导致整个应用程序终止。 一个应用程序既可以编写为多线程应用程序,也可以编写为多进程应用程序。范例之一就是最近在Web浏览器设计上的变化。谷歌的Chrome浏览器是多进程的,浏览器可以使用多个标签页显示不同的网页。每个标签页是一个单独的进程,这样一个标签页失败不会导致整个浏览器终止。浏览器曾经是多线程的,如果一个线程执行不良代码,整个浏览器就会崩溃。由于网络的无约束性质,着眼于稳健性而非低共享成本当是一个明智的设计决策。