SpiritiualWander

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  10 随笔 :: 0 文章 :: 0 评论 :: 297 阅读

深入理解计算机系统

latex符号

1. 概论

1.1 程序的格式

  1. 预处理阶段:预处理器(cpp)将以#开头的语句引入替换成相关文件内容或数据内容,变为文本文件
  2. 编译阶段: 编译器(ccl)将文本转化为汇编语言,也是文本文件
  3. 汇编阶段: 汇编器(as)将汇编语言转化机器指令,打包成可重定位目标程序(Relocatable Object Program),变为二进制文件
  4. 链接阶段: 连接器(ld)将动态库或者静态库与上述形成的可重定位文件进行合并,得到可执行目标文件.

1.2 硬件系统

  1. 总线: 携带信息字节在各个组件间传递

  2. I/O设备: 系统与外部世界的联系通道.每个I/O设备通过控制器(是I/O设备本身或者系统的主板上的芯片组)或适配器(插在主板插槽上的卡)与I/O总线相连,通过 PCI 总线

  3. 主存: 主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据.

  4. 处理器: 即CPU,是解释(执行)主存中的指令的引擎,其核心是大小一个字的寄存器,称为程序计数器(PC).任何时刻,PC都指向主存中的某条机器指令.CPU的模型由指令架构集决定.CPUPC指向的内存中读取指令,解释其中的位,执行该指令指示的简单操作,随后更新PC.这些简单操作主要围绕主存,寄存器文件(Register File)算数/逻辑单元(Algorithm and Logic Unit).ALU计算新的数据和地址值.

    • 加载: 从主存的某一地址处复制不大于一个字的数据到寄存器
    • 存储: 从寄存器复制不大于一个字的数据到主存中的某一地址
    • 操作: 把两个寄存器中的值复制到ALU,由ALU对其进行计算,并将计算结果返回至一个寄存器中
    • 跳转: 从指令中复制一个字到PC中,

    处理器的组成分为:

    • 处理器的指令集架构: 其描述每条机器指令的含义,各种汇编代码都有其一定的格式,CPU可以根据对应格式将二进制代码转化成汇编代码.即二者一一对应
    • 处理器的微体系结构: 其描述处理器的具体实现

1.3 存储设备层次结构

1.4 操作系统管理硬件

所有程序对硬件的操作必须通过操作系统实现

操作系统的两个基本功能:

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

文件对I/O设备的抽象表示,虚拟内存是对主存和I/O设备的抽象表示,进程是对处理器,主存和I/O设备的抽象表示

1.4.1 进程

进程是操作系统对一个正在运行的程序的一种抽象,即,在一台系统上可以同时运行多个程序,每个程序都好像在独占硬件.
并发运行,则是说一个进程和另外一个进程交错执行.因此需要运行的进程数可以多于运行他们的CPU数.这种交错执行的机制被称为上下文切换(上下文即为进程运行所需的所有状态信息,如PC,寄存器值以及主存的内容).
任何一个时刻,单核处理器只能运行一个进程的代码.当操作系统决定将控制权从一个进程切换到另个进程的时候,就会进行上下文切换,即保存当前进程的上下文,恢复另一个进程的上下文,并将控制传递给另一个进程.新进程就会从上次停止的地方开始运行.
上下文切换操作系统内核(Kernel)管理.
内核是操作系统代码常驻内存的部分.
当某个进程需要操作系统的某些操作(例如读写操作)时,它就执行系统调用(system call)指令,将控制权传递给内核.内核执行后得到结果再传递给进程.
内核不是一个独立的进程,它是系统管理全部进程所用代码数据结构集合.

1.4.2 线程

现代系统中,一个进程可以由多个称为线程的执行单元组成.每个线程都运行在进程的上下文中,并共享同样的代码和全局数据.

1.4.3 虚拟内存

虚拟内存也是一种抽象,是对内存(后文也称为物理存储器)空间映射,使得每个进程都认为自己运行在独立的内存空间,且每个内存一致.

该图中的内核虚拟内存是为内核保留的,不运行进程读写该区域的内容或者直接调用内核代码定义的函数.进程只能调用内核来执行这些操作.

1.4.4 文件

文件即字节序列,在Unix系统中,任何设备都被看成文件,甚至包括网络.

1.5 总结

计算机系统是硬件与系统软件的集合

1.5.1 Amdahl定律

主要思想: 当我们对系统的某个部分加速时,其对系统整体的性能影响取决于该部分的重要性和加速程度.要想显著加速整个系统,必须提升全系统中相当大的部分的速度
设系统执行某应用程序所需的时间为Told,系统部分所需执行时间与该时间的比为α,该部分性能提升比例为k,则总执行时间为

Tnew=(1α)Told+αTold/k=Told[(1α)+α/k]

1.5.2 并发(concurrency)与并行(parallelism)

1.5.2.1 线程级并发

并发可由进程间的快速切换进行模拟

多核处理器将多个CPU(称为)集成到一个集成电路芯片上

超线程(hyperthreading),有时也被称为同时多线程(simultaneous multi-threading),是一项允许一个CPU执行多个控制流的技术.即CPU的某些硬件有多个备份,如PC和寄存器文件,而其他硬件部分只有一份,如执行浮点算数运算的单元.常规处理器大约需要20000个时钟周期做不同进间的切换,但是超线程处理器在一个时钟周期即可决定执行哪一线程

1.5.2.2 指令级并发

在较低层次上,现代处理器可以同时执行多条指令的属性称为指令级并行.流水线(pipelining)是它的一种实现机制.
流水线将执行一条执行所需要的活动划分成不同的步骤,将系统的硬件组织成一系列的阶段,每个阶段执行一个步骤,将处理器的硬件组织成一系列的阶段,每个阶段执行一个步骤.这些阶段可以并行地操作,用来处理不同指令的不同部分.
如果处理器可以达到比一个周期一个条指令更快的执行速率,就称之为超标量(superscaler)

1.5.2.3 单指令,多数据并行

在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据,即SIMD并行.如较新几代的IntelAMD处理器都具有并行地对8对单精度浮点数(C数据类型float)做加法的指令.

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

一、程序结构与执行

2. 信息的表示与处理

2.0 概论

计算机世界中的信息由以比特为单位的二进制数字组成,人们对其进行编码解释(interpretation),赋予不同的可能模式位不同的涵义

2.1 信息存储

机器级程序将内存看成一个巨大的字节数组,称为虚拟内存(virtual memory),同时将动态随机访问存储器(DRAM),闪存,磁盘存储器,特殊硬件和操作系统软件相结合.内存中每一个字节都由唯一的数字标识,即为地址.所有可能的地址集合即为虚拟地址空间(virtual address space).
程序对象(program object):即程序数据,指令控制信息.

2.1.1 字数据大小

每台计算机都有一个字长w(word size),指明指针数据的标称大小(normal size).它决定了计算机的虚拟地址空间大小.
64位机器会向32位兼容,所以可以运行32位程序.我们将程序称为32位程序或64位程序时,区别在于该程序是如何编译的,而不是其运行的机器类型.
在编写编译程序之前,可以利用关键字sizeof()获取相应类型数据的长度

2.1.2 寻址与字节排序

为多字节对象建立的规则:

  1. 对象的地址是什么:地址为对象在内存中最低位置
  2. 对象如何排列:与操作系统有关
    1. 小端模式(little endian): 在地址中按照最低有效位到最高有效位的顺序存储
    2. 大端模式(big endian): 在地址中按照最高有效位到最低有效位的顺序存储

注意网络传输时因此而出现的问题
C语言中可以使用强转换(cast)和联合(union)进行来允许以一种数据类型引用一个对象,而这种数据类型与创建这个对象时定义的数据类型不同.其实只是告诉编译器换一种方式去理解这种类型,并没有改变对象的任何数值.因为底层数据是不分类型的.

2.1.3 表示字符串

ASCII码由一个字节编码,所以不必考虑大小端问题,具有更强的平台移植性.
由于世界各地字符太多,一个字节无法全部编码,所以引入Unicode标准

2.1.4 表示代码

不同处理器的指令集架构不同,所以编码而成的二进制字节也就不同.故而不同机器上的二进制代码难以兼容

2.1.5 布尔代数

围绕数值0和1的数学知识体系被称为布尔代数

上述运算可以被推广到位向量
布尔运算的性质:

  1. &|有分配律,即: a&(b|c)=(a&b)|(a&c)
  2. |&有分配律,即: a|(b&c)=(a|b)&(a|c)
  3. 布尔运算中的加法逆元(additive inverse)是其值本身,即: a ^ a=0.且其满足交换律,即: (a ^ b) ^ a=b.
    位向量[aw1,,a1,a0]可以编码任何子集A{0,1,2,w}.其中ai=1,当且仅当iA.则使用这种编码集合的方法,布尔运算|,&,分别对应于集合中的,,A

2.1.6 C语言的位级运算


位级运算的一个常见用法就是实现掩码运算

2.1.7 C语言的逻辑运算

其逻辑运算符为||,&&,!,分别对应于命题逻辑中的OR,AND,NOT.

只有运算的参数为0或1时,位运算才和其对应的逻辑运算有相同的行为
对二元逻辑运算符来说,如果第一个参数求值就可以确定表达式的结果,他们就不会对第二个参数继续求值

2.1.8 C语言的移位运算

C语言的移位运算有三种:

  1. 左移运算: x<<k,即x向左移动6位,丢弃最高的k位,并在右端补k0
  2. 逻辑右移运算: x>>Lk,即在左端填充k个0.用于无符号数
  3. 算术右移运算: x>>Ak,即在左端填充k个最高有效位的值.用于有符号数

C语言的移位运算是从左至右结合的,C语言的其他运算符的优先级可以参考这篇文章
C语言标准中,如果k>w,则位移量为k mod w

2.2 整数表示

本节的一些术语如下图

2.2.1 整数数据类型


2.2.2 无符号数的编码

假设一个整数数据类型有w位,其位向量为x=[xw1,xw2,,x0],其中xi=0,1.把x看成一个二进制表示的数,即得到其无符号的表示,用函数B2Uw(Binary to Unsigned)表示:

B2Uw(x)i=0w1xi2i

其中 表示左边被定义为等于右边
函数B2Uw可以被定义为一个映射:

B2Uw:{0,1}w{0,1,,2w1}

显然,无符号数编码具有唯一性,且函数B2Uw是一个双射(即二者一一对应,类似于代数中的单调函数,它存在反函数),其反函数为U2Bw.

2.2.3 补码(two's-complement)编码

最常见的有符号数的计算机表示方式为补码形式.其将字的最高位解释为负权(negative weight).我们用B2Tw表示:

B2Twxw12w1+i=0w2xi2i

其中,最高有效位xw1称为符号位,其权重为2w1.其最小值为TMinw=2w1,最大值为TMaxw=2w11.则可以得到一个位模式到数字的映射:

B2Uw:{0,1}w{TMinw,,TMaxw}

显然,补码编码具有唯一性,且函数B2Tw也是一个双射,其反函数为T2Bw.

由上可得补码和无符号编码的一些性质
|TMin|=|TMax|+1 , UMaxw=2TMaxw+1
上述的补码不对称的原因是负数占一半,非负数占一半,而0为非负数,也就意味着能表示的正数比负数少一个.

C语言标准并没有要求使用补码的形式,但是几乎所有机器使用补码形式.
C库文件<limits.h>定义了一组常量,限定不同数据的取值范围,定义了INT_MAX,INT_MIN,UNIT_MAX等值.<stdin.h>中定义了intN_tuintN_t,为了无歧义地声明N位数据类型,其中N=8,16,32,64
在网络中,最重要的是保持数据类型与协议的数据类型的兼容
有符号数还有两种表示方法:

  1. 反码(Ones' Complement): $$B2O_w\doteq-x_{w-1}(2{w-1}-1)+\sum\limits_{i=0}{\vec{x}_i2^i}$$
  2. 原码(Sign-Magnitude): $$B2S_w\doteq(-1){x_{w-1}}\bullet\sum\limits_{i=0}{\vec{x}_i2^i}$$

这两种编码方式对0都有两种编码方式即0,+0.

注意术语Two's Complement表示的是:对于非负数x,用2wx(一个2)计算x的w位表示.而Ones' Complement[111]x(多个1)计算x的w位表示.

2.2.4 有符号数与无符号数的转换

C语言通过上述的类型强制转换,将有符号数转化成无符号数.注意,强制类型转换不改变数据位数值,只改变解释解释这些位模式的方法.于是我们可以获得其相互转化的函数:

  1. 补码转无符号T2Uw:
    对满足TMinwxTMaxwx有:

    T2Uw(x)=B2Uw(T2Bw(x))=x+xw12w={x+2w,x<0x,x0

  2. 无符号转补码U2Tw
    对满足0uUMaxwu有:

    U2Tw(u)=B2Tw(U2Bw(u))={u,uTMaxwu2w,u>TMaxw

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

C语言中,常量都是采用补码表示的,可在数值后面加uU表示无符号,如:1234U或0x1a2bu
当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号参数强制类型转换为无符号数

在<limits.h>中INT_MIN被定义成(-INT_MAX-1)

2.2.6 扩展一个数字的位表示

即从一个较小的数据类型转换到一个较大的类型,且保持数值不被改变.

  1. 无符号数的零扩展:
    定义宽度为w的位向量u=[uw1,uw2,,u0]和宽度为w的位向量u=[0,,0,uw1,uw2,,u0],其中w>w,则B2Uw(u)=B2Uw(u)
  2. 补码数的符号扩展:
    如果是无符号数,则在这个过程中可以执行一个符号扩展(sign extension),在位向量中填充最高有效位的值.
    定义宽度为w的位向量x=[xw1,xw2,,x0]和宽度为w的位向量x=[xw1,,xw1,xw1,xw2,,x0],其中w>w,则B2Tw(x)=B2Tw(x)
    其证明为:

    B2Tw+1([xw1,xw1,xw2,,x0])=xw12w+i=0w1xi2i=xw12w+xw12w1+i=0w2xi2i=xw12w1+i=0w2xi2i=B2Tw([xw1,xw2,,x0])

2.2.7 截断数字

即减少数值的位数

  1. 截断无符号数:
    定义宽度为w的位向量x=[xw1,xw2,,x0]和宽度为k的位向量x=[xk1,xk2,,x0].令x=B2Uw(x),x=B2Uk(x),则x=x mod 2k.
  2. 截断补码数:
    定义宽度为w的位向量x=[xw1,xw2,,x0]和宽度为k的位向量x=[xk1,xk2,,x0].令x=B2Uw(x),x=B2Tk(x),则x=U2Tk(x mod 2k).

需要注意的是,在不同类型间的转换中,特别是当两种不同类型出现在同一个运算式中时,容易出现错误.

书中给出的建议是:绝不使用无符号数

2.3 整数运算

2.3.1 无符号加法


本文为参数xy定义运算+wu,其中0x,y2w,该操作是把整数和x+y截断为w位得到的结果,相当于对其进行模运算.则可得:

x+wuy={x+y,x+y2wx+y2w,2wx+y<2w+1


检测无符号加法中的溢出:
对于满足0x,yUMaxw中的x,y,令sx+wuy,则当且仅当s<x(或s<y)时,发生了溢出
模数加法形成了一种数学结构,称为阿贝尔群(Abelian group).它是可交换的可结合的.它有一个单位元0,并且每个元素都有一个加法逆元.即对于每个x,都必然有一个值wux满足wux+wux=0.
则可得无符号求反的定义:对满足0x<2w的任意x,其w位的无符号逆元wux由下式给出:

wux={x,x=02wx,x>0

无符号数的逆元时通过加法溢出实现的

2.3.2 补码加法

定义x+wty为整数和x+y被截断为w位的结果,并将这个结果看成补码数.
补码加法定义为:对于满足2w1x,y2w11中的x,y,有:

x+wty={x+y2w,2w1x+y x+y,2w1x+y<2w1 x+y+2w,x+y<2w1 


原理:不管哪种类型数据相加,都是对其位向量进行操作,再将其转化为对应十进制数值.由于相加时存在溢出,所以需要截断.
其证明如下:

x+wty=U2Tw(T2Uw(x)+wuT2Uw(y))=U2Tw[(xw12w+x+yw12w+y) mod 2w]=U2Tw[(x+y) mod 2w]={(x+y) mod 2w,(x+y) mod 2w2w11(x+y) mod 2w2w,(x+y) mod 2w>2w11={x+y2w,2w1x+y x+y,2w1x+y2w1 x+y+2w,x+y<2w1 

补码加法溢出的检测:
满足TMinwx,yTMaxw中的x,y,令sx+wty,则:

  1. 当且仅当x>0,y>0,但s<0时,发生了正溢出
  2. 当且仅当x<0,y<0,但s>0时,发生了负溢出

对于补码来说,若sx+wty,则不管是否发生溢出,都有x=swty,y=swtx

2.2.3 补码的非

补码求反的定义:对满足2w1x2w11的任意x,其w位的无符号逆元wtx由下式给出:

wtx={x,x=2w1x,x>2w1

计算位级表示补码非的值的方式:

  1. 对位级取反再加1,即:x+1
  2. 以最右边的1为界,在其左方取反

2.3.4 无符号乘法

因为0x,y2w1x,y相乘的取值范围为[0,22w2w1+1],这需要2w位表示.C语言取其积的低w位作为结果其表示为xwuy.则有:

xwuy=(xy) mod 2w

2.3.5 补码乘法

范围2w1x,y2w11的整数x,y的乘积xy的取值范围为[22w2+2w1,22w2].这需要2w位表示.C语言取其积的低w位作为结果其表示为xwty.则有:

xwty=U2Tw((xy) mod 2w)

其原理为:无符号和补码乘法的位级等价
给定长度w的位向量x,y,用补码形式定义x=B2Tw(x),y=B2Tw(x).无符号定义x=B2Uw(x),y=B2Uw(y).
由无符号与反码的转换公式可知:x=x+xw12w,y=y+yw12w.对其进行模运算可得:

(xy) mod 2w=[(x+xw12w)(y+yw12w)]=(xy) mod 2w

再联合上式可得:

T2Uw(xwty)=T2Uw(U2Tw((xy) mod 2w))=(xy) mod 2w U2Bw(T2Uw(xwty))=T2Bw(xwty)=U2Bw(xwuy)

2.3.6 乘以常数

整数乘法指令相当慢,一般需要十个时钟周期,而加法、减法、位级运算和移位只要一个时钟周期.因此,编译器会试着用加法和移位来替代常数因子的乘法(后两者需要截断处理).

  1. 乘以2的幂
    即数的位模式左移k位,右方用0填充:
    [xw1,xw2,,x0,0,,0k]
  2. 2的幂相乘的无符号乘法
    C变量x和k有无符号数值xk,且0k<w,则C表达式x<<k产生数值xwu2k
  3. 2的幂相乘的补码乘法
    C变量x和k有补码数值x和无符号数值k,且0k<w,则C表达式x<<k产生数值xwt2k

对于常数K的表达式x*K生成代码,首先将K转化成一组0,1交替的数字序列[(00)(11)(00)(11)].考虑一组从位位置nm的连续1则可写成:

  1. (x<<n)+(x<<(n-1))+⋯+(x<<m)
  2. (x<<(n+1))-(x<<m)

2.3.7 除以2的幂

整数除法要比整数乘法更慢,需要30个或者更多的时钟周期.除以2的幂可以用右移运算.无符号和补码数分别使用逻辑移位和算术移位来达到目的
整数除法会涉及小数,所以我们引入一些运算使其规整到零.

  1. 定义a为唯一的整数a使得aa<a+1
  2. 定义a为唯一的整数a使得a1<aa

则对于x0,y>0,结果是x/y;对于x<0,y>0,结果是x/y.即下舍正,上舍负.则有:

  1. 除以2的幂的无符号除法
    C变量x和k有无符号数值xk,且0k<w,则C表达式x>>k产生数值x/2k.此为逻辑右移
    其原理是x的低k位的无符号数值小于2k
  2. 除以 2 的幂的补码除法,向下舍入
    C变量x和k有补码数值x和无符号数值k,且0k<w,则C表达式x>>k产生数值x/2k.此为算术右移
    x<0时,向下舍入得到的数值,不等于相应位模式的补码表示.所以需要加入偏置(biasing),使其向上舍入.如图,其中12340/24=771.25,则其整数表示应该为771,即舍去小数部分,取整数部分.而其位模式经移位后得到的补码数值为772.
  3. 除以 2 的幂的补码除法,向上舍入
    C变量x和k有补码数值x和无符号数值k,且0k<w,则C表达式(x+(1<<k)-1)>>k产生数值x/2k.此为算术右移
    此表达式中加入了偏置

    其原理为:x/y=(x+y1)/y

2.4 浮点数

2.4.1 二进制小数

对于形如bmbm1b1b0.b1b2b(n1)bn的表示的二进制数b齐备定义为:b=i=nmbi2i.
显然,小数的有限位编码的二进制表示法只能表示能分解为x×2y的数,其他的数都只能被近似表示

2.4.2 IEEE浮点数表示

IEEE浮点标准用V=(1)s×M×2E形式表示一个数:

  1. 符号(sign): s决定这个数是负数(s=1)还是正数(s=0).数值0的符号位解释做特殊情况处理.其被一个单独的符号位s编码
  2. 尾数(significand): M是一个二进制小数,其范围是12ε,或01ε.n位的小数字段frac=fn1f1f0编码尾数M,但是编码的值取决于阶码字段的值是否是0.
  3. 阶码(exponent): E的作用是对浮点数加权,权重是2E次幂(可能是负数).k位的阶码字段exp=ek1e1e0E进行编码

    被编码的值主要有3种情况
  4. 规格化的值:
    exp的位模式即不全为0又不全为1.这种情况下,阶码字段被解释为以偏置(biased)形式表示的有符号整数.所以阶码的值为E=eBias,其中e即为ek1e1e0的无符号数表示,Bias=2k11.
    小数字段frac被解释为小数位f,其中0f<1,其二进制表示为0.fn1f1f0.尾数定义为M=f+1,这种方式也叫隐含的以1开头的(implied leading 1)表示.我们可以调整阶码E使得尾数M始终在范围1M<2
  5. 非规格化的值:
    阶码域全为0时,所表示的数时非规格化形式.在此种情况下,阶码值为E=1Bias(注意,他是一个定值,用于使非规格化值平滑地转换到规格化值),而尾数M=f,也就是小数字段的值,不包括隐含的开头的1
    其用于表示0:+0.0的浮点表示的位模式为全0,当符号位为0时,其他字段全0,得到0.0.
    也可以表示非常接近于0的数.他们提供了一种属性,称为逐渐溢出(gradual underflow),其中,可能的数值分布均匀地接近0.0
  6. 特殊值:
    阶码全为1的情况:
    1. 当小数域全为0时,表示无穷.s=0+,s=1,其用来表示两个大数相乘得到的溢出的结果.
    2. 当小数域非0时,结果值表示为NaN,即Not a Number.一些运算的结果不能是实数或者无穷,就会返回这样的NaN值.也可用于表示非初始化的值.

2.4.3 数字示例



从上图可以看出,最大非规格数7512与最小规格数8512之间的平滑转变

可得到整数数值以浮点数的的存储形式.如下例,将整数12345(0x3039)存储为浮点数形式:

2.4.4 舍入

由于表示方法限制了浮点数的范围与精度.因此,对于值x,我们只能找一个最接近的可以用浮点形式表示的匹配值x.以上便是舍入(rounding)的任务.
IEEE规定了四种舍入方式,默认的方法是找到对接近它的值,其他的三种是寻找它的上界与下界.

  1. 向偶数舍入(round-to-even)或向最近的数舍入(round-to-nearest): 它将数字向上或向下舍入,使得结果的最低有效位数字为偶数.所以这种方法将1.52.5都舍入成2.其作用为避免统计偏差,因为它既可向上舍入,也可向下舍入.
  2. 其他三种产生实际值的确界(guaranteed bound).
    向偶数舍入也可用于二进制小数中.将最低位0看作偶数,1看成奇数.例如考虑舍入值到小数点右边2位,即四分之一:
  3. 10.000112(2332)向下舍入到10.002(2).
  4. 10.001102(2316)向上舍入到10.012(214).
    注意到此例中的最低有效位是1,这是因为这些值不是可能值的中间值,所以舍入到最近的.如果这些值是可能值得中间值,则选择最低有效位为0的值,如:
  5. 10.111002(278),向上舍入到11.002(3)
  6. 10.101002(258),向下舍入到10.102(212).

2.4.5 浮点运算

IEEE标准制定一个了规则:把浮点数x,y看成实数,而某个运算定义在实数上,计算将产生Round(xy),这是舍入后的结果.并定义了1/0产生,1/+0产生+.其优点是可以独立于任何具体的硬件或者软件实现.
虽然实数的加法也形成了阿贝尔群,但是舍入导致了浮点数的运算不可交换(此特性可能会造成一些意想不到的漏洞).如:(3.14+1e10)1e10=0.0,而3.14+(1e101e10)=3.14.
作为阿贝尔群,大多数值在浮点加法下都有逆元,即x+fx=0.无穷(因为+=NaN)和NaN是例外情况,因为对于任何x,都有NaN+fx=NaN.
浮点数的加法满足了单调性属性,即:若ab,则a,b,xNaN,都有x+ax+b.

无符号和补码加法无单调性属性

浮点乘法被定义为xfy=Round(x×y).它是封闭的,可交换的,且乘法单元为1.0,但也不具有交换性,如(1e201e20)1e(20)=+,而1e20(1e201e(20))=1e20.也不具有分配性,如1e20(1e201e20)=0.0,而1e201e201e201e20=NaN.其满足单调性

2.4.6 C语言中的浮点数

因为C语言标准不要求机器使用IEEE浮点标准,所以没有标准的方法改变舍入方式或者得到诸如0,+,,NaN之类的特殊值.大多数系统提供头文件和读取这些特征的过程库,如,出现下列句子时,GNU编译器GCC会定义程序常数INFINITY(表示+)和NAN(表示NaN)

#define _GNU_SOURCE 1
#include <math.h>

在int,float,double格式进行强制类型转换的时候,程序改变数值和位模式的原则如下(假设int为32位):

  1. int→float,数字不会溢出,但出现舍入
  2. int\float→double,能保留精确的数值
  3. double→float,可能溢出成±,也可能出现舍入
  4. float\double→int,值会向零舍入.也可能出现溢出,C语言对此没有指定固定的结果.与Intel兼容的微处理器指定位模式[1000](字长wTMinw)为整数不确定(integer indefinite)值.一个浮点数到整数的转换,如果不能找一个合理的近似值,就会产生这样一个值.如(int)+1e20会得到2147483648(即231),即从一个正值变成一个负值.

3. 程序的机器级表示

3.0 概论

计算机执行机器代码,用字节序列编码低级的操作,包括处理数据,管理内存,读写存储设备上的数据,以及利用网络通信.编译器基于编程语言的规则,目标机器的指令集和操作系统遵循的惯例,经过一系列的阶段生成机器代码.
GCC C语言汇编器以汇编代码的形式产生输出,汇编代码是机器指令的文本表示,给出程序的每一条指令.然后GCC调用汇编器链接器,根据汇编代码生成可执行的机器代码.其中,汇编代码由CPU的指令集架构决定.
本章对源代码和对应汇编代码进行研究,这是一种逆向工程(reverse engineering):通过研究系统和逆向工作,来试图了解系统的创建过程

IA32是x86-64的32位前身,Intel在1985年提出

3.1 历史观点

3.2 程序编码

3.3 数据格式

3.4 访问信息

3.5 算数和逻辑操作

3.6 控制

3.7 过程

3.8 数组分配和访问

3.9 异数的数据结构

3.10 在机器级程序中将控制和数据结合起来

3.11 浮点代码

4. 处理器体系结构

5. 优化程序性能:永远的好兄弟,局部性!!!

6. 存储器层次结构:永远的好兄弟,局部性!!!

问题:

  1. 存储器的发展,种类和结构,由此解决的问题和新的问题
    1. SRAM:最快,强抗干扰
    2. DRAM:较快,访问需两次,参数为 d×w,d=r×c,主要瓶颈 硬件本身架构 以及 缓存不命中的存在
      1. FPM DRAM:缓存不立即擦除
      2. EDO DRAM:减少 RAS/CAS 间隔
      3. SDRAM:同步,根据时钟上升沿
      4. DDR DRAM:两个时钟沿, 参考计算机是如何工作的?探索主内存,以DDR5为例
      5. VRAM:处理图片,通过对缓存的偏移
    3. ROM
    4. FLASH
      1. SSD:Page in Block in Flash in SSD
    5. 磁盘
      1. 主要瓶颈:寻道
      2. 结构:CHS 和 LBA,实际 LBA 会被驱动器转换为 CHS
  2. 存储器与 CPU 的通信
    1. CPU 通过 I/O 桥转发到 存储器,除了 Ln Cache
    2. 存储器操作完毕后,直接发送到 内存,通过 DMA 传输
    3. 发送信号告诉 CPU 操作完成
  3. 存储器与存储器之间的关系与通信
    1. k + 1 层向 k 层提供一块原始数据,块的大小始终不变,由二者决定,几个块组成一个区域
      1. 若 k 有,则直接读,是为缓存命中
      2. 若 k 没有,则向下读,k + 1 层向 k 层提供对应数据,是为缓存不命中
        1. k 层什么都没有,是为冷不命中,需要使用放置策略,向下读,但是由于上层比下层小,会将块的编号进行 模 处理,之后防止在对应快上(这种策略也提供了映射机制),会发生冲突,此为冲突不命中,类似于 hash 碰撞,此时需要调整策略
  4. 高级存储器和低级存储器之间的文件映射实现
    1. 猜测是由多级表项实现,譬如,内存中有块区域叫做 Swap Buffer,用来存储磁盘的满足局部性的数据,可以在此处放置映射表项,类似于虚拟地址和物理地址的转换,结合目录项,获得磁盘地址,表项对应地映射到物理地址
    2. 譬如:32 位地址的
  5. 因为 永远的好兄弟,局部性 的存在能优化访问速度,所以会被存储器大量运用,但若有朝一日它抛弃了我们怎么办?即缓存的命中问题
  6. 高速缓存器 Cache(读 cashing),参考理解计算机Cache(一):从块到缓存结构,以及逐步推出映射策略
    1. 主要参数 (S,E,B,m)S 组,每组 E 行,一行有 B 个字节加上 t 的标志位,一个有效位, m 表示的是寻址空间总大小。
    2. 注意:寻址可能是按寻址,而非按字节寻址,字的大小一般与 CPU 相关,64位,应该是一字等于八字节
    3. E=1:直接映射
    4. E1:组相联映射
    5. S=1:全相联映射

二、系统中运行程序

7. 链接

8. 异常控制流

9. 虚拟存储器

问题:

  1. 什么是虚拟存储器,它为何出现
  2. 它与物理存储器的关系与转换
  3. 它的异常策略:缺页、盈满、碎片管理

三、程序间的交互与通信

10. 系统级I/O

I/O是在主存和外部设备拷贝数据的过程

10.1 Unix I/O

Unix文件是一个m个字节的序列.所有的I/O设备,如网络,磁盘终端,都被模型化成为一个个文件,所有的输入和输出即为读和写:

  1. 程序打开文件时,内核会创建一个非负整数,叫描述符,用于唯一标识所打开的文件.每个进程开始时都会有三个打开的文件,为:标准输入(描述符0),标准输出(描述符1),标准错误(描述符2).在<unistd.h>中定义了成了常量STDIN_FILENO,STDOUT_FILENOSTDERR_FILENO.
  2. 内核中保持着一个文件当前位置k,其初始为0,意义为字节偏移量
  3. 读文件或者说输入,即使从文件拷贝相应个数的的字节到内存,一直到遇到EOF时结束
  4. 关闭文件时,内核会根据相应的描述符释放打开文件时创建的数据结构,并将该描述符恢复到可用描述符池

10.2 打开和关闭文件

open函数来打开或者创建文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(char *filename, int flags, mode_t mode)

其返回值为文件描述符,且为当前进程中没有打开的最小的描述符

  1. filename为文件名,
  2. flags为访问形式,
  3. mode为新文件的访问权限位.通过每个进程都有的unmask函数进行设置

11. 网络编程

socket相当于定义了一个文件的类型,告诉系统这是个网络类型的文件,其中的类型信息包含了协议类型.随后的 connect 或者 bind 用于产生连接,即相当于手动或者系统自动的分配"存储地址".
该文件的地址相当于被扯成了两半, 一半在服务端手中, 一半在客户端手中, 两者结合才能访问文件内容

/*************************************
文件名: server.c 
linux 下socket网络编程简例  - 服务端程序
服务器端口设为 0x8888   (端口和地址可根据实际情况更改,或者使用参数传入)
服务器地址设为 192.168.1.104
*/
 
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
 
int main()
{
int sfp,nfp; /* 定义两个描述符 */
struct sockaddr_in s_add,c_add;
int sin_size;
unsigned short portnum=0x8888; /* 服务端使用端口 */
 
printf("Hello,welcome to my server !\r\n");
sfp = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfp)
{
    printf("socket fail ! \r\n");
    return -1;
}
printf("socket ok !\r\n");
 
/* 填充服务器端口地址信息,以便下面使用此地址和端口监听 */
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr=htonl(INADDR_ANY); /* 这里地址使用全0,即所有 */
s_add.sin_port=htons(portnum);
/* 使用bind进行绑定端口 */
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
    printf("bind fail !\r\n");
    return -1;
}
printf("bind ok !\r\n");
/* 开始监听相应的端口 */
if(-1 == listen(sfp,5))
{
    printf("listen fail !\r\n");
    return -1;
}
printf("listen ok\r\n");
 
while(1)
{
sin_size = sizeof(struct sockaddr_in);
/* accept服务端使用函数,调用时即进入阻塞状态,等待用户进行连接,在没有客户端进行连接时,程序停止在此处,
   不会看到后面的打印,当有客户端进行连接时,程序马上执行一次,然后再次循环到此处继续等待。
   此处accept的第二个参数用于获取客户端的端口和地址信息。
    */
nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
if(-1 == nfp)
{
    printf("accept fail !\r\n");
    return -1;
}
printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",ntohl(c_add.sin_addr.s_addr),ntohs(c_add.sin_port));
 
/* 这里使用write向客户端发送信息,也可以尝试使用其他函数实现 */
if(-1 == write(nfp,"hello,welcome to my server \r\n",32))
{
    printf("write fail!\r\n");
    return -1;
}
printf("write ok!\r\n");
close(nfp);
 
}
close(sfp);
return 0;
}
/*************************************
文件名: client.c 
linux 下socket网络编程简例  - 客户端程序
服务器端口设为 0x8888   (端口和地址可根据实际情况更改,或者使用参数传入)
服务器地址设为 192.168.1.104
*/
 
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
 
int main()
{
int cfd; /* 文件描述符 */
int recbytes;
int sin_size;
char buffer[1024]={0};    /* 接受缓冲区 */
struct sockaddr_in s_add,c_add; /* 存储服务端和本端的ip、端口等信息结构体 */
unsigned short portnum=9875;  /* 服务端使用的通信端口,可以更改,需和服务端相同 */
 
printf("Hello,welcome to client !\r\n");
/* 建立socket 使用因特网,TCP流传输 */
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == cfd)
{
    printf("socket fail ! \r\n");
    return -1;
}
printf("socket ok !\r\n");
/* 构造服务器端的ip和端口信息,具体结构体可以查资料 */
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr= inet_addr("43.248.128.155"); /* ip转换为4字节整形,使用时需要根据服务端ip进行更改 */
s_add.sin_port=htons(portnum); /* 这里htons是将short型数据字节序由主机型转换为网络型,其实就是将2字节数据的前后两个字节倒换,和对应的ntohs效果、实质相同,只不过名字不同。htonl和ntohl是操作的4字节整形。将0x12345678变为0x78563412,名字不同,内容两两相同,一般情况下网络为大端,PPC的cpu为大端,x86的cpu为小端,arm的可以配置大小端,需要保证接收时字节序正确。
 */
 
printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port); 
/* 这里打印出的是小端和我们平时看到的是相反的。 */
 
/* 客户端连接服务器,参数依次为socket文件描述符,地址信息,地址结构大小 */
if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
    printf("connect fail !\r\n");
    return -1;
}
printf("connect ok !\r\n");
/*连接成功,从服务端接收字符*/
if(-1 == (recbytes = read(cfd,buffer,1024)))
{
    printf("read data fail !\r\n");
    return -1;
}
printf("read ok\r\nREC:\r\n");
 
buffer[recbytes]='\0';
printf("%s\r\n",buffer);
 
getchar(); /* 此句为使程序暂停在此处,可以使用netstat查看当前的连接 */
close(cfd); /* 关闭连接,本次通信完成 */
return 0;
}

12. 并发编程

posted on   逝玄  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示