Linux 从头学 01:CPU 是如何执行一条指令的?_linux从头学 01 cpu是

但是,在计算机内部,有一种专门与 CPU 相连接,用来存储正在执行的程序和数据的存储器,一般称作内存储器或者主存储器,简称:内存或主存。

内存按照字节来组织,单次访问的最小单位是 1 个字节,这是最基本的存储单元。

每一个存储单元,也就是一个字节,都对应着一个地址,如下图所示:

CPU 就通过地址总线来确定:对内存中的哪一个存储单元中的数据进行访问。

第 1 个字节的地址是 0000H,第 2 个字节的地址是 0001H,后面以此类推。

图中的这个内存,最大存储单元的地址是 FFFFH,换算成十进制就是 65535,因此这个内存的容量是 65536 字节,也就是 64 KB

这里有一个原子操作的问题可以考虑一下。

Linux 内核代码中,很多地方使用了原子操作,比如:互斥锁的实现代码。

为什么原子操作需要对变量的类型限制为 int 型呢?这就涉及到对内存的读写操作了。

尽管内存的最小组成单位是字节,但是,经过精心的设计和安排,不同位数的 CPU,能够按照字节、字、双字进行访问。

换句话说,仅通过单次访问,16 位处理器就能处理 16 位的二进制数,32 位处理器就能处理 32 位的二进制数。

寄存器是什么?

CPU 内部,一些都是代表 0 或 1 的电信号,这些二进制数字的一组电信号出现在处理器内部线路上,它们是一排高低电平的组合,代表着二进制数中的每一位。

在处理器内部,必须用一个称为寄存器的电路把这些数据锁存起来。

因此,寄存器本质上也属于存储器的一种。只不过它们位于处理器的内部,CPU 访问寄存器比访问内存的速度更快。

处理器总是很忙的,在它操作的过程中,所有数据在寄存器里面只能是临时存在一小会,然后再被送往别处,这就是为什么它被叫做“寄存器”。

8086 中的寄存器都是 16 位的,可以存放 2 个字节,或者说 1 个字。高字节在前(bit8 ~ bit15),低字节在后(bit0 ~ bit7)。

8086 中有下面这些寄存器:

刚才说了,这些寄存器都是 16 位的。由于需要与以前更古老的处理器兼容,其中的 4 个寄存器:AX、BX、CX、DX 还可以当成 2 个 8 位的寄存器来使用。

比如:AX 代表一个 16 位的寄存器,AH、AL 分别代表一个 8 位的寄存器。

mov AX, 5D  表示把 005D 送入 AX 寄存器(16 位)
mov AL, 5D  表示把 5D 送入 AL 寄存器(8 位)

 
 
 
 
  • 1
  • 2

三个总线

当我们启动一个应用程序的时候,这个程序的代码和数据都被加载到物理内存中。

CPU 无论是读取指令,还是操作数据,都需要与内存进行信息的交互:

  1. 确定存储单元的地址(地址信息);
  2. 器件的选择,读或写的命令(控制信息);
  3. 读或写的数据(数据信息);

在计算机中,有专门连接 CPU 和其他芯片的数据,称为总线。

从逻辑上来分类,包括下面 3 种总线:

地址总线:用来确定存储单元的地址;
控制总线: CPU 对外部期间进行控制;
数据总线: CPU 与内存或其他器件之间传送数据;

8086 有 20 根地址线,称作地址总线的宽度,它可以寻址 2 的 20 次方个内存单元。

同样的道理,8086 数据总线的宽度是 16,也就是一次性可以传送 16 bit 的数据。

控制总线决定了 CPU 可以对外进行多少种控制,决定了 CPU 对外部器件的控制能力。

CPU 如何对内存进行寻址?

Linux 2.6 内核代码中,编译器产生的地址叫做虚拟地址(也称作:逻辑地址),这个逻辑地址经过段转换之后,变成线性地址,线性地址再经过分页转换,就得到最终物理内存上的物理地址。

还记得文章开头的那张段描述符的表格吗?

其中的代码段和数据段描述符的起始地址都是 0x00000000,也就是说: 在数值上虚拟地址和转换后的线性地址是相等的(稍后就会明白为什么是这样)。

我们再来看看一下 8086 中更简单的地址转换。

刚才说到,内存是一个线性的存储器件,CPU 依赖地址来定位每一个存储单元。

对于 8086 CPU 来说,它有 20 根地址线,可以传送 20 位地址,达到 1MB 的寻址能力。

但是 8086 又是 16 位的结构,在内部一次性处理、传输、暂时存储的地址只有 16 位。

从内部结构来看,如果将地址从内部简单的发出到地址总线上,只能送出 16 位的地址,这样的话,寻址能力只有 64KB

那么应该怎么才能充分利用 20 根地址线呢?

8086 CPU 采用: 在内部使用两个 16 位地址合成的方法,来形成一个 20 位的物理地址,如下所示:

第一个 16 位的地址称为段地址,第二个 16 位的地址称为偏移地址。

地址加法器采用下面的这个公式,来“合成”得到一个 20 位的物理地址:

物理地址 = 段地址 x 16 + 偏移地址

例如:我们编写的程序,在加载到内存中之后,放在一个内存空间中。

CPU 在执行这些指令的时候,把 CS 寄存器当做段寄存器,把 IP 寄存器当做偏移寄存器,然后计算 CS x 16 + IP 的值,就得到了指令的物理地址。

从以上的描述中可以看出:8086 CPU 似乎是因为寄存器无法直接输出 20 位的物理地址,不得已才使用这样的地址合成方式。

其实更本质的原因是:8086 CPU 就是想通过 基地址 + 偏移量 的方式来对内存进行寻址(这里的基地址,就是段地址左移 4 位)。

也就是说,即使 CPU 有能力直接输出一个 20 位的地址,它仍然可能会采用 基地址 + 偏移量的方式来进行内存寻址。

想一下:我们在 Linux 系统中编译一个库文件的时候,一般都会在编译选项中添加 -fPIC 选项,表示编译出来的动态库是地址无关的,在被加载到内存时需要被重定位。

而基地址+偏移量的寻址模式,就为重定位提供了底层支撑。

我们是如何控制 CPU 的?

CPU 其实是一个很纯粹、很呆板的一个东西,它唯一做的事情就是:到 CS:IP 这两个寄存器指定的内存单元中取出一条指令,然后执行这条指令:

当然了,还需要预先定义一套指令集,在内存中的指令区中,存储的都必须是合法的指令,否则 CPU 就不认识了。

每一条指令都是用某些特定的数(指令码)来指示 CPU 进行特定的操作。

CPU 认识这些指令,一看到这些指令码,CPU 就知道这个指令码后面还有几个字节的操作数、需要进行什么样的操作。

例如:指令码 F4H 表示让处理器停机,当 CPU 执行这条指令的时候,就停止工作。

(其实这里说 CPU 已经有点不准确了,因为 CPU 是囊括了很多器件的一个整体,也许这里说 CPU 中的执行单元会更准确些。)

另外有一点可以提前说一下:内存中的一切都是数据,至于把其中的哪一部分数据当做指令来执行,哪一部分数据当做被指令操作的“变量”,这完全是由操作系统的设计者来规划的。

在 8086 处理器的层面来说,只要是 CS:IP “指向”的内存区域,都被当做指令来执行。

从以上描述可以看出:在 CPU 中,程序员能够用指令读写的器件只有寄存器,我们可以通过改变寄存器中的内容,来实现对 CPU 的控制。

更直白的说就是:我们可以通过改变 CS、IP 寄存器中的内容,来控制 CPU 执行目标指令。

作为一名合格的嵌入式开发者,大家估计都配置过一些单片机里的寄存器,以达到一些功能定义、端口复用的目的,其实这些操作,都可以看做是我们对 CPU 的控制。

如果把 CPU 比作木偶,那么 寄存器就是控制木偶的绳索。

我们再把 CPU 与 工控领域的 PLC 编程进行类比一下。

我们在拿到一个新的 PLC 设备之后,其中只有一个运行时(runtime),这个运行时执行的本职工作就是:

  1. 扫描所有的输入端口,锁存在输入映象区;
  2. 执行一个运算、控制逻辑,得到一些列输出信号,锁存到输出映象区;
  3. 把输出映象区的信号,刷新到输出端口;

在一个全新的 PLC 中,其中第 2 个步骤中需要的运算、控制逻辑可能就不存在。

因此,单单一个 runtimePLC 是无法完成一件有意义的工作的。

为了让 PLC 完成一个具体的控制目标,我们还需要利用 PLC 厂家提供的上位机编程软件,开发一个运算、控制逻辑程序,编程语言一般都是梯形图居多。

当这个程序被下载到 PLC 中之后,它就可以控制运行时来做一些有意义的工作了。

我们可以简单的认为:梯形图就是用来控制 PLC 的运行时。

对于 CPU 来说,想让它执行某个内存单元的指令,只要修改寄存器 CSIP 即可。

换句话说:只要对一个程序的内存布局足够的清楚,可以把 CPU 玩弄于股掌之间,让它执行哪里的代码都可以。

CPU 执行指令流程

现在我们已经明白了地址转换、内存的寻址,距离 CPU 执行一条指令需要的最小单元还剩下:指令缓冲区和控制电路。

简单来说:指令缓冲区用来缓存从内存中读取的指令,控制电路用来协调各种器件对总线等资源的使用。

对于下面这张图来说,它一共有 4 条指令:

以第一条指令来举例,它一共经过 5 个步骤:

  1. 把 CS:IP 内容送入地址加法器,计算得到 20 位的物理地址 20000H;
  2. 控制电路把 20 位的地址,送入到地址总线;
  3. 内存中 20000H 单元处的指令 B8 23 01,经过数据总线被送到指令缓冲区;
  4. 指令偏移寄存器 IP 的值要加 3,指向下一条等待被执行的偏移地址(因为指令码 B8 代表当前指令的长度是 3 个字节);
  5. 执行指令缓冲区中的指令: 把数值 0123H 送入寄存器 AX 中;

以上就是一条指令的执行最基本步骤,当然,现代处理器的指令执行流程,比这里的要复杂的多得多。

------ End ------

万丈高楼平地起!

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

g-lazgP3cp-1715585060007)]

[外链图片转存中…(img-Jupr02S7-1715585060008)]

[外链图片转存中…(img-tm0WMV17-1715585060009)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值