深入理解计算机系统 - 第一章:计算机系统漫游

第一章:计算机系统漫游

下面是一个简单的hello程序,本书的目的就是要帮助你了解当你在系统上执行hello程序时,系统发生了什么,以及为什么会如何运作。

#include <stdio.h>
int main()
{
    printf("hello, world\n");
}

本章我们通过跟踪hello程序的生命周期来开始对计算机系统的学习。它的生命周期从它被程序员创建开始,包括编译、在系统上运行、输出简单的消息,然后终止。

1. 程序的表示

hello程序的生命周期从一个源程序开始,源程序实际上是由0/1构成的比特序列。而8个比特被组织成一组,称为字节,所以源程序也可以看做一个字节序列。

将源程序转换成字节序列需要进行编码。ASCII编码使用单字节大小的整数值(0~255)来对字符进行编码,将hello.c编码为下图:

hello.c 的表示方法说明了一个基本思想:系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。

作为程序员,我们需要了解数字的机器表示方式,因为它们与常见的整数和实数是不同的。它们是对真值的有限近似值,有时候会有意想不到的行为表现。这方面的基本原理将在第 2 章中详细描述。

2. 程序的编译

高级语言需要通过编译才能生成能够被机器读懂的机器语言。

预处理器:读取源程序,导入需要的其他包;

编译器:将高级机器语言转换成汇编语言;

汇编器:将汇编语言翻译成机器语言指令;

链接器:合并程序中需要调用的标准库中的函数等。

只有ASCII字符构成的文件称为文本文件,其他文件称为二进制文件。

3. 程序的运行

现在hello.c源程序已经被编译系统翻译成了可执行目标文件hello,并被存放在磁盘上。要想在Unix系统上运行该可执行文件,我们将它的文件名输入到被称为shell的应用程序中:

linux> ./hello
hello, world
linux>

shell是一个命令行解释器,它输出一个提示符,等待用户输入一个命令,然后执行这个命令。如果该命令的第一个单次不是内置的shell命令,那么shell就会假设这是一个可执行文件的名字,它将加载并运行这个文件。在此例中,shell将加载并运行hello程序,然后等待程序终止。hello程序在屏幕上输出它的消息,然后终止。shell随后输出一个提示符,等待用户输入下一条命令。

3.1 系统的硬件组成

为了理解运行hello程序时发生了什么,我们需要了解一个典型系统的硬件组织。

总线

贯穿整个系统的一组电子管道,称为总线。图中的总线包括系统总线、内存总线、IO总线。

总线携带信息字节并负责在各个部件之间传递。

I/O设备

IO设备是计算机系统与外部世界的联系通道,示例系统中包含四个IO设备:作为用户输入的鼠标和键盘、作为用户输出的显示器,以及用于长期存储数据和程序的磁盘驱动器。

每个IO设备都通过一个控制器或适配器与IO总线相连。控制器和适配器的作用都是在IO总线和IO设备之间传递信息。

主存储器

主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。

从物理上来说,主存是由一组动态随机访问存储器DRAM芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引),这些地址是从零开始的。

处理器

中央处理单元CPU,简称处理器,是解释或执行存储在主存中指令的引擎。

CPU的核心是一个大小为1个字节的存储设备(或寄存器),称为程序计数器PC。在任何时刻,PC都指向主存中的某条机器语言指令(即含有该条指令的地址)。

从系统通电开始,直到系统断电,CPU一直在不断地从程序计数器指向的内存处读取指令,解释指令中的位,执行该指令指示的简单操作,然后更新程序计数器,使其指向下一条指令。

这样的操作主要围绕着主存储器、寄存器文件和算术逻辑单元ALU进行。寄存器文件是一个小的存储设备,由一些单个字长的寄存器组成,每个寄存器都有唯一的名字。ALU计算新的数据和地址值。CPU在指令的要求下可能会执行下面这些操作:

  • 加载:从主存储器复制一个字节到寄存器,覆盖寄存器原来的内容。
  • 存储:从寄存器复制一个字节到主存储器的某个位置,覆盖主存储器原来的内容。
  • 操作:把两个寄存器的内容复制到ALU,ALU对这两个字做算术运算,并将结果存放到一个寄存器中,覆盖寄存器原来的内容。
  • 跳转:从指令中抽取一个字,将这个字复制到程序计数器中,覆盖原来的值。

3.2 运行hello程序

初始时,shell程序执行它的指令,等待我们输入一个命令。当我们在键盘上输入字符串./hello之后,shell程序将字符串逐一读入寄存器,再把它放到内存中,如下图所示:

当我们在键盘上敲回车键时,shell程序就知道我们已经结束了命令的输入。然后shell程序执行一系列指令来加载可执行的hello文件,将hello目标文件中的代码和数据从磁盘复制到主存储器。

一旦目标文件hello中的代码和数据被加载到主存,处理器就开始执行hello程序的main程序中的机器语言指令。这些指令将hello, world\n字符串中的字节从主存储器复制到寄存器文件,再从寄存器文件复制到显示设备,最终显示在屏幕上。

3.3 存储设备

上面的例子中,我们花费大量的时间把信息从一个地方挪到另一个地方。

hello程序的机器指令最初存放在磁盘上,当机器加载时,它们被复制到主存;当处理器运行程序时,它们被从主存复制到处理器。类似地,数据串hello, world\n开始时在磁盘上,然后被复制到主存,最后从主存复制到显示设备。

根据机械原理,较大的存储设备要比较小的存储设备运行地慢,而快速设备的造价远高于同类的低速设备。比如,一个磁盘的存储空间可能比主存大1000倍,但是对处理器而言,从磁盘上读取一个字的时间要比从主存中读取的开销大1000万倍。

针对处理器与主存之间的差异,系统设计者采用了更小更快的存储设备,称为高速缓存存储器,作为暂时的集结区域,存放处理器近期可能会需要的信息。下图展示了一个典型系统中的高速缓存存储器。

位于处理器芯片上的L1高速缓存的容量可以达到数万字节,访问速度几乎和访问寄存器文件一样快。一个容量为数十万到数十百万字节的L2高速缓存通过一条特殊的总线连接到处理器。进程访问L2高速缓存的时间要比访问L1高速缓存的时间长5倍,但是这仍然比访问主存的时间快5~10倍。L1和L2高速缓存使用一种叫做静态随机访问存储器SRAM的硬件技术实现的。比较新的系统还有三级高速缓存L3。

在处理器和一个较大较慢的设备(例如主存)之间插入一个更小更快的存储设备(例如高速缓存)的想法已经成为一个普遍的观念。实际上,每个计算机系统中的存储设备都被组织成了一个存储器层次结构,如下图所示:

在这个层次结构中,从上至下,设备容量越来越大、访问速度越来越慢,并且每字节的造价也越来越便宜。寄存器文件在层次结构中处于最顶层L0,下面是三层高速缓存L1到L3,L4层是主存,以此类推。

存储器层次结构的主要思想是:**上一层的存储器作为低一层存储器的高速缓存。**因此,寄存器文件就是L1的高速缓存,L1是L2的高速缓存,L2是L3的高速缓存,L3是主存的高速缓存,主存是磁盘的高速缓存。在某些具有分布式文件系统的网络系统中,本地磁盘又是存储在其他系统磁盘的高速缓存。

正如可以运用不同的高速缓存的知识来提高程序性能一样,程序员同样可以利用对存储器层次结构的理解来提高程序性能。

4. 操作系统

当shell程序加载和运行hello程序时,以及hello程序输出自己的消息时,shell和hello程序都没有直接访问键盘、显示器、磁盘或者主存。取而代之的是,它们依靠操作系统提供的服务。我们可以把操作系统看成是应用程序和硬件之间插入的一层软件,如下图所示。所有应用程序对硬件的操作都必须通过操作系统。

操作系统有两个基本功能:(1)防止硬件被失控的应用程序滥用;(2)向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。

操作系统通过几个基本的抽象概念(进程、虚拟内存和文件)来实现这两个功能。

如下图所示:文件是对I/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程则是对处理器、主存和I/O设备的抽象表示。

4.1 进程

像hello这样的程序在现代系统上运行时,操作系统会提供一种假象,就好像系统上只有这个程序在运行。程序看上去是在独占处理器、主存和IO设备。处理器看上去就像在不间断地执行程序中的指令,即该程序的代码和数据是系统内存中唯一的对象。这些假象是通过进程的概念来实现的,进程是计算机科学中最重要和最成功的概念之一。

进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程就好像是在独占地使用硬件。而并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的。

处理器根据CPU数量可以分为单核处理器和多核处理器。单核处理器在一个时刻只能执行一个程序,而多核处理器在一个时刻可以执行多个程序,即并行

无论是在单核处理器还是在多核处理器中,一个CPU看上去都像是在并发地执行多个进程,而这正是通过处理器在进程间切换来实现的,这种机制叫做上下文切换

操作系统保持跟踪进程运行所需的所有状态信息。这种状态信息就是上下文。上下文包括:程序计数器、寄存器文件的当前值、主存的内容等。在任何一个时刻,一个CPU都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新的进程时,就会进行上下文切换。上下文切换的具体做法是:保存当前进程的上下文、恢复新进程的上下文,然后吧控制权交给新进程。新进程就会从它上次停止的地方开始。

示例场景中有两个并发进程:shell进程和hello进程。最开始只有shell进程在运行,即等待命令行的输入。当我们让它运行hello程序时,shell通过调用一个专门的函数,即系统调用,来执行我们的请求,系统调用会将控制权传递给操作系统。操作系统保存shell进程的上下文,创建一个新的hello进程及其上下文,然后将控制权交给新的hello进程。hello进程终止后,操作系统恢复shell进程的上下文,并将控制权交给shell进程,shell进程则继续等待下一个命令的输入。

综上所述,操作系统内核管理从一个进程到另一个进程的转换,内核是操作系统代码常驻内存的部分。当应用程序需要操作系统的某些操作时,比如读写文件,它就执行一条特殊的系统调用(system call)指令,将控制权传递给内核。然后内核执行被请求的操作并返回应用程序。注意,内核不是一个独立的进程。相反,它是系统管理全部进程所用代码和数据结构的集合。

实现进程这个抽象概念需要低级硬件和操作系统软件之间的紧密合作。

4.2 线程

一个进程可以由多个称为线程的执行单元组成。每个线程都运行在进程的上下文中,共享同样的代码和全局数据。由于网络服务器中对并行处理的需求,线程成为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据,线程一般来说比进程更高效。当有多处理器可用的时候,多线程也是一种使得程序可以运行地更快的方法。

4.3 虚拟内存

虚拟内存是一个抽象概念,它为每个进程提供了一个假象,即每个进程都独占的使用主存。每个进程看到的主存都是一样的,叫做虚拟地址空间。图1-13是Linux进程的虚拟地址空间。在Linux中,地址空间最上面的区域是保留给操作系统中的代码和数据的,这对所有进程来说都是一样的。地址空间的底部区域存放用户进程定义的代码和数据。

每个进程看到的虚拟地址空间由大量准确定义的区构成,每个区有专门的功能。从最低的地址开始。

  • 程序代码和数据。对所有的进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化的,在示例中就是可执行文件hello。详情可以参考第七章。
  • 堆。代码和数据区后紧跟着的是运行时堆。代码和数据区在进程一开始运行时就被指定了大小,与此不同,当调用向malloc和free这样的C标准库函数时,堆可以在运行时动态地扩展和收缩。详情可以参考第九章。
  • 共享库。用来存放像C标准库和数学库这样的共享库的代码和数据的区域。详情参考第七章。
  • 栈。位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆一样,用户栈在程序执行期间可以动态扩展和收缩。特别地,每次我们调用一个函数时,栈就会增长;从一个函数返回时,栈就会收缩。第三章学习编译器是如何使用栈的。
  • 内核虚拟内存。地址空间顶部的区域是为内核保留的。不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。相反,它们必须通过调用内核再执行这些操作。

虚拟内存的运作需要硬件和操作系统软件之间精密复杂的交互,包括对处理器生成的每个地址的硬件翻译。**基本思想是把一个进程虚拟内存的内容存储在磁盘上,然后用主存作为磁盘的高速缓存。**第九章将介绍虚拟内存的工作原理。

4.4 文件

文件就是字节序列,仅此而已。

每个I/O设备,包括磁盘、键盘、显示器,甚至网络,都可以看成是文件。系统中所有输入输出都是通过使用一小组称为Unix I/O的系统函数调用读写文件来实现的。

文件,向应用程序提供了一个统一的视图,来看待系统中可能含有的所有各式各样的I/O设备。

第十章介绍Unix I/O。

5. 系统之间通过网络通信

至此为止,我们一直把系统视为一个孤立的硬件和软件的集合体。实际上,现代系统经常通过网络和其他系统连接到一起。从一个单独的系统来看,网络可视为一个I/O设备,如图1-14。当系统从主存复制一串字节到网络适配器时,数据流经过网络到达另一台机器,而不是比如说到达本地磁盘。相似地,系统可以读取从其他机器发送来的数据,并把数据复制到自己的主存。

现在,从一台主机复制信息到另外一台主机已经成为计算机系统最重要的用途之一。比如电子邮件、即时消息、FTP等应用都是基于网络复制消息的功能。

以hello程序为例,我们可以使用telnet应用在一个远程主机上运行hello程序。假设用本地主机上的telnet客户端连接远程主机上的telnet服务器。在我们登录到远程主机并运行shell之后,远端的shell程序就在等待接收输入命令。此后在远端运行hello程序包括图1-15所示的五个基本步骤。

这种客户端和服务器之间交互的类型在所有的网络应用中是非常典型的。在第11章将学习如何构造网络应用程序,并创建一个简单的web服务器。

小结

计算机系统是由硬件和系统软件构成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们根据不同的上下文又有不同的解释方式。程序被其他程序翻译成不同的形式,开始时是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存放在主存里的二进制指令。因为计算机花费了大量的时间在存储器、IO设备和CPU寄存器之间拷贝数据,所以系统中的存储设备就被按层次排列(CPU寄存器、多级高速缓存、主存、磁盘)。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象概念:文件是对IO设备的抽象;虚拟内存是对主存和磁盘的抽象;进程是对处理器、IO和主存的抽象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值