您的位置:首页 > 理论基础

计算机组成原理

2019-06-25 19:21 1056 查看

目录

文章目录

  • 计算机的逻辑电路部件
  • 中央处理器(CPU)
  • 指令系统与运算
  • 计算机组成原理的研究目的

    计算机组成(Computer Organization),其中 Organization 是 “组织机构” 的意思。所以计算机组成原理就是研究计算机这一实体内部的多个不同部件之间是如何变成一个 “组织机构” 的,这个组织机构最终又是如何能够进行各种计算、控制、读取输入、返回输出,从而提供各种强大功能的。

    • 学习控制器、运算器的工作原理,也就是 CPU 的工作原理;
    • 学习存储器的工作原理;
    • 学习 CPU 是如何与输入/输出设备交互的;
    • 学习计算机指令和计算,即高级语言到机器指令的翻译过程;
    • 学习浮点数的实现。

    冯·诺依曼体系结构的五大组成部分

    冯·诺依曼体系结构,也叫存储程序计算机。即程序本身是存储在计算机的内存里的,可以通过加载不同的程序来解决不同的问题。

    冯·诺依曼在《First Draft of Report o the EDVAC》(《第一份草案》)中提出了现代计算机组成的五大部分 —— 控制器,处理器,存储器,输入和输出设备。

    :EDVAC(Electronic Numerical Integrator And Computer,电子数值积分计算机) 是实际上第一台电子计算机。

    • 处理单元(Processing Unit,PU):又称数据通路(Datapath)或运算器,包含了算术逻辑单元(Arithmetic Logic Unit,ALU)和处理器寄存器(Processor Register)。用于完成各种算术和逻辑运算。

    • 控制器单元(Control Unit,CU):包含了指令寄存器(Instruction Register)和程序计数器(Program Counter)。用于控制程序的流程(程序流),通常是条件判断和跳转。

    NOTE:PU 和 CU 就组成了 CPU(Central Processing Unit,中央处理器)。

    • 储存器:包括用于存储数据(Data)和指令(Instruction)的主存储器和容量更大但速度却慢的外部存储器。

    • 输入/输出设备:键盘、鼠标属于输入设备,显示器是输出设备,网卡即是输入设备又是输出设备。

    冯·诺依曼机与图灵机之间的关系

    • 图灵机是一个抽象的思想模型(计算机的基本理论基础),是一种有穷的、构造性的问题的问题求解思路。背后对应着可计算性理论,告诉我们什么样的问题是计算机解决得了的,什么样的问题是解决不了的。图灵认为凡是能用算法解决的问题也一定能用图灵机解决;

    • 冯诺依曼提出了 “存储程序” 的计算机设计思想,并“ 参照” 图灵模型设计了历史上第一台电子计算机,即冯诺依曼机。

    计算机的逻辑电路部件

    组合逻辑电路

    • 三态电路
    • 异或门
    • 加法器
    • 译码器
    • 数据选择器

    时序逻辑电路

    • 触发器
    • 寄存器和移位寄存器
    • 计数器

    阵列逻辑电路

    阵列 指的是逻辑元件在硅芯片上以阵列的形式排列。

    • 只读存储器(ROM)
    • 可编程序逻辑阵列(PLA)
    • 可编程序阵列逻辑(PAL)
    • 通用阵列逻辑(GAL)
    • 门阵列(GA)、宏单元阵列(MCA)、标准单元阵列(SCA)
    • 现场可编程序门阵列(FPGA)

    中央处理器(CPU)

    微处理器的出现得益于集成电路的发展,将控制器和运算器集成在一个芯片上,称为中央处理器(CPU)。CPU 从逻辑上可以分为 3 个模块:控制单元、运算单元和存储单元,这三部分由 CPU 的片内总线连接起来。

    :片内总线,指计算机各芯片内部传送信息的通道。

    控制单元(控制器)

    控制单元是 CPU 的指挥控制中心,主要由 PC 程序计数器、指令寄存器 IR(Instruction Register)、指令译码器 ID(Instruction Decoder)和操作控制器 OC(Operation Controller)组成。控制单元工作时,PC 根据程序依次从存储器中取出一条条指令并暂存在 IR 中,通过 ID 分析指令的内容,以确定应该进行什么操作,然后通过 OC 按照确定的时序,向相应的部件发出微操作控制信号。OC 中主要包括节拍脉冲发生器、控制矩阵、时钟脉冲发生器、复位电路和启停电路等控制逻辑。


    PC 程序计数器:本质是指令地址寄存器,存放当前正在执行的指令的地址或即将要执行的下一条指令的地址。有两种方式形成指令地址:一是顺序执行通过程序计数器 +1 得到下一条指令的地址;二是跳跃执行通过跳转类指令得到下一条指令的地址。

    IR 指令寄存器:区别于指令地址寄存器,是存放当前正在执行的指令。以便在指令执行的过程中控制完成一条指令的全部功能。

    ID 指令译码器:对指令的操作码(OPCODE)进行解析,并产生相应的控制型号。

    脉冲源及启停线路:脉冲源产生一定频率的脉冲作为 CPU 的时钟脉冲,是 CPU 工作周期的基准信号。而启停线路则保证了可靠地送出或封锁完整的时钟脉冲。

    时序控制信号形成部件:在 CLK 时钟的作用下,根据当前正在执行的指令的需要,产生对应的时间控制信号,并根据被控功能部件的反馈信号调整时序控制信号。

    运算单元(运算器)

    运算单元是可以执行算术运算(e.g. 加减乘除)和逻辑运算(e.g. 与或非、位移、比较)。运算单元所进行的全部操作都由控制单元发出的控制信号指挥,运算单元是单纯的执行部件。

    定点运算部件:由逻辑运算部件(ALU)、若干个寄存器、移位电路、计数器和门电路组成。其中 ALU 主要完成加减法算术运算及逻辑运算。
    浮点运算部件:由阶码运算部件和尾数运算部件组成。阶码部分仅执行加减法运算,尾数部分则可执行加减乘除运算。

    存储单元(寄存器组和片内缓存)

    存储单元是 CPU 暂存数据的地方,保存着等待处理的数据,或是已经处理过的数据。CPU 访问寄存器的速度比访问主存储器的速度更快,采用寄存器,可以减少 CPU 访问内存的次数,从而提高工作速度。但因为受到芯片面积和集成度的限制,寄存器组的容量不会很大。寄存器组可分为专用寄存器和通用寄存器。专用寄存器的作用是固定的,寄存相应的数据。而通用寄存器的用途则更加广泛,并可由程序员规定其用途。

    寄存器由多个触发器(Flip-Flop)或锁存器(Latches)组成的简单电路,触发器和锁存器是两种原理不同的数字电路组成的逻辑门,属于数字电路领域。N 个触发器或锁存器就可以组成一个 N Bit 的寄存器,能够保存 N 为数据。我们常说的 64 位 Intel CPU,其寄存器就是 64 位的,所以其最大的直接寻址空间就为 264 bit。需要注意的是,并不是说寄存器是 64 位的,那么该 CPU 只支持使用 264 bit 的存储器空间,因为 CPU 除了支持直接寻址方式之外,还支持基址寄存器寻址方式,实现了基于内存分页的地址寻址方式,寻址空间得到了巨大的提升。

    CPU 的工作原理


    控制单元在时序脉冲的作用下,从程序入口将第一条指令的地址写入 PC 程序计数器(本质是寄存器),控制单元将 PC 程序计数器的指令地址送到地址总线上,CPU 就将这个地址指向的指令读到 IR 指令寄存器,再通过 ID 指令译码器进行译码,了解指令的操作内容和操作数。对于执行指令过程中所需要用到的操作数,会将操作数的地址码也送到地址总线上,CPU 就将这个地址指向的数据读取到寄存器组中暂存起来,最后 OC 操作控制器发出控制型号到运算单元,由运算单元完成对数据的加工处理。周而复始,一条一条指令执行下去,直到停电为止。

    将过程简化为下述步骤

    1. 取指令
    2. 分析指令
    3. 执行指令
    4. 修改指令计数器

    上述步骤的执行期间还可能会穿插着

    • 控制程序和数据的输入与结果输出:根据程序的内容或人工干预,控制单元在适当的时候向输入/输出设备发出相应的指令来完成 I/O 功能。
    • 对异常情况和某些请求的处理:在特定的时刻(异常中断、输入/输出中断),由相应的部件或设备向 CPU 发出中断请求信号或 DMA 请求信号。 中断请求信号:待 CPU 执行完当前指令后,响应该中断请求,中止当前执行的程序,转而执行中断程序。待中断程序执行完后,再回到原程序继续执行下去。
    • DMA 请求信号:待 CPU 完成当前机器周期的操作后,暂停工作,让出总线给 I/O 设备。待完成 I/O 设备和存储器之间的数据传输之后,CPU 从暂时中止的机器周期开始继续执行原程序的指令。

    指令系统与运算

    指令系统决定了计算机的基本功能。计算机的性能与它所设置的指令系统有很大的关系,而指令系统的设置又与机器的硬件结构密切相关。指令系统的改进主要围绕缩小指令与高级语言的语义差异以及有利于操作系统的优化而进行的,例如:高级语言中经常用到的 if 语句、do 语句,所以设置了功能较强的条件跳转指令;为了操作系统的实现和优化,设置了有控制系统状态的特权指令、管理多道程序和多处理机系统的专用指令。

    然后指令系统太过于复杂并不完全是好事,在大多数场景中实际上只有算术逻辑运算、数据传输、跳转和程序调用等几十条指令会被频繁的使用到,而需要大量硬件支持的大多数复杂的指令却并不常用,由此会造成硬件资源的浪费。为了解决这个问题,指令系统被分为 精简指令系统计算机(RISC) 和 复杂指令系统计算机(CISC)

    计算机指令集与程序指令

    计算机指令集是 CPU 提供的可执行指令的集合;而程序所描述的指令是程序员希望在 CPU 上执行的指令,这些指令的范围不会超出计算机指令集的范围。不同类型的 CPU 能够理解不同的 “语言”,即具有不同的计算机指令集(Instruction Set)。同一个程序可以在相同型号的 CPU 中运行,反之则无法运行。

    CPU 运行一个计算机程序的本质就是运行这个程序所描述的指令,有如我们在 Linux 操作系统上执行指令一般,只是前者由 CPU 硬件支持。一个计算机程序通常由成千上万条指令组成,CPU 显然不能存放所有的指令,而是将这些指令存放在存储器中,只有在运行时才会被 CPU 读取。又因为现代计算机被设计为可以运行多种程序,存储器被各种各样的程序共享着,所以存储器也不会持久化的保存它们。而是当我们要运行(启动)某个程序时,才会将其加载到存储器中,最终再由 CPU 从存储器中逐一读取其指令。我们常见的内部存储器多为 RAM(随机存储器),这是一种被设计成掉电就会自动重置的存储设备。

    以上就是冯·诺依曼机的典型特性,所以又称之为 “存储程序计算机”。冯·诺依曼体系结构解决了计算机实现领域的一个重要难题:如何能够动态加载程序指令。解决了这个问题,“计算器” 才得以成为 “计算机”,我们今天才得以在计算机上运行各种各样的应用程序。

    :计算器的 “程序” 是焊死在主板上的。

    指令格式

    计算机是通过执行指令来处理各种数据的,为了了解数据的来源、操作结果的去向及所执行的操作类型,一条计算机指令一般包含以下信息。

    1. 操作码:说明操作的性质和功能(e.g. 加减乘除、数据传输),长度有指令系统的指令条数决定(e.g. 256 条指令,则操作码需要 8 位长度)。
    2. 操作数的地址:CPU 通过该地址读取到所需要的操作数,可能是存储器的地址,也可能是寄存器的地址。
    3. 操作结果的储存地址:保存操作结果数据的地址。
    4. 下一条指令的地址:当程序顺序执行时,下一条指令的地址又程序计数器(PC)给出,仅当改变程序的执行顺序时(e.g. 跳转、函数调用),下一条指令的支持才会有指令本身给出。

    综上,指令格式主要有 操作码地址码 组成。需要注意的是,在指令字长较长的计算机中,操作码的长度一般是固定的,并且由指令集的数量决定。但在指令字较短的计算机中,为了能够充分利用指令字的位数,在有限的长度中实现更多的指令集数目,所以其操作码长度被设计成是可变的,即把它们的操作码在必要的时候扩充到地址码字段。这就是所谓的 指令操作码扩展技术。指令字的长度与 CPU 的位数密切相关。

    指令类型

    日常使用的 Intel CPU 大概有 2000 多条 CPU 指令。可以分为以下 5 大类型:

    • 算术类指令:加减乘除。
    • 数据传输类指令:变量赋值、读写内存数据。
    • 逻辑类指令:与或非。
    • 条件分支类指令:条件判断语句。
    • 无条件跳转指令:方法、函数的调用跳转。


    继续细分的话,具有如下指令类型

    • 算术逻辑运算指令
    • 移位操作指令 算术移位
    • 逻辑移位
    • 循环移位
  • 矢量运算指令(矩阵运算)
  • 浮点运算指令
  • 十进制运算指令
  • 字符串处理指令
      字符串传送
    • 字符串比较
    • 字符串查询
    • 字符串转换
  • 数据传输指令
      寄存器与寄存器传输
    • 寄存器与主存储器单元传输
    • 存储器单元与存储器单元传输
    • 数据交换(源操作数与目的操作下互换)
  • 转移指令
      条件转移
    • 无条件转移
    • 过程调用与返回
    • 陷阱
  • 堆栈及堆栈操作指令
  • I/O 指令
  • 特权指令
  • 多处理机指令(在多处理器系统中保证共享数据的一致性等)
  • 控制指令
  • 指令寻址

    指令寻址,即是根据指令字的地址码来获取到实际的数据,寻址的方式跟硬件关系密切,不同的计算机有不同的寻址方式。有的计算机寻址方式种类少,所以会直接在操作码上表示寻址方式;有些计算机的寻址方式种类多,就会在指令字中添加一个特别用于标记寻址方式的字段,例如:假设该字段具有 3 位,那么就可以表示 8 种寻址方式。

    NOTE:寻址方式与 CPU 内的寄存器设计密切相关。

    直接寻址:指令字的地址码直接给出了操作数在存储器中的地址,是最简单的寻址方式。

    间接寻址:指令字的地址码所指向的寄存器或存储器的内容并不是真实的操作数,而是操作数的地址。间接寻址常用于跳转指令,只要修改寄存器或存储器的地址就可以实现跳转到不同的操作数上。

    相对寻址:把程序计数器(PC)的内容,即当前执行指令的地址与地址码部分给出的偏移量(Disp)之和作为操作数的地址。这种寻址方式同样常用于跳转(转移)指令,当程序执行到本条指令后,跳转到 PC+Disp。

    立即数寻址:即地址码本身就是一个操作数的寻址方式,该方式的特点就是数据块(因为实际上没有寻址),但操作数固定。常用于为某个寄存器或存储器单元赋初值,或提供一个常数。

    通用寄存器寻址:CPU 中大概会有几个到几十个通用寄存器用于临时储存操作数、操作数的地址或中间结果,指令字的地址码可以指向这些寄存器。通用寄存器具有地址短,存取速度快的特性,所以地址码指向通用寄存器的指令的长度也会更短,节省存储空间,执行效率更快。常被用于执行速度要求严格的指令中。

    基址寄存器寻址:基址,即基础地址,基址寄存器就是存放基址的寄存器,可以是一个专用寄存器,也可以使用通用寄存器来充当基址寄存器。执行指令时,需要将基址与指令字的地址码结合得到完成的地址,此时的地址码充当着偏移量(位移量)的角色。当存储器容量较大时,直接寻址方式是无法存取到所有存储单元的,所以通常会采用 分段分页 的内存管理方式。此时,段或页的首地址就会存放于基址寄存器中,而指令字的地址码就作为段或页的长度,这样只要修改基址寄存器的内容就可以访问到存储器的任意单元了。这种寻址方式常被用于为程序或数据分配存储区,与虚拟地址实现密切相关。基址寄存器寻址方式解决了程序在存储器中的定位存储单元和扩大 CPU 寻址空间的问题。

    变址寄存器寻址:变址寄存器内的地址与指令字地址之和得到了实际的有效地址,如果 CPU 中存在基址寄存器,那么就还得加上基址地址。这种寻址方式常用于处理需要循环执行的程序,例如:循环处理数组,此时变址寄存器所改变的就是数组的下标了。

    堆栈寻址:堆栈是有若干个连续的存储器单元组成的先进后出(FILO)存储区。堆栈是用于提供操作数和保存运算结果的主要存储区,同时还主要用于暂存中断和子程序调用时的线程数据及返回地址。

    通过 MIPS 感受指令字的设计

    MIPS(Millions of Instructions Per Second)是一种最简单的精简指令集架构,由 MIPS 科技公司设计。MIPS 指令具有 32 位(最新版本为 64 位),高 6 位为操作码(OPCODE),描述了指令的操作类型。其余 26 位具有 3 种格式:R、I 和 J。不同的指令类型和操作码组合能够完成多种功能实现,如下:

    加法算数指令

    add $t0,$s2,$s1
    的指令字及其对应的机器码如下:

    • opcode:0
    • rs:代表第一个寄存器 s1 的地址是 17
    • rt:代表第二个寄存器 s2 的地址是 18
    • rd:代表目标临时寄存器 t0 的地址是 8
    • shamt:0,表示不位移

    最终加法算数指令

    add $t0,$s2,$s1
    的二进制机器码表示为 000000 10001 10010 01000 00000 1000000(0X02324020)。可以看见,机器码中没有保存任何实际的程序数据,而是保存了程序数据的储存的地址,这也算是存储程序计算机指令集设计的一大特点。

    如何将高级语言翻译成计算机指令

    计算机指令是一种逻辑上的抽象设计,而机器码则是计算机指令的物理表现。机器码(Machine Code),又称为机器语言,本质是由 0 和 1 组成的数字序列。一条机器码就是一条计算机指令。程序由指令组成,但让人类使用机器码来编写程序显然是不人道的,所以逐步发展了对人类更加友好的高级编程语言。这里我们需要了解计算机是如何将高级编程语言编译为机器码的。

    Step 1. 编写高级语言程序。

    // test.c
    int main()
    {
    int a = 1;
    int b = 2;
    a = a + b;
    }

    Step 2. 编译(Compile),将高级语言编译成汇编语言(ASM)程序。

    gcc -g -c test.c

    Step 3. 使用汇编器将汇编语言程序翻译成机器码。下述左侧的一堆数字序列就是一条条机器码,右侧 push、mov、add、pop 一类的就是汇编代码。

    $ objdump -d -M intel -S test.o
    
    test.o:     file format elf64-x86-64
    
    Disassembly of section .text:
    
    0000000000000000 <main>:
    int main()
    {
    0:	55                   	push   rbp
    1:	48 89 e5             	mov    rbp,rsp
    int a = 1;
    4:	c7 45 fc 01 00 00 00 	mov    DWORD PTR [rbp-0x4],0x1
    int b = 2;
    b:	c7 45 f8 02 00 00 00 	mov    DWORD PTR [rbp-0x8],0x2
    a = a + b;
    12:	8b 45 f8             	mov    eax,DWORD PTR [rbp-0x8]
    15:	01 45 fc             	add    DWORD PTR [rbp-0x4],eax
    }
    18:	5d                   	pop    rbp
    19:	c3                   	ret

    为什么要保留汇编语言

    虽然汇编语言基本上与机器语言对应,与硬件的关系密切,占用内存小,运行速度快,但也具有可读性低、可重用性差,开发效率低下的问题。高级语言的出现是为了解决这些问题,让软件开发更加简单。但高级语言也存在自己的缺陷,例如:难以编写直接操作硬件设备的程序等。

    所以为了权衡上述的问题,最终汇编语言被作为中间的状态保留了下来。一些高级语言(e.g. C 语言)提供了与汇编语言之间的调用接口,汇编程序可作为高级语言的外部过程或函数,利用堆栈在两者之间传递参数或参数的访问地址。两者的源程序通过编译或汇编生成目标文件(OBJ)之后再利用连接程序(linker)把它们连接成为可执行文件便可在计算机上运行了。保留汇编语言还为程序员提供一种调优的手段,无论是 C 程序还是 Python 程序,当我们要进行代码性能优化时,了解程序的汇编代码是一个不错的切入点。

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: