一、计算机系统漫游
1.1 信息就是位+上下文
位/比特:0和1
字节:8位一组
系统上所有信息(磁盘文件、内存中的程序、内存的数据、网络传送的数据)都是一串比特表示。根据数据对象的上下文区分不同数据。
1.2 程序被其他程序翻译成不同的格式
源文件—编译器驱动程序—>目标文件
预处理阶段:cpp根据#开头的命令修改原始的C程序。
编译阶段:col翻译成汇编语言程序。
汇编阶段:as翻译位机器语言指令,打包指令成可重定位目标程序。
链接阶段:负责合并标准C库等单独预编译好的目标文件,得到可执行目标文件。
1.3 了解编译系统如何工作是大有益处的
1.4 处理器读并解释储存在内存中的指令
shell命令行解释器,若输入第一个单词不是内置shell命令则加载这个文件。
1.4.1 系统的硬件组成
总线
贯穿整个系统的电子管道。
携带信息自己并在各个部件中传递。传送定长的字节块(字,字长是字中的字节数)
I/O设备
输入:键盘、鼠标
输出:显示器、磁盘驱动器(即磁盘)
I/O设备通过控制器(I/O设备本身或系统主板上的芯片)/适配器(主板插槽上的卡)与总线相连。传递I/O总线和I/O设备之间的信息。
主存
临时存储设备,处理器执行程序时用来存放程序和程序处理的数据。
物理上,一组动态随机存取存储器(DRAM)芯片组成。
逻辑上,线性的字节数组,每个字节有唯一的从0开始的地址(数组索引)。
处理器
中央处理单元(CPU),解释(或执行)存储在主存中指令的引擎。
核心:大小为一个字的存储设备(或寄存器),称为程序计数器(PC)。任何时候,PC都指向主存中某条机器语言指令(含有该指令的地址)
处理器从系统通电到断电,一直执行PC指向的指令,再更新PC,使其指向下一条指令(不一定是相邻指令)。
寄存器文件:存储设备,由一些单个字长的寄存器组成
算术/逻辑单元ALU:计算新的数据和地址值
注:
指令集架构:描述每条机器代码指令的效果。
微体系结构:描述处理器实际上如何实现。
1.4.2 运行hello程序
在键盘输入字符串,shell将字符逐一读入寄存器并存放到内存。
在键盘敲回车键,shell执行一系列指令加载文件,指令将目标文件代码和数据从磁盘复制到主存。利用直接存储器存取DMA,数据可不通过处理器而直接从磁盘到达主存。
加载到主存后处理器执行程序main程序中的机器语言指令。将字节从主存复制到寄存器文件,从寄存器文件复制到显示设备,最终到达屏幕。
1.5 高速缓存至关重要
高速缓存存储器cache:更小更快的存储设备,存放处理器近期可能会需要的信息。
L1、L2高速缓存用静态随机访问存储器SRAM实现。
系统甚至有三级高速缓存L1、L2、L3,利用高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码趋势。
1.6 存储设备形成层次结构
上一层是下一层的高速缓存。
1.7 操作系统管理硬件
操作系统是应用程序域硬件之间的一层软件。
操作系统功能
防止硬件被失控的应用程序滥用
向应用程序提供简单一致的机制来控制复杂又不大相同的低级硬件设备。
抽象表示
文件 = I/O设备
虚拟内存 = 主存 + 磁盘I/O设备
进程 = 处理器 + 主存 + I/O设备
1.7.1 进程
进程是操作系统对一个正在运行的程序的一种抽象,系统上可同时运行多个进程,而每个进程都好像独占硬件。
并发运行:一个进程的指令和另一个进程的指令交错执行。
上下文切换:实现交错执行的机制。
上下文:进程运行所需的所有状态信息。
内核:操作系统常驻主存部分。不是一个独立的进程。是系统管理全部进销存所有的代码和数据机构的集合。
一个进程到另一个进程的转换是由内核管理。
如:读写文件,执行系统调用指令,控制权传递给内核,内核执行被请求的操作并返回应用程序。
1.7.2 线程
一个进程由多个称为线程的执行单元组成。
每个线程都运行在进程的上下文中,共享同样的代码和全局数据。
1.7.3 虚拟内存
为每个进程提供假象,即每个进程都在独占使用主存。
虚拟地址空间:每个进程看到一样的内存。
从低到高地址依次为:
程序代码和数据
对所有进程来说,代码从同一固定地址开始,接着C全局变量相对应的数据位置。代码和数据区是之间按可执行文件内容初始化。指定大小。
堆
运行时动态扩展(malloc)或收缩(free)。
共享库
存放像C标准库和数学库等共享库的代码和数据的区域。
栈
位于用户虚拟地址空间顶部的是用户栈,实现函数调用。程序执行期间可动态扩展(调用函数)或收缩(函数返回)。
内核虚拟内存
地址空间顶部。不允许应用程序读写或调用。必须调用内核来执行操作。把一个进程虚拟内存的内容存储到磁盘上,然后用主存作为磁盘的高速缓存。
1.7.4 文件
字节序列。
每个I/O设备都可看成是文件。
1.8 系统之间利用网络通信
网络可视为一个I/O设备。
系统从主存复杂一串字节到网络适配器时,数据流经过网络到达另一台机器。系统读取从其他机器发送来的数据并把数据复制到自己的主存。
1.9 重要主题
系统是硬件和软件互相交织的集合体,共同协作达到运行应用程序的最终目的。
1.9.1 Amdahl定律
对系统某个部分加速时,对系统整体性能的影响取决于该部分的重要性和加速程度。
1.9.2 并发和并行
并发:一个同时具有多个活动的系统。
并行:用并发来使一个系统运行得更快。
线程级并发
构建在进程抽象上,设计出同时有多个程序执行的系统。
传统上,通过计算机在执行进程间快速切换来实现。
多核处理器
将多个CPU集成到一个集成电路芯片上。
超线程(同时多线程)
允许一个CPU执行多个控制流的技术。
设计CPU某些硬件有多个备份。
指令级并行
现代处理器可同时执行多条指令的属性。
超标量处理器:一个周期一条指令更快的执行速率的处理器
单指令、多数据并行
允许一条指令产生多个可以并行执行的操作,即SIMD并行。
1.9.3 计算机系统中抽象的重要性
虚拟机 = 整个计算机的抽象 = 操作系统 + 处理器 + 程序
1.10 小结
第一部分:程序结构和执行
二、信息的表示和处理
2.1 信息存储
虚拟内存:机器级程序将内存视为一个很大的字节数组
地址:内存中每个字节由一个唯一的数字标识
虚拟地址空间:所有可能地址的集合,只是一个展示给机器级程序的概念性映像
2.1.1 十六进制表示法
十六进制与二进制互转
十进制与十六进制转换
2.1.2 字数据大小
每台计算机都有一个字长,指明指针数据的标称大小。
对于一个字长为w位机器而言,虚拟地址范围0~2^w-1,程序最多访问2^w个字节。
大多数64位机器也可以运行为32位机器编译的程序,向后兼容。
“32位”或“64位”程序的区别是如何编译的,而不是运行的机器类型。
有些数据类型的确切字节数依赖于程序是如何被编译的。
大部分数据类型编码位有符号数值。
2.1.3 寻址和字节顺序
对于跨越多字节的程序对象,两个规则:
这个对象的地址是什么(一般是所使用字节中最小的地址)
在内存中如何排列这些字节(一般被存储位连续的字节序列)
排列表示一个对象的字节两个规则:
小端法:在内存中按照从最低有效字节到最高有效字节的顺序存储对象,最低有效字节在前面
大端法:在内存中按照从最高有效字节到最底有效字节的顺序存储对象,最高有效字节在前面
2.1.4 表示字符串
C语言字符串被编码为一个以null(其值为0)字符结尾的字符数组。每个字符都有某个标准编码表示,最常见的是ASCII字符码。
ASCII作为字符码在任何系统上都得到相同的结果,与字节顺序和字大小规则无关。因此,文本数据比二进制数据有更强的平台独立性。
Java使用Unicode表示字符串。
2.1.5 表示代码
二进制代码不兼容。
2.1.6 布尔代数简介
1 == TRUE
0 == FALSE
扩展到位向量运算。位向量是固定长度为w,由0和1组成的串。
布尔环
用位向量对集合编码
(注:集合中元素表示位向量中为1的位置)
2.1.7 C语言中的位级运算
确定一个位级表达式方法:十六进制转成二进制,执行二进制运算,再转回十六进制。
常用于掩码运算,掩码是一个位模式,表示从一个字中选出的位的集合。
2.1.8 C语言中的逻辑运算
逻辑运算中,所有非0的参数都为TRUE,参数0为FALSE;返回1或0,表示TRUE或FALSE。
若对第一个参数求值就能确定表达式结果,就不会对第二个参数求值。
2.1.9 C语言中的移位运算
左移运算
x<
右移运算
x>>k :
逻辑右移:左端补k个0
算术右移:左端补k个最高有效位的值
C中无规定对有符号数使用哪种类型的右移。
实际上,几乎所有编译器/机器组合都对有符号数使用算术右移。
对于无符号数,必须是逻辑右移。
JAVA规定,x>>k是算术右移,x>>>k是逻辑右移。
加减法的优先级高于移位运算
2.2 整数表示
术语
B:二进制 T:补码 U:无符号数
2.2.1 整型数据类型
C支持多种整型数据类型——表示有限范围的整数。
负数范围比整数的范围大1。
注:C和C++都支持有符号(默认)和无符号数。JAVA只支持有符号数。
2.2.2 无符号数的编码
无符号数编码有唯一性
2.2.3 补码编码
最常见的有符号数的计算机表示是补码形式。
补码编码有唯一性
注:Java要求采用补码表示。单字节数据称为byte,而不是char。
有符号位的其他表示方法
反码
原码
2.2.4 有符号数和无符号数之间的转换
大多数C语言的实现,是从位级角度来看的,而不是数的角度。
强制转换的结果保持位置不变,只是改变了解释这些位的方式。
B:二进制 T:补码 U:无符号数
补码转无符号数函数 = 先将补码转成二进制数,在将二进制数转成无符号数
无符号数转补码函数 = 先将无符号数转成二进制数,在将二进制数转成补码
T2U函数属性
U2T函数属性
总结
2.2.5 C语言中的有符号数与无符号数
默认是有符号数,创建无符号常量必须加上后缀字符‘u’或‘U’。
允许无符号和有符号的转换,大部分系统遵从的原则是底层的位表示保持不变。
注:执行一个运算时,有符号与无符号的一起运算,C会隐式将有符号强转为无符号,并假设两个数都是非负的来执行。
2.5.6 扩展一个数字的位表示
无符号数转换成一个更大的数据类型
补码数字转换成一个更大的数据类型
补码数值的符合扩展
注:一个数据大小到另一个数据大小的转换,以及无符号和有符号之间的转换能够影响一个程序的行为。
2.2.7 截断数字
截断无符号数
截断补码数值
2.2.8 关于有符号数与无符号数的建议
若把字仅仅看作位的集合而无任何数学意义时,则无符号数值非常有用。
无符号运算的细微特征尤其是有符号数到无符号数的隐式转换会导致错误或漏洞。
2.3 整数运算
2.3.1 无符号加法
字长膨胀
无符号数加法
把整数x+y截断为w位得到结果,再把这个结果看做是一个无符号数。
检测无符号数加法中的溢出
无符号数求反
2.3.2 补码加法
检测补码加法中的溢出
2.3.3 补码的非
执行补码非的两种方法:
对每一位求补,再对结果加1。
建立在将位向量分为两部分的基础之上的。
2.3.4 无符号乘法
C中定义产生为w位的值,就是2w位的整数乘积的低w位表示的值。
2.3.5 补码乘法
将一个补码数截断为w位相当于先计算该值莫2^w,再把无符号数转换为补码。
对于无符号和补码乘法来说,乘法运算的位级表示都是一样的。
2.3.6 乘以常数
整数乘法指令比加、减、位级运算、移位要慢。
与2的幂相乘的无符号乘法
与2的幂相乘的补码乘法
无论是无符号运算还是补码运算,溢出时移位的结果是一样的。
很多C语言编译器用移位、加、减的组合来消除很多整数乘以常数的情况。
2.3.7 除以2的幂
整数除法比乘法更慢。
右移。
无符号数:逻辑移位
补码数:算术移位
除以2的幂的无符号除法
除以2的幂的补码除法
对于非负数来说,效果与逻辑右移一样。
对于负数来说,不需要舍入时,x/(2^K);需要舍入,结果向下舍入。
通过移位前“偏置”这个值修正不合适的舍入。
关于偏置量的取值,当a>=0时,偏置量值为0;当a<0时,设偏移量为n,偏置量取值为(2^n-1)。
注:同乘法不同,除以2的幂的除法不能用来表示任意常数K的除法。
2.3.8 关于整数运算的最后思考
执行“整数”运算实际上是模运算形式。
2.4 浮点数
2.4.1 二进制小数
2.4.2 IEEE浮点表示
10进制中,125=0.125*10^3。那么可以说任意一个J进制数N,总可以写成 N = M * J^e
其中M是数N的尾数,M(0.125)是一个纯小数
e(3)就是N的阶码
C语言中的单精度浮点格式和双精度浮点格式
根据exp的值,有以下3种情况:
规格化的值
exp不全为0(数值0)且不全为1(单255;双2047)
阶码字段被解释为以偏置Bias形式表示的有符号整数。
非规格化的值
阶码域全为0。
提供一种表示数值0的方法
表示非常接近于0.0的数,逐渐溢出,可能的数值分布均匀地接近0.0
特殊值
2.4.3 数字示例
2.4.4 舍入
浮点运算只能近似地表示实数运算。
四种舍入方式:
向偶数舍入/向最接近的值舍入(默认方式):对中间数舍入,向上或向下使得结果最低有效数字是偶数
向零舍入:正数向下舍入,负数向上舍入
向下舍入
向上舍入
二进制小数的舍入
2.4.5 浮点运算
浮点数加法
浮点数乘法
2.4.6 C语言中的浮点数
支持IEEE浮点格式的机器,float和double对应单精度和双精度浮点。使用向偶数舍入。
C不支持IEEE浮点。
int转换成float,不会溢出,但可能被舍入。
int或float转换成double,能保留精确数值。
double转换float,可能溢出成正无穷或负无穷,还可能被舍入。
float或double转换int,值向零舍入。
2.5 小结
计算机编码为位(比特),通常组织成字节序列。
64位优势是可以突破32位程序具有4GB的地址限制。
大多数机器对整数使用补码编码,对浮点数使用IEEE标准754编码。
再相同长度的无符号和有符号整数之间进行强制类型转换时,C实现遵循的原则是位模式不变。
当超出表示范围时有限长度能引起数值溢出;当浮点数非常接近于0.0从而转换成零时,会下溢。
无符号数和补码的运算都能满足整数运算的许多其他属性,包括结合律、交换律、分配律。