计算机程序的三段人生--计算机原理2(寄存器)
前言(wxy):说到寄存器,迷迷茫茫好多年,首先不知道到底有哪些寄存器,因为不同的博客总会出现几个我不认识的寄存器;其次,想记住每个寄存器的名称,真的太南了;最后,他们到干什么用的,寄存器之间到底什么关系?
一:寄存器的分类:
寄存器是硬件的东西,自然其跟CPU的架构有关,随着不同的处理器演变,寄存器个数也在不断的增加。总的来说可先从如下两个维度进行分类,毕竟这样分类完了,我们就可以专心只研究第二类(用户可见的):
控制和状态寄存器,一般用户不可对其进行编程,他们被控制部件或操作系统使用,以控制CPU的操作,从而控制程序的执行。一般不会在用户代码中出现。
比如寻址用的索引表等
运算(用户可见)寄存器,用户可以对这些寄存器进行编程,还可以通过优化使CPU因使用这类寄存器而减少对主存的访问次数,也就是说我们使用汇编语言可以直接对其操作;一般我们说到寄存器基本就指这一类。
二:控制和状态寄存器:
参考:https://blog.csdn.net/kwame211/article/details/77773621
CPU中至少要有五类控制寄存器和一类状态寄存器:指令寄存器(IR)、程序计数器(PC)、地址寄存器(AR)、数据寄存器(DR)、累加寄存器(AC)、程序状态字寄存器(PSW)。这些寄存器用来暂存一个计算机字,其数目可以根据需要进行扩充。
1. 数据寄存器(Data Register,DR/MDR)又称数据/指令缓冲寄存器
数据寄存器用来暂时存放由主存储器读出的一条指令或一个数据字;反之,当向主存存入一条指令或一个数据字时,也将它们暂时存放在数据寄存器中; 在单累加器结构的运算器中,还可兼作操作数寄存器。
2. 地址寄存器(Address Register,AR/MAR)用来保存CPU当前要所访问的主存单元的地址。
当CPU和主存进行信息交换,即CPU向主存存入数据/指令或者从主存读出数据/指令时,都要使用该地址寄存器进行寻址,接着用数据寄存器存放寻址得来的数据。
如果我们把外围设备与主存单元进行统一编址,那么,当CPU和外围设备交换信息时,我们同样要使用地址寄存器和数据寄存器。
3. 指令寄存器(Instruction Register,IR)用来保存当前正在执行的一条指令。
当执行一条指令时,首先把该指令从PC指向的主存地址读取到数据寄存器(DR)中,然后再传送至指令寄存器。
指令包括操作码和地址码两个字段,为了执行指令,必须对操作码进行测试,识别出所要求的操作,指令译码器(Instruction Decoder,ID)就是完成这项工作的。指令译码器对指令寄存器的操作码部分进行译码,以产生指令所要求操作的控制电位, 并将其送到微操作控制线路上,在时序部件定时信号的作用下,产生具体的操作控制信号。
4. 程序计数器(Program Counter,PC)用来指出下一条指令在主存储器中的地址。
在程序执行之前,首先必须将程序的首地址,即程序第一条指令所在主存单元的地址送入PC,因此PC的内容即是从主存提取的第一条指令的地址。
当执行指令时,CPU能自动递增PC的内容,使其始终保存将要执行的下一条指令的主存地址,为取下一条指令做好准备。若为单字长指令,则(PC)+1àPC,若为双字长指令,则(PC)+2àPC,以此类推。--此时PC体现的是其计数功能;
但是,当遇到转移指令时,下一条指令的地址将由转移指令的地址码字段来指定,而不是像通常的那样通过顺序递增PC的内容来取得。--此时PC寄存器体现的是其寄存功能。
因此,程序计数器的结构应当是具有寄存信息和计数两种功能的结构。
5. 累加寄存器(Accumulator,AC)是一个通用寄存器。
当运算器的算术逻辑单元ALU执行算术或逻辑运算时,为ALU提供一个工作区,可以为ALU暂时保存一个操作数或运算结果。显然,运算器中至少要有一个累加寄存器。
6. 程序状态字寄存器(Program Status Word,PSW),用来表征当前运算的状态及程序的工作方式。
程序状态字寄存器用来保存由算术/逻辑指令运行或测试的结果所建立起来的各种条件码内容,如运算结果进/借位标志(C)、运算结果溢出标志(O)、运算结果为零标志(Z)、运算结果为负标志(N)、运算结果符号标志(S)等,这些标志位通常用1位触发器来保存。除此之外,程序状态字寄存器还用来保存中断和系统工作状态等信息,以便CPU和系统及时了解机器运行状态和程序运行状态。因此,程序状态字寄存器是一个保存各种状态条件标志的寄存器。
小小结:运行一段程序,都是怎么使用这些寄存器呢?
程序/指令是存放在存储系统中的,由CPU负责取指令,分析指令,执行指令,然后再读.....第一条指令可以由系统设定也可以人为指定,具体的工作为
从PC中读取(第一条)指令的地址到AR中,根据AR寻址读取出数据(在这里是指令)到DR中,再因为是指令所以交给IR,进而由逻辑运算部件ALU去做运算
但是做运算光有指令不行,还要由被计算的数据啊,所以ALU还会访问DR(这里是数据)以及其他用户操作的寄存器,
运算的过程中还会产生中间值,那么就需要借助AC来存放了
整个运算的过程中,PSW作为辅助提供条件码等,以及记录状态等
另外,在下一章中会提到(E)IP这个寄存器,同样是用来跟踪下一个将要执行的指令,他们什么关系呢?
说法1:(E)IP就是PC,只不过前者是汇编语言中的用来指代PC的编程语言符号;
说法2:PC是非intel厂家对IP的称呼,也就是说PC起始跟CS:IP是一回事儿;
说法3:参考http://blog.sina.com.cn/s/blog_5ede281a0100sn4w.html#commonComment
总结起来就是:(E)IP是后来Interl家的X86体系具有的东西,其在工作原理上和PC是不同的,但是最终呈现的功能效果是一样的,即都是指导CPU接下来执行哪条指令的,
而对于现在学习微机原理,我们知道(E)IP就行了。
三,计算(用户可见)寄存器
以32位架构(IA-32,即X86体系)为:
CPU相关的:8个通用寄存器;1个指令指针寄存器;1个状态寄存器;6个段寄存器
系统寄存器:5个控制寄存器;3个系统地址寄存器;1个任务寄存器;8个调试寄存器
实际上,还有别的寄存器,只不过跟我们写程序没啥关系,一般也就不提了.....
1. 通用寄存器(Extended * register): 8个32位,可以由程序设计者指定其功能,可用于存放操作数,也可以作为满足某种寻址方式所需的寄存器,一般我们会划分这些寄存器做如下用途。
1)数据/一般寄存器:4个X(?)
不是做什么特别的功能,就是在程序运行的过程中需要做运算时,所借助的媒介,具体为:
- EAX(Accumulator):被称为累加寄存器,用以进行算数运算和返回函数结果等。
- EBX(Base):被称为基址寄存器,在内存寻址时(比如数组运算)用以存放基地址。
- ECX(Counter):被称为记数寄存器,用以在循环过程中记数。
- EDX(Data) : 被称为数据寄存器,常配合 eax 一起存放运算结果等数据。
2)索引寄存器:2个I(index),也叫做变址和指针寄存器
用于字符串操作,字符串是一串有序的字符,所以在操作的时候肯定要有索引来标示处理的那个字符,具体为:
- ESI(Source Index): 指向要处理的数据地址?
- EDI(Destination Index): 指向存放处理结果的数据地址?
3)指针寄存器: 2个P(Pointer):
程序运行和函数调用时,都用到栈,不同的程序以及程序中不同的函数,都有各自的栈空间,那么怎么来维护不同的栈?,具体为:
- ESP(Stack Pointer): 栈指针寄存器,其内存放着一个指针,永远指向当前栈的栈顶;
- EBP(Base Pointer):基址指针寄存器,指向当前栈的栈底。
wxy: 说到栈,都知道是用来存放函数中的一些局部变量等,即那些由程序临时分配存储的局部变量,一个函数对应一块栈空间,称为栈帧,这俩指针就是指向一个栈帧(函数)的区间。
这里要区分一点是,SS也是栈相关的记录,他们有什么关系呢?
答:每一个进程/任务都有一块虚拟空间,这块空间有一个段是分给用作栈的,所以用SS记录这段栈空间从哪里开始,可以认为是一旦进程启动,它就是一个静态的属于进程的属性。
栈给谁用?给函数代码在运行时动态使用的,所以用了俩指针指向一个函数的首尾。其实ebp是当前栈帧的栈底。esp指向栈帧的栈顶,同样是整个栈段的顶。
所以,esp和ebp表示接下来要执行的函数(的栈帧),所以也常常说指向系统栈最上面一个栈帧的栈顶/底。 至于具体怎么使用,看编译原理那个章节。
小小结:
1.关于AX,BX,CX,DX,SI,DI,SP,BP
有的文档会将寄存器分成如上的类别,那是因为对于16位的操作系统例如8086,通用寄存器的名称就是这些。
而对于32位的操作系统,刚好就是这些寄存器的名称加上Extended,并且,每个寄存器的的低16位仍然可以用这些名称来表示 。
2. 对于AX,BX,CX,DX这4个(包括16位和32位)还可以划分成8个独立的8位寄存器,可独立存取,名称为:AH/AL,BH/BL....
3. 32位的ABCD这四个通用寄存器还可以代替专用寄存器作为指针寄存器,但16位不可以
同样的,(E)SI, (E)DI, (E)BP, (E)SP也可以用于算术逻辑运算的操作数和结果,
即,通用寄存器既有其本职工作,也具备"通用"的能力
2.指令指针寄存器:1个P(Pointer), 32位,
- EIP(Instruction Pointer):处理器使用EIP来跟踪下一条要执行的指令,也称为程序计数寄存器
3.状态寄存器:1个P, 32位, 2)EFLAGS(Flags): 1个Flags
4.段寄存器(Segment):6个16位,这些寄存器就和多进程相关了,可以说每个进程都有如下的段,然后这些寄存器就是存放的是当前运行的那个进程的段地址。
具体用法是,在内存分段的管理模式下,段地址结合偏移量(offset)得到目前需要的指令或者数据。
另外,需要注意的是,这个地址是进程空间范围的,那么在保护模式下就是虚拟地址,至于如何到达实际的物理地址,则是另外的原理了。
1)CS(Code):存放代码段的段值,通过他可以找到代码再内存中的位置。因为二进制在被操作系统加载进内存的时候,总得存放到一个地方,所以这个寄存器就是存放当前进程的代码段的地址。
然后结合偏移量(存放再IP寄存器中),一起定位到下一条指令应该去哪里去取。
2)ES(Extra):存放附加数据段的段值
3)DS(Data):存放数据段的段值。即如果想要访问内存中的数据(一般是全局变量之类的,详细看其他章节),则同样是数据段的地址 结合偏移量(存放在通用寄存器中)
4)FS():
5)GS():
6)SS(Stack):桟寄存器。桟这块和程序调用以及局部变量有关。下面有详细描述二进制程序加载到内存的原理。
解析:
- 在16位的系统中,只有4个16位段寄存器,GS和SS属于32位新增的;
- 针对CS和DS,想要得到实际的地址,在8086(20位地址总线)架构中,地址=段中的起始地址 *16 + 偏移量,即把CS和DS中的指左移4位变成20位的数据,再加上16位的偏移量
sgz碎碎念:不知道有没有人疑惑一下,为什么是*16,偏移量也是16是因为IP就是16位的么?反正我当时就是自然而然接受了,都没多想。想在想想,其实很简单呀
首先16变成20位自然需要*16(左移4位);然后何为段,段是一个区间,这种段式寻址就意味着 偏移量 == 段区间的大小,CS和DS代表是第几个段;
最后就得出这样的结论:一个段是16位(因为要左移4位),一共也就有16位(因为段寄存器是16位),所以偏移量只能是16位(刚好偏移量寄存器就是16位或者大于16位)
所以我的结论就是,并不是因为偏移量寄存器是16位所以偏移量才是16位,而是因为20位地址决定了偏移量只能是16位(这个理论待证实)
- 针对CS和DS,想要得到实际的地址,在32位地址总线架构中,因为其他寄存器都是32位了,也就是说偏移量可以很大了(32位)了,直接就可以寻址了呀,
- 但是又要兼容之前的寻址模式(即分段并左移4位+寄存器这种),并且现在又出现了保护模式,即每个进程都有4G地址空间,但是是虚拟的,后面会详细说明什么是保护模式,现在就先理解多个进程共享4G空间,所以保护模式下的寻址方式为:
首先,段寄存器不能改,因为如果改成32位,就直接可以寻址了,就不需要偏移了,所以为了统一,还是16位
然后,出现了一个新的中间项,叫做段描述符(Segment Descriptor)表项,存放这些表项的叫做全局描述符表(Global Descriptor Table ,GDT,全局唯一)或局部描述符表(Local Descriptor Table, LDT,一个进程一个),
每个表项存放的是段的起始地址,(相当于之前的 段大小 * 第几段), 原来的段寄存器中存放的是选择子(Selector),他表示的含义是 段描述符表中的哪个表项。具体的原理可以看看其他的博文,这里总结下就是:
段描述表:存放在内存中,GDT表的具体的入口地址存放在一个新的寄存器GDTR(48位),通过LGDT指令将GDT的入口地址装入此寄存器,CPU就根据此寄存器中的内容访问GDT表了。
而LDT表的入口地址是通过又一个新的寄存器LDTR(16位),结合GDT表间接访问到的。
疑问:谁来决定GDT表存放在内存的哪里?然后又是谁执行的LGDT命令去写入到GDTR寄存器中?
在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。在保护模式初始化过程中必须给GDTR加载一个新值。详见。。。。
段描述符表项:是一个8字节长的数据结构,用来描述一个段的位置、大小、访问控制和状态等信息,其中最基本内容是段基址和边界。段基址以4字节表示(图中可看出3,4,5,8字节); 段边界20位表示(1,2字节及7字节的低四位)。
这些表项初始是存放在内存的段描述表中,一旦开始寻址,根据选择子的内容硬件会自动将表项的内容拷贝到 一个非编程用户不可见的寄存器:段描述符寄存器(64位)中。
即CPU只知道去段寄存器中拿基地址,因为本来CPU的工作原理就是依附寄存器做下一步行动的....
选择子:分3块,index(13bit)表示所需要表项在表的位置,表最多有2^13个表项。TI值只有一位0或1,0代表选择子是在GDT选择,1代表选择子是在LDT选择。请求特权级(RPL)则代表选择子的特权级,共有4个特权级(0级、1级、2级、3级。
这个特权及很重要,在保护模式下都知道内核地址分为了内核区和用户区,所以哪里可以访问哪里不可以访问就是根据他来决定的,详细见。。。
选择子存放在6个专用寄存器(CS,,,,,,SS)和一个特殊寄存器LDTR中,如果是获取系统段基址则选择子就是6个专用寄存器,如果是获取任务级别的段基址,则需要两级选择子,第一级为LDRT,第二级就是那6个专用寄存器。
图1:段描述符(2字节 * 4=8字节=64位,地址从下向上生长) 图2: 段选择子(16位) 与 段描述符寄存器 (图片来源网络) 图3: GDTR的结构
具体的地址怎么算,其实如上都只是讲解了寄存器在寻址的时候扮演的角色,但是具体怎么最后寻址到物理地址还有很大一段路要走。参见?
========================================================================
8个通用寄存器: EAX, EBX, ECX, EDX, ESI, EDI,ESP, EBP
对应的汇编符号: rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp
2)索引寄存器:2个I(index),也叫做变址和指针寄存器
用于字符串操作,字符串是一串有序的字符,所以在操作的时候肯定要有索引来标示处理的那个字符,具体为:
- (Source Index): 指向要处理的数据地址?
- (Destination Index): 指向存放处理结果的数据地址?
3)指针寄存器: 2个P(Pointer):
程序运行和函数调用时,都用到栈,不同的程序以及程序中不同的函数,都有各自的栈空间,那么怎么来维护不同的栈?,具体为:
- ESP(Stack Pointer): 栈指针寄存器,其内存放着一个指针,永远指向当前栈的栈顶;
- EBP(Base Pointer):基址指针寄存器,指向当前栈的栈底。
1个指令指针寄存器:
1个状态寄存器:
6个段寄存器:
系统寄存器:5个控制寄存器;3个系统地址寄存器;1个任务寄存器;8个调试寄存器
其他参考链接:
https://blog.csdn.net/chance_yin/article/details/8944038
http://ilinuxkernel.com/?p=1276
https://zhuanlan.zhihu.com/p/25892385
------------恢复内容结束------------