Computer_Systems_A_Programmer's_Perspective_读书笔记

- 9 mins

书中存在的错误

简介

我读的是《Computer Systems:A programmer’s Perspective》的中文翻译版《深入理解计算机系统》

Chapter 1: 计算机系统漫游

上述过程是gcc读取源程序文件hello.c,并翻译成可执行目标文件hello。这个翻译过程是分四个阶段完成的。

预处理器(cpp)-> 编译器(ccl)-> 汇编器(as)-> 链接器(ld)

编译系统

1.4.1 系统硬件组成

1.4.2 - 1.10

存储器层次模型

计算机系统分层模型 操作系统提供的抽象表示

进程的上下文切换

进程的虚拟地址空间

Chapter 2 信息的表示和处理

进制对照表

2.1.3 寻址和字节顺序

大端法和小端法

2.1.4 表示字符串

生成一张ascii表

2.1.5 表示代码

2.1.6 布尔代数和环(!!!)

布尔运算

练习题2.8 ~a = 10010110 ~b = 10101010 a & b = 01000001 a | b = 01111101 a ^ b = 00111100

练习题2.9 A. 黑 <-> 白 蓝 <-> 黄 绿 <-> 红紫 蓝绿 <-> 红 B. ??? C. 101 001 101

2.1.7 C中的位级运算

练习题2.10 ??? 第一步:x = a^b , *y = b 第二步:x = a^b , y = a 第三步:x = b , *y = a

练习题2.12 x|m x&m

2.1.8 C中的逻辑运算

&&, ||, !

练习题2.14 !(x^y)

2.1.9 C中的移位运算

<<, >>

2.2 整数表示

练习题2.18 * -8 = 8 * -6 = 10 * -4 = 12 * -1 = 15 * 0 = 0
* 3 = 3

2.2.3 补码编码

2.2.4 有符号和无符号数之间的转换(!!!)

P64-T2U.png

P64-二进制补码到无符号数的转换

2.2.5 C中的有符号与无符号数

练习题:2.20 * 无符号 1 * 有符号 0 (-2147483648在32位机器上会溢出,但会被机器识别成什么数字呢?) * 无符号 0 * 有符号 1 * 无符号 1

2.2.6 拓展一个数字的位表示

练习题:2.21 * 127, 127 * 128, -128 * 255, -1 * 0, 0

2.2.7 截断数字

2.2.8 关于有符号数和无符号数的建议

练习题 2.25 * 因为length - 1,当无符号数和有符号数进行运算时,机器自动将有符号数强制类型转换为无符号数, 这就会造成length - 1 != 0,而是UMax_w * 修改为 (int)length - 1

练习题 2.26 * 当s<t时 ???

2.3 整数运算

2.3.1 无符号加法

练习题2.27

练习题2.28 * 0, 0, 0 * 5, 11, B * 8, 8, 8 * 13, 3, 3 * 15, 1, 1

2.3.2 补码加法

2.3.3 补码的非

练习题 2.33 * 0, 0, 0 * 5, 11, B * 8, 8, 8 * 13, 3, 3 * 15, 1, 1

2.3.4 无符号数乘法

2.3.5 补码的乘法

2.4.2 IEEE浮点表示

标准:`V = (-1)^s * M * 2^E

单精度格式

Chapter3 程序的机器级表达

3.2.1 机器级代码

3.2 程序编码

3.3 数据格式

C语言数据类型在x86-64中的大小

3.4 访问信息

3.4.1 操作数指示符

操作数格式

3.4.2 数据传递指令

3.4.3 数据传送示例

3.4.4 压入和弹出栈数据

3.5 算术和逻辑操作

P165整数算术操作

3.5.1 加载有效地址(load effective address)

3.5.2 一元和二元操作

3.5.3 移位操作

3.5.5 特殊的算术操作

3.6 控制

用jump指令可以改变程序运行的顺序,指令可能依赖于某一次测试的值。编译器必须产生构建在这种低级机制的指令序列, 来控制C语言的控制结构

3.6.1 条件码

3.6.2 访问条件码

条件码(condition code)一般不会直接读取,它一般有三种使用方式

  1. 根据条件码的某种组合,将一个字节设为0或1
  2. 可以条件跳转到程序的某个其他的部分
  3. 可以有条件的传输数据

3.6.3 跳转(jump)指令

3.6.4 跳转指令的编码

我们不需要理解机器代码格式的相关细节,但了解跳转指令的目标如何编码十分重要

3.6.5 用条件控制来实现条件分支(传统方式,低效)

将条件表达式翻译称机器代码,最常用的方式是有条件跳转和无条件跳转组合使用实现跳转

3.6.6 用条件传送来实现条件分支(高效但非常局限)

但不是所有条件表达式都可以用条件传送来编译

GCC只有当分支中是两条加法的时候会使用条件传送,大部分情况下都会使用条件控制,虽然预测错误的开销很大

3.6.7 循环

练习题3.23

A. x -> %rdi, y -> %rcx, n -> %rdx

B. 通过leaq 1(%rcx,%rax),%rax指令消除了对指针变量p和表达式(*p)++间接引用的需求

C. 第4行:compute y = x * x 第5行:compute n = 2 * x 第7行:compute x += y ; (*p)++ 第9行:compare n:n 第10行:If n>0, goto .L2

第一种翻译方法:jump to middle

jump to middle

第二种翻译方法:guarded-do

guarded-do

C语言标准说明,for循环与while循环的代码行为一样(有一个例外,使用continue时)

for循环

3.6.8 switch语句

3.7 过程

对于不同的编程语言,过程的形式多样

机器级语言,过程P调用过程Q,Q执行后返回到P,这些动作包含下面的一个或多个机制:

3.7.1 运行时栈

一种内存管理机制

运行时栈

  • 栈帧(stack frame)
  • 返回地址
  • 被保存的寄存器
  • 局部变量
  • 参数构造区

  • 通过寄存器过程P传递最多6个整数值(也就是指针或整数),但如果过程Q需要更多的参数,P可以在调用Q之前在自己的栈帧里 存储好字节参数

3.7.2 传递控制

3.7.3 数据传送

在x86-64中,过程间的数据传送大部分是通过寄存器实现的。(%rdi, %rsi和其他寄存器)

传递函数参数的寄存器

通过寄存器只可以传最多6个参数,如果一个函数有大于6个的参数,那可以通过栈传递。 前6个参数通过寄存器传递,7~n个参数放入栈中,而第7个参数会在栈顶。看运行时栈对应的部分

通过栈传递参数时,所有的数据大小都向8的倍数看齐。

3.7.4 栈上的局部变量

局部数据必须存放在内存中的情况包括:

  • 寄存器不足够存放所有的本地数据。
  • 对一个局部变量使用地址运算符&,因此必须能够为它产生一个地址。
  • 某些局部变量是数组或结构,因此必须能够通过数组或结构引用被访问到。

3.7.5 寄存器中的局部存储空间

寄存器组是唯一被所有过程共享的资源

为了避免被调用者不会覆盖调用者稍后会使用的寄存器值。x86-64采用了一组统一的寄存器使用惯例,所有的过程(包括程序库)都必须遵守

根据惯例,寄存器%rbx、%rbp和%r12~%r15被划分位被调用者保存寄存器。当过程P调用过程Q时,Q必须保存这些寄存器的值。

所有其他寄存器,除了%rsp都被称为调用者保存寄存器

3.7.6 递归调用

3.8 数组的分配和访问

3.8.1 基本原理

3.8.2 指针计算

3.8.3 嵌套的数组

3.8.4 定长数组

3.8.5 变长数组

3.9 异质的数据结构

3.9.1 结构

C语言的struct声明创建一个数据类型

3.9.3 数据对齐

3.10 浮点代码

AVX浮点体系结构允许数据存储在16个YMM寄存器中

  • YMM寄存器(媒体寄存器)
    • 给个寄存器保存32个字节
    • 低16字节可以作为XMM寄存器来访问
  • 当对标量数据进行操作时,这些寄存器值保存浮点数,而且值使用低32位(float)或64位(double)

3.11.1 浮点传送和转换操作

Chapter 7:链接

链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。

链接可以执行于编译时(compile time),加载时(load time),运行时(run time)

分离编译(separate compilation) * 我们不用将一个大型的应用程序组织成一个巨大的源文件,而是可以将其分解成一个个更小、更好管理的模块。

学习链接知识的好处: 1. 理解链接器将帮助你构造大型程序:缺少库、缺少模块或不兼容库版本引起的链接器错误。 2. 理解链接器将帮助你避免一些危险的编程 3. 理解链接器将帮助你理解语言的作用域是如何实现的:全局变量和局部变量的差别是什么?static一个变量或函数到底意味着什么? 4. 理解链接器将帮助你理解系统的其他重要概念:加载和运行程序、虚拟内存、分页、内存映射。 5. 理解链接器你将能使用共享库

本章的链接知识的讨论基于这样一的环境:一个运行linux的x86-64系统,使用标准的ELF-64(此后简称ELF)目标文件格式(.o

不同的操作系统、ISA、目标文件格式。细节可能不尽相同,但概念是相通的。

7.1 编译器驱动程序

7.2 静态链接

像Linux LD这样的静态链接器(static linker)以一组可重定位的目标文件和命令行参数作为输入,生成一个完全链接的、可加载 和运行的可执行的目标文件作为输出。

为了构造可执行文件,链接器必须完成下面两个重要的任务:

  • 符号解析(symbol resolution)
  • 重定位(relocation)

7.3 目标文件

目标文件有三种形式:

  • 可重定位目标文件
  • 可执行目标文件
  • 共享目标文件

从技术上来说,一个目标模块(object module)就是一个字节序列,而一个目标文件(object file)就是一个以文件形式存放 在磁盘中的目标模块

7.4 可重定位目标文件

典型的ELF可重定位目标文件

7.5 符号和符号表

  • 全局符号:由模块m定义并能被其他模块引用,对应模块m中的非静态的C函数和全局变量
  • 外部符号:由其他模块定义被模块m引用的全局变量,对应其他模块中的非静态的C函数和全局变量
  • 局部符号:由模块m定义,在模块m中任何位置都可见,但不能被其他模块引用,对应模块m中的带static属性的C函数和全局变量

链接器不关注本地非静态变量

练习题7.1 ???

7.6 符号解析

  • 旁注:对C++和Java中链接器符号的重整!!!

7.6.1 链接器如何解析多重定义的全局符号

强符号:函数和已定义的全局变量 弱符号:未定义的全局变量

  • 规则1:不允许有多个同名的强符号
  • 规则2:如果有一个强符号和多个弱符号,那么选择强符号
  • 规则3:如果有多个弱符号同名,那么从这些弱符号中任意选择一个

7.6.2 与静态库链接

静态库(static library)

7.6.3 链接器如何使用静态库来解析引用

7.7 重定位

重定位由两步组成:

  • 重定位节和符号定义
  • 重定位节中的符号引用

7.7.1 重定位条目

ELF定义了32种不同的重定位类型,我们只关注最基本的两种

  • R_X86_64_PC32:重定位一个使用32位PC相对地址的引用。
  • R_X86_64_32:重定位一个使用32位绝对地址的引用。

7.7.2 重定位符号引用

7.8 可执行目标文件

7.9 加载可执行目标文件

  • 加载器(loader)

Linux x86-64运行时内存映射

7.10 动态链接共享库

静态库缺陷:

  1. 静态库和所有软件一样需要定期维护和更新
  2. 几乎每个C语言程序都使用标准的I/O函数,比如printfscanf,在运行时,这些函数的代码会被复制到每个 运行进程的文本段中,在一个运行上百个进程的典型系统上,对稀缺的内存资源来说是极大的浪费。

共享库(shared library)是解决静态库缺陷的一个现代创新产物

共享库是一个目标模块,在运行和加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程叫做 动态链接(dynamic linking),是由一个动态链接器(dynamic linker)的程序来执行的。共享库也称共享目标, 在linux中一般使用.so后缀表示,在windows中成为DLL(动态链接库)

动态链接过程

动态链接器通过执行下面的重定位完成链接任务

  • 重定位libc.so的文本和数据到某个内存段
  • 重定位libvector.so的文本和数据到另一个内存段
  • 重定位prog21中所有对由libc.so和libvector.so定义的符号的引用 最后,动态链接器将控制传递给应用程序。

7.11 从应用程序中加载和链接共享库

7.12 位置无关代码

多个进程如何共享程序的一个副本?

可以加载而无需重定位的代码称为位置无关代码(Position-Independent Code,PIC)

  1. PIC数据引用
    • 全局偏移量表(Global Offset Table,GOF)
  2. PIC函数调用
    • 延迟绑定(lazy binding),将过程地址绑定推迟到第一次调用该过程时
    • 延迟绑定是通过两个数据结构之间简洁但又有些复杂的交互来实现的
      • GOT:数据段的一部分
      • 过程链接表(Procedure Linkage Table,PLT):代码段的一部分

![用PLT和GOF调用外部函数][P527]

7.13 库打桩机制(library interpositioning)

Chapter 8:异常控制流

  • 控制转移(control transfer)
  • 控制流(control flow)
  • 异常控制流(Exceptional Control Flow,ECF)
    • 异常控制流会发生在计算机系统的各个层次。
      • 在硬件层,硬件检测到的事件会触发控制突然转移到异常处理流。
      • 在操作系统层,内核通过上下文切换将控制一个用户进程转移到另一个用户进程。
      • 在应用层,一个进程可以发送信号到另一个进程,而接受者会将控制突然转移到它的一个信号处理程序。

作为程序员理解ECF(Exceptional Control Flow)非常重要,原因如下:

  • 理解ECF将帮助你理解重要的系统概念
  • 理解ECF将帮助你理解应用程序是如何与操作系统交互的
  • 理解ECF将帮助你编写有趣的新应用程序
  • 理解ECF将帮助你理解并发
  • 理解ECF将帮助你理解软件异常是如何工作。软件异常允许程序进行非本地跳转来响应错误情况

8.1 异常

异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。

状态变化称为事件(event)

8.1.1 异常处理

异常表的起始地址放在一个叫做异常表基址寄存器(exception table base register)的特殊CPU寄存器里

生成异常处理程序的地址

8.1.2 异常的类别

异常可以分为四类:中断(interrupt)、陷阱(trap)、故障(fault)和终止(abort)。

除了interrupt其他属于faulting instruction(故障指令)

异常的类型

1.中断(interrupt)

interrupt是异步的,是来自处理器外部的I/O设备的信号的结果。硬件终端不是由任何一条专门的指令造成的,从这个意义上 来书interrupt是异步的。硬件interrupt的异常处理程序常常成为中断处理程序(interrupt handler)

2.陷阱(trap)和系统调用(system call)

从程序员的角度看,系统调用和普通函数调用是一样的。但并不完全相同,普通函数运行在用户模式中,系统调用 运行在内核模式

3.故障(fault)

故障是由错误引起的。

故障处理

一个典型的fault示例是缺页异常,当指令引用一个虚拟地址,而与该地址相对应的物理页面不在内存中,因此必须从磁盘中 取出时,就会发生fault。

4.终止(abort)

8.1.3 Linux/x86-64系统中的异常

1.Linux/x86-64故障和终止

  • 除法错误
  • 一般保护故障
  • 缺页
  • 机器检查

2.Linux/x86-64系统调用

8.2 进程

异常是允许操作系统内核提供进程(process)概念的基本构造块。process是计算机科学中最深刻、最成功的概念之一。

进程的经典定义就是一个执行中程序的实例。系统的每个程序都运行在某个进程的上下文(context)中。

我们关注进程提供给应用程序的关键抽象:

  • 一个独立的逻辑控制流,它提供一个假象,好像我们的进程独占地使用处理器
  • 一个私有的地址空间,它提供一个假象,好想我们的程序独占地使用内存系统

8.2.1 逻辑控制流

抢占(preempted)(暂时挂起)

8.2.2 并发流

一个逻辑流的执行在时间上与另一个流重叠,称为concurrent flow(并发流),这两个流称为并发地运行。

  • 并发(concurrent)
  • 多任务(multitasking):一个进程和其他进程轮流运行的概念
  • 时间片(time slice):一个进程执行它的控制流的一部分的每一时间段

注意:并发流的思想与流运行的处理器核数或者计算机数无关

  • parallel flow(并行流):是并发流的一个真子集。两个流并发地运行在不同的处理器核或者计算机上。

8.2.3 私有地址空间

8.2.4 用户模式和内核模式

模式位(mode bit)

当设置了模式位以后,进程运行在内核模式中(有时叫超级用户模式)

没有设置模式位,进程就运行在用户模式中,用户模式中的进程不允许执行特权指令(privileged instruction)

8.2.5 上下文切换

context switch

  1. 保存当前进程的上下文
  2. 恢复某个先前被抢占的进程被保存的上下文
  3. 将控制传递给这个新恢复的进程

scheduling(调度):在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。scheduling是 由内核中称为调度器(scheduler)的代码处理的。

中断也可能引发上下文切换。比如,所有的系统都有某种产生周期性定时器中断的机制,通常为每1毫秒或每10毫秒。 每次发生定时器中断时,内核就能判定当前进程已经运行了足够长的时间,并切换到一个新的进程。

8.3 系统调用错误处理

8.4 进程控制

进程的三种状态

  • 运行
  • 停止(suspended)
  • 终止

9.6.3 多级页表

  • 一个32位地址空间、4KB页面和一个4字节的PTE

Dawson Lee

A man who loves to watch Korean dramas

© 2019 DAWSON LEE
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora