第1章 工欲善其事,必先利其器
1.1 代码编辑工具:Vim
1.1.1 安装Vim
Ubuntu环境下:
#apt-get install vim
1.1.2 Vim常用命令
Vim常见的工作模式如下。
● 普通模式:打开文件时的默认模式,在其他模式下按下ESC键都可返回到该模式。
● 插入模式:按i/o/a键进入该模式,进行文本编辑操作,不同之处在于插入字符的位置在光标之前还是之后。
● 命令行模式:普通模式下输入冒号(:)后会进入该模式,在该模式下输入命令,如输入:set number或:set nu可以显示行号。
● 可视化模式:在普通模式下按v键会进入可视化模式。在该模式下移动光标可以选中一块文本,然后可以进行复制、剪切、删除、粘贴等文本操作。
● 替换模式:在普通模式下通过光标选中一个字符,然后按r键,再输入一个字符,你会发现你输入的字符就替换掉了原来那个被选中的字符。在该模式下进行文本替换很方便,省去了先删除再插入这种常规操作。
光标移动
k:在普通模式下,敲击k键,光标向上移动一个字符。
j:在普通模式下,敲击j键,光标向下移动一个字符。
h:在普通模式下,敲击h键,光标向左移动一个字符。
l:在普通模式下,敲击l键,光标向右移动一个字符。
$:将光标移动到当前行的行尾。
0:将光标移动到当前行的行首。
nG:光标跳转到指定的第n行。
gg/G:光标跳转到文件的开头/末尾
文本的基本操作
i/a:在当前光标的前或后面插入字符。
dd:删除当前光标所在处的一整行。
2dd:删除当前光标所在处的一整行和下一行。
yw:复制一个单词。
yy:复制光标所在处的一整行。
p:粘贴,注意是粘贴到光标所在处的下一行。
文本的查找与替换
/string:在Vim的普通模式下输入/string即可正向往下查找字符串string。
?string:反向查找字符串string。
:set hls:高亮显示光标处的单词,敲击n浏览下一个。
s/old/new:将当前行的第一个字符串old替换为new。
s/old/new/g:将当前行的所有字符串old替换为new。
%s/old/new/g:将文本中所有字符串old替换为new。
%s/^old/new/g:将文本中所有以old开头的字符串替换为new。
文本的保存与退出
u:撤销上一步的操作。
q:若文件没有修改,则直接退出。
q!:若文件已修改,则放弃修改,退出。
wq:若文件已修改,则保存修改,退出。
e!:若文件已修改,则放弃修改,恢复文件打开时的状态。
w !sudo tee %:在Shell的普通用户模式下保存root读写权限的文件,%表示当前的文件名。
1.1.3 Vim配置文件:vimrc
Vim配置文件分为系统级配置文件和用户级配置文件。用户级配置文件只对当前用户有效,一般位于$HOME/.vimrc和~/.vim/vimrc这两个路径下,而系统级配置文件则对所有用户都有效,一般位于/etc/vim/vimrc路径下。
我们可以通过vim --version命令来查看Vim配置文件的路径。
1.1.4 Vim的按键映射
通过修改vimrc文件可以实现按键映射。
除了通过vimrc配置文件来定制功能,Vim还支持通过插件来扩展功能。在Vim的官方网站上有很多xx.vim格式的插件供用户下载使用。如果你想通过插件来扩展Vim的功能,方法很简单:先在你的当前用户下创建一个~/.vim/plugin目录,然后将这些xx.vim格式的插件复制到这个目录,在$HOME/.vimrc配置文件里对这些插件进行配置,就可以直接使用了。
第2章 计算机体系结构与CPU工作原理
2.2 一颗CPU是怎么设计出来的
2.2.1 计算机理论基石:图灵机
图灵机的构造:一条无限长的纸带Tape、一个读写头Head、一套控制规则Table、一个状态寄存器。图灵机内部有一个机器读写头Head,读写头可以一直读取纸带,图灵机根据自己有限的控制规则,根据纸带的输入,不断更新机器的状态,并将输出打印到纸带上。
比较图灵机原型与现代计算机,你会发现有很多相似的地方:
● 无限长的纸带:相当于程序代码。
● 一个读写头Head:相当于程序计数器PC。
● 一套控制规则Table:相当于CPU有限的指令集。
● 一个状态寄存器:相当于程序或计算机的状态输出。
不同架构的CPU,指令集不同,支持运行的机器指令也不同,但是有一条是相同的:每一种CPU只能支持有限个指令,任何复杂的运算最终都可以分解成有限个基本指令来完成:加、减、乘、除、与、或、非、移位等算术运算或逻辑运算。
2.2.2 CPU内部结构及工作原理
CPU内部构造很简单,只包含算术逻辑运算单元(ALU)、控制单元、寄存器等,仅支持有限个指令。CPU支持的有限个基本指令集合,称为指令集。程序代码存储在内部存储器(内存)中,CPU可以从内存中一条一条地取指令、翻译指令并执行它。
ALU由算术单元和逻辑单元组成,算术单元负责数学运算,如加、减、乘等;逻辑单元负责逻辑运算,如与、或、非等。
ALU只是纯粹的运算单元,要想完成一个指令运行的整个流程,还需要控制单元的协助。
控制单元根据程序计数器PC中的地址,会不断地从内存RAM中取指令,放到指令寄存器中并进行译码,将指令中的操作码和操作数分别送到ALU,执行相应的运算。
CPU的工作频率要比RAM高很多,防止RAM拖后腿,CPU一般都会在内部配置一些寄存器,用来保存CPU在计算过程中的各种临时结果和状态值。ALU在运算过程中,当运算结果为0、为负、数据溢出时,也会有一些Flags标志位输出,这些标志位对控制单元特别有用,如一些条件跳转指令,其实就是根据运算结果的这些标志位进行跳转的。CPU跳转指令的实现其实也很简单:根据ALU的运算结果和输出的Flags标志位,直接修改PC寄存器中的值即可
2.2.3 CPU设计流程
- 设计芯片规格
根据需求,设计出芯片基本的框架、功能,进行模块划分。有些复杂的芯片可能还需要建模,使用MATLAB、CADENCE等工具进行前期模拟和仿真。 - HDL代码实现
使用VHDL或Verilog硬件描述语言把要实现的硬件功能描述出来,接着通过EDA工具不断仿真、修改和验证,直到芯片的逻辑功能完全正确。这种仿真我们一般称为前端仿真,简称前仿。前仿只验证芯片的逻辑功能是否正确,不考虑延时等因素。这个阶段也是芯片设计最重要的阶段,会耗费大量的时间去反复验证芯片逻辑功能的正确性。芯片公司内部一般也会设有数字IC验证工程师岗位,招聘工程师专门从事这个工作。 - 逻辑综合
前端仿真通过后,通过EDA工具就可以将HDL代码转换成具体的逻辑门电路。专业说法是将HDL代码翻译成门级网表:Gate-level netlist,网表文件用来描述电路中元器件之间的连接关系。有数字电路基础的人都知道,任何一个逻辑运算都可以转化为基本的门级电路(与门、或门、非门等)的组合来实现,而网表就是用来描述这些门级电路的连接信息的。在综合过程中,有时候还需要设定一些约束条件,让综合出来的具体电路在芯片面积、时序等参数上满足预期要求。此时的电路考虑了延时等因素,和实际的芯片电路已经很接近了。
Foundry在集成电路领域一般指专门负责生产、制造芯片的厂家,如台积电、中芯国际等。
Fabless是Fabrication(制造)和less的组合词,专指那些只专注于集成电路设计,而没有芯片制造工厂的IC设计公司。
对于一些Fabless的IC设计公司而言,门级电路一般是由晶圆厂,也就是芯片代工厂以工艺库的形式提供的,如中芯国际、台积电、三星半导体等。如果你设计的芯片委托台积电代工制造,工艺制程是14nm,那么当你在设计芯片时,台积电会提供给你14nm级的工艺库,里面包含各种门电路,经过逻辑综合生成的电路参数,如延时参数,和台积电生产芯片实际使用电路的工艺参数是一致的。 - 仿真验证
通过逻辑综合生成的门级电路,已经包含了延时等各种信息,接下来还需要对这些门级电路进行进一步的静态时序分析和验证。为了提高工作效率,除了使用仿真软件,有时候也会借助FPGA平台进行验证。前端仿真发生在逻辑综合之前,专注于验证电路的逻辑功能是否正确;逻辑综合后的仿真,一般称为后端仿真,简称后仿。后端仿真会考虑延时等因素。后端仿真通过后,从HDL代码到生成门级网表电路,整个芯片的前端设计就结束了。 - 后端设计
通过前端设计,我们已经生成了门级网表电路,但门级网表电路和实际的芯片电路之间还有一段距离,我们还需要对其不断完善和优化,将其进一步设计成物理版图,也就是芯片代工厂做掩膜版需要的电路版图,这一阶段称为后端设计。后端设计包括很多步骤,具体如下:
● DFT:Design For Test,可测试性设计。芯片内部一般会自带测试电路,如插入扫描链、引出JTAG调试接口。
● 布局规划:各个IP电路模块的摆放位置、时钟线综合、信号线的布局等。
● 物理版图验证:检查设计规则、连线宽度、间距是否符合工艺要求和电气规则。
物理版图验证通过后,芯片设计公司就可以将这个物理版图以GDSII文件的格式交给芯片制造代工厂(Foundry)去流片了。到了这一步,整个芯片设计、仿真、验证的流程就结束了,我们称为tap-out。
芯片代工厂根据物理版图提供的这些信息来制造掩膜版,然后使用光刻机,通过掩膜版在晶圆的硅片衬底上开凿出各种掺杂窗口,接着对硅片进行离子注入,掺杂不同的三价元素和五价元素,生成PN结,进而构成二极管、三极管、CMOS管等基本元器件,构建出各种门电路。光刻机根据物理版图的不同层,制作不同的掩膜版,从底层开始,逐层制作,就可以在晶圆硅片衬底上生成多层立体的3D电路结构。
晶圆上的一个个CPU芯片电路在经过切割、封装、引出管脚、测试后,就是我们在市场上常见的各种CPU芯片了。
2.3 计算机体系结构
CPU内部的结构其实很简单,除了ALU、控制单元、寄存器和少量Cache,根本没有多余的空间存放我们编写的代码,我们需要额外的存储器来存放我们编写的程序(指令序列)。
随机访问,CPU可以随机到它的任意地址去读写数据
计算机主要用来处理数据。我们编写的程序,除了指令,还有各种各样的数据。指令和数据都需要保存在存储器中,根据保存方式的不同,计算机可分为两种不同的架构:冯·诺依曼架构和哈弗架构。
2.3.1 冯·诺依曼架构
采用冯·诺依曼架构的计算机,其特点是程序中的指令和数据混合存储,存储在同一块存储器上。
冯·诺依曼架构的特点是结构简单,工程上容易实现,所以很多现代处理器都采用这种架构,如X86、ARM7、MIPS等。
2.3.2 哈弗架构
哈弗架构的特点是:指令和数据被分开独立存储,它们分别被存放到程序存储器和数据存储器。每个存储器都独立编址,独立访问,而且指令和数据可以在一个时钟周期内并行访问。使用哈弗架构的处理器运行效率更高,但缺点是CPU实现会更加复杂。8051系列的单片机采用的就是哈弗架构。
2.3.3 混合架构
现在的CPU工作频率越来越高,很容易和内存RAM之间产生带宽问题:CPU的频率可以达到GHz级别,而对应的内存RAM一般工作在几百兆赫兹(目前的DDR4 SDRAM也能工作在GHz级别了)。CPU和RAM之间传输数据,要经过找地址、取数据、配置、等待、输出数据等多个时钟周期,内存带宽瓶颈会拖慢CPU的工作节奏,进而影响计算机系统的整体运行效率。为了减少内存瓶颈带来的影响,CPU引入了Cache机制:指令Cache和数据Cache,用来缓存数据和指令,提升计算机的运行效率。
SoC芯片内部的Cache层采用哈弗架构,集成了指令Cache和数据Cache。当CPU到RAM中读数据时,内存RAM不是一次只传输要读取的指定字节,而是一次缓存一批数据到Cache中,等下次CPU再去取指令和数据时,可以先到这两个Cache中看看要读取的数据是不是已经缓存到这里了,如果没有缓存命中,再到内存中读取。当CPU写数据到内存RAM时,也可以先把数据暂时写到Cache里,然后等待时机将Cache中的数据刷新到内存中。Cache缓存机制大大提高了CPU的访问效率,而SoC芯片外部则采用冯·诺依曼架构,工程实现简单。现代的计算机集合了这两种架构的优点,因此我们很难界定一款芯片到底是冯·诺依曼架构还是哈弗架构,我们就姑且称之为混合架构吧。
2.4 CPU性能提升:Cache机制
2.4.1 Cache的工作原理
Cache在物理实现上其实就是静态随机访问存储器(Static Random Access Memory,SRAM)
Cache的工作原理很简单,就是利用空间局部性和时间局部性原理,通过自有的存储空间,缓存一部分内存中的指令和数据,减少CPU访问内存的次数,从而提高系统的整体性能。
Cache的工作流程以图2-28为例:当CPU读取内存中地址为8的数据时,CPU会将内存中地址为8的一片数据缓存到Cache中。等下一次CPU读取内存地址为12的数据时,会首先到Cache中检查该地址是否在Cache中。如果在,就称为缓存命中(Cache Hit),CPU就直接从Cache中取数据;如果该地址不在Cache中,就称为缓存未命中(Cache Miss),CPU就重新转向内存读取数据,并重新缓存从该地址开始的一片数据到Cache中。
CPU写内存的工作流程和读类似:以图2-29为例,当CPU往地址为16的内存写入数据0时,并没有真正地写入RAM,而是暂时写到了Cache里。此时Cache和内存RAM的数据就不一致了,缓存的每块空间里一般会有一个特殊的标记位,叫“Dirty Bit”,用来记录这种变化。当Cache需要刷新时,如Cache空间已满而CPU又需要缓存新的数据时,在清理缓存之前,会检查这些“Dirty Bit”标记的变化,并把这些变化的数据回写到RAM中,然后才腾出空间去缓存新的内存数据。
以上只是对Cache的工作原理做了简化分析,实际的Cache远比这复杂,如Cache里存储的内存地址,一般要经过地址映射,转换为更易存储和检索的形式。除此之外,现代的CPU为了进一步提高性能,大多采用多级Cache:一级Cache、二级Cache,甚至还有三级Cache。
2.4.2 一级Cache和二级Cache
在CPU内部,Cache和寄存器的电路比内存DRAM复杂了很多,会占用很大的芯片面积,如果大量使用,芯片发热量会急剧上升,所以在CPU内部寄存器一般也就几十个,靠近CPU的一级Cache也就几十千字节。既然无法继续增加一级Cache的容量,一个折中的办法就是在一级Cache和内存之间添加二级Cache,二级Cache的工作频率比一级Cache低,但是电路成本会降低,元器件的运行速度总是和电路成本成正比
现在的CPU一般都是多核结构,一个CPU芯片内部会集成多个Core,每个Core都会有自己独立的L1 Cache,包括D-Cache和I-Cache。在X86架构的CPU中,一般每个Core也会有自己独立的L2 Cache,L3Cache被所有的Core共享。而在ARM架构的CPU中,L2 Cache则被每簇(Cluster)的Core共享。
2.5 CPU性能提升:流水线
2.5.1 流水线工作原理
一条指令的执行一般要经过取指令、翻译指令、执行指令3个基本流程。CPU内部的电路分为不同的单元:取指单元、译码单元、执行单元等,指令的执行也是按照流水线工序一步一步执行的。如图2-34所示,我们假设每一个步骤的执行时间都是一个时钟周期,那么一条指令执行完需要3个时钟周期。
没引入流水线时,CPU执行指令的3个时钟周期里,取指单元只在第一个时钟周期里工作,其余两个时钟周期都处于空闲状态,其他两个执行单元也是如此。
引入流水线后,除了刚开始的第一个时钟周期大家可以偷懒,其余的时间都不能闲着:从第二个时钟周期开始,当译码单元在翻译指令1时,取指单元也不能闲着,要接着去取指令2。从第三个时钟周期开始,当执行单元执行指令1时,译码单元也不能闲着,要接着去翻译指令2,而取指单元要去取指令3。从第四个时钟周期开始,每个电路单元都会进入满负荷工作状态,像富士康工厂里的流水线一样,源源不断地执行一条条指令。
2.5.2 超流水线技术
每一道工序都称为流水线中的一级,流水线越深,每一道工序的执行时间就会变得越小,处理器的时钟周期就可以更短,CPU的工作频率就可以更高,进而可以提升CPU的性能,提高工作效率。我们把5级以上的流水线称为超流水线结构。
流水线中耗时最长的那道工序单元的执行时间(即时间延迟)决定了CPU流水线的性能。CPU流水线中的每一级电路单元一般都是由组合逻辑电路和寄存器组成的,组合逻辑电路用来执行本道工序的逻辑运算,寄存器用来保存运算输出结果,并作为下一道工序的输入。
流水线通过减少每一道工序的耗费时间来提升整条流水线的效率。在CPU内部也是如此,CPU内部的数字电路是靠时钟驱动来工作的,既然每条指令的执行时钟周期数不变,即执行每条指令都需要3个时钟周期,但是我们可以通过缩短一个时钟周期的时间来提升效率,即减少每条指令所耗费的时间。一个时钟周期的时间变短,CPU主频也就相应提升,影响时钟周期时间长短的一个关键的制约因素就是CPU内部每一个工序执行单元的耗费时间。
流水线的本质是拿空间换时间,流水线越深,电路会越复杂,就需要更多的组合逻辑电路和寄存器,芯片面积也就越大,功耗也就随之上升了。
流水线越深,就越能提升性能吗?也不一定。流水线是靠指令的并行来提升性能的,第一条指令还没有执行完,下面的第二条指令就开始取指、译码了。执行的程序指令如果是顺序结构的,没有中断或跳转,流水线确实可以提高执行效率。但是当程序指令中存在跳转、分支结构时,下面预取的指令可能就要全部丢掉了,需要到跳转的地方重新取指令执行。
流水线越深,一旦预取指令失败,浪费和损失就会越严重,因为流水线中预取的几十条指令可能都要丢弃掉,此时流水线就发生了停顿,无法按照预期继续执行,这种情况我们一般称为流水线冒险(hazard)。
2.5.3 流水线冒险
引起流水线冒险的原因有很多种,根据类型不同,我们一般分为3种。
● 结构冒险:所需的硬件正在为前面的指令工作。
● 数据冒险:当前指令需要前面指令的运算数据才能执行。
● 控制冒险:需根据之前指令的执行结果决定下一步的行为。
结构冒险可以通过将冲突的内存单元或是寄存器进行替换进行解决,比如两条紧跟着的指令都要用R1寄存器,且没有依赖关系,则可以通过编译器或者硬件自动完成这种类似替换动作;
数据冒险和控制冒险,则会通过插入空指令,等待前一条指令运行完毕之后再去运行或者读取后面的指令
2.5.4 分支预测
条件跳转引起的控制冒险虽然也可以通过在流水中插入空泡来避免,但是当流水线很深时,需要插入更多的空泡。
根据工作方式的不同,分支预测可分为静态预测和动态预测。静态预测在程序编译时通过编译器进行分支预测,这种预测方式对于循环程序最有效,它可以根据你的循环边界反复取指令。而对于跳转分支,静态预测就比较简单粗暴了,一般都是默认不跳转,按照顺序执行。我们在编写有跳转分支的程序时,要记得把大概率执行的代码分支放在前面,这样可以明显提高代码的执行效率。
动态预测则指在程序运行时进行预测。在CPU内部,除了Cache,就数分支预测器的电路版图最大。
2.5.5 乱序执行
我们编写的代码指令序列按照顺序依次存储在RAM中。当程序执行时,PC指针会自动到RAM中去取,然后CPU按照顺序一条一条地依次执行,这种执行方式称为顺序执行(in order)。当这些指令前后有数据依赖关系时,就会产生数据冒险,我们可以通过在指令序列之间添加空指令,让流水线暂时停顿来避免流水线中预期的指令被冲刷掉。除此之外,我们还可以通过乱序执行(out of order)来避免流水线冲突。
造成流水线冲突的根源在于指令之间存在相关性:前后指令之间要么产生数据冒险,要么产生结构冒险。我们可以通过重排指令的执行顺序,而不是被动地填充空指令来去掉这种依赖。
支持乱序执行的CPU处理器,其内部一般都会有专门的乱序执行逻辑电路,该控制电路会对当前指令的执行序列进行分析,看能否提前执行。如整型计算、浮点型计算会使用不同的计算单元,同时执行这些指令并不会发生冲突。CPU分析这些不相关的指令,并结合各电路单元的空闲状态综合判断,将能提前执行的指令进行重排,发送到相应的电路单元执行。
2.5.6 SIMD和NEON
CPU的控制单元到内存中取数据,将操作数送到算术逻辑单元中,取数据的方法有两种:第一种是先取第一个操作数,然后访问内存读取第二个操作数,最后才能进行求和计算。这种数据操作类型一般称为单指令单数据(Single Instruction Single Data,SISD);第二种方法是几个执行部件同时访问内存,一次性读取所有的操作数,这种数据操作类型称为单指令多数据(Single Instruction Multiple Data,SIMD)。毫无疑问,SIMD通过单指令多数据运算,帮助CPU实现了数据并行访问,SIMD型的CPU执行效率更高。
NEON是适用于Cortex-A和Cortex-R52系列处理器的一种128位的SIMD扩展指令集
2.5.7 单发射和多发射
每个时钟周期只能从存储器取一条指令,每个时钟周期也只能执行一条指令,这种处理器一般叫作单发射处理器。
多发射处理器在一个时钟周期内可以执行多条指令。处理器内部一般有多个执行单元,如算术逻辑单元(ALU)、乘法器、浮点运算单元(FPU)等。
根据实现方式的不同,多发射处理器又可分为静态发射和动态发射。静态发射指在编译阶段将可以并行执行的指令打包,合并到一个64位的长指令中。在打包过程中,若找不到可以并行的指令配对,则用空指令NOP补充。这种实现方式称为超长指令集架构(Very Long Instruction Word,VLIW)。
采用SuperScalar结构的处理器又叫超标量处理器,这种处理器在多发射的实现过程中会增加额外的取指单元、译码单元、逻辑控制单元等硬件电路。在指令运行时,将串行的指令序列转换为并行的指令序列,分发到不同的执行单元去执行,通过指令的动态并行化来提升CPU的性能。
VLIW和SuperScalar分别从编译器和硬件上实现了指令的并行化,各有各的优势和局限性:VLIW虽然实现简单,但由于兼容性问题,不支持目前主流的X86、ARM处理器;而采用SuperScalar结构的处理器,完全依赖流水线硬件去动态识别可并行执行的指令,并分发到对应的执行单元执行,不仅大大增加了硬件电路的复杂性,而且也存在极限。学者和工业界一致认为,同时执行8条指令将是SuperScalar结构的极限。
现在新架构的处理器没有指令集兼容的历史包袱,一般会采用显式并行指令计算(Explicitly Parallel Instruction Computing,EPIC)的指令集结构。EPIC结合了VLIW和SuperScalar的优点,允许处理器根据编译器的调度并行执行指令而不增加硬件的复杂性。EPIC的实现原理也很简单,就是在指令中使用3个比特位来表示相邻的两条指令有没有相关性、当前指令要不要等上一条指令运行结束后才能执行。程序在运行时,流水线根据指令中的这些信息可以很轻松地实现指令的并行化和分发工作。
2.6 多核CPU
多核CPU需要考虑CPU之间的同步以及资源竞争问题
2.6.4 超线程技术
超线程技术通过增加一定的控制逻辑电路,使用特殊指令可以将一个物理处理器当两个逻辑处理器使用,每个逻辑处理器都可以分配一个线程运行,从而最大限度地提升CPU的资源利用率。
在CPU内部很多资源其实也是可以共享的,如ALU、FPU、Cache、总线等,也有很多资源是每个线程独有的,如寄存器状态、堆栈等。我们通过增加一些控制逻辑电路,保存各个线程的状态,共享ALU、Cache等共享资源,就可以在一个物理Core上实现两个逻辑Core,操作系统可以给每个逻辑Core都分配1个线程运行。
在同一个物理Core上的两个线程并不是同时运行的,因为每个线程都需要使用物理Core上的共享资源(如ALU、Cache等)
超线程技术其实就是“欺骗”操作系统,让操作系统认为它有更多的Core,给它分配更多的任务执行,通过减少CPU的空闲时间来提高CPU的利用率。
2.7 后摩尔时代:异构计算的崛起
什么TPU,APU,XPU等等
2.8 总线与地址
CPU与内存、各种外部设备等IP之间都是通过总线相连的。CPU如果想访问内存,或控制外部设备的运行,该如何操作呢?很简单,通过地址访问。在一个计算机系统中,CPU内部的寄存器是没有地址的,可直接通过寄存器名访问。而内存和外部设备控制器中的寄存器都需要有一个地址,然后CPU才能通过地址去读写这些外部设备控制器的寄存器,控制外部设备的运行,或者根据地址去读写指定的内存单元。
计算机的内存简单点理解,其实就是将一系列存储单元和译码器组装在一起。内存中包含很多存储单元,为了方便管理,我们需要将这些存储单元进行编号管理,每一个存储单元对应一个编号。当CPU想访问其中一个存储单元时,可通过CPU管脚发出一组信号,经过译码器译码,选中与这个信号对应的存储单元,然后就可以直接读写这块内存了。CPU管脚发出的这组信号,也就是存储单元对应的编号,即地址。
地址的本质其实就是由CPU管脚发出的一组地址控制信号。因为这些信号是由CPU管脚直接发出的,因此也被称为物理地址。地址信号线的位数决定了寻址空间的大小,如两根A1A0地址信号线,有4字节的寻址空间;CSA1A0三根地址信号线有8字节的寻址空间(38译码器)。在一个32位的计算机系统中,32位的地址线有4GB大小的寻址空间。