计算机系统笔记- 计算机系统漫游

深入理解计算机系统

第一章 计算机系统漫游

程序运行时发生了什么以及为什么是这样的

这个问题的答案是整本书700多页的内容,在此不可能完整并正确地回答出来,但是通过跟踪一个简单的C程序的生命周期可以展开这个问题的一部分答案。

在此声明:本文仅仅是为了引出一些基本的问题,特别是一些重要地抽象概念,并不能从底层或原理上解释相关问题。大神们可以忽略。

鼓舞人心的话

如果你全力投身学习本书中的概念,完全理解底层计算机系统以及他们对应用程序的影响,那么你将会步上成为为数不多的“大牛”的道路–引用自《深入理解计算机系统》。

简单的C程序

//hello.c  打印输出hello,world
#include <stdio.h>

int main(void){
    printf("hello,world\n");

    return 0;
}

计算机系统

硬件与系统软件结合,共同工作来运行应用程序。

源程序是什么

  • 源程序,也叫源文件或源代码。指的是人类可读的计算机语言指令。

    其概念是相对目标程序和可执行程序而言的,所以也可以说指的是汇编语言写出来的代码或抽象层次比汇编语言更高的编程语言写出来的代码。

  • 文件,本质是字节序列,是计算机系统对I/O设备的抽象。

  • 字节,8个位组成的一组就是字节。

  • 位,就是值为0或1存储单元。

因此,可以说源文件就是一个位(比特)序列。

为什么没有在源文件中看到0/1

计算机系统采用了数字编码(不同计算机系统会采用不同的编码系统),即将一定数量的位按照编码(比如ASCII编码)规则映射到具体的符号。这类文件也叫文本文件。文本编辑器会读取文本文件,根据该文件的编码格式,找到对应的字符,呈现在使用者眼前。

  • 字符,包括字母,数字,字和符号。
  • 字,并非汉字,而是定长的字节块。现在大多数计算机要么字长为4,即4个字节,32位。要么字长为8,64位的系统。
  • ASCII编码,是美国信息交换标准编码。规定0~127这128个数字代表信息的规范编码,而在C语言中用一个字节来存储一个字符。一个字节有8位,共256种组合,足以应对。比较奇怪的是,C语言中用int类型来存储字符字面量,如果有char code=‘abcd’;,则code打印出来的值为d。

从源程序得到的启示

计算机系统中所有信息都是由比特序列表示的。区别不同数据对象的唯一方式,是读取到数据对象时的上下文

继续追问

  • 上下文是进程所需要的所有状态。

  • 简单地来说,进程是操作系统对处理器,主存和I/O设备的抽象。

  • 操作系统,是应用程序和硬件设备的中间层软件。作用是防止失控的应用程序对硬件的滥用和对外提供简单一致的机制来控制复杂和低级的硬件设备。

  • 处理器,也叫中央处理单元(CPU),是解释和执行存储在主存中的指令的引擎。

  • 主存,就是一个临时存储设备。用来存放程序和程序处理的数据。

  • 程序,就是特定的指令集合。

  • 指令,就是机器指令码,用于指示计算机应做的操作以及操作数所在的地址的一组二进制数。

  • I/O设备,就是系统与外界连接的通道。包括键盘,鼠标,显示器和磁盘驱动器。通过控制器或适配器与总线相连。

  • 总线,就是贯穿整个系统的电子管道。信息通过总线在系统各个部件间传递。源程序启示中已经说了,所有信息都是比特序列,这里也不例外。不过,总线传送的信息以字为单位。字的长度随系统而异。

系统各个部件

包括总线,处理器,I/O设备,主存。上面的追问只做了概念性的描述,这里总结的更具体一些。

  1. 处理器

    英文缩写CPU,是解释或执行存储在主存中指令的引擎。CPU的核心是大小为1个字的存储设备(或寄存器),叫做程序计数器PC,在任何时刻PC都指向主存中的某条指令。其实现代处理器的制作工艺已经极大程度地提高了,单个芯片电路上集成了更多的核,叫多处理器。

    对于最简单的单核处理器,它的结构(结构决定功能,所以先总结结构)包括总线接口,程序计数器(简称PC),寄存器文件和算数/逻辑单元(简称ALU)。现代处理器结构中还包括*高速缓存器(很重要)*和集成显卡。总线接口与系统总线连接,系统总线连接到I/O桥,I/O桥连接着主存和I/O设备,所以任何部件都可以发送信息到CPU。

    PC也是一种存储设备,大小为一个字,也可以叫寄存器。从电源通电开始直至断电,PC连续不断地更新存储在其中的指令地址,而实际指令存储在主存中,PC连续指向的两条指令在主存中不一定是连续的。CPU根据PC中的地址去主存中获取指令,然后解释指令中的位和执行指令的操作。指令的含义由指令集架构决定,但是现代处理器并非是指令集架构的简单实现,处理器的实现由处理器的微体系架构决定。可以这样理解:指令集架构是描述每条指令的效果的,而处理器的微体系架构是描述处理器实际上是如何实现的。

    寄存器文件也是一种存储设备,是一组寄存器,但是每一个寄存器都有唯一的名字。

    ALU用于计算新的数据和地址。CPU在指令的要求下可能会执行的操作:

    • 加载:从主存复制一个字节或字到寄存器,以覆盖寄存器原来的内容
    • 存储:从寄存器复制一个字节或字到主存上的某个位置,已覆盖这个位置上原来的内容
    • 操作:把两个寄存器的内容复制到ALU,ALU对这两个字做算术运算,并将结果存储到一个寄存器当中,以覆盖该寄存器原来的内容
    • 跳转:从指令本身中抽取一个字,并将该字复制到PC中,以覆盖PC中原来的内容
  2. 主存

    也是一种存储设备,但它是临时的存储设备。物理结构上,它由一组动态随机存取存储器(DRAM)芯片组成。逻辑上,它由线性的字节数组组成,每个字节都有唯一的地址(数组索引),这些地址是从零开始的。一般来说,组成程序的每条机器指令都由不同数量的字节构成。与C程序相对应的数据项的大小是根据类型变化的。

  3. I/O设备

    拥有控制器或适配器,通过控制器与适配器与总线连接,二者区别在于控制器是I/O设备本身或主印制电路板上的芯片组,而适配器是插在主板插槽中的卡。* 网络也可以看做一个I/O设备。*比如:图形适配器,俗称显卡,是插在主板上的卡。USB控制器,鼠标,键盘都是通过USB接口与计算机连接。无论如何,他们的功能都是在总线与IO设备之间传递信息。

源程序怎么运行

先通过编译器将源程序编译为可执行目标文件,再运行。

其实,上述说法不够准确,因为将源程序翻译为可执行目标文件的不只是编译器。整个编译过程由编译驱动程序(编译系统)完成。这部分在C Primer Plus总结过了。在此再总结一次。

编译系统包括预处理器,编译器,汇编器和链接器共同组成。

  • 预处理器根据源文件hello.c中的预处理命令进行文本替换(本程序的#include <stdio.h>被替换为stdio.h的文本内容,并删除注释),结果是文本格式的修改了的源程序hello.i。
  • 编译器进行严格地语法检查然后将hello.i翻译为文本格式的汇编程序hello.s。
  • 汇编器将hello.s翻译为二进制格式的机器指令代码,结果是可重定位目标程序hello.o(或叫机器代码,机器语言指令)。
  • 链接器将hello.o,系统标准启动代码和hello.o引用的可重定位目标程序stdio.o三者合并在一起,形成最终的可执行目标程序。

高速缓存至关重要

从上述过程可以看出,数据在计算机中不断的从一个地方搬到另一个地方。从我们程序员的角度来看,这些时间耗费是多余的。但是,我们又没有办法不按照这个步骤执行,这是由计算机系统的结构决定的。因此,系统设计的主要目标就是使复制操作尽可能快的完成。根据机械原理,较大的存储设备比较小的设备运行的慢。利用高速缓存的特性可以将程序的运行速度提高一个数量级

还有一点值得提出,CPU从寄存器中读取数据的速度几乎是从主存中读取的100倍,而从主存中读取数据的速度几乎是从磁盘中读取的1000万倍。随着处理器制程工艺的提高,这种差距仍在加大。针对这种处理器与主存之间的差异,系统设计者采用了更小更快的存储设备,称为高速缓存存储器(cache memory),存放CPU近期可能会需要的信息。

现代CPU中集成了L1,L2甚至L3高速缓存存储器。比如:AMD公司的锐龙7 2700X处理器,一级缓存L1为768KB,二级缓存L2为4MB,三级缓存L3为16MB。从L1高速缓存中读取数据的速度几乎与从寄存器中读取一样,从L2中读取的时间耗费几乎是L1的5倍。L1,L2和L3都是利用静态随机访问存储器(SRAM)的硬件技术实现的。AMD公司宣称锐龙三代实现了高达15%的IPC(每时钟周期执行指令)性能提升。

系统获取了一个较大的且读取速度较快的存储器,由于高速缓存的局部性原理,让高速缓存存放尽可能经常访问的数据,大部分的内存操作都能快速完成。高速缓存的局部性原理:程序具有访问局部区域里数据和代码的趋势。包括时间局部性和空间局部性,从时间角度看,程序倾向于访问刚访问过的数据,比如for循环,从空间角度看,程序倾向于访问前一个数据相邻的下一个数据。据统计,一个程序常用的代码只占10%。

存储设备形成层次结构

在处理器与较大的存储设备之间插入一个更小更快的存储设备的想法已经成为了一个普遍的观念。实际上每个计算机系统的存储设备都被有意或者无意的组织为一个存储器层次结构。寄存器位于顶部,接下来是L1高速缓存,L2…本地二级存储(本地磁盘),远程二级存储(Web服务器,分布式文件系统).

进一步了解操作系统

当shell加载和运行hello程序的时候,并没有直接访问键盘,磁盘,寄存器和主存,而是依靠系统提供的服务。可以将操作系统看作应用程序与硬件之间的一层软件。所有程序对硬件的操作都必须通过操作系统。操作系统有两项基本功能:

  • 防止失控的程序对硬件的滥用
  • 为程序提供简单一致的机制来控制复杂而通常又大不相同的低级设备。

操作系统通过三个伟大的抽象概念来管理硬件,分别是进程,虚拟内存和文件。

  1. 进程

进程是对运行中的程序的抽象,也可以说是对CPU,主存和I/O设备的抽象。

传统操作系统(单核CPU)同一时刻只能有一个进程在执行,而现代操作系统使用多核处理器可以在同一时刻执行多个进程。打个比方,游戏主播可以一边打游戏,一边放音乐,一边将视频数据发送到客户端。

操作系统上可以同时运行多个进程,而每个进程好像都在独占的使用硬件。而并发运行是,一个进程的指令与另一个进程的指令交错执行。在大多数系统中,需要运行的进程数是远大于CPU的个数的。无论单核还是多核系统,一个CPU都好像在并发的执行多个进程,这是通过处理器在进程间切换来实现的。操作系统实现这种交错执行的机制叫做上下文切换

上下文是进程运行所需的所有信息,包括PC,寄存器文件的当前值,主存中内容。在单处理器系统中,任何时刻都只能执行一个进程的代码,当操作系统决定要将控制权从当前进程转移到新进程时,就会进行上下文切换,即保存当前进程的上下文,恢复新进程的上下文。

操作系统的内核并非一个独立的进程,而是操作系统常驻主存中的代码和数据的集合。
提到进程,就不得不提线程。实际上,一个进程可以由多个称为线程的执行单元组成。同一进程的多个线程可以共享数据,因为更高效。所以,学会多线程编程也是提高程序运行效率的一种方式。但是,这并不容易。

  1. 虚拟内存

虚拟内存是对主存和磁盘I/O设备的抽象。

原文中就这一句话比较贴切,其余的内容大多是描述虚拟地址空间的。

虚拟内存在windows操作系统中就是一个文件,叫做PAGEFILE.SYS。为什么要发明这个东西?因为计算机系统的内存(这里的内存都指主存)是有限的,为应对内存消耗殆尽的情况,提出的解决方案。从结构上看,它是部分内存(RAM)和部分硬盘空间的临时组合。当计算机系统中内存即将消耗殆尽的时候,虚拟内存就会对内存进行补偿,即将内存中部分数据转移到虚拟内存中。

虚拟地址空间,是对主存的抽象,结构上就是由一个个准确定义的区组成。每一个区都有专门的功能。这些区包括:

  • 内核虚拟内存,所有程序都不可以读写这块区域,必须通过调用内核本身来操作。

  • 用户栈,编译器用它来实现函数调用,即当函数调用时用户栈扩张,函数返回后用户栈收缩。

  • 共享库,用于存放像C标准库这样的代码和数据。

  • 运行时堆,不清楚。

  • 程序代码和数据,就是可执行文件的二进制编码和数据。

  1. 文件
    对I/O设备的抽象,本质就是字节序列。

Unix历史

20世纪60年代,大型计算机兴盛,IBM 的OS/360取得成功,贝尔实验室参与的项目Mutilcs在1969年失败,其研究人员随后开发了Unix,在1973年用C语言编写了内核,并在1974年对外发布。

20世纪70年代末到80年代初,Unix空前发展,20世纪80年代中期厂商企图加入新的,不兼容的特性来使他们的程序与众不同。IEEE为阻止这种趋势,所以标准化Unix开发,称为Posix。

让计算机做的更多更快

让计算机做的更多和更快是驱动计算机系统进步的动力。

想要真正理解并发和并行,必须先理解操作系统的设计。包括进程和线程,以及一些其他的相关概念。原书中在第一章并没有作过度的讲解。

进程与线程

20世纪60年代提出进程概念,让进程作为计算机系统进行资源分配和运算调度的基本单位。但是,随后人们发现,由于进程是资源的拥有者,创建,撤销和切换进程存在较大时空开销。而且由于对称处理机的出现,使得可以同时有多个运行单位,而多个进程开销过大。所以在20世纪80年代,提出了线程。

现代计算机系统支持超线程(同时多线程)技术。仍然以进程作为资源调度的单位,但是以线程作为运算调度的最小单位。
进程可以说是线程的容器,一个进程中可以有多个线程,这些线程共享进程的资源。线程之间是相互独立的,线程的数据存放在各自的寄存器中,一般来说无法通信,但是也可以通过主存实现。

并发指的是同一时间有多个活动的程序,这个“活动”含义是程序处于已经运行到运行完毕之间。对于只有一个CPU的系统,某个时刻只能有一个程序在运行。而对于有多个CPU的系统,一个时刻可以有多个程序一起运行。这是极容易混淆的几句描述。考虑这样一种情形,在只有一个CPU的处理器上,先打开QQ,再打开IE浏览器,此时我们是否认为一台电脑“同时”执行了两个程序?其实这是一种假象,这种假象是CPU和操作系统共同造成的。CPU将把运行时间划分为一个个的时间片段,把时间片段分给不同的线程,当一个线程执行的时候,其他线程必须处于挂起状态。这就是并发。

并行是指利用并发让程序运行的更快,可以说是并发的一种特殊情况。起初,我并不理解原书中这句话。查阅资料后,觉得很正确。对于集成了多个CPU的处理器(多核处理器),同一时刻多个程序可以真正的一起运行而无不干扰。

程序,进程与线程

程序,是指令,数据及其组织形式的描述。进程是程序的实体。程序是“死”的,只有通过CPU运算才算激活。线程是进程的组成单元,常见的单线程程序都必须包含一个主线程,比如:C语言中main函数就代表当前程序的主线程,JAVA中也一样。多线程编程和并行编程因摩尔定律的失效变得越来越重要了,多核CPU也是未来的主流(如果没有新的CPU制造技术发明的话)。

并行的层次

这是对原书对应内容的总结。

  1. 线程级并发

建立在进程这个抽象层次。

  1. 指令级并行

建立在指令层次。简单来说,就是一个CPU同时运行多条指令。如果一个处理器在一个时钟周期可以执行超过一条指令,就属于超标量执行。

  1. 单指令,多数据并行

建立在硬件层次。一条指令,可以产生多个并行的操作。

发布了14 篇原创文章 · 获赞 0 · 访问量 725

猜你喜欢

转载自blog.csdn.net/qq_38878217/article/details/104737456