CSAPP(深入理解计算机系统学习笔记)

CSAPP目录

  • 第一章 计算机系统漫游
    • 1.1 信息就是位+上下文
    • 1.2 程序被其他程序翻译成不同的格式
    • 1.3 了解编译系统如何工作是大有益处的
    • 1.4 处理器读并解释存储在存储器中的指令
      • 1.4.1 系统的硬件组成
      • 1.4.2 运行hello程序
    • 1.5 高速缓存至关重要
    • 1.6 存储设备形成层次结构
    • 1.7 操作系统管理硬件
      • 1.7.1 进程
      • 1.7.2 线程
      • 1.7.3 虚拟存储器
      • 1.7.4 文件
    • 1.8 系统之间利用网络通信
    • 1.9 重要主题
      • 1.9.1 并发和并行
      • 1.9.2 计算机系统中抽象的重要性
  • 第二章 信息的表示和处理
  • 第三章 程序的机器级表示
  • 第四章 处理器体系结构
  • 第五章 优化程序性能
  • 第六章 存储其层次结构
  • 第七章 链接
  • 第八章 异常控制流
  • 第九章 虚拟存储器
  • 第十章 系统级I/O
  • 第十一章 网络编程
  • 第十二章 并发编程
  • 附录A 错误处理

第一章 计算机系统漫游

本章是对整本书的概览

1.1 信息就是位+上下文

每一个c语言程序的生命周期都是由一个以.c为后缀的源文件开始的,每一个这样的源文件实际上都是有0和1组成的位(bit)序列,8个位被组织成一组,称为字节,每个字节表示程序中的某个相应文本字符。
大部分的现代系统都使用ASCII标准来表示文本字符(即ASCII码表),这种方式就是用一个唯一的单字节大小(8 bit,00000000 - 11111111, 即十进制的 0 - 127)的整数值来表示每个字符。
CSAPP(深入理解计算机系统学习笔记)_第1张图片
hello.c程序

#include 
int main()
{
	printf("hello, world\n");
}

hello.c程序的ASCII码表示

# i n c l u d e < s t d i o .
35 106 110 99 108 117 100 101 32 60 115 116 100 105 111 46
h > \n i n t m a i n ( ) \n { \n
104 62 10 105 110 116 32 109 97 105 110 40 41 10 123 10
p r i n t f ( " h a l l
32 32 32 32 112 114 105 110 116 102 40 34 104 101 108 108
o , w o r l d \ n " ) ; \n }
111 44 32 119 111 114 108 100 92 110 34 41 59 10 125

像这种hello.c的源程序以字节序列的方式存储在文件中。每个字节都有一个整数值(在计算机中是以8位二进制形式存在),而该整数值对应于某个字符。注意,每个文本行都是以一个不可兼得换行符“\n”来结束的,它所对应的整数值为10。这种只有ASCII字符构成的文件称为文本文件,所有其他的文件都成为二进制文件。
hello.c的表示方法说明了一个基本思想:系统中所有的信息——包括磁盘文件、存储器中的程序、存储器中存放的用户数据以及网络上传送的数据,都是有一串位表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文,这里上下文的意思是相同的东西在不同的地方表示不同的含义,比如,在不同的上下文中,一个同样的字节序列,既可能表示一个机器指令,也可能表示一个字符串。

最后来看一下本节标题,计算机中存储的所有东西都可以说是信息(包括文件、程序、数据等),而信息在计算机中全部由一串位表示(即01组成的序列),区分不同信息只能靠读信息的上下文,所以位+上下文可以唯一确定计算机中一个具体的信息。

1.2 程序被其他程序翻译成不同的格式

hello程序的源文件虽然能够被人读懂,但为了能让它在系统上运行,每条c语句都要被其他程序转化为一系列的低级机器语言指令,然后这些指令按照一种可执行目标程序的格式打包好并以二进制磁盘文件的形式存放起来,才能在系统中运行。
在unix系统上,从源文件到目标文件的转化是由编译器驱动程序完成的。

gcc -o hello hello.c

这条命令会让GCC编译器驱动程序读取源程序文件hello.c,并把它翻译成一个可执行目标文件hello,这个翻译过程分为四个阶段:
CSAPP(深入理解计算机系统学习笔记)_第2张图片
1.预处理阶段:
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,比如hello.c文件中第1行#include告诉预处理器读取系统头文件stdio.h中的内容,所以在这个阶段,预处理器(cpp)就会把这个头文件中的内容插入到hello.c程序文本中,从而得到另一个C程序,通常这个新得到的C程序以.i作为文件扩展名。
2.编译阶段:
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,即将C语言程序编译成汇编语言程序
3.汇编阶段:
汇编器(as)将hello.s翻译成机器语言指令,然后把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中,hello.o文件是一个二进制文件,它的字节编码是机器语言指令(01)而不是字符。
4.链接阶段:
hello程序调用了printf函数,这个每个C编译器都会提供的标准C库中的一个函数,printf函数存在于一个名为printf.o的单独预编译好了的目标文件中,(我对这段话的理解是printf函数最开始是在一个printf.c文件中实现的,然后经过预处理、编译、汇编形成了目标文件printf.o,,可能每一个自己程序中非自己亲手实现的函数都是一个以.o为后缀的目标文件,然后在链接阶段由链接器(ld)把这所有的目标文件合并,最终实现完整的程序,可执行文件,可以被加载到内存,由系统执行。)

1.3 了解编译系统如何工作是大有益处的

有很多原因促使程序员知道编译系统是如何工作的,其原因如下:

  • 优化程序性能:
    了解一些机器代码以及编译器将不同的C语句转化为机器代码的方式可以让我们知道一个switch语句是否总是比一系列if-then-else语句高效,一个函数调用的开销,while循环是否比for循环更有效,指针引用比数组索引是否更有效,为什么将循环求和的结果放到一个本地变量中与将其放到一个通过引用传递过来的参数中相比运行速度会快很多,为什么只是简单重新排列一下一个算术表达式中的括号就能让一个函数运行的更快,等等等等。。。
  • 理解链接时出现的错误:
    根据经验,一些令人困扰的程序错误往往都与链接器操作有关,尤其是构建大型软件系统时,比如,链接器报告它无法解析一个引用是什么意思?静态变量和全局变量的区别是什么?如果在不同C文件中定义名字相同的两个全局变量会发生什么?静态库和动态库的区别是什么?我们在命令行上排列库的顺序有什么影响?最严蔗的是,为什么有些链接错误知道运行时才会出现?
  • 避免安全漏洞:
    多年来,缓冲区溢出错误是造成大多数网络和Internet服务器上安全漏洞的主要原因,学习安全编程的第一步就是理解数据和控制信息存储在程序栈上的方式会引起后果。作为学习汇编语言 的一部分,我们会在第3章描述堆栈原理和缓冲区溢出错误并且学习程序员、编译器和操作系统可以用来降低攻击威胁的方法。

1.4 处理器读并解释存储在存储器中的指令

此刻,hello.c源程序已经被编译系统翻译成了可执行目标文件hello,并存放在磁盘上,也就是电脑上的某个文件夹下面。要想运行该文件,可以将他的文件名输入到成为外壳(shell)的应用程序中,也就是终端,即Windows的cmd中
CSAPP(深入理解计算机系统学习笔记)_第3张图片
外壳是一个命令行解释器,它输出一个提示符,等待你输入一个命令行。然后执行这个命令。如果该命令行的第一个单词不是一个内置的外壳命令,那么外壳就会假设这是一个可执行文件的名字,它将加载并运行这个文件。 所以在此例中,外壳将加载并运行hello程序。然后等待程序终止。hello程序在屏幕上输出它的信息,然后终止。外壳随后输出一个提示符,等待下一个输入的命令行。

1.4.1 系统的硬件组成

为了理解运行hello程序时发生了什么,我们需要了解一个典型系统的硬件组织
CSAPP(深入理解计算机系统学习笔记)_第4张图片

  1. 总线
    贯穿整个系统的一组电子管道,称作总线,它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节快,也就是字(word)。字中的字节数(即字长)是一个基本的系统参数,在各个系统中的情况都不相同。 现在机器字长有的是4个字节(4个字节 * 每个字节8位 = 32位),有的是8个字节(8个字节 * 每个字节8位 = 64位),为了讨论方便,假设字长4个字节,并且总线每次只传送一个字。
    CSAPP(深入理解计算机系统学习笔记)_第5张图片

  2. I/O设备
    输入/输出(I/O)设备是系统与外部世界的联系通道,我们的示例系统包括4个I/O设备:作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据和程序的磁盘驱动器(磁盘),最初,可执行程序hello就存放在磁盘上。
    每个I/O设备都通过一个控制器或适配器与I/O总线相连。控制器和适配器之间的区别主要在于它们的封装方式。控制器是置于I/O设备本身的或者系统的主印制电路板(主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。 无论如何,他们的功能都是在I/O总线和I/O设备之间传递信息。

  3. 主存
    主存是一个临时存储设备, 在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(即数组索引),这些地址从0开始。 一般来说,组成程序的每条机器指令都由不同数量的字节组成。与C程序变量相对应的数据项的大小是根据类型变化的。例如,32位机器上,short类型的数据需要2个字节,int、float、long类型需要4个字节,double类型需要8个字节。

  4. 处理器
    中央处理单元(CPU),简称处理器,是解释(或执行)存储在主存中指令的引擎。处理器的核心是一个字长的存储设备(寄存器),称为程序计数器(PC)。 在任何时刻,PC都指向主存中的某条机器语言指令(即含有该条指令的地址)
    从系统通电开始,直到系统断电,处理器一直在不断地执行程序计数器指向的指令,再更新程序计数器,使其指向下一条指令。处理器看上去是按照一个非常简单的指令执行模型来操作的。这个模型是由指令集结构决定的。在这个模型中,指令按照严格的顺序执行,而执行一条指令包含一系列的步骤。处理器从程序计数器(PC)指向的存储器处读取指令,解释指令中的位,执行该指令指示的简单操作,然后更新PC,使其指向下一条指令,而这条指令并不一定与存储器中刚刚执行的指令相邻。
    指令指示的简单操作并不多,大多都比较复杂,而且操作是围绕着主存、寄存器未见(register file)和算术/逻辑单元(ALU)进行的。寄存器文件是一个小的存储设备,有一些1字长的寄存器组成,每个寄存器都有唯一的名字。 ALU计算新的数据和地址值,下面列举一些简单操作的例子,CPU在指令的要求下可能会执行以下操作:
    CSAPP(深入理解计算机系统学习笔记)_第6张图片
    处理器看上去只是它的指令集结构的简单实现,但是实际上现代处理器使用了非常复杂的机制来加速程序的执行。因此,我们可以这样区分处理器的指令集结构和微体系结构:指令集结构描述的是每条机器代码指令的效果;而微体系结构描述的是处理器实际上是如何实现的。

1.4.2 运行hello程序

前面简单描述了系统的硬件组成和操作,现在开始介绍当我们运行示例程序时到底发生了什么。

  1. 初始时,外壳程序(shell)执行它的指令,等待我们输入一个命令。当我们在键盘上输入字符串"./hello"后,外壳程序将字符逐一读入寄存器,再把它存放到存储器中。(注意,这一步与hello这个程序文件无关,读入寄存器和存放到主存中的是"./hello"这个字符串)
    CSAPP(深入理解计算机系统学习笔记)_第7张图片
  2. 当我们在键盘上敲回车键时,外壳程序就知道我们已经结束了命令的输入,然后外壳执行一系列指令来加载可执行的hello文件,将hello目标文件中的代码和数据从磁盘复制到主存。数据包括最终会被输出的字符串”Hello, world“。
    CSAPP(深入理解计算机系统学习笔记)_第8张图片
  3. 一旦目标文件hello中的代码和数据被加载到主存,处理器就开始执行hello程序的main程序中的机器语言指令。这些指令将”Hello, world\n“字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示器设备,最终显示在屏幕上。
    CSAPP(深入理解计算机系统学习笔记)_第9张图片

总的来说就是,我们在终端一边输入字符,终端一边将这些字符读入寄存器然后放到主存中,等到我们在键盘上敲回车键时,外壳程序就知道我们已经完成了命令的输入,然后外壳就执行一系列的指令来确定我们输入的是个啥,如果确定这东西不是一个内置的外壳命令,那么外壳程序就会假设这是一个可执行文件,然后就会将目标文件中的代码和数据从磁盘复制到主存,一旦被加载到主存,处理器就开始执行程序main中的机器语言指令,把需要显示到屏幕的数据从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示到屏幕上。

1.5 高速缓存至关重要

系统花费了大量的时间把信息从一个地方挪到另一个地方,所以系统设计者的一个主要目标就是使这些挪的步骤尽快完成。

  • 寄存器文件的存储空间小,但是读取块
  • 主存的存储空间相对大一些,但读取速度比寄存器文件慢100倍
    CSAPP(深入理解计算机系统学习笔记)_第10张图片
    所以针对这两者之间的差异,系统设计者采用了更小、更快的存储设备,即高速缓存存储器(简称高速缓存)来作为暂时的集结区域,用来存放处理器近期可能会需要的信息。位于处理器芯片上的L1高速缓存的容量可以达到数万字节,访问速度几乎和访问寄存器文件一样快。一个容量为数十万到数百万的更大的L2高速缓存通过一条特殊的总线连接到处理器。进程访问L2高速缓存的时间要比访问L1高速缓存的时间长5倍,但仍比访问主存的时间快5-10倍。L1和L2高速缓存是用一种叫静态随机访问存储器(SRAM)的硬件技术实现的。通过使用高速缓存,系统可以获得一个很大的存储器,同时访问速度也会很快,原因是利用高速缓存的局部性原理,程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成。
    意识到高速缓存存在的应用程序员可以利用高速缓存将他们程序的性能提高一个数量级。

1.6 存储设备形成层次结构

每个计算机系统中的存储设备都被组织成了一个存储器层次结构。
存储器层次结构的主要思想是一层上的存储器作为低一层存储器的高速缓存,因此寄存器文件就是L1的高速缓存,L1是L2的高速缓存,L2是L3的高速缓存,L3是主存的高速缓存,而主存又是硬盘的高速缓存。在某些具有分布式文件系统的网络系统中,本地磁盘就是存储在其他系统中磁盘上的数据的高速缓存。
正如可以运用不同的高速缓存的知识提高程序性能一样,程序员同样可以利用对整个存储器层次结构的理解来提高程序性能。
CSAPP(深入理解计算机系统学习笔记)_第11张图片

1.7 操作系统管理硬件

回到hello程序的例子,外壳(shell)没有直接访问磁盘,hello程序也没有直接访问显示器。取而代之的是,它们都是依靠操作系统提供的服务来达到的上述功能。我们可以把操作系统看成是应用程序和硬件之间插入的一层软件,所有应用程序对硬件的操作都必须通过操作系统。
CSAPP(深入理解计算机系统学习笔记)_第12张图片
操作系统有两个基本功能:

  1. 防止硬件被失控的应用程序滥用
  2. 向程序提供简单一致的机制来控制复杂而又通常大相径庭的低级硬件设备

操作系统通过几个基本的抽象概念(进程、虚拟存储器和文件)来实现这两个功能
CSAPP(深入理解计算机系统学习笔记)_第13张图片
如图所示,文件是对I/O设备的抽象表示,虚拟存储器是对主存和磁盘I/O设备的抽象表示,进程则是对处理器,主存和I/O设备的抽象表示。

1.7.1 进程

进程是操作系统对一个正在运行的程序的一种抽象, 使得就好像系统上只有这一个程序在运行,只有这一个程序在使用处理器、主存和I/O设备,处理器看上去就像在不间断地一条接一条的执行程序中的指令,即该程序的代码和数据是系统存储器中唯一的对象。
一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件,无论是单核还是多核系统,一个CPU看上去都像是在并发执行多个进程,并发运行的意思是一个进程的指令和另一个进程的指令是交错执行的。这通过处理器在进程间切换来实现。
操作系统实现这种交错执行的机制称为上下文切换。 操作系统保持跟踪进程运行所需的所有状态信息。这种状态(上下文)包括许多信息,比如PC和寄存器文件的当前值,以及主存的内容。在任何一个时刻,单处理器系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换(保存当前进程上下文,恢复新进程的上下文,然后把控制权传递到新进程,新进程从上次停止的地方开始)
CSAPP(深入理解计算机系统学习笔记)_第14张图片
上图为两个并发的进程:shell进程和hello进程,最初,只有shell进程在运行,即等待命令行上的输入,当我们让它运行hello程序时,shell通过调用一个专门的函数,即系统调用,来执行我们的请求,系统调用会将控制权传递给操作系统。操作系统保存shell的上下文,然后将控制权传递给新的hello进程。hello进程终止后,操作系统恢复shell的上下文,并将控制权传回给它,shell将继续等待下一个命令行的输入。

1.7.2 线程

一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。 由于网络服务器对并行处理的需求,线程称为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据,也因为线程一般来说都比进程更高效。 当有多处理器可用的时候,多线程也是一种使程序可以更快运行的方法。

1.7.3 虚拟存储器

虚拟存储器是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占的使用主存。每个进程看到的是一致的存储器,称为虚拟地址空间。 在Linux中,地址空间最上面的区域是为操作系统中的代码和数据保留的,这对所有进程来说都是一样的。地址空间的底部区域存放用户进程定义的代码和数据。注意:图中的地址是从下往上增大的。

每个进程看到的虚拟地址空间有大量准确定义的区构成,每个区都有专门的功能。 现在先从最低的地址开始,逐步向上地简单了解一下每一个区。

  • 程序代码和数据:
    对于所有的进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化的。,在实例中就是可执行文件hello。
  • 堆:
    代码和数据区后紧随着的是运行时堆。代码和数据区是在进程一开始运行时就被规定了大小,n与此不同,当调用如melloc和free这样的C标准库函数时,堆可以在运行时动态地扩展和收缩。
  • 共享库:
    大约在地址空间的中间部分是一块用来存放像C标准库和数学库这样共享库的代码和数据的区域。
  • 栈:
    位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆一样,用户栈在程序执行期间e可以动态地扩展和收缩,特别是每次我们调用一个函数时,栈就会增长:从一个函数返回时,栈就会收缩。
  • 内核虚拟存储器:
    内核总是驻留在内存中,是操作系统的一部分。地址空间顶部的区域是为内核保留的,不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。

虚拟存储器的运作需要硬件和操作系统软件之间精密复杂的交互。包括对处理器生成的每个地址的硬件编译。其基本思想是把一个进程虚拟存储器的内容存储在磁盘上,然后用主存作为磁盘的高速缓存。

1.7.4 文件

文件就是字节序列。 每个I/O设备,包括磁盘、键盘、显示器,甚至是网络,都可以视为文件。系统中的所有输入输出都是通过使用一小组称为Unix I/O的系统函数调用读写文件来实现的。

1.8 系统之间利用网络通信

现代系统经常通过网络和其他系统连接到一起,从一个单独的系统来看,网络可视为一个I/O设备。当系统从主存将一串字符复制到网络适配器时,数据流经过网络到达另一台机器,相似的,系统可以读取从其他机器发送来的数据,并把数据复制到自己的主存。

继续讨论hello示例,我们可以使用Telnet应用在一个远程主机上运行hello程序。假设用本地主机上的Telnet客户端连接到远程主机上的Telnet服务器。在我们登录到远程主机并运行外壳后,远端的外壳就在等待接收输入命令。从这点开始,远程运行hello程序包括五个基本步骤:
CSAPP(深入理解计算机系统学习笔记)_第15张图片

  1. 但我们在Telnet客户端键入”hello“字符串并敲下回车键后,客户端软件就会将这个字符串发送到Telnet服务器
  2. Telnet服务器从网络上接受到这个字符串后,会把它传递给远端外壳程序
  3. 远端外壳运行hello程序,并将输出行返回给Telnet服务器
  4. Telnet服务器通过网络把输出串转发给Telnet客户端,客户端就将输出串输出到我们的本地终端上

1.9 重要主题

系统不仅仅只是硬件,系统是硬件和系统软件互相交织的集合体,它们必须共同协作以达到运行应用程序的最终目的。

1.9.1 并发和并行

并发是一个通用的概念,指一个同时具有多个活动的系统
并行指的是用并发是一个系统运行的更快。
我所理解的并发就是多个进程交换执行,因为交换的非常快,所以给人一种同时执行的假象,而并行就是真正的多个进程同时执行。
并行可以在计算机系统的多个抽象层次上运用。在此,我们按照系统层次结构中由高到低的顺序重点强调三个层次。

  1. 线程级并发
    传统意义上,并发执行只是模拟出来的,通过使一台计算机在它正在执行的进程间快速切换的方式实现的。
    以前,即便处理器必须在多个任务间切换,大多数实际的计算也都是由一个处理器来完成的。这种配置称为单处理器系统。
    当构建一个由单操作系统内核控制的多处理器组成的系统时,我们就得到了一个多处理器系统。
    多核处理器是将多个CPU(称为”核“)集成到一个集成电路芯片上。每个核都有自己的L1和L2高速缓存,但它们共享更高层次的高速缓存以及到主存的接口。
    超线程,有时称为同时多线程,是一项允许一个CPU执行多个控制流的技术。常规的处理器需要大约20000个时钟周期做不同线程间的转换,而超线程的处理器可以在单个周期的基础上决定要执行哪一个线程。这使得CPU能够更好的利用它的处理资源。假设一个线程必须等到某些数据被装载到高速缓存中,那CPU就可以继续去执行另一个线程。
  2. 指令级并行
    在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。
    如果处理器可以达到比一个周期一条指令更快的执行速率,就称之为超标量处理器。
  3. 单指令,多数据并行
    在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令,多数据,即SIMD并行。

1.9.2 计算机系统中抽象的重要性

抽象的使用是计算机科学中最为重要的概念之一。为一组函数规定一个简单的应用程序(API)就是一个很好地编程习惯,程序员无需了解它内部的工作便可以使用这些代码。

  • 文件是对I/O的抽象
  • 虚拟存储器是对程序存储器的抽象
  • 进程是对一个正在运行的程序的抽象
  • 虚拟机提供对整个计算机(包括操作系统、处理器和程序)的抽象

第二章 信息的表示和处理

有三种重要的数字表示:

  • 无符号
    表示大于或者等于0的数字
  • 补码
    表示可以为正或者为负的数字
  • 浮点数
    表示实数的科学计数法的以二为基数的版本

计算机的表示法使用有限数量的位来对一个数字编码,因此,当结果太大以至于不能表示时,某些运算就会溢出。

整数表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;而浮点数虽然可以编码一个较大的数值范围,但是这种表示只是近似的,且由于表示的精度有限,浮点运算是不可结合的。

十进制转十六进制
如果一个数可以表示成2的非负整数n次幂时,即 x= 2 n 2^{n} 2n,n可以写成 i + 4j (0 ≤ \leq i ≤ \leq 3),这个数x既可以转换成开头十六进制数字1( i=0 ),2( i=1 ),4( i=2 )或者8( i=3 ),后面跟着j个十六进制0
比如:

  • 2 9 2^{9} 29 = 2 1 + 4 ∗ 2 2^{1 + 4 * 2} 21+42 = 0x200
  • 2 19 2^{19} 219 = 2 3 + 4 ∗ 4 2^{3 + 4 * 4} 23+44 = 0x80000
  • 2 17 2^{17} 217 = 2 1 + 4 ∗ 4 2^{1 + 4 * 4} 21+44 = 0x20000
  • 2 5 2^{5} 25 = 2 1 + 4 ∗ 1 2^{1 + 4 * 1} 21+41 = 0x20

字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为w位的机器而言,虚拟地址的范围为0 ~ 2 w − 1 2^{w}-1 2w1 ,程序最多访问 2 w 2^{w} 2w个字节。
这句话不太好理解,可以用字长为8位,画图说明:
CSAPP(深入理解计算机系统学习笔记)_第16张图片

2.1.4 p28 参考 《C++ primer plus 第六版 》p111 4.8.3 指针和字符串

第三章 程序的机器级表示

第四章 处理器体系结构

第五章 优化程序性能

第六章 存储其层次结构

第七章 链接

第八章 异常控制流

第九章 虚拟存储器

第十章 系统级I/O

第十一章 网络编程

第十二章 并发编程

附录A 错误处理

你可能感兴趣的:(读书笔记)