计算机组成原理(五)-一条指令是怎么被执行的

article/2025/8/24 6:07:17

什么是指令:

程序代码的本质就是一条一条的指令,我们需要通过编码的方式让CPU知道我们需要它干什么,最后由译码器翻译成一条条的机器指令。机器指令主要有两部分组成:操作码、地址码。地址码直接给出操作数和操作数的地址,分三地址指令、二地址指令和一地址指令,最后还有零地址指令,零地址指令在机器指令中没有地址码,用来进行空操作、停机操作、中断返回操作等。

 

那么一条简单的指令执行,涉及到了那些组件?

控制器(CU)和运算器(ALU)

PC寄存器(程序计数器):用于存放下一条需要执行的指令地址信息,注意是下一条指令的地址!

指令寄存器:用于存放当前正在执行的指令,注意是当前的指令!

指令译码器:用于翻译指令信息,与指令寄存器相连

 

那么一条指令的执行就应该包含

 

1.取指令:CU根据PC寄存器中的指令地址,去内存或者指令缓存中获取具体的指令,并加载到指令寄存器中。

2.分析指令:拿到指令之后,指令译码器将指令寄存器中的指令进行翻译,向ALU发起控制信号和指令信息。此时PC寄存器自增即+1,加载下一个要执行的指令地址(至少此时是这样的,此为顺序寻址)。

3.执行指令:ALU拿到相关信息之后进行相关的计算。

4.访问内存(或缓存):如果涉及到相关数据需要去缓存中拿,那就去取数据。

5.数据写回:将计算结果写入到寄存器中,写入到缓存中,甚至写入到内存中。

 

看到这里你肯定会想,并不是所有的指令都是顺序执行的,比如if····else,比如方法调用,此时PC寄存器也是自增加1嘛?我们来讲讲if····else和方法调用。

先来看if····else,下边有一小块代码

int r = 1;
int a = 10;if (r == 0){a = 1;} else {a = 2;} 

很简单的一个if判断以C语言为例 if部分翻译为汇编代码就是

if (r == 0)3b:   83 7d fc 00             cmp    DWORD PTR [rbp-0x4],0x03f:   75 09                   jne    4a <main+0x4a>{a = 1;41:   c7 45 f8 01 00 00 00    mov    DWORD PTR [rbp-0x8],0x148:   eb 07                   jmp    51 <main+0x51>}else{a = 2;4a:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x251:   b8 00 00 00 00          mov    eax,0x0} 

可以看到,这里对于 r == 0 的条件判断,被编译成了 cmp 和 jne 这两条指令。

这两条指令中cmp表示比较的意思,这明显是一条二地址指令,cmp是操作码,后边两个是地址码,意思就是将rbp寄存器中偏移量为4的位置的数取32位(DWORD PTR)出来,和0(0x0)比较,并将比较结果存到条件码寄存器中。

跟着的 jne 指令,是 jump if not equal 的意思,它会查看对应的零标志位。如果为 0,会跳转到后面跟着的操作数 4a 的位置。这个 4a,对应这里汇编代码的行号,也就是上面设置的 else 条件里的第一条指令。当跳转发生的时候,PC 寄存器就不再是自增变成下一条指令的地址,而是被直接设置成这里的 4a 这个地址。

那么CPU 再把 4a 地址里的指令加载到指令寄存器中来执行。mov 指令把 2 设置到对应的寄存器里去,相当于一个赋值操作。然后,PC 寄存器里的值继续自增,执行下一条 mov 指令。

最后一条mov指令的第一个操作数 eax,代表累加寄存器,第二个操作数 0x0 则是 16 进制的 0 的表示。这条指令其实没有实际的作用,代表我执行完了,给 main 函数生成了一个默认的为 0 的返回值到累加器里面。

然后是if条件之后我们注意到有个jmp指令,它之前的mov指令和4a是一样的意思,jmp指令,也就是jump的缩写,这是一个无条件跳转指令。跳转的地址就是这一行的地址 51,也就是说这个指令同样会改变PC寄存器中的地址信息,即将PC寄存器中的值设置为51(51大家都能使用,没必要生成两条指令)。具体流程如图所示,for循环也是同样的道理,不过涉及到具体的指令意义需要大家自己探索,但是流程是一样的,都是通过指令改变PC寄存器中的指令地址,引导CPU执行自己想要执行的指令。

 

下面是方法调用

方法调用之前,我们先了解一个数据结构-栈

是内存中一段连续的物理地址组成的一个后进先出(LIFO)的数据结构,这种栈结构大小确定,满了就溢出,所以底部确定,被称为栈底,我们向里边写入数据被称为压栈(PUSH),读取数据被称为出栈(POP),我们每次POP都是拿栈的最后一个数据,被称为栈顶,栈底地址最大,因为每次从栈顶拿数据,可以减少寻址时间。

 

函数 A 在调用 B 的时候,需要传输一些参数数据,这些参数数据在寄存器不够用的时候也会被压入栈中。整个函数 A 所占用的所有内存空间,就是函数 A 的栈帧(Stack Frame)。

以一段程序作为举例

int static add(int a, int b)
{return a+b;
}int main()
{int x = 5;int y = 10;int u = add(x, y);
}

汇编为

int static add(int a, int b)
{0:   55                      push   rbp1:   48 89 e5                mov    rbp,rsp4:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi7:   89 75 f8                mov    DWORD PTR [rbp-0x8],esireturn a+b;a:   8b 55 fc                mov    edx,DWORD PTR [rbp-0x4]d:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]10:   01 d0                   add    eax,edx
}12:   5d                      pop    rbp13:   c3                      ret    
0000000000000014 <main>:
int main()
{14:   55                      push   rbp15:   48 89 e5                mov    rbp,rsp18:   48 83 ec 10             sub    rsp,0x10int x = 5;1c:   c7 45 fc 05 00 00 00    mov    DWORD PTR [rbp-0x4],0x5int y = 10;23:   c7 45 f8 0a 00 00 00    mov    DWORD PTR [rbp-0x8],0xaint u = add(x, y);2a:   8b 55 f8                mov    edx,DWORD PTR [rbp-0x8]2d:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]30:   89 d6                   mov    esi,edx32:   89 c7                   mov    edi,eax34:   e8 c7 ff ff ff          call   0 <add>39:   89 45 f4                mov    DWORD PTR [rbp-0xc],eax3c:   b8 00 00 00 00          mov    eax,0x0
}41:   c9                      leave  42:   c3                      ret    

这里需要先介绍几个专用的寄存器,简要介绍如下:

  • ax(accumulator): 可用于存放函数返回值
  • bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
  • sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
  • ip(instruction pointer): 指向当前执行指令的下一条指令

从汇编代码中可以看出一个函数被call调用,首先默认要完成以下动作:

   push   rbpmov    rbp,rsp

1.将调用函数的栈帧栈底地址入栈,即保存调用函数的栈帧的栈底地址

2.建立新的栈帧,把 rsp 这个栈指针(Stack Pointer)的值复制到 rbp 里,并保存rsp的值,而 rsp 始终会指向栈顶。这个命令意味着,rbp 这个栈帧指针指向的地址,变成当前最新的栈顶。

而调用结束栈帧出栈,跳转到方法调用方的调用位置的下一个位置,即call的下一行继续执行。

//以下两步等同于leave
mov    rsp,rbp
pop    rbp
//以下两步等同于ret
pop rip
jmp rip

所以一个完整的方法调用过程

调用方使用call指令调用方法,此时rip中存放的是call的下一条指令的地址,将rip压栈到栈底,生成新的栈帧,将栈帧压栈,执行方法,栈帧弹出,弹出rip,跳转到rip继续运行调用方的方法。

 

 


http://chatgpt.dhexx.cn/article/TEqvZcTw.shtml

相关文章

CSS 斜条纹进度条动画

这是第一版进度条 ,用css写的.但是后续因为数据不同,要显示不同的颜色和数据,所以又改了一版,直接用的el-progress.自定义的样式.对于新手小白来说比较友好.先上这一版代码. <div class"state"><span>开机时间</span><!-- 进度条 --><div…

Acrobat DC 更改背景颜色会有一条条白色横纹

解决方法如下&#xff1a; 编辑->首选项->页面显示->取消 使用2D图形加速

turtle模块还能这样玩?(一条条金龙鱼、雨景)

文章目录 一条条金龙鱼雨景 Python的turtle模块不仅可以用来绘制一些基本的图形&#xff0c;还有与图片结合&#xff0c;做出一些特殊的效果&#xff0c;还可以用来做二维小游戏。本篇是介绍用turtle模块做出的一幅动态的鱼儿游过的画面和动态的雨景图 一条条金龙鱼 1、先看一…

2.Python # 代码注释

2. # 代码注释 文章目录 2. # 代码注释1. 什么是代码注释2. 注释语法3. 注释位置1. 注释在代码的上一行2. 注释在代码的末端 4. 课堂练习 1. 什么是代码注释 代码注释即对代码进行批注说明。 相当于给一个英文单词批注中文释义。 【温馨提示】注释是给程序员自己看的&#xf…

python:导入第三方库greenlet,gevent方法

greenlet&#xff0c;gevent greenlet&#xff0c;gevent是python支持的第三方库&#xff0c;它们可以帮助我们完成协程的使用&#xff0c;其中greenlet是手动调换方式&#xff08;switch方法&#xff09;&#xff0c;gevent是自动调换方式&#xff08;遇到IO操作&#xff09;…

python gevent使用

对大部分语言来说&#xff0c;经常用到并发来处理一些情况。比如必须要多次查询数据库&#xff0c;多次请求API&#xff0c;python内置的gevent就很简单好用。传参&#xff0c;获取返回值&#xff0c;捕获协程的错误都很方便。 直接上例子&#xff1a; import gevent as gevent…

指定Geany使用的Python版本

本文介绍&#xff1a; 在win7下配置Geany,使其使用 Python 3 因为电脑上安装了不同版本的Python&#xff0c;需要根据实际情况来进行版本切换。 第一步&#xff1a;首先点击"生成"按钮的三角箭头&#xff0c;再点击"设置生成命令" 第二步&#xff1a;在弹出…

ModuleNotFountError:No module named ‘gensim‘(在python代码中导入gensim模块)

运行窗口&#xff1a; conda install 模块 pip install 模块 第一个命令应该由于网速太慢没有下载完全&#xff0c;第二个命令我手动输入n退出了。 网速慢&#xff0c;可以使用如下命令&#xff1a; pip install -i https://pypi.douban.com/simple gensim pip install -i…

Python学习笔记--图例 legend

Python学习笔记--图例 legend 参靠视频:《Python数据可视化分析 matplotlib教程》链接&#xff1a;https://www.bilibili.com/video/av6989413/?p6 所用的库及环境: IDE:Pycharm Python环境&#xff1a;python3.7 Matplotlib: Matplotlib 1.11 Numpy&#xff1a; Numpy1.1…

【pybind11笔记】eigen与numpy数据交互

系列文章 【pybind11笔记】eigen与numpy数据交互 【pybind11笔记】python调用c函数 【pybind11笔记】python调用c结构体 【pybind11笔记】python调用c类 文件结构 为了方便演示&#xff0c;我们使用cmake构建该样例&#xff0c;文件结构如下&#xff1a; pybind11与eigen…

python学习笔记:问题一,Geany编辑器无法使用中文注释

python学习笔记&#xff1a; 问题一&#xff1a; Geany编辑器无法使用中文注释 Geany编译python时运行弹出SyntaxError: (unicode error) ‘utf-8’ codec can’t提升&#xff0c;文本编辑器Geany无法使用中文注释&#xff0c;可以设置一下文本编码格式就好了设置方法为&…

Python--注释

Python--注释 <font size4, colorblue> 一、Python中注释的形式<font size4, colorblue> 1、单行注释&#xff1a;使用“#”符号注释<font size4, colorblue> 2、多行注释&#xff1a;使用一对三个英文单引号注释<font size4, colorblue> 3、多行注释&…

python中generate什么意思_python generate怎么用

generate语句允许细化时间(Elaboration-time)的选取或者某些语句的重复。这些语句可以包括模块实例引用的语句、连续赋值语句、always语句、initial语句和门级实例引用语句等。细化时间是指仿真开始前的一个阶段&#xff0c;此时所有的设计模块已经被链接到一起&#xff0c;并完…

Python Gevent

参考资料 http://www.gevent.org/contents.htmlhttps://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Gevent.html Python脚本的执行效率一直来说并不是很高&#xff0c;特别是Python下的多线程机制&#xff0c;长久以来一直被人们诟病。很多人都在思考如何让Python执行的更快…

符号回归工具之 geppy: Python中的基因表达编程框架

符号回归工具之 geppy&#xff1a; Python中的基因表达编程框架 geppy是一个专门用于基因表达编程&#xff08;GEP&#xff09;的计算框架&#xff0c;由 C. Ferreira 在 2001 年提出 [1]。 geppy是在 Python 3 中开发的。这个框架个人认为稍微了解下遗传算法和遗传规划即可入…

如何在Geany中添加python的中文注释

在Geany中编译Python中直接添加中文注释会出现如下错误 只需要在程序的开始位置添加一句&#xff1a;# coding:utf-8

python中安装gensim包

安装gensim扩展包需要先安装numpy和scipy 先下载这三个文件的安装包。 注意安装包版本问题&#xff0c;可以进入python查看&#xff1a; 这里注意图中划线的两个地方即可&#xff0c;win代表的是是windows系统 WinR cmd 切换到安装包所在路径&#xff1a; pip install numpy…

搭建云端数据库【MongoDB】

MongoDB官方网站&#xff1a;http://www.cloud.mongodb.com 进入官网后首先点击TRY FREE 创建database 数据库创建完成 点击连接按钮 选择第二个选项 复制连接到项目中&#xff0c;输入密码和数据库名称 在服务端做一些相关配置 1、在根目录下新建d…

云端数据库的春天真来了

点 10月24日&#xff0c;阿里云推出了国内首个云端数据库跑分平台&#xff0c;在行业圈中引起了不少讨论。在阿明看来&#xff0c;这个平台推出的背后&#xff0c;实际上仍然潜藏着数据库领域&#xff0c;用云数据库替换传统数据库的大趋势&#xff0c;正如19世纪末汽车的出现…

【腾讯云技术沙龙预告】云端数据库的设计之美

数据科学俱乐部 中国数据科学家社区 以数据为中心的信息化社会&#xff0c;数据库可以看做是所有应用程序成功运行的核心。而结合云计算&#xff0c;数据库的高可用性能够被放大到极致&#xff0c;可以实现按需付费、按需扩展、高可用性以及存储整合等优势。 本期云社区技术沙龙…