深入理解计算机系统 1.7 操作系统管理硬件

回到我们的hello程序。当shell加载并运行hello程序时,当hello程序打印出它的消息时,程序并没有直接访问键盘、显示器、硬盘和内存,而是依赖于操作系统提供的服务。我们可以把操作系统想成放置在应用程序和硬件之间的一个软件层,就像Figure1.10展示的那样。所有应用程序对硬件的操作都必须经过操作系统才能完成。

操作系统有两个主要的功能:(1)保护硬件被失去控制的应用程序误用,(2)提供给应用程序操作各种各样复杂硬件简单同意的机制。操作系统通过展示在Figure1.11的基本抽象来实现这两个功能:进程,虚拟内存,和文件。正如图中所展示的,文件是I/O设备的抽象;虚拟内存是内存和磁盘I/O设备的抽象;进程是处理器,内存和I/O设备的抽象。我们将依次讨论它们。

深入理解计算机系统 1.7 操作系统管理硬件_第1张图片

1.7.1 进程

当一个程序(例如hello)运行在一个现代操作系统上时,操作系统提供了一个假象:只有这一个程序运行在系统上。这个程序似乎独占处理器,内存和I/O设备。处理器似乎一条又一条的执行着程序中的指令,从不间断。程序的代码和数据似乎是系统内存里的唯一对象。进程的概念提供了这个假象,进程是计算机科学中最重要和最成功的概念之一。

对一个运行的程序来说,进程是操作系统的抽象。多个进程同时运行在同一个系统上,每个进程都感觉自己独占着硬件。同时,意味着一个进程的指令穿插在另一个进程的指令之间执行。在大多数系统中,运行的进程数量比系统的CPU数量多。传统的系统一次只能执行一个程序,而更新的多核处理器能同时执行多个程序。不管是传统的,还是更新,它们都是让处理器在程序间切换来给人同时执行多个程序的假象的。操作系统通过叫做上下文切换(context switching)的机制来实现程序的切换的。为了简化讨论的剩余部分,我们只考虑包含单个CPU的单处理器系统。我们将在1.9.1讨论多处理器系统。

操作系统追踪进程运行需要的所有状态信息。这些状态被称为上下文,包含程序计数器(PC)的当前值,寄存器文件和内存中的内容。在任何时刻,单处理器系统只能执行单个进程的代码。当操作系统决定把控制权从一个进程传递给一个更新的进程时,它通过保存当前进程的上下文和恢复新进程的上下文来执行一次上下文切换,然后把控制权交给新进程。新进程从它上次离开的地方接着执行。Figure 1.12展示了我们的hello程序执行的过程片段。

在我们的例子中,有两个同时执行的进程:shell进程和hello进程。最初,shell进程独自运行着,等待着命令行的输入。当我们让它运行hello程序的时候,shell进程通过执行一个叫做系统调用(system call)的特殊函数执行我们的请求,系统调用传递控制给操作系统。操作系统保存shell的上下文环境,创建一个新的hello进程和它的上下文环境,然后传递控制给新的hello进程。在hello进程结束之后,操作系统恢复shell进程的上下文环境并把控制传递给它,然后,shell进程等待着下一个命令行的输入。

实现进程抽象需要底层硬件和操作系统软件的合作。我们将在第八章讨论这是如何实现的以及应用程序如何能够创建和控制它们自己的进程。

1.7.2 线程

尽管我们通常认为一个进程只包含一个控制流,但是实际上,现代系统中,一个进程可能由多个执行单元组成,这些执行单元叫做线程,它们运行在进程的上下文环境中,共享相同的代码和全局数据。因为在网络服务器中并发的需要,线程编程模型正变得越来越重要,因为在多线程之间共享数据比在多进程之间共享数据更加容易,也因为线程通常情况下比进程效率更高。当有多个处理器可使用时,多线程也是一种让程序运行更快的方式,我们将在1.9.1讨论这一点。在十二章,你将会学到并发的基本概念,包括怎样写多线程程序。

1.7.3 虚拟内存

虚拟内存是一个抽象,这个抽象给所有进程独占内存的假象。每一个进程拥有关于内存相同的统一的视图,这个视图被称为虚拟地址空间(virtual address space)。Linux系统中进程的虚拟地址空间展示在Figure 1.13。(其它Unix系统使用相似的布局)。在Linux中,地址空间的最上面的区域是为操作系统的代码和数据的,这对所有进程都是一样的。往下的区域存储用户进程定义的代码和数据。注意,图中的地址空间是从下往上增长的。

深入理解计算机系统 1.7 操作系统管理硬件_第2张图片

每一个进程看到的虚拟地址空间由几个定义好的区域组成,每一个区域都用于特定的目的。在这本书的后面,你将会学到更到关于这些区域的知识,但是现在简要地看一下它们是很有帮助的,我们从最下面的地址空间开始:

程序代码和数据(Program code and data)。对所有进程,代码从相同的固定地址开始,数据紧随其后,数据位置和全局C变量一致。代码和数据区域直接被一个可执行对象文件的内容初始化,在我们的例子中,是hello可执行对象文件。当我们在第七章讨论链接和加载时,你将会学到更多地址空间的这部分内容。
堆(heap)。紧跟着代码和数据区的是运行时堆。与代码和数据区不同(一旦程序开始运行,代码和数据区的大小是固定的),堆区随着对C标准库(比如malloc和free)的调用扩展和压缩。当我们在第九章讨论管理虚拟内存时将仔细讨论堆。
共享库(Shared libraries)。地址空间的中间是这样一个区域,它存储着共享库的代码和数据,比如C标准库和数学库。共享库是一个强大却有点儿难的概念。当我们在第七章讨论动态链接时,你将会学到它们是怎样工作的。
栈(Stack)。在用户虚拟地址空间的顶端是用户栈,编译器使用它来实现函数调用。和堆一样,用户栈在程序执行期间动态扩展和压缩。特别的,每次我们调用一个函数,栈就增长。每一次我们从一个函数返回,栈就压缩。在第三章,你将会学到编译器如何使用栈。
内核虚拟内存。内核是操作系统的一部分,操作系统总是待在内存里。地址空间的顶端空间是为内核保留的。应用程序不允许去读或者写这个区域的内容或者直接调用内核代码中定义的函数。
为了虚拟内存能工作,需要硬件和操作系统间复杂的交互,包括每一个被处理器产生的地址的硬件翻译。基本的观点是把存储进程的虚拟内存的内容到磁盘上,然后把主存作为磁盘的缓存。第九章将解释这是怎么工作的以及为什么它对现在系统的操作如此重要(原文:Chapter 9 explains how this works and why it is so important to theoperation of modern systems.)。

1.7.4 文件

一个文件仅仅就是一个字节序列。任何I/O设备,包括磁盘,键盘,显示器,甚至网络,都是文件模型(注:也就是这些设备都可以看作文件)。在系统中,所有输入和输出都是通过都或者写文件完成的,读写文件是通过叫做Unix I/O的一个小的系统调用集合实现的。
文件这个简单优雅的概念是非常强大的,因为它提供给应用程序关于不同的I/O设备统一的视图。例如,操作硬盘文件里数据的应用程序员不用关心特定的硬盘技术。进一步说,就是,相同的程序可以运行在使用不同硬盘技术的系统上。你将在第十章学到更多关于Unix I/O的知识。

你可能感兴趣的:(深入理解计算机系统,操作系统管理硬件)