计算机指令简介

236 阅读3分钟

承接上文 juejin.cn/post/709522…

参考:深入浅出计算机组成原理,九曲阑干"深入理解计算机系统" www.bilibili.com/video/BV1Ci…

前言

从硬件的角度来看,CPU 就是一个超大规模集成电路,通过电路实现了加法、乘法乃至各种各样的处理逻辑。

从软件工程师的角度,CPU 就是一个执行各种计算指令(Instruction Code)的逻辑机器。

不同的 CPU 有着不同的计算机指令集,本系列文章将依据 Intel 的 x86-64 指令集来进行介绍。

接下来,我们以一个例子来简单介绍一下 Intel 的 x86-64 指令集。

背景知识

  • 在 Intel x86-64 的处理器中包含了 16 个通用目的寄存器,这些寄存器用来存放整数数据和指针
%rax    %rbx    %rcx    %rdx    
%rsi    %rdi    %rbp    %rsp
%r8     %r9     %r10    %r11
%r12    %r13    %r14    %r15

通用寄存器:可用于存储任何指令的地址或数据的寄存器。

  • 调用者保存寄存器和被调用者保存寄存器。
func_A:
    ...
    movq $123, %rbx
    call func_B
    addq %rbx, %rax
    ...
    ret

Caller

func_B:
    ...
    addq $456 %rbx
    ...
    ret

Callee

寄存器 rbx 的内容在调用函数 B 的前后应该保持一致。解决这个问题,有两个策略:

  1. 调用者保存
func_A:
    ...
    movq $123, %rbx
    // save register rbx
    call func_B
    // restore register rbx
    addq %rbx, %rax
    ...
    ret
  1. 被调用者保存
func_B:
    ...
    // save register rbx
    addq $456 %rbx
    // restore register rbx
    ...
    ret

Callee

对于具体使用哪一种策略,不同的寄存器被定义成不同的策略。

Callee saved:

%rbx, %rbp, %r12, %r13, %r14, %r15

Caller saved:

%r10, %r11
%rax
%rdi, %rsi, %rdx, %rcx, %r8, %r9

例子

首先看一个 c 代码的示例:

// main.c
#include <stdio.h>

void mulstore(long, long, long*);

int main()
{
    long d;
    multstore(2, 3, &d);
    printf("2 * 3 --> %ld\n", d);
    return 0;
}

long mult2(long a, long b)
{
    long s = a * b;
    return s;
}
// mstore.c
long mult2(long, long);

void mulstore(long x, long y, long* dest)
{
    long t = mult2(x, y);
    *dest = t;
}

我们可以通过以下命令来编译程序:

// 其中编译选项 -Og 是用来告诉编译器生成符合原始 C 代码整体结构的机器代码
linux > gcc -Og -o prog main.c mstore.c

首先以源文件 mstore.c 为例,看一下 C 代码与汇编代码之间的关系

// 产生汇编文件 mstore.s
linux > gcc -Og -S mstore.c

1.PNG

其中以"."开头的行都是知道汇编器和链接器工作的伪指令,也就是说我们完全可以忽略这些以"."开头的行。

mulstore:
    pushq    %rbx         
    movq     %rdx, %rbx
    call     mult2
    movq     %rax, (%rbx)
    popq     %rbx
    ret
  • 第一条命令
pushq    %rbx // 将寄存器rbx的值压入程序栈进行保存

与该命令相对应的是 popq 命令

    pushq    %rbx    // 保存寄存器 rbx 的内容  
...
    popq     %rbx    // 恢复寄存器 rbx 的内容
  • 第二条命令
movq     %rdx, %rbx // 将寄存器 rdx 的内容复制到寄存器 rbx

根据寄存器的用法,函数 multstore 的三个参数分别保存在

x -> %rdi

y -> %rsi

dest -> %rdx

当这条指令执行完成后 rdx 寄存器和 rbx 寄存器内的值相同,都是 dest 变量的内容。 其中 movq 中的 q 表示数据的大小。

C declarationIntel data typeAssembly-code suffixSize(bytes)
charByteb1
shortWordw2
intDouble wordl4
longQuad wordq8
char*Quad wordq8
floatSingle precisions4
doubleDouble precision18

大多数 GCC 生成的汇编指令都有一个字符后缀来表示操作数的大小

例如,数据传输指令就有四种:

movb, movw, movl, movq

  • 第三条指令
call mult2 // 函数调用,函数返回值会保存到寄存器 rax 中

x * y -> %rax

movq     %rax, (%rbx)  // 将寄存器 rax 的值送到内存中,内存的地址就是存放在 rbx 中
ret  // 函数返回

额外介绍:

// 生成机器代码文件 mstore.o
linux > gcc -Og -c mstore.c

反汇编器 objdump,将机器码反汇编成汇编代码

linux > objdump -d mstore.o

3.PNG

常见指令

常见的指令可以分成五大类:

  • 算术类指令。加减乘除。
  • 数据传输类指令。给变量赋值、在内存里读写数据,用的都是数据传输类指令。
  • 逻辑类指令。逻辑上的与或非。
  • 条件分支类指令。日常的 "if/else",其实都是条件分支类指令。
  • 无条件跳转指令。函数调用。