本文参考《深入理解计算机系统》机械工程出版社
文章目录
Chapter 1 计算机系统漫游
1.1 引入案例hello.c
代码
#include <stdio.h>
int main()
{
printf ("hello world\n");
return 0;
}
编译过程
运行hello程序 P7
1.2 重要概念
1.2.1 系统的主要硬件
- 总线
- I/O设备
- 主存
- 处理器
1.2.2 存储设备的层次结构
1.2.3 进程、虚拟内存和文件
1.2.4 系统间网络通信
1.2.5 并发和并行
并行是指两个或者多个事件在同一时刻发生;
并发是指两个或多个事件在同一时间间隔内发生。
在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。
倘若在计算机系统中有多个处理器,则这些可以并发执行的程序便可被分配到多个处理器上,实现并行执行,即利用每个处理器来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
并行可以在计算机系统的多个抽象层次上运用
由高到低的三个层次
- 线程级并发:一个进程执行多个控制流(多个CPU)
- 指令级并行:CPU同时执行多条指令
- 单指令、多数据并行:允许一个指令产生多个可以并行执行的操作
1.2.6 计算机系统中的抽象
Chapter 2 信息的表示和处理
Chapter 3 程序的机器级表示
ATT 与 Intel 汇编代码格式的不同
- Intel 代码省略了指示大小的后缀,我们看到指令 push 和 mov,而不是 pushq 和 movq
- Intel 代码省略了寄存器名字前面的 ’%‘ 符号,用的是 rbx,而不是 %rbx
- Intel 代码用不同的方式来描述内存中的位置,例如是 ’QWORD PTR [rbx]‘ ,而不是 ’(%rbx)‘。
- 在带有多个操作数的指令情况下,列出操作数的顺序相反。当在两种格式之间进行转换的时候,这一点非常令人困惑。
Chapter 4 处理器体系结构
4.1 基本概念
本章主要介绍一个处理器的硬件和指令,以及处理器的工作原理
本章定义了一个ISA为“Y86-64”指令集,数据类型、指令、寻址方式都比x86少些,设计它的CPU译码逻辑也比较简单,但足够完整。
指令集体系结构ISA
一个处理器支持的指令和指令的字节级编码
ISA在编译器编写者和处理器设计人员之间提供了一个概念抽象层,编译器编写者只需要知道允许哪些指令,以及它们是如何编码的;而处理器的设计者必须建造出支持这些指令的处理器。
4.2 Y86-64指令集体系结构
定义一个ISA,包括定义
- 各种状态单元
- 指令集
- 指令集编码
- 一组编程规范和异常事件处理
程序员可见的状态:可修改的处理器的某些部分
- 15个程序寄存器:%rax %rbx %rcx %rdx %rsp %rbp %rsi %rdi %r8 %r14
- 3个一位条件吗:ZF、SF、OF
- 程序计数器PC
- DMEM内存
- 状态码Stat:表明程序执行的总体状态,会指示是正常运行,还是出现了某种异常
指令
- 4个movq指令:irmovq、rrmovq、mrmovq、rmmovq。显式地指明源和目的操作数。第一个字母表明源类型,可以是立即数(i)、寄存器(r)和内存(m),第二个字母表示目的类型,可以是寄存器(r)或内存(m)
内存传送指令中的内存引用方式是简单的基址+偏移量形式
不允许内存间直接传数据,也不允许将立即数传送到内存 - 4个整数操作指令:addq、subq、andq、xorq。只能对寄存器数据进行操作,x86可对内存操作,这些指令会设置3个条件码ZF、SF、OF
- 7个跳转指令:jmp、jle、jl、je、jne、jge、jg。
- 6个条件传送指令:cmovle、cmovl、cmove、cmovne、cmovge、cmovg。
- call指令:将返回地址入栈,然后跳到目的地址。
ret指令:从调用中返回 - pushq和popq:入栈和出栈
- halt:停止指令的执行,并将状态码设置为HLT
指令编码
见书P246
异常
状态码Stat
值 | 名字 | 含义 |
---|---|---|
1 | AOK | 正常操作 |
2 | HLT | 遇到器执行halt指令 |
3 | ADR | 遇到非法地址 |
4 | INS | 遇到非法指令 |
4.3 逻辑设计和硬件控制语言HCL
在当前技术中,逻辑1是用1.0伏特左右的高电压表示的,逻辑0是用0.0伏特左右的低电压表示的。
要实现一个数字系统需要三个主要的组成部分
- 计算对位进行操作的函数的组合逻辑
- 存储位的存储器单元
- 控制存储器单元更新的时钟信号
现代逻辑设计
- HDL:一种文本表示,用来描述硬件结构而非程序行为的
- Verilog:语法类似C
- VHDL:语法类似Ada
组合逻辑
存储器和时钟
组合电路从本质上讲,不存储任何信息。它们只是简单地响应输入信号,产生等于输入的某个函数的输出。
为了产生时序电路(sequential circuit),就是有状态并且在这个状态上进行计算的系统,我们必须引入按位存储信息的设备。
存储设备都是由同一个时钟控制的。
两类存储设备
- 时钟寄存器(简称寄存器):存储单个位或字,由时钟信号控制。
- 随机访问存储器(简称内存):存储多个位或字
4.4 Y86_64的顺序实现(SEQ)
每个时钟周期上,SEQ执行处理一条完整指令所需的所有步骤。不过,这需要很长的时钟周期时间,因此时钟周期频率会低到不可接受。我们的最终目的是实现一个高效的、流水线化的处理器。
处理一条指令的各个阶段
- 取指(fetch)
- 译码(decode)
- 执行(execute)
- 访存(memory)
- 写回(write back)
- 更新PC(PC update)
SEQ阶段的硬件实现P272
4.5 Y86_64的流水线实现
流水线原理
不需要完全将当前指令执行完成后再执行下一条指令,而是在当前指令结束某个阶段后,下一条指令便开始这个阶段。
流水线化提高了系统吞吐量(throughput)
流水线是一种优化的顺序执行系统,可标记为SEQ+
Chapter 5 优化程序性能
5.1 基本概念
程序性能的表示
引入度量标准:每元素周期数(Cycles Per Element,CPE)
5.2 基本优化
妨碍优化的因素
妨碍优化的因素就是程序行为中那些严重依赖于执行环境的方面。
必须消除不必要的函数调用、条件测试、内存引用
这些优化都不依赖于目标机器的任何特性
5.2.1 消除循环的低效率
5.2.2 减少过程调用
5.2.3 消除不必要的内存引用
5.3 处理器微体系结构的优化
两种下界描述了程序的最大性能
- 延迟界限:在一个顺序执行系统中,当代码中的数据相关限制了处理器利用指令级并行的能力时,延迟界限能够限制程序性能。
- 吞吐量界限:刻画了处理器功能单元的原始计算能力,是程序性能的终极限制。
5.3.1 循环展开
5.3.2 提高并行性
AVX指令集
5.4 存储优化
5.5 大型应用场景的优化
Chapter 6 存储器层次结构
6.1 存储技术
随机访问存储器RAM
- 静态RAM(SRAM):作为高速缓存,可以在CPU芯片上,也可以在片下
- 动态RAM(DRAM):作为主存以及图形系统的帧缓冲区
非易失性存储器ROM
- 可编程ROM(PROM):只能被编程一次
- 可擦写可编程ROM(EPROM):用紫外光擦除,可达1000次
- 电子可擦除PROM(EEPROM):能被编程的次数可达 次
闪存是一类非易失性存储器,基于EEPROM,使用该技术的包括数码相机、手机、音乐播放器、PDA和笔记本、台式机、服务器计算机系统。
磁盘
固态硬盘SSD
固态硬盘是一种基于闪存的存储技术。
6.2 局部性
具有良好局部性的程序倾向于一次又一次访问相同的数据项集合,或是倾向于访问邻近的数据项集合。也更倾向于从存储层次结构中的较高层次处访问数据项,因此运行地更快。这种倾向性,被称为局部性原理
局部性的两种不同形式
- 时间局部性:在一个具有良好时间局部性的程序中,被引用过一次的内存位置很可能在不远的将来再被多次引用。
- 空间局部性:在一个具有良好空间局部性的程序中,如果一个内存位置被引用了一次,那么程序很可能在不远的将来引用附近的一个内存位置。
局部性小结
- 重复引用相同变量的程序具有良好的时间局部性
- 对于具有步长为K的引用模式的程序,步长越小,空间局部性越好。
- 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。
6.3 存储器结构层次
缓存的命中和管理
存储器层次结构的概念小结
基于缓存的存储器层次结构行之有效,是因为较慢的存储设备比较快的便宜,还因为程序倾向于展示局部性:
- 利用时间局部性:期望更高的缓存命中率
- 利用空间局部性:期望更小的复制块入缓存的花销
6.4 高速缓存
6.4.1 高速缓存种类
直接映射
全相联
组相联
6.4.2 编写高速缓存友好的代码
关于编写高速缓存友好代码的两个重要问题
- 时间局部性
- 空间局部性
时间局部性和空间局部性越好的程序,对高速缓存就越友好。
基本方法
- 程序通常把大部分时间都花在核心函数上,而这些函数通常把大部分时间都花在了少量循环上。所以要把注意力集中在核心函数里的循环上,而忽略其他部分。
- 尽量减少每个循环内部的缓存不命中数量
6.4.3 高速缓存对程序性能的影响
存储器山(memory mountain)
Chapter 7 链接
7.1 基本概念
目标文件三种形式
- 可重定位目标文件
- 可执行目标文件
- 共享目标文件
可重定位目标文件
可执行目标文件
7.2 静态链接
静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可加载和运行的可执行目标文件作为输出。
输入的可重定位目标文件由各种不同的代码和数据节(section)组成,每一节都是连续的字节序列。指令在一节中,初始化了的全局变量在另一节中,而未初始化的变量又在另一节中。
链接器必须完成的两个主要任务
- 符号解析(symbol resolution):目的是将每个符号引用和一个符号定义关联起来。
- 重定位(relocation)
7.3 符号解析
符号和符号表
每个可重定位目标文件都有一个符号表,它包含模块定义和引用的符号的信息。在链接器上下文中,有三种不同的符号:
- 由该模块定义并能被其他模块引用的全局符号
- 由其他模块定义并能被该模块引用的全局符号
- 只被该模块定义和引用的局部符号
C程序员使用static属性隐藏模块内部的变量和函数声明。任何带有static属性声明的全局变量或者函数都是模块私有的。
符号解析
函数已经初始化的全局变量是强符号,未初始化的全局变量是弱符号
Linux链接器处理多重定义符号名的规则
- 不允许有多个同名的强符号
- 如果有一个强符号和多个弱符号同名,那么选择强符号
- 如果有多个弱符号同名,那么就从这些弱符号中任意选择一个
实际上最好不要有重名的,很麻烦的!!!!如果确实想在另一个模块引用定义的全局变量,请用extern声明变量
7.4 重定位
一旦链接器完成了符号解析这一步,就把代码中的每个符号引用和正好一个符号定义关联起来。此时链接器就知道它的输入目标模块中的代码节和数据节的确切大小。现在开始重定位步骤,这个步骤中,将合并输入模块,并为每个符号分配运行时地址。
重定位两步
- 重定位节和符号定义
- 重定位节中的符号引用
7.5 静态链接库
所有编译系统都提供一种机制,将所有相关的目标模块打包成一个单独的文件,称为静态库(static library),它可以用作链接器的输入。当链接器构造一个输出的可执行文件时,它只复制静态库里被应用程序引用的目标模块。
和静态链接库链接的过程
7.4 动态链接库
静态库有一些明显的缺点,它需要和每个运行进程(相关的进程)进行链接,把静态库的函数代码复制到这些运行进程的文本段中,对内存资源是个极大的浪费。
共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接,是由一个叫动态链接器的程序来执行的。
动态链接库的链接过程
7.5 库打桩机制
Chapter 8 异常控制流
8.1 基本概念
控制转移
一条指令的地址跳到下一条指令的地址的过渡
控制流
控制转移系列,最简单的控制流是一个“平滑序列”(在这个序列中,下一条指令永远与当前指令相邻)
异常控制流
平滑流的突变(也就是下一条要执行的指令与当前指令不相邻)通常是由诸如跳转、调用和返回这样一些熟悉的程序指令造成的。这些突变称为异常控制流(Exceptional Control Flow,ECF)。
异常控制流发生在计算机系统的各个层次:
- 在硬件层,硬件检测到的事件会触发控制突然转移到异常处理程序
- 在操作系统层,内核通过上下文切换将一个控制从一个用户进程转移到另一个用户进程。
- 在应用层,一个进程可以发送信号到另一个进程,而接收者会将控制突然转移到它的一个信号处理程序。
- 一个程序可以通过回避通常的栈规则,并执行到其他函数中任意位置的非本地跳转来对错误作出反应。
8.2 异常
异常(exception)是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。
在任何情况下,当处理器检测到由事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。当异常处理程序完成处理后,根据引起异常事件的类型,会发生以下情况的一种:
- 处理程序将控制返回给当前指令,即当事件发生时正在执行的指令。
- 处理程序将控制返回个当前指令的下一条指令,如果没有发生异常将会执行下一条指令。
- 处理程序终止被中断的程序。
8.2.1 异常处理
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。其中一些号码是由处理器的设计者分配的,其他号码是由操作系统内核的设计者分配的。
在系统启动时,操作系统初始化一张称为异常表的跳转表,使得表目k包含异常k的处理程序地址。
异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
8.2.2 异常的类别
8.3 进程
进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文中。
进程提供给应用程序的抽象
- 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
- 一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
逻辑控制流
进程给每个程序提供一种它在独占使用处理器的假象,如果单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称逻辑流
一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发地运行。多个流并发地执行的一般现象称为并发(concurrency)。一个进程和其他进程轮流执行的概念称为多任务。一个进程执行它的控制流的一部分的每一个时间段叫做时间片。因此,多任务也叫时间分片。
如果两个流并发地运行在不同的处理器核上或者计算机上,那么我们称它们为并行流,它们并行地运行,并且并行地执行。
私有的地址空间
进程控制
- 获取进程ID
- 创建和终止进程
- 回收子进程
- 让进程休眠
- 加载并运行程序
- 利用fork和execve运行程序
8.4 信号
本节研究一个更高层的软件形式的异常,称为Linux信号,它允许进程和内核中断其他进程。
Chapter 9 虚拟内存
虚拟内存是OS作为内存使用的一部分硬盘空间。虚拟内存在硬盘上其实就是为一个硕大无比的文件,文件名是PageFile.Sys,通常状态下是看不到的。必须关闭资源管理器对系统文件的保护功能才能看到这个文件。虚拟内存有时候也被称为是“页面文件”就是从这个文件的文件名中来的。
虚拟内存提供了三个重要能力
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
- 它为每个进程提供了一致的地址空间,从而简化了内存管理。
- 它保护了每个进程的地址空间不被其他进程破坏。
9.1 基本概念
虚拟寻址
早期PC使用物理寻址,诸如数字信号处理器、嵌入式微控制器和Cray超级计算机这样的系统。然而,现代处理器使用的是一种成为虚拟寻址的寻址形式。
地址翻译
使用虚拟地址,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。将一个虚拟地址转换成物理地址的任务叫做地址翻译。地址翻译需要CPU硬件和操作系统之间的紧密合作。CPU芯片上叫做 内存管理单元(MMU) 的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表内容由OS管理。
地址空间
地址空间是一个非负整数地址的有序集合:{0,1,2,…}
在一个带虚拟内存的系统中,CPU从一个有
个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间:{0,1,2,…,N-1}。一个地址空间的大小是由表示最大地址所需要的位数来描述的,例如,这个虚拟地址空间就叫做一个n位地址空间。
一个系统还有一个物理地址空间,对应于系统中物理内存的M个字节:{0,1,2,…,M-1}
9.2 虚拟内存的工作原理
虚拟内存被组织为一个存放在磁盘上的N个连续的字节大小的单元组成的数组。每个字节都有唯一的虚拟地址,作为到数组的索引。磁盘上的数组内容被缓存在主存中。按照存储器层次结构原理,磁盘(低层)上的数据被分割成块,这些块作为磁盘和主存(高层)之间的传输单元。虚拟内存中被分割成的固定大小的块叫虚拟页(VP),每个虚拟页大小为 字节。类似,物理内存被分割为物理页(PP),也称页帧,大小也为 字节。
在任意时刻,虚拟页面的集合都分为三部分:
- 未分配的
- 缓存的:已经缓存在物理内存的已分配页
- 未缓存的:未被缓存在物理内存的已分配页
页表
一个存放在物理内存中的叫页表的数据结构,将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换成物理地址时,都会读取页表。OS负责维护页表内容,以及在磁盘与DRAM(主存)之间来回传送页。
页表就是一个页表条目(PTE) 的数组,虚拟地址空间中的每个页在页表中的一个固定偏移量处都有一个PTE。我们假设每个PTE是由一个有效位和一个n位地址字段组成的。有效位表明该虚拟页是否已被缓存,如果已被缓存,则地址字段表示DRAM中相应的物理页的起始位置;如果未被缓存,则地址字段指向虚拟页在磁盘上的起始位置。
页命中和缺页
如果需要提取的虚拟地址的字段已被缓存,则会命中。
DRAM缓存不命中就会称为缺页。
虚拟内存的其他作用
- 内存管理
- 内存保护
9.3 地址翻译
地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射,
MAP : VAS
PAS
CPU中有一个控制寄存器,页表基址寄存器(PTBR) 指向当前页表。n位虚拟地址包含两部分:
- 一个(n-p)位的虚拟页号(VPN)
- 一个p位的虚拟页面偏移(VPO)
【图9-12】
当页命中时,CPU执行的步骤:
- 处理器生成一个虚拟地址,并把它传送给MMU
- MMU生成PTE地址,并从高速缓存/主存请求得到它
- 高速缓存/主存向MMU返回PTE
- MMU构造物理地址,并把它传送给高速缓存/主存
- 高速缓存/主存返回请求的数据字给处理器
当发生缺页时,CPU执行的步骤:
1~3步与页命中相同
见书P569
。案例研究
。内存映射
。动态内存分配
。垃圾收集
。C程序中常见的与内存有关的错误