废话不多说,开搞!
一:总线
1.每个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
2.什么是总线?
总线就是一根根导线的集合
3.总线的种类?
<1> 地址总线
<2> 数据总线
<3> 控制总线
4.微型计算机的基本结构
下面看一张图
CPU从内存的3号单元读取数据
地址总线
地址总线的宽度决定了CPU的寻址能力
8086CPU的地址总线宽度是20,所以寻址能力是1M
怎么计算的呢?
这里比较绕,我仔细说下
假如我有两根地址总线,全部通电,它的值是11(二进制)也就是3,可以理解吗?
0(00)
1(01)
2(10)
3(11)
结合上图,那么它的寻址能力就是0 1 2 3,也就是4个字节
地址总线寻址能力的计量单位是Byte,公式是 寻址能力= 2 ^ n,n是地址总线的宽度
那20根是多少呢?
答案是:2 ^ 20 B == 2 ^10 KB == 1 MB
如果对上面的表达式看不懂的话,那么你就需要了解B KB MB 的换算了
小常识:10根地址总线寻址能力是1KB
20根地址总线寻址能力是1MB
30根地址总线寻址能力是1GB
32根地址总线寻址能力是4GB(2^2 GB)这也是为什么32位系统内存最大只支持4G,更大的利用不了
数据总线
数据总线的宽度决定了CPU的单次数据传输量,也就是数据传输速度(俗称数据吞吐量)
8086的数据总线的宽度是16,所以单次最大传递2个字节的数据
这又是怎么计算的呢?看图
1Byte = 8bit
一个字节等于8位(8个二进制位),也就是说,8根数据总线可以组成8个二进制位的数据,也就是一个字节的数据,而16根数据总线能组成2个字节的数据
控制总线
控制总线的宽度决定了CPU对其他器件的控制能力,能有多少种控制
接下来做个小计算:
1 . 一个CPU 的寻址能力为8KB,那么它的地址总线的宽度为____
2. 8080,8088,80286,80386 的地址总线宽度分别为16根,20根,24根,32根.那么他们的寻址能力分别为多少____KB, ____MB,____MB,____GB?
3. 8080,8088,8086,80286,80386 的数据总线宽度分别为8根,8根,16根,16根,32根.那么它们一次可以传输的数据为:____B,____B,____B,____B,____B,
4. 从内存中读取1024字节的数据,8086至少要读____次,80386至少要读取____次.
答案我会放在最下面
二:内存
这几张图了解下就可以了,我们主要在ARM(主存储器中玩),其他的地方我们操作不了。
0x00000~0x9FFFF:主存储器。可读可写
0xA0000~0xBFFFF:向显存中写入数据,这些数据会被显卡输出到显示器。可读可写
0xC0000~0xFFFFF:存储各种硬件、系统信息。只读
内存地址空间的大小受CPU的地址总线宽度的限制,8086的地址总线宽度为20,可以定位(2^20)个不同的内存单元(内存地址范围0x00000~0xFFFFF),所以8086的内存大小为1MB
同样的问题,怎么计算的?
首先0x代表16进制,每一位都是16进制
那么0xFFFFF == 16^5 == 2^4^5 == 2^20 == 1MB(F等于10进制的16)
如过看不懂上面的公式没关系,接着往下看
三:进制
目前主流有4种进制
<1> 二进制:由二个符号组成(0 1) 逢二进一
<2> 八进制:由八个符号组成(0 1 2 3 4 5 6 7)逢八进一
<3> 十进制:由十个符号组成(0 1 2 3 4 5 6 7 8 9)逢十进一
<4> 十六进制:由十六个符号组成(0 1 2 3 4 5 6 7 8 9 a b c d e f)逢十六进一
拓展:N进制:由N个符号组成 逢N进一
注意:我说的是符号!符号!也就是说,你可以用任意数字,字母来组成任意进制,这有什么好处呢?两个字,安全。能在进制上做手脚进行加密安全操作的是非常高端的,所以它有个更重要的作用,装逼!
比如:
我用0 1 3 4 2 5 6 7 8 9 组成一个10进制
那么1+1=?
是不是等于3
这时候别人知道这个“3”的是真的“3”吗?
这就是进制加密的好处,安全!
进制的运算
八进制的运算:
2 + 3 = __ , 2 * 3 = __ ,4 + 5 = __ ,4 * 5 = __.
277 + 333 = __ , 276 * 54 = __ , 237 - 54 = __ , 234 / 4 = __ .
大家可以算下。对照下面的运算表,这可是神器哦,跟我们小时候背的99乘法表一样
进制之间的比对
看懂了进制之间的变换吗?
二进制:从0 写到 1111
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
这种二进制使用起来太麻烦,改成更简单一点的符号:
0 1 2 3 4 5 6 7 8 9 A B C D E F 这就是十六进制了
数据的宽度
数学上的数字,是没有大小限制的,可以无限的大。但在计算机中,由于受硬件的制约,数据都是有长度限制的(我们称为数据宽度),超过最多宽度的数据会被丢弃。
我写个例子,有兴趣的可以在Xcode上跑跑看
int max(){
int max = 0x1FFFFFFFF
return max;
}
printf("%x\n",max());
看看最后输出的max()函数值是多少,是不是我们赋值的值?
计算机中常见的数据宽度
位(Bit): 1个位就是1个二进制位.0或者1
字节(Byte): 1个字节由8个Bit组成(8位).内存中的最小单元Byte.
字(Word): 1个字由2个字节组成(16位),这2个字节分别称为高字节和低字节.
双字(Doubleword): 1个双字由两个字组成(32位)
计算机中的存储数据它会分为有符号和无符号数。
四:寄存器
CPU内部部件之间由总线连接
对我们程序员来说,CPU中最主要部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制。它的作用就是进行数据的临时存储。
CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时的存储区域,并在进行运算时陷阱数据从内存复制到这一小块临时存储区域中,运算时就在这一小块临时存储区域内进行,我们统称这一小块临时存储区域为寄存器
不同的CPU,寄存器的个数、结构是不同的
另外,还有一个概念--高速缓存
iPhoneX上搭载的ARM处理器A11它的一级高速缓存的容量时64KB,2级高速缓存的容量时8MB
CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运算速度相比内存读写要快得多,为了性能,CPU还集成了一个高速缓存-存储区域。当程序运行时,先将要执行的指令代码以及数据复制到高速缓存中去(这一步是由操作系统完成的)。而CPU直接从高速缓存中依次读取指令来执行。
内存地址与高速缓存之间有个一一对应的映射关系,当我们读取指令的时候,pc寄存器指向谁,我们就读谁,当我们pc的地址给了CPU,它其实我们是去高速缓存中寻找,因为它们是映射关系
还有一个问题:当高速缓存中8MB的地址全部用完了怎么办,pc所指向的地址在高速缓存中找不到了怎么办?
不要急,因为这时候会重新从内存中cpoy一份到高速缓存中去,它中间其实有个高速缓存的这个过程
那么为什么不将整个内存条都做成高速缓存区域呢?只开辟出8MB?
越是高效,越是精良的硬件,它的做工要求就更高,成本就更高!
1.通用寄存器
ARM64拥有31个64位(每个寄存器是64位的,CPU是64位的)的通用寄存器x0到x30,这些寄存器通常用来存放一般性的数据,成为通用寄存器(有时候也有特定用途)
咦?不是说是x0到x30吗?为什么只有到x28?
注意:fp就是x29,lr就是x30
并且:sp我们俗称它是x31,但是它不属于通用寄存器
w0到w28是32位的,但是64位的CPU可以向下兼容32位,所以只使用64位寄存器的低32位,如w0就是x0的低32位
这些寄存器是怎么看的呢,打开Xcode,运行程序,大哥断点,然后将这个选为all(原来是auto)
通常CPU会先讲内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
举个例子:
假设内存中有快红色空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
<1> CPU首先会会将红色内存空间的值放到x0寄存器中:
mov x0,红色内存空间 (伪代码)
mov 数据传送指令(相当于赋值)
上面伪代码的意思就是将红色空间的值移动到x0中
<2> 让后让x0寄存器与1相加:add x0,1 (伪代码)
add 相加指令
意思是 将x0的值加1 再存到x0中
<3> 最后将值赋值给内存空间:mov 蓝色内存空间,x0 (伪代码)
这句代码的意思就不说了,自己想
注意:我们所有运算都不能直接拿内存中的数据进行直接计算,必须要把值放到寄存器中进行运算!运算结束后再写进内存中。
2.pc寄存器(program counter)
重点,很有意思
<1> 为指令指针寄存器,它指示CPU当前要读取指令的地址
<2> 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息
<3> CPU在工作的时候把有的的信息看作指令,有的信息看做数据,为同样的信息赋予了不同的意义
例:
比如 1110 0000 0000 0011 0000 1000 1010 1010
可以当作数据 0xE003008AA
也可以当作指令 mov x0,x8
<4> CPU根据什么将内存的信息看作指令呢?
CPU将pc指向的内存单元的内容看作指令
如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过
接下来演示一下这个有意思的pc
我们现在断点在0x102b58bac这个地址上对吧!
ni是单行执行下一步的操作
之前有说过通过改变寄存器的内容来实现对CPU的控制
那么现在就来改一下
当前这个寄存器的内容地址是0x102b58bac
现在我将它改掉
这句代码 register write pc 0x102b58b94
它的意思是将当前pc寄存器的地址改为0x102b58b94
当我们进行ni操作的时候
咦?
它并没有执行到下一步地址为0x102b58bb0的操作,而是进到了我们改动的内存地址下,是不是‘哦莫西罗伊’
3.数据地址寄存器
数据地址寄存器通常用来做数据计算的临时存储、累加、计数、地址保存等功能。定义这些寄存器的作用主要用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
ARM64(CPU)中
64位:X0-X30,XZR(零寄存器)
32位:W0-W30,WZR(零寄存器)
注意:在Inter架构CPU中还有中特殊的寄存器-段寄存器:CS、DS、SS、ES,主要是用来保存这些段的基地址。但是ARM中是没有的。
4.浮点和向量寄存器
因为浮点数的存储以及运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数
<1>浮点数寄存器:
64位:D0-D31
32位:S0-S31
目前的CPU它支持向量运算(向量运算在图形处理相关的领域用的非常多)。喂了支持向量计算,系统也提供了众多的向量寄存器。
<2>向量寄存器:
128位:V0-V31
5.SP和FP寄存器
<1> sp寄存器在任意时刻会保存我们栈顶的地址
<2> fp寄存器也叫做x29寄存器,它属于通用寄存器,但在某些时刻我们利用它保存栈底的地址
<3> 另外:ARM64开始,取消了32位的LDM,STM,PUSH,POP指令,取而代之的是ldr\ldp str\stp
ldr\ldp str\stp 这些都是内存读写指令
注意:读/写操作都是数据往高地址中进行读/写
str(store register)指令
将数据从寄存器中读出来,存到内存中
ldr(load register)指令
将数据从内存中读出来,存到寄存器中
ldp和stp是str和ldr的两个变种指令
<4> ARM64里面,对栈的操作是16字节对齐的
延伸:什么是栈?
栈:一种遵循后入先出法(LIFO)的特殊访问方式的存储空间
什么叫后入先出法?
Last In Out First. 最后一个进去的,第一个出来
有个举烂的例子:
将羽毛球放到羽毛球筒中,最后一个放进去的羽毛球肯定是第一个拿出来
5.bl指令
<1> CPU从何处执行指令是pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
<2> ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值
如:
mov x0,#10、mov x1,#20
这里解释下#号的含义
#后面跟数字,它有个概念,叫“立即数”
什么事立即数?
就是它代表的是个数字,而不是符号!
听懂没,之前说进制的时候,说0 1 2…… 这些是符号
当前面加上#号后,它就代表数字了
所以mov x0,#10这条指令的意思就是:把10这个数放到x0中
<3> but,mov指令不能用于设置pc的值,ARM64没有提供这样的功能
如:mov pc,#0x102b58bb0 这条汇编指令是错误的,不能这么写
<4> ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的就是bl指令
计算题答案
1. 之前有说10根地址总线的寻址能力是1KB,那么8KB == 8 * 1 KB == 2^3 *1KB == 2^3 * 2^10 B = 2^13 B
所以地址总线的宽度是13
2.同样的计算方式
<1> a: 2^16B == (2^10B)^6 KB == 2^6 KB == 64KB
<1> b: 2^20B == 2^10 KB == 1MB
<1> c: 2^24B == b^4 MB ==16MB
<1> d:2^32B == 2^2 GB = 4GB(上面有提到30根地址总线的寻址能力是1GB)