// 本章节内容根据下列代码的生命周期来讲解计算机系统的各个部分 hello.c
#include <stdio.h>
int main {
printf("hello, world\n");
return 0;
}
文章目录
- 1.1信息 = 位 + 上下文
- 1.2程序的编译过程
- 1.3系统的硬件组成
- 1.4运行hello程序
- 1.5高速缓存
- 1.6操作系统管理硬件
- 1.6.1进程
- 1.6.2虚拟内存
- 1.7并发和并行
1.1信息 = 位 + 上下文
hello
程序的生命周期从一个源文件开始,源文件实际上就是一个由0和1组成的位序列,8个位被组织成一组成为字节。计算机系统一般采用ASCII
来表示文本字符,上诉代码在计算机系统的表示如下:
# i n c ...
35 105 110 99 ...
这种由ASCII
组成的文件称为文本文件,其它的文件都称为二进制文件。
hello.c
表明系统中的所有信息:磁盘的文件、内存中的程序等,都以比特的形式存在,区分不同数据对象的唯一方法是当我们读到这些数据对象时的上下文, 在不同情况下,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。
1.2程序的编译过程
当我们在编译器或者使用shell
去编译hello.c
文件时,内部其实分成了四个步骤:预处理、编译、汇编和链接,而我们的目标就是生成一个可执行文件(hello)。
-
预处理阶段:将程序引入的头文件导入和处理用户定义的宏变量,然后程序就变成了以
.i
结尾的文件。 -
编译阶段:翻译成汇编语言的过程,程序变成以
.s
结尾的文件。下面代码就是hello.i
汇编化的部分程序,每一行都表示一条低级机器语言指令main: subq $8, %rsp movl $.LCO, %edi ......
-
汇编阶段:将
hello.s
翻译为机器语言指令,将这些指令打包成一种叫做可重定位目标程序,保存在hello.o
中,该文件是一个二进制文件,在此之前的文件都属于文本文件。 -
链接阶段:有时候我们的程序会去调用标准库定义好的一些函数,需要某种方式将这些函数合并到我们自己的程序中,连接器(ld)就负责处理这种合并,最后得到的
hello
就是一个可执行文件,然后被加载到内存中等待用户调用,系统执行。
1.3系统的硬件组成
下图是一个典型的系统硬件构成,可以看到主要由4个部分组成:总线、I/O设备、主存和处理器。
-
总线:贯穿整个系统的一组电子管道,它的功能就是携带信息字节并负责在各个部件间传递。一般总线被设计成传送定长的字节块
-
I/O设备:上图中鼠标、键盘、磁盘以及显示器都属于I/O设备。每一个I/O设备都通过一个控制器或者适配器于I/O总线相连,控制器和适配器的区别在于它们的封装方式,控制器是I/O设备本身或者系统主板上的芯片组,而适配器则是主板插槽上的卡,但是它们都有着相同发的功能:在I/O总线和I/O设备之间传递信息
-
主存:一个临时的存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。物理上,主存是由一组动态随机存取存储器(DRAM)组成,逻辑上是一个线性的字节数组。
-
处理器:中央处理单元,也称为
CPU
,是解释或执行存储在主存中的指令的引擎。处理器的核心是一个大小为一个字的存储设备或寄存器,称为程序计数器(PC)。任何时候PC都指向主存中的某条机器语言指令,从系统通电到断电,处理器一种在不断地执行程序计数器指向的指令,再更新程序计数器,使其指向下一条指令,这样的操作一般围绕着主存、寄存器文件和算术/逻辑单元(ALU)进行。CPU在指令的要求下可能会进行下面的操作:- 加载:从主存复制一个字节或一个字到寄存器,覆盖寄存器原来的内容
- 存储:从寄存器复制一个字节或者一个字到主存的某个位置,覆盖整个位置上原来的内容
- 操作:把两个寄存器的内容复制的ALU,ALU对这两个字做算术运算,并将结果存放到一个寄存器中,覆盖该寄存器原来的内容
- 跳转:从指令本身抽取一个字,将整个字复制到程序计数器(PC)中,覆盖原来的值、
一般会将处理器的指令集架构和处理器的微体系结构区分开:指令集架构描述的是每条机器代码指令的效果;微体系结构描述的是处理器实际上如何实现。
1.4运行hello程序
当我们通过shell
执行./hello
,首先shell
会执行自己的指令,然后等待用户的输入,当我们输入./hello
时,shell
程序会将字符统一读入寄存器,然后存放到内存中。
当我们输入回车时,shell
程序就知道我们已经结束了命令的输入,然后shell
执行一系列指令加载可执行的hello
文件,这些指令将hello
目标文件中的代码从磁盘复制到主存。一旦目标文件hello
代码和数据被加载到主存,处理器就开始执行hello
程序的main
程序中的机器语言指令,这些指令将输出的字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示再屏幕上。
1.5高速缓存
上面程序运行的例子可以发现我们把程序的机器指令不断的从一个地方挪到另一个地方(复制),这是一种开销,而当我们有大量的数据需要复制时,肯定会减慢程序的允许速度,因此需要一种方法来使这些复制操作更快的完成。
根据机械原理,较大的存储设备要比小的存储设备允许得慢,一个寄存器文件只存储几百字节的信息,而主存里可存放几十亿字节,但是处理器从寄存器文件中读数据比从主存中读取数据几乎要快100倍。针对处理器和主存之间的差异,系统设计者采用了更小更快的存储设备,称为高速缓存存储器,作为暂时的集结区域,存放近期可能会需要的信息。
下图是一个高速缓存存储器。位于处理器芯片上的L1高速缓存的容量可以达到数万字节,访问速度几乎和访问寄存器文件一样块。一个容量为数十万到数百万字节的更大的L2高速缓存通过一条特殊的总线连接到处理器,虽然访问L2的时间要比L1长5倍,但是这任然比访问主存的时间快5-10倍。L1和L高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现
虽然高速缓存的出现,计算机系统的存储器层次结果一般如下所示,随着设备的访问速度越来越慢,容量却越来越大。存储器层次结构的主要思想是上一层的存储器作为第一层存储器的高速缓存。
1.6操作系统管理硬件
在我们通过shell
程序执行hello
程序时,它们都没有直接的访问键盘、显示器、磁盘或者主存,而都是依靠操作系统提供的服务,我们可以把操作系统看成是应用程序和硬件之前插入的一层软件。
操作系统的两个基本功能可以归结于下:
- 放置硬件被失控的应用程序滥用
- 像应用程序提供简单一致的机制俩控制复杂而通常大不相同的低级硬件设备。
操作系统通过3个基本的抽象概念(进程、虚拟内存和文件)来实现这两个功能,文件时对I/O设备的抽象表示,虚拟内存时对主存和磁盘I/O设备的抽象表示,进程则是对处理器、主存和I/O设备的抽象表示。
1.6.1进程
进程是操作系统对一个正在运行的程序的一种抽象,在一个系统上可以同时运行多个进程,每一个进程都好像在独占地使用硬件。并发运行说的是一个进程的指令和另一个进程的指令是交错执行的。即使在单核系统中,好像一个CPU也能并发地执行多个进程,这其实是通过处理器在进程间切换来实现的。操作系统实现这种交错执行的机制称为上下文切换。上下文就是操作系统跟踪进程运行所需的所有状态信息。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,既保存当前进程的上下文,恢复新进程的上下文,如下图。
从一个进程到另一个进程的转换时由操作系统内核管理,内核时操作系统代码常驻主存的部分。当应用程序需要操作系统的某些操作时,它就执行一条特殊的系统调用指令,将控制权传递给内核,然后内核执行被请求的操作并返回给应用程序。内核不是一个独立的进程,相反它时系统管理全部进程所用代码和数据结构的集合。
1.6.2虚拟内存
虚拟内存为每个进程提供了一个假象,既每个进程都在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间。下图时Linux进程的虚拟地址空间
,在linux
中,地址空间最上面的区域时保留给操作系统中的代码和数据的,底部区域存放用户进程定义的代码和数据。
每个进程看到的虚拟地址空间由大量准确定义的区构成,下列是每个区的简单介绍:
- 程序代码和数据:所有的进程代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标我呢见的内容初始化的
- 堆:代码和数据区在进程一开始运行就被固定了大小,与此不同,当调用像
malloc
和free
这样的C标准库函数时,堆可以在运行时动态地扩展和收缩。 - 共享库:用来存放像C标准库和数学库这样的共享库的代码和数据的区域
- 栈:编译器利用栈实现函数调用,用户栈在程序执行期间可以动态地扩展和收缩
- 内核虚拟内存:该部分为内核独占,应用程序如果像调用里面的函数只能通过调用内核实现
1.7并发和并行
并发只一个同时具有多个活动的系统;并行指的是用并发来使一个系统运行得更快,并行可以在计算机系统的多个抽象层次上运用。
-
线程级并发
并发的执行其实就是一个处理器在多个任务之间的迅速切换,这种配置称为单处理器系统。当构建一个由单操作系统内核控制的多处理器组成的系统时,就得到了一个多处理器系统。多核处理器是将多个CPU集成到一个集成电路芯片上,如下图。一共有4个CPU核集成在微处理器芯片上,每个核都有自己的L1和L2高速缓存,L1高速缓存又分为两部分,一个保存最近取到的指令,另一个存放数据。这些核还一起共享一个更高层次的高速缓存,以及主存的接口。
多核处理器的使用从两方面提高了系统性能。首先,它减少了在执行多个任务时模拟并发的需要。其次,当程序以多线程方式书写时,由于线程可以并行地高效执行,所以可以使程序运行得更快。
-
指令级并行
在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。
-
单指令、多数据并行
在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据,既
SIMD
并行。