深入理解计算机系统 读书笔记1
汇编语言:一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程。由于汇编更接近机器语言,能够直接对硬件进行操作,生成的程序与其他的语言相比具有更高的运行速度,占用更小的内存,因此在一些对于时效性要求很高的程序、许多大型程序的核心模块以及工业控制方面大量应用。(https://zh.wikipedia.org/wiki/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80)
hello程序的生命周期是从一个源程序开始的,即程序员利用编辑器创建并保存的文本文件,文件名是hello.c。源程序实际上就是个由值0和1组成的位(bit)序列。8个位被组织成一组,称为字节。每个字节表示程序中的某个文本字符。大部分的现代系统都使用ASCII标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符。
像hello.c这样只由ASCII字符构成的文件称为文本文件,所有其他文件都称为二进制文件。
系统的所有信息——包括磁盘文件,存储器中的程序、存储器中存放的用户数据以及网络上传送的数据,都是由一连串位表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。
C语言与Unix操作系统关系密切。Unix几乎全部都是用C编写的,所以可以很方便地移植到新的机器上,这种特点为C和Unix赢得了更为广泛的支持。C语言的指针是造成困惑和程序错误的一个常见原因。同时,C还缺乏对非常有用的抽象(例如类、对象和异常)的显式支持。Cpp和Java这样针对应用级程序的新程序设计语言解决了这些问题。
hello程序的生命周期是从一个高级C语言程序开始的,因为这种形式能够被人读懂。然后,为了在系统上运行hello.c,每条C语句都必须被其他程序转化为一系列的低级机器语言指令。然后这些指令按照一种可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为可执行目标文件。
unix> gcc -o hello hello.c
预处理阶段-预处理器(cpp)根据以字符#开头的命令,修改C的原始程序。#include<stdio.h>告诉预处理器读取系统头文件stdio.h的内容,并把它插入到程序文本中,结果得到另一个C程序,通常以.i作为文件扩展名。
编译阶段-编译器(ccl)将hello.i转换为汇编语言程序。不同语言编译器产生的输出文件用的都是一样的汇编语言。
汇编阶段-汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序(relocatable object program)的格式,并将结果保存在hello.o中,一个二进制文件,其字节编码是机器指令而不是字符,如果在文本编辑器中打开hello.o,看到的将是一堆乱码。
链接阶段-printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到hello.o程序中。链接器(ld)就负责处理这种合并。结果得到hello文件,一个可执行目标文件,可以被加载在内存中,由系统执行。
unix> ./hello
Shell(外壳)是一个命令行解释器,它输入一个提示符,等待你输入一个命令行,然后执行这个命令。如果该命令行的第一个单词不是一个内置的外壳命令,那么外壳就会假设这是一个可执行文件的名字,它将加载并运行这个文件。
系统硬件组成
1.总线:贯穿整个系统的一组电子管道。携带信息字节并负责在各个部门间传递。通常总线被设计成传送定长的字节块,也就是字(word)。字中的字节数(即字长)是一个基本的系统参数,各个系统不尽相同,大多数是四个字节(32位)/八个字节(64位)。为讨论方便,假设字长为4个字节,并且总线每次只传送一个字。
2.I/O设备:输入/输出设备是系统与外部世界的联系通道。示例中的包括——作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据和程序的磁盘驱动器(即磁盘)。最初,可执行程序hello就存放在磁盘上。每个I/O设备都通过一个控制器或适配器与I/O总线相连。控制器和适配器之间的区别主要在于其封装方式。控制器是置于I/O设备本身的或者系统的主印制电路板(主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。其功能都是在I/O总线和I/O设备之间传递信息。
3.主存:一个临时存储设备。在处理器执行程序时,用来存放程序和程序处理的数据。物理上说,是由一组动态随机存取存储器DRAM芯片组成的。逻辑上说,存储器是一个线性数组,每个字节都有其唯一的地址(即数组索引),这些地址从0开始。一般而言,组成程序的每条机器指令都由不同数量的字节构成。与C程序变量相对应的数据项的大小是根据类型变化的。例如,在运行Linux的IA32机器上,short类型的数据需要2个字节,int、float和long类型需要4个字节,double类型需要8个字节。
4.处理器(CPU):即中央处理单元,是解释(或执行)存储在主存中指令的引擎。处理器的核心是一个字长的存储设备(或寄存器),称为程序计数器(PC)。在任何时刻,PC都指向主存中的某条机器语言指令(即含有该条指令的地址)。从系统通电开始直到断电,处理器一直在不断执行PC指向的指令,再更新PC,使其指向下一条指令。处理器按照非常简单的指令执行模型来操作,这个模型是由指令集结构决定的。在这个模型中,指令按照严格的顺序执行,而执行一条指令包含一系列的步骤。处理器按照PC指向的存储器处读取指令,解释指令中的位,执行该指令指示的简单操作,然后更新PC,而这条指令并不一定和存储器中刚刚执行的指令相邻。然而并非所有操作都是简单如此,大多数操作围绕着主存、寄存器文件(register file)和算数/逻辑单元(ALU)进行。寄存器文件是一个小的存储设备,由一些1字长的寄存器组成,每个寄存器都有唯一的名字。ALU计算新的数据和地址值。一些操作如加载(把一个字节/字从主存复制到寄存器,以覆盖寄存器原来的内容),存储(把一个字节/字从寄存器复制到主存的某个位置,以覆盖这个位置原来的内容),操作(把两个寄存器的内容复制到ALU,令ALU对这两个字做算术操作,并将结果存放到一个寄存器中覆盖原先内容),跳转(从指令本身抽取一个字,并将这个字复制到PC中,以覆盖PC中原来的值)。指令集结构描述的是每条极其代码指令的结果,而微体系结构描述的是处理器实际上如何实现。
高速缓存存储器:为解决处理器和主存之间的差异所用的更小更快的存储设备所用的,作为暂时的集结区域,以存放处理器近期可能会需要的信息。达到数万字节的L1高速缓存,访问速度几乎和访问寄存器一样快,其使用的是静态随机访问存储器(SRAM)的硬件技术实现的。
操作系统:可以看作是应用程序和硬件之间的一层软件。所有应用程序对硬件的操作尝试都必须通过操作系统。操作系统的基本功能1)防止硬件被失控的应用程序滥用;2)向应用程序提供简单一致的机制来控制复杂又通常大相径庭的低级硬件设备。
进程:操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件。而并发运行,是指一个进程的指令和另一个进程的指令是交错执行的。传统系统在一个时刻只能执行一个程序,而先进的多核处理器可以同时执行多个程序。无论是单核还是多核系统中,一个CPU看上去都像是在并发地执行多个进程,这是通过处理器在进程间切换实现的。操作系统实现这种交错执行的机制称为上下文切换。操作系统保持跟踪进程运行所需的所有状态信息,这种状态即上下文,包括许多信息,如PC和寄存器文件的当前值,以及主存的内容。
线程:尽管通常我们认为进程只有单一的控制流,但在现代系统中,一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型,因为多线程之间比多进程间更容易共享数据,也因为线程一般都比进程更高效。当有多处理器可用的时候,多线程也是一种使程序可以更快运行的办法。
虚拟存储器:是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用内存。每个进程看到的是一致的存储器,称为虚拟地址空间。Linux中,地址空间最上面的区域是为操作系统中的代码和数据保留的,这对所有进程都是一样的。地址空间的底部区域存放用户进程定义的代码和数据。
程序代码和数据:对所有进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化的,在示例中就是可执行文件hello。
堆:代码和数据区后紧随着的是运行时堆。代码和数据区在一开始运行时就被规定了大小,与此不同,当调用如malloc和free这样的C标准库函数时,堆可以在运行时动态地扩展和收缩。
共享库:在地址空间约中间部分是一块用于存放像C标准库和数学库这样共享库代码和数据的区域。
栈:用于编译器实现函数调用。和堆一样在程序执行期间可动态扩展和收缩。每次调用一个函数时,栈会增长。从一个函数返回时,栈会收缩。
内核虚拟存储器:内核总是驻留在内存中,是操作系统的一部分。地址空间顶部区域是为内核保留的,不允许应用程序读写这个区域的内容或直接调用内核代码定义的函数。
文件:即字节序列。每个I/O设备,包括磁盘、键盘、显示器、网络,都可视为文件。系统中所有输入输出都是通过使用一小组称为Unix I/O的系统函数调用读写文件实现的。
并发(concurrency)和并行(parallelism):并发指一个同时具有多个活动的系统,并行指的是用并发使一个系统运行更快。
超线程(hyperthreading):有时称为同时多线程(simultaneous multi-threading),是一项允许一个CPU执行多个控制流的技术。涉及CPU某些硬件有多个备份,比如程序计数器和寄存器文件,而其它硬件部分只有一份,如执行浮点算术运算的单元。常规处理器需要约20000个时钟周期做不同线程间的转换,而超线程的处理器可以在单个周期的基础上决定要执行哪一个线程,这使得CPU能够更好地利用它的处理资源。
指令集并行:在较低的抽象层次上,现代处理器可以同时执行多条指令的属性。如果处理器可以达到比一个周期一条指令更快的执行效率,就称之为超标量(superscaler)处理器。
单指令、多数据并行:在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,即SIMD并行。提供SIMD指令多是为了提高处理影像、声音和视频数据应用的执行速度。虽然有些编译器试图从C程序中自动抽取SIMD并行性,但更可靠的方法是使用编译器支持的特殊向量数据类型来写程序。