深入理解计算机系统(第三版)笔记
第1章 计算机系统漫游
GitHub笔记地址:https://github.com/loulan-ling/CSAPP3e-Notes
1.1 程序编译流程
编译命令
1 | gcc -E hello.c -o hello.i |
1、源程序文本hello.c
- 源代码文本
1 | #include <stdio.h> |
经过预处理器(cpp)后,会将stdio.h
的代码添加到hello.c
中,这时候程序后缀是.i
- 编译阶段
1 | "hello.c" |
预处理结束后到了编译阶段,编译器(ccl)会将修改了的源程序代码编译为汇编语言,版本不一样的汇编时的代码也不一致
- 汇编阶段
1 | .file "hello.c" |
现在到了汇编阶段,汇编器(as)会将hello.s
翻译为机器语言
- 汇编阶段
使用vim
查看hello.o
只能看到乱码
- 汇编阶段
使用objdump
可以看到十六进制的机器语言
1 | hello.o: file format elf64-x86-64 |
- 5 链接阶段
hello程序调用了printf
函数,该函数是每个C编译器都提供的标准C库中的一个函数。printf
函数存在一个名为printf.o
的单独预编译好了的目的文件中,而这个文件会在链接的过程中合并到我们的hello.o
程序中,连接器(ld)就负责处理这种合并。
1 | gcc -o hello hello.o -Wl,-t |
1.2 可重定位目标程序(relocatable object program)格式
可重新定位目标程序格式是一种中间代码格式,通常由汇编器(as)生成。这种格式的主要作用是支持程序的动态加载和重定向,从而使得程序能够在不同的内存位置上执行。
- 格式结构:
- 机器指令的二进制编码:
- 可重定位目标程序包含了编译后的机器指令,这些指令由汇编器生成,是计算机能够直接执行的代码。
- 位置信息:
- 此格式还包含重定位信息,用于指示在程序最终链接时需要调整的地址。这些信息用户管理不同模式之间的地址依赖,确保程序在加载时可以正确引用。
- 符号表:
- 可重定位目标程序通常会包含符号表,列出所有的函数和全局变量符号,以供后续连接器解析。这使得程序在链接阶段能够顺利处理不同模块之间的符号引用。
- 机器指令的二进制编码:
- 生成过程:
- 预处理: 将源代码进行处理,添加标准库代码,生成预处理文件
- 编译: 将预处理文件翻译位汇编语言
- 汇编: 汇编器将汇编代码转换为机器代码,同时生成可重定位(如hello.o),其中包括机器指令和相关
1.4 处理器读并解释储存在内存中的指令
shell是一个命令行解释器,它输出一个提示符,等待输入一个命令行,然后执行这个命令。如果该命令行的第一个单词不是一个内置的shell命令,那么shell会认为这个命令是一个可执行的文件的名字,它将会加载并运行文件。
1.4.1 系统的硬件组成
系统硬件组成思维导图
1.4.1.1 总线
贯穿整个系统的是一组电子管道,称作总线,它携带信息字节并负责在各个部件间传递。
1.4.1.2 I/O设备
I/O(输出/输入)设备是系统与外部世界的联系通道。
示例中展示的I/O设备:
- 键盘
- 鼠标
- 显示器
- 磁盘
1.4.1.3 主存(内存条)
主存是处理器执行程序时用于临时存储程序及其数据的设备。物理上,主存由动态随机存取存储器(DRAM)芯片组成;逻辑上,它是一个线性的字节数组,每个字节都有唯一的地址。程序指令由不同数量的字节构成,C程序变量的大小取决于其数据类型(例如,在x86-64 Linux系统上,short类型占2字节,int和float字节占4字节,long和double字节占8字节)。这里只是说明程序指令和C程序变量会根据情况变化
1.4.1.4 处理器(CPU)
CPU是计算机系统的核心处理引擎,负责解释和执行存储在主存中的指令
CPU主要组成部件
- 程序计算器(PC)
- 存储当前执行指令的地址
- 决定程序执行的指令流
- 寄存器文件
- 小型存储设备,由单字长寄存器(寄存器是CPU中用于存储数据和指令的小型存储设备)组成
- 每个寄存器都有唯一名称
- 可存储临时数据和指针
- 算术/逻辑单元(ALU)
- 执行算术和逻辑单元
- 计算新的数据和地址值
- 高速缓存(Cache)
- 多级缓存: L1 L2 L3
- 分为指令高速缓存(i-cache)和数据高速缓存(d-cache)
- 提高数据访问速度
- 控制单元(CU)
- 控制指令的解码和执行流程
- 协调各个组件的操作
CPU的基本功能
- 加载: 从主存复制数据到寄存器
- 存储: 从寄存器复制数据到主存
- 操作: 执行算术和逻辑运算
- 跳转: 更新程序计算器
1.4.2 运行hello程序
指令到达寄存器后,CPU就会执行指令。
1.5 高速缓存只关重要
根据上面示例可以看到,运行程序就是将信息从一个地方挪到另一个地方,最后到达处理器。
信息运行速度表格
存储设备类型 | 容量 | 访问时间相对(从处理器访问) | 备注 |
---|---|---|---|
寄存器文件 | 几百字节 | ≈主存的1000倍快 | 用于存储最常用的数据,速度非常快 |
主存(DRAM) | 几十亿字节 | 假设1 | 容量大,但访问速度相对较慢 |
磁盘驱动器 | 大约主存的1000倍 | ≈ 主存的10000000(1000万倍) | 存储容量极大,但访问速度极慢 |
针对处理器和主存之间的差异,系统设计者采用了更小更快的存储设备,成为高速缓存存储器。
- L1高速缓存位于处理器芯片上,容量是数万字节,速度仅比寄存器慢
- L2高速缓存通过一条特殊的总线连接处理器,容量为数十万到数百万字节,速度比L1慢5倍,但是依然比主存快5~10倍
1.6 存储设备形成层次结构
看图
1.7 操作系统管理硬件
操作系统是介于应用程序和硬件之间的软件
操作系统有两个基本功能:
- 防止硬件被失控应用程序滥用
- 向应用程序提供简单一致的机制控制不同的硬件设备
操作系统通过几个基本的抽象概念(进程、虚拟内存和文件)来实现这两个功能。
文件只是对I/O设备的抽象表示
虚拟内存是对主存和磁盘I/O设备的抽象表示
进程则是对处理器、主存和I/O设备的抽象表示
1.7.1 进程
一个处理器可以运行多个进程,每个进程在一个线性上来回运行不同的程序,操作系统内核管理进程时,会为每个进程提供一个假象,就是每个进程都能独自占有整个处理器,处理器也只有占有的那么大。
关键点:
- 并发执行: 多个进程可以在同一个系统上并发运行,这意味着一个进程的指令和另一个进程的指令交错执行。
- 多任务: 操作系统使用多任务处理(或时间片)技术,将处理器时间划分时间片,给每个进程安排一个时间片,让进程在时间片内运行(如果时间片时间到了,进程还没结束将会保留上下文切换),通过快速切换进程,操作系统可以实现多进程同时运行的效果。
- 上下文切换: 当操作系统决定切换到另一个进程时,它会保存当前上下文信息(例如, 程序、计数器、寄存器内容),然后恢复上下文。
1.7.2 线程
每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
线程切换上下文的速度比进程切换上下文速度快很多,因为线程之间会共享大部分的信息。线程切换上下文只需保存线程独有的信息,例如:栈、寄存器等。
多线程一般来说是比多进程更高效。
1.7.3 虚拟内存
虚拟内存是一个抽象概念,它为每个进程提供一个假象,即每个进程都在独占使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间。
- 程序代码和数据。对所有的进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置。
- 堆。代码和数据区后紧随着的是运行时堆。堆可以在运行时动态的扩展和收缩。
- 共享库。地址空间的中间部分有一个块存放像C标准库和数学库这样的共享库的代码和数据的区域。
- 栈。每次调用函数时,栈就会增长,从一个函数返回时,栈就会收缩。
- 内核虚拟内存。地址空间顶部的区域是为内核保留。
1.7.4 文件
文件就是字节序列。每个I/O设备,包括磁盘、键盘、显示器,甚至是网络,都可以看成是文件
。Linux系统上的所有东西都是文件,因此Linux可以称为文件操作系统。
1.8 系统之间利用网络通信
跟上面的hello运行差不多,只是输入地方和输出地方不同
笔记/Pasted image 20250724181127.png)
1.9 重要主题
系统不仅仅只是硬件。系统是硬件和系统软件互相交织的集合体,它们必须共同协作以达到运行应用程序的最终目的。
1.9.1 Amdahl定理
Amdahl定律的主要观点–想要显著加速整个系统,必须提升全系统中相当大部分的速度.
练习题1:
行程中固定消耗10个小时也就是1000公里,因此2500-1000=1500公里,我们只能在这1500公里作文章.
25小时/1.67=15小时,15-10=5小时
现在通过蒙大拿州的时间只有5小时,1500/5=300,因此我们通过蒙大拿的时速为300公里
练习题2:
Amdahl公式:
$$
S_\infty = \frac{1}{(1 - \alpha)+\alpha/k}
$$
已知:性能加速是2X,可以加速部份为80%,那么公式表达如下:
$$
2_\infty = \frac{1}{(1 - 0.8)+0.8/k}
$$
- 两边取倒数(互换分子和分母):1/2 = (1−0.8) + 0.8/k
- 代入 (1−0.8)=0.2:
- 1/2 = 0.2 + 0.8/k
- 两边同时减去 0.2:
- 1/2 − 0.2 = 0.8/k
- 0.5 − 0.2 = 0.8/k
- 0.3 = 0.8/k
- 交叉相乘或两边取倒数解 k:
- k = 0.8 / 0.3 ≈ 2.67
1.9.2 并发和并行
- 线性级并发
- 传统意义上,并发执行只是模拟出来,是通过使用一台计算机在它正在执行的进程间快速切换来实现的。
- 指令级并行
- 在较低层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。
- 单指令、多数据并行
- 在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据。即SIMD并行。
1.9.3 计算机系统中抽象的重要性
抽象的使用是计算机科学中最为重要的概念之一。
- 文件是对I/O设备的抽象
- 虚拟内存是对程序存储器的抽象
- 进程是对一个正在运行程序的抽象
- 虚拟机是对整个计算机的抽象,包括操作系统、处理器和程序。
1.10 小结
- 计算机系统是由硬件和系统软件组成的,它们共同协作以运作应用程序。计算机内部的信息被表示为一组组的位,它们根据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始时是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。
- 处理器读取并解释存放在主存里的二进制文件。因为计算机花费大量的时间在内存、I/O设备和CPU寄存器之间复制数据,所以将系统中的存储设备划分层层级结构——CPU寄存器在顶级,接着是多层硬件高速缓存存储器、DRAM主存和磁盘存储器。在层级模型中,位于更高层的存储设备比低层的存储设备更快,单位比特造价也更高。层次结构中较高层次的存储设备可以作为较低层次设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化C程序的性能。
- 操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:
- 文件是I/O的抽象
- 虚拟内存是对主存和磁盘的抽象
- 进程是处理器、主存和I/O设备的抽象
- 网络也是一种I/O设备
2.1 信息存储
大多数计算机使用8位的字节(byte),字节作为可通过虚拟内存地址找到的内存单位,而不是访问内存中单独的位。
机器级程序(对计算机发出指令的程序)将内存视为一个非常大的字节数组,称为虚拟内存(virtual memory)。虚拟内存中的每个字节都由一个唯一的数字标识,这个数字被称为地址(address),所有可能地址的集合就称为虚拟地址空间(virtual address space)。
虚拟地址空间是展现机器级程序的抽象概念,实际的实现是将动态随机访问存储器(DRAM)、闪存、磁盘存储器、特殊硬件和操作系统结合起来,给程序提供一个字节数组。
每个程序对象都可以简单地视为一个字节块,而程序本身就是一个字节序列。
2.1.1 十六进制表达法
一个字节有8位组成。
在C语言中,以0x或0X开头的数字常量被认为是十六进制的值。
十六进制转二进制时,如果总数不是4的倍数,在最左边的一组可以少于4位,前面用0补足。
练习题 2.1 完成下面的转换:
1 | A. 将 Ox39A7F8 转换为二进制。 |
A.
1 | 分解 |
B.
1 | 1100 1001 0111 1011 |
C.
1 | 分解 |
D.
1 | 前面不足4位,补位加到4位 |
练习题 2.2
N | 2N(十进制) ) | 2N(十六进制) | |
---|---|---|---|
9 | 512 | 0x200 | |
19 | 524288 | 0x80000 | |
14 | 16384 | 0x4000 | |
16 | 65536 | 0x10000 | |
17 | 131072 | 0x20000 | |
5 | 32 | 0x20 | |
7 | 128 | 0x80 |
1 | 解: |
练习题 2.3
十进制 | 二进制 | 十六进制 |
---|---|---|
0 | 0000 0000 | 0x00 |
167 | 1010 01111 | 0xA7 |
62 | 0011 1110 | 0X3E |
188 | 1011 1100 | 0XBC |
55 | 0011 0111 | 0x37 |
136 | 1000 1000 | 0x88 |
243 | 1111 0011 | 0xf3 |
82 | 0101 0010 | 0x52 |
172 | 1010 1100 | 0xAC |
231 | 1110 0111 | 0xE7 |
解: | ||
十进制转二进制、十六进制 |
1 | 167=10x16...7 |
二进制转十六进制、十进制
1 | 0011 0111 |
十六进制转二进制、十进制
1 | Ox52 |
练习题 2.4
A.0x503c+0x8=0x5044
B.0x503c-0x40=0x4ffc
C. 0x503c+64=0x507c
D.0x50ea-0x503c=0xAE
使用小学教的加减法,不过进位数是16即可。