一、总体认识CPU

1、软硬件角度

        CPU,全称就是中央处理器。从硬件上来说,CPU是一个超大规模集成电路,通过电路实现加法、乘法乃至各种各样的处理逻辑。从软件来说,CPU就是一个执行各种计算机指令的逻辑机器。

2、计算机指令

        所谓的计算机指令,其实就是CPU能听懂的语言,我们可以叫做机器语言。不同的CPU能够听懂的语言也是不一样的。例如,一般的电脑所用的是Intel的CPU,而苹果手机用的是ARM的CPU。这两种CPU各自支持的语言,就是两组不同的计算机指令集。

        所以,我们在电脑上写的一个C语言程序,经过编译、汇编之后形成exe文件,把这个文件复制到手机上一般是没法正常运行的。而把这台电脑上的exe文件复制到另一个相同OS的电脑上,是可以正常运行的。

3、存储程序型计算机

        一个计算机程序,一般有很多指令组成,但是CPU里不能一直放着所有指令(CPU中主要用于存放当前正在执行即将执行的指令和数据。),所以计算机程序平时是存储在存储器中的(这个存储器一般称之为内存或主存)。

二、简单了解编译与汇编

1、整体过程

        平时编写的代码,到底是怎么变成一条条计算机指令,最后被 CPU 执行的?以一段C语言程序举例:

        

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

        要让这段程序在一个 Linux 操作系统上跑起来,我们需要把整个程序翻译成一个汇编语言(ASM,Assembly Language)的程序,这个过程我们一般叫编译(Compile)成汇编代码。

        针对汇编代码,我们可以再用汇编器(Assembler)翻译成机器码(Machine Code)。这些机器码由“0”和“1”组成的机器语言表示。这一条条机器码,就是一条条的计算机指令。这样一串串的 16 进制数字,就是我们 CPU 能够真正认识的计算机指令。        

        在一个 Linux 操作系统上,我们可以简单地使用 gcc 和 objdump 这样两条命令,把对应的汇编代码和机器码都打印出来。

$ gcc -g -c test.c
$ 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   rbp1:   48 89 e5                mov    rbp,rspint a = 1; 4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1int b = 2;b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2a = 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    rbp19:   c3                      ret    

        可以看到,左侧有一堆数字,这些就是一条条机器码;右边有一系列的 push、mov、add、pop 等,这些就是对应的汇编代码。一行 C 语言代码,有时候只对应一条机器码和汇编代码,有时候则是对应两条机器码和汇编代码。汇编代码和机器码之间是一一对应的。

        小问题:为什么要经过汇编而不是把代码直接编译成机器码?因为汇编代码其实就是“给程序员看的机器码”,也正因为这样,机器码和汇编代码是一一对应的。我们人类很容易记住 add、mov 这些用英文表示的指令,而 8b 45 f8 这样的指令,由于很难一下子看明白是在干什么,所以会非常难以记忆。所以编译、汇编的整体过程如下:

        但其实,这里是有更深层次的原因。将编译过程划分为“编译器 -> 汇编器”两步,而不是直接从高级语言生成机器码,主要是出于软件工程上的考量,即模块化、可移植性和简化设计

        编译器和汇编器各自的任务是解耦的:

这样分工的好处是:

2、简单解析指令与机器码

(1)汇编语言的指令分类

①算数类指令

        我们的加减乘除,在 CPU 层面,都会变成一条条算术类指令。

②数据传输类指令

        给变量赋值、在内存里读写数据,用的都是数据传输类指令。

③逻辑类指令

        逻辑上的与或非。

④条件分支类指令

        类似"if/else"编译形成的指令

⑤无条件跳转指令

        写一些大一点的程序,我们常常需要写一些函数或者方法。在调用函数的时候,其实就是发起了一个无条件跳转指令。

(2)机器码是怎样生成的

        不同的 CPU 有不同的指令集,也就对应着不同的汇编语言和不同的机器码。这里以MIPS指令集为例(MIPS 是一组由 MIPS 技术公司在 80 年代中期设计出来的 CPU 指令集)

        MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。

        R 指令是一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。

        I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。

        J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。

        以一个简单的add指令为例:

add $t0, $s2, $s1

        这个指令的含义是,将s2寄存器和s1寄存器中的值相加,放到t0寄存器中。

        这条指令对应的 MIPS 指令里 opcode 是 0,rs 代表第一个寄存器 s1 的地址是 17,rt 代表第二个寄存器 s2 的地址是 18,rd 代表目标的临时寄存器 t0 的地址,是 8。因为不是位移操作,所以位移量是 0。把这些数字拼在一起,就变成了一个 MIPS 的加法指令。

        为了读起来方便,我们一般把对应的二进制数,用 16 进制表示出来。在这里,也就是 0X02324020。这个数字也就是这条指令对应的机器码。

        小拓展:区分是R、I、J指令主要就是看 前 6 位的 opcode 字段。opcode = 0几乎是所有 R 型,真正的操作由 funct 字段决定。opcode ≠ 0->I型(区别不同 I 型指令)。opcode = 0x02(j) 或 0x03(jal)->J型。

3、python和java的执行

        除了 C 这样的编译型的语言之外,不管是 Python 这样的解释型语言,还是 Java 这样使用虚拟机的语言,其实最终都是由不同形式的程序,把我们写好的代码,转换成 CPU 能够理解的机器码来执行的。只是解释型语言,是通过解释器在程序运行的时候逐句翻译,而 Java 这样使用虚拟机的语言,则是由虚拟机对编译出来的中间代码进行解释,或者即时编译成为机器码来最终执行。

三、CPU执行指令的过程

        以Intel的CPU为例,里面差不多有几百亿个晶体管。实际上,一条条计算机指令执行起来非常复杂。但是在 CPU 在软件层面已经为我们做好了封装。对于我们这些做软件的程序员来说,我们只要知道,写好的代码变成了指令之后,是一条一条顺序执行的就可以了。

        逻辑上,我们可以认为,CPU 其实就是由一堆寄存器组成的。而寄存器就是 CPU 内部,由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路。(触发器和锁存器,其实就是两种不同原理的数字电路组成的逻辑门。)

        N 个触发器或者锁存器,就可以组成一个 N 位(Bit)的寄存器,能够保存 N 位的数据。比方说,我们用的 64 位 Intel 服务器,寄存器就是 64 位的。(没错,一个寄存器就是这么小,换算成高级语言中相当于一个32位整型或64位长整型。但是寄存器只是用来保存当前正在计算的数据,不像内存需要存储程序等)

1、CPU的几种寄存器

一个 CPU 里面会有很多种不同功能的寄存器。这里介绍三种比较重要、特殊的。

(1)PC寄存器

        我们也叫指令地址寄存器。顾名思义,它就是用来存放下一条需要执行的计算机指令的内存地址。

(2)指令寄存器

        用来存放当前正在执行的指令。

(3)条件码寄存器

        用里面的一个一个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。

(4)其他寄存器

        除了这些特殊的寄存器,CPU 里面还有更多用来存储数据和内存地址的寄存器。这样的寄存器通常一类里面不止一个。我们通常根据存放的数据内容来给它们取名字,比如整数寄存器、浮点数寄存器、向量寄存器和地址寄存器等等。有些寄存器既可以存放数据,又能存放地址,我们就叫它通用寄存器。

2、执行指令(顺序)

        一个程序执行的时候,CPU 会根据 PC 寄存器里的地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。可以看到,一个程序的一条条指令,在内存里面是连续保存的,也会一条条顺序加载。

        注意:PC寄存器自增(包括后面所说的更新),一般是在CPU的指令周期(取指 → 译码 → 执行 → 写回)的取指阶段

  • CPU 取到当前指令时,就会把 PC 自增(例如 +4)到下一条指令地址。

  • 如果当前指令是跳转/分支类指令(如 jmpbeq),那么译码/执行阶段可能会修改 PC 的值(覆盖掉刚才的 +4)。

        而有些特殊指令,比如 J 类指令,也就是跳转指令,会修改 PC 寄存器里面的地址值。这样,下一条要执行的指令就不是从内存里面顺序加载的了。

3、指令的跳转——条件语句

        下面是一个简单的包含条件判断if...else...的指令:

// test.c#include <time.h>
#include <stdlib.h>int main()
{srand(time(NULL));int r = rand() % 2;int a = 10;if (r == 0){a = 1;} else {a = 2;} 

        我们用 rand 生成了一个随机数 r,r 要么是 0,要么是 1。当 r 是 0 的时候,我们把之前定义的变量 a 设成 1,不然就设成 2。

        用以下代码生成汇编代码:

$ gcc -g -c test.c
$ objdump -d -M intel -S test.o 
    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 指令比较了前后两个操作数的值,这里的 DWORD PTR 代表操作的数据类型是 32 位的整数,而[rbp-0x4]则是变量 r 的内存地址。所以,第一个操作数就是从内存里拿到的变量 r 的值。第二个操作数 0x0 就是我们设定的常量 0 的 16 进制表示。cmp 指令的比较结果,会存入到条件码寄存器当中去。

        在这里,如果比较的结果是 True,也就是 r == 0,就把零标志条件码(对应的条件码是 ZF,Zero Flag)设置为 1。除了零标志之外,Intel 的 CPU 下还有进位标志符号标志以及溢出标志,用在不同的判断条件下。

        cmp 指令执行完成之后,PC 寄存器会自动自增,开始执行下一条 jne 的指令。

        跟着的 jne 指令,是 jump if not equal 的意思,它会查看对应的零标志位。如果 ZF 为 1,说明上面的比较结果是 TRUE,则继续往下顺序执行;如果是 ZF 是 0,也就是上面的比较结果是 False,会跳转到后面跟着的操作数 4a 的位置。这个 4a,对应这里汇编代码的行号,也就是上面设置的 else 条件里的第一条指令。当跳转发生的时候,PC 寄存器就不再是自增变成下一条指令的地址,而是被直接设置成这里的 4a 这个地址。这个时候,CPU 再把 4a 地址里的指令加载到指令寄存器中来执行。

        跳转到执行地址为 4a 的指令,实际是一条 mov 指令,第一个操作数和前面的 cmp 指令一样,是另一个 32 位整型的内存地址,以及 2 的对应的 16 进制值 0x2。mov 指令把 2 设置到对应的内存里去,相当于一个赋值操作。然后,PC 寄存器里的值继续自增,执行下一条 mov 指令。

        这条 mov 指令的第一个操作数 eax,代表累加寄存器,第二个操作数 0x0 则是 16 进制的 0 的表示。这条指令其实没有实际的作用,它的作用是一个占位符。我们回过头去看前面的 if 条件,如果满足的话,在赋值的 mov 指令执行完成之后,有一个 jmp 的无条件跳转指令。跳转的地址就是这一行的地址 51。我们的 main 函数没有设定返回值,而 mov eax, 0x0 其实就是给 main 函数生成了一个默认的为 0 的返回值到累加器里面。if 条件里面的内容执行完成之后也会跳转到这里,和 else 里的内容结束之后的位置是一样的。

        指令可以往后跳转也可以向前跳转——这就是for/while循环实现的原理。

4、指令的跳转——循环语句

        下面是段简单的c语言for循环指令:

int main()
{int a = 0;for (int i = 0; i < 3; i++){a += i;}
}

        循环自增变量 i 三次,三次之后,i>=3,就会跳出循环。整个程序,对应的 Intel 汇编代码就是这样的:

    for (int i = 0; i <= 2; i++)b:   c7 45 f8 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x012:   eb 0a                   jmp    1e {a += i;14:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x4]17:   01 45 fc                add    DWORD PTR [rbp-0x8],eax1a:   83 45 f8 01             add    DWORD PTR [rbp-0x4],0x11e:   83 7d f8 02             cmp    DWORD PTR [rbp-0x4],0x222:   7e f0                   jle    14 24:   b8 00 00 00 00          mov    eax,0x0}

        对应的循环也是用 1e 这个地址上的 cmp 比较指令,和紧接着的 jle 条件跳转指令来实现的。主要的差别在于,这里的 jle 跳转的地址,是在这条指令之前的地址 14,而非 if…else 编译出来的跳转指令之后。往前跳转使得条件满足的时候,PC 寄存器会把指令地址设置到之前执行过的指令位置,重新执行之前执行过的指令,直到条件不满足,顺序往下执行 jle 之后的指令,整个循环才结束。

        小拓展:jne和jle

        在 x86 汇编 里,像 jnejle 这样的条件跳转指令,本身并不会做比较运算,它们只是检查标志寄存器 (EFLAGS)(这个寄存器叫做条件码寄存器) 的状态。而这个状态一般需要由 cmp 指令 或者 test 指令 先设置。

5、总结

        对于多条指令,除了简单地通过 PC 寄存器自增的方式顺序执行外,条件码寄存器会记录下当前执行指令的条件判断状态,然后通过跳转指令读取对应的条件码,修改 PC 寄存器内的下一条指令的地址,最终实现 if…else 以及 for/while 这样的程序控制流程。

        虽然我们可以用高级语言,可以用不同的语法,比如 if…else 这样的条件分支,或者 while/for 这样的循环方式,来实现不同的程序运行流程,但是回归到计算机可以识别的机器指令级别,其实都只是一个简单的地址跳转而已,也就是一个类似于 goto 的语句。

        想要在硬件层面实现这个 goto 语句,除了本身需要用来保存下一条指令地址,以及当前正要执行指令的 PC 寄存器、指令寄存器外,我们只需要再增加一个条件码寄存器,来保留条件判断的状态。这样简简单单的三个寄存器,就可以实现条件判断和循环重复执行代码的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97671.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97671.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97671.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用Java让家政服务触手可及

家政服务不仅仅包括日常保洁&#xff0c;随着社会的发展&#xff0c;从日常保洁、衣物清洁到家电维修、月嫂保姆&#xff0c;家政服务的场景越发多元。用户不仅追求服务的 “专业度”&#xff0c;更看重 “便捷性”—— 能否快速找到服务、预约服务、了解服务效果&#xff1f;上…

Python OpenCV图像处理与深度学习:Python OpenCV特征检测入门

特征检测与描述&#xff1a;探索图像中的关键点 学习目标 通过本课程&#xff0c;学员们将掌握特征检测的基本概念&#xff0c;了解如何使用OpenCV库中的SIFT和SURF算法进行特征点检测和特征描述符的计算。实验将通过理论讲解与实践操作相结合的方式&#xff0c;帮助学员深入理…

ECDH (椭圆曲线迪菲-赫尔曼密钥交换)

文章目录一、什么是ECDH&#xff1f;二、为什么需要 ECDH&#xff1f;要解决什么问题&#xff1f;三、原理与图示四、核心比喻&#xff1a;混合颜料五、技术实现步骤1. 约定公共参数2. 生成密钥对3. 交换公钥4. 计算共享密钥5. 密钥派生六、注意事项七、安全性基础八、优势特点…

Spring Boot实战:打造高效Web应用,从入门到精通

目录一、Spring Boot 初相识二、搭建开发环境2.1 安装 JDK2.2 安装 IDE&#xff08;以 IntelliJ IDEA 为例&#xff09;2.3 初始化 Spring Boot 项目三、Spring Boot 基础配置3.1 配置文件详解&#xff08;application.properties 和 application.yml&#xff09;3.2 自定义配置…

2025网络安全宣传周知识竞赛答题活动怎么做

网络安全答题PK小程序可以结合竞技性、趣味性和知识性&#xff0c;设计以下核心功能模块&#xff0c;提升用户参与度和学习效果&#xff1a;一、核心PK功能实时对战匹配 随机匹配在线用户&#xff08;按段位/积分相近原则&#xff09; 好友定向PK&#xff08;支持分享邀请对战&…

echo、seq、{}、date、bc命令

文章目录echo、seq、{}、date、bc命令echo案例seq命令案例{}花括号列表扩展序列扩展嵌套扩展datebc(高精度计算器)echo、seq、{}、date、bc命令 echo echo命令是一个常用的Shell命令&#xff0c;用于在终端上输出文本。它的基本语法如下&#xff1a; echo [option] [string]…

Vue2之Vuex

文章目录 数据准备新建项目选择模块安装vscode工具打开 删除无用文件删除src/assets文件下的所有内容删除src/components文件下的所有内容修改src/app.vuevscode运行项目 一、 概述1.是什么2. 使用场景3.优势4 Vuex流程图5.注意&#xff1a; 二、需求: 多组件共享数据创建三个组…

2025具身智能赛道观察:技术、产业与视频基础设施

引言 2025 年&#xff0c;具身智能&#xff08;Embodied Intelligence&#xff09;毫无疑问已经成为全球资本追逐的“风口赛道”。从人形机器人、无人配送&#xff0c;到低空经济和智能驾驶&#xff0c;几乎所有与物理世界深度结合的领域&#xff0c;都被纳入具身智能的广义范…

【商业银行风控模型(python版本,实操合集,附带anaconda安装教程,持续更新)】

Anaconda&#xff08;Python工具&#xff09;安装1.Mac中安装Anaconda2.点击“Free Download”下载后&#xff0c;点击“Skip registration”&#xff0c;跳过注册环节。 3.conda list4.安装完成Anaconda基本操作命令 # 查看当前虚拟环境下的所有包 conda list # 查看某个特定的…

FPGA DDR 地址映射-黄金法则

FPGA 中 DDR 控制器的地址映射顺序&#xff08;Address Mapping Order&#xff09; 是优化设计速度&#xff08;带宽和效率&#xff09; 的关键。简单来说&#xff0c;地址映射顺序决定了线性地址如何映射到 DDR 芯片内部的物理结构&#xff08;Bank、Row、Column&#xff09;。…

网络安全设备监控指标

网络安全设备监控指标 近日看到一篇设备情况汇报&#xff0c;内容写得有些欠缺&#xff0c;因此我特意问了一下AI&#xff0c;整理了一下思路。以下是监控需要关注的性能指标。权当抛砖引玉。根据指标可以做监控&#xff0c;也可以做调研指标。 业务承载能力 吞吐量&#xff08…

JSP程序设计之JSP指令

1、JSP指令概念与分类 &#xff08;1&#xff09;概念 JSP指令相当于在编译期间的命令&#xff0c;用来设置与整个JSP页面相关的属性&#xff0c;它并不直接产生任何可见的输出&#xff0c;用来设置全局变量、声明类、要实现的方法和输出内容的类型等。在JSP文件被解析为Java…

Generative Art with p5.js: Creating Beauty from Code

Are you ready to make something truly beautiful with p5.js? Forget about boring bar charts and sales data—let’s create art that moves, breathes, and responds to your touch. We’re going to explore generative art, where code becomes your paintbrush and a…

Wi-Fi技术——网络安全

一、数据帧的安全 1、无线网络安全的发展 理论上无线电波范围内的任何一个站点都可以监听并登录无线网络&#xff0c;所有发送或接收的数据&#xff0c;都有可能被截取&#xff0c;因此无线网络安全十分重要。 原始802.11的安全策略为WEP&#xff0c;其存在根本性的漏洞&#x…

Java提供高效后端支撑,Vue呈现直观交互界面,共同打造的MES管理系统,含完整可运行源码,实现生产计划、执行、追溯一站式管理,提升制造执行效率

前言在当今竞争激烈的制造业环境中&#xff0c;企业面临着提高生产效率、降低成本、保证产品质量以及快速响应市场变化等多重挑战。制造执行系统&#xff08;MES&#xff0c;Manufacturing Execution System&#xff09;作为连接企业上层计划管理系统&#xff08;如ERP&#xf…

【macOS】垃圾箱中文件无法清理的常规方法

【macOS】垃圾箱中文件无法清理的方法如果外接 SSD 移动盘上的垃圾文件无法删除&#xff0c; 可能是由于文件系统格式不兼容、文件被占用、权限不足等原因导致的&#xff0c; 以下是一些常见的解决方法&#xff1a;检查移动硬盘文件系统格式&#xff1a;如果移动硬盘是 NTFS 格…

鸿蒙ArkTS 核心篇-15-条件渲染(组件)

目录 根据逻辑条件结果&#xff0c;渲染不同的 UI 内容 DevEco Studio代码实战 预览效果 总结 根据逻辑条件结果&#xff0c;渲染不同的 UI 内容 DevEco Studio代码实战 let num: number 20Entry Component struct Index {build() {Column() {if (num 1) {Text(文本 1)} …

大模型微调显存内存节约方法

大模型微调时节约显存和内存是一个至关重要的话题&#xff0c;尤其是在消费级GPU&#xff08;如RTX 3090/4090&#xff09;或资源有限的云实例上。下面我将从显存&#xff08;GPU Memory&#xff09; 和内存&#xff08;CPU Memory&#xff09; 两个方面&#xff0c;为你系统地…

Linux笔记12——shell编程基础-6

字符截取命令一、cut命令功能&#xff1a;用于从文件或标准输入中提取指定字段或列语法&#xff1a;cut [选项] 文件名-f&#xff1a;列号&#xff0c;提取第几列&#xff0c;默认识别制表符分割出来的列&#xff08;列号之间用,隔开&#xff09;-d&#xff1a;分隔符&#xff…

高效浏览器标签页管理:Chrome扩展开发完全指南

Hi&#xff0c;我是前端人类学&#xff08;之前叫布兰妮甜&#xff09;&#xff01; 在信息过载的时代&#xff0c;浏览器标签页管理已成为提高工作效率的关键技能。本文将介绍如何开发一个功能完整的Chrome扩展&#xff0c;帮助用户高效管理浏览器标签页&#xff0c;并探讨其实…