• 计算机基础(二):汇编语言与内存结构


    上一篇 计算机基础(一):面向CPU编程 介绍了计算机的起源,以及CPU的主要结构,并引入了 ISA 的概念。本篇继续总结计算机基础相关的内容。

    汇编语言

    我们知道了ISA是定义了面向CPU编程的 0、1 组合起来的指令规则,最开始人们就是这样按照 ISA 指令集中的定义来编写成语,但这样由0和1组合起来的指令编写、维护起来并不容易,一旦出错也很难定位问题,因此,人们发明了一种使用助记符来编写指令的方式,即:汇编语言。继而后期,又在汇编语言的基础上抽象出了C语言、及其他众多的语言。这就是后话了。

    汇编语言其实并没有什么高深的,它是一门与 ISA 指令集一一对应的产物,只是通过对人类更加友好的助记符来进行编写和维护,CPU其实还是识别的ISA指令,执行的还是二进制。那么,也就是说,汇编语言必须转换为 ISA指令 后,才能被 CPU 执行。谁来做这个转换的工作呢?它就是汇编器!

    汇编语言

    汇编语言的最终作用,其实也和 ISA 一样,抽象机器语言,操作CPU,去控制其他硬件完成操作。具体来说:操作CPU的控制单元,控制存储单元、运算单元去对来自缓存的数据进行CRUD操作。

    上一篇在介绍ISA时提到,指令是由操作码和操作数构成,操作码定义了这个指令要干什么,操作数指明当前指令参与操作的东西(立即数、寄存器、内存中的值等等),汇编语言中的指令其实也类似,只不过,将二进制转换为了更加友好一些的助记符,比如:

    • 移动数据使用 mov 指令
    • 加减乘除分别对应 add、sum、mul、div 指令。

    具体这些指令的使用说明哪里有定义呢?它是由 CPU 厂家进行的定义,那我们常用的 Intel 公司的 CPU 来说,他们提供了其汇编指令的定义,具体是在 Intel 开发手册(下载地址 )查看。

    Intel手册

    Intel开发手册包含很多信息,不只是汇编指令的说明。手册总共分为4卷,采用总分结构:

    1. 第一卷介绍 Intel 处理器的体系结构;
    2. 第二卷介绍指令集的参考说明;
    3. 第三卷介绍(操作)系统的编程指南,具体包括内存管理、保护、任务管理、中断和异常处理等;
    4. 第四卷介绍处理器的特定型号寄存器。

    可以看到,想系统了解底层 CPU 硬件提供的支持,可以详细查看该手册,第一卷、第二卷介绍一些原理、基础方面的内容,第二卷就是对具体指令的索引说明,第四卷是对特定寄存器的说明,因此可以把 2、4 卷看作是字典来查即可。比如我们要查看 mov 汇编指令的描述,直接定位到第二卷的 4.2 节即可。

    内存结构

    内存结构划分

    我们知道了CPU执行指令的基本底层原理(通过 汇编指令/ISA 控制CPU中的控制器,继而控制其他组件),而接下来了解下 CPU 运行时的数据来源:内存。

    1. 按照计算机执行的指令片段是否共享数据划分的话,分为指令片段私有的数据、以及指令片段之间共享的数据。
      • 指令片段私有的数据随着指令片段的执行开始与结束,进行创建与销毁,生命周期与指令片段的执行保持一致,而指令片段执行时很大情况下通过 CALL 指令调用其他指令片段,也就意味着结构上存在嵌套的关系,并且需要有嵌套执行时先进后出的特性。因此,使用一种结构表示这种关系,即栈,这片表示指令段私有数据(栈结构)的内存空间叫做栈空间;
      • 指令片段之间共享的数据,通过堆内存空间表示。
    2. 以上划分是在指令执行时动态分配的内存,除了动态分配的内存空间,还有必要在指令执行前就需要分配的空间:比如我们的代码占用的空间:代码段内存,代码中定义的一些共享的数据,在执行前就可以确定,会分配在:数据内存。
    3. 一段程序执行时,有以上这些内存空间就够了,但“程序”这个维度上有一个特殊的存在,那就是操作系统,它相对于我们编写的普通程序还不一样,它的地位更高一些,因为我们的程序需要通过操作系统去操作硬件,因此内存划分上有单独的一片操作系统独占的内存空间,普通程序是无法访问到操作系统的内存的。
    4. 补充一下,除了以上内存空间,还有个空间,是表示为空的情况的,比如在Java中定义一个对象,默认为Null,这个Null也需要一个空间来表示。

    以上总结起来,就是下面这张内存结构的示意图:
    内存结构

    栈内存结构

    栈内存的实现有两种方式,一种是连续内存、一种是不连续的内存,我们这里讨论连续内存的情况。

    我们知道了栈内存空间,是为了指令动态执行时分配私有数据用的,而指令嵌套执行时,只能访问当前指令段自己的私有空间,仅此将栈内存进一步的进行了划分,栈的一片空间里,保存了自己指令段的私有数据的区域,叫做:栈帧。

    指令片段通过 call、ret 指令调用、退出其他指令段时,也就意味着栈帧在栈空间的入栈、出栈操作。比如,当指令段A调用指令段B,指令段B调用指令段C的时候,整体的栈帧结构示意图如下:
    栈帧分配
    之所以叫示意图,是因为它并不是十分准确,因为要实现栈的操作,我们还需要搭配 CPU 的寄存器。CPU中有两个专门的寄存器,分别指向当前执行栈结构中的栈底和栈顶,这两个足够必要,因为我们要动态开辟、回收栈空间,必须依赖这两个寄存器中保存的内存地址。(哦,对了,之前我们还没有引入过内存地址。要想访问某个内存,肯定也是需要对我们的内存进行编码的嘛,给每一个内存单元一个地址,我们的CPU才能访问得到呀。)

    说回栈底和栈顶寄存器,保存了当前指令片段栈帧中的地址,最初始时的状态是这样:
    栈帧执行时
    通过移动栈顶寄存器,实现动态分配、回收栈中的数据。

    当代 码段A 调用 代码段B 的时候呢,就需要开辟一个新的栈帧了,这时候A的栈顶、栈底是不是也得需要保存一下呢??(换一组寄存器保存?那寄存器肯定不够用嘛,我们还得复用这两个寄存器)实际只需要保存栈底空间即可,因为A的栈顶我们可以通过 代码段B 的栈底自动识别到。因此在为代码段B开辟新的栈帧的时候,需要将A的栈底地址保存一下,然后将栈底寄存器更新为B的栈底寄存器:

    更新栈底寄存器

    随着代码段B的执行,栈帧不断扩展,栈顶寄存器向上移动开辟空间,当调用代码段C的时候,也是类似的,会开辟新的栈帧,因此,实际的示意图如下:
    栈执行示意图
    嵌套代码段退出时,类似的,弹出栈帧,弹出保存着的上一级的栈底地址,更新栈底寄存器,继续执行。以上,便是指令片段执行时,栈空间操作的基本流程。

    总线机制

    CPU与内存的交互,通过总线(计算机组成原理)传递信息来实现,总线分为三类:控制总线(CB)、地址总线(AB)、数据总线(DB)

    地址总线:决定了处理器将从内存中读取数据或写入数据的内存位置。
    数据总线:包含已从内存位置读取或将写入内存位置的数据内容。
    控制总线:管理组件之间的信息流,指示操作是读取还是写入,并确保操作在正确的时间发生。

    内存分段与分页

    寄存器分类

    寄存器主要分为两大类:专用寄存器、通用寄存器。专用寄存器只能用于其特有的功能,比如上面我们提到的栈底寄存器、栈顶寄存器;通用寄存器除了用于自己特有表明的功能外,还可以用作其他用途。

    小结

    本篇总结了汇编语言的出现、计算机运行时的内存结构划分,以及总结了栈内存空间在程序运行时的示意图。

  • 相关阅读:
    2022年数维杯国际赛A题 自动地震水平跟踪
    【MySQL核心SQL】
    Open3D(C++) 点云旋转的轴角表示法和罗德里格斯公式
    设计模式-命令模式
    Android9.0 去掉录屏权限弹框,默认给录屏权限
    【 背包九讲——完全背包问题】
    SpringBoot中最常用的5个内置对象
    聚观早报 |2024年春节连休8天;RTE2023开幕
    leetcode 2520 统计能整除数字的位数
    寒冬已逝,“量子春天”正来
  • 原文地址:https://blog.csdn.net/lyg673770712/article/details/126559566