CPU执行指令

  1. CPU是由一堆寄存器组成的,而寄存器是由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路
    • 触发器和锁存器是两种不同原理的数字电路组成的逻辑门
  2. N个触发器或者锁存器,就可以组成一个N位的寄存器,能保存N位的数据,64位的Intel服务器,寄存器就是64位的
  3. 寄存器分类
    • PC寄存器(Program Counter Register),也称为指令地址寄存器(Instruction Address Register)
      • 用来存放下一条需要执行的计算机指令的内存地址
    • 指令寄存器(Instruction Register)
      • 用来存放当前正在执行的指令
    • 条件码寄存器(Status Register)
      • 用里面的一个个标志位(Flag),存放CPU进行算术或者逻辑计算的结果
    • 其它
      • 整数寄存器、浮点数寄存器、向量寄存器、地址寄存器、通用寄存器
  4. 程序执行
    • CPU会根据PC寄存器里面的地址,从内存里把需要执行的指令读取到指令寄存器里面执行
    • 然后根据指令长度自增,开始顺序读取下一条指令,一个程序的指令,在内存里面是连续保存的,也会一条条顺序加载
    • 特殊指令,如J类指令(跳转指令),会直接修改PC寄存器里面的地址值
      • 这样下一条要执行的指令就不是从内存里面顺序加载的了

if-else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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;
}
}
1
2
$ gcc -g -c test.c
$ objdump -d -M intel -S test.o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......
int main()
{
......
if (r == 0)
33: 83 7d fc 00 cmp DWORD PTR [rbp-0x4],0x0
37: 75 09 jne 42 <main+0x42>
{
a = 1;
39: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
40: eb 07 jmp 49 <main+0x49>
} else {
a = 2;
42: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
}
}
49: c9 leave
4a: c3 ret
  1. 对于r == 0的条件判断,被编译成了cmpjne这两条指令
  • cmp指令比较了前后两个操作数的值
    • DWORD PTR代表操作的数据类型是32位的整数[rbp-0x4]是一个寄存器的地址,从寄存器里拿到的变量r的值
    • 第2个操作数0x0:常量0的16进制表示
    • cmp指令的比较结果,会存入到条件码寄存器当中去
      • 如果比较的结果为True,即r==0,就把零标志条件码(对应的条件码是ZF,Zero Flag)设置位1
      • Intel CPU的其它标志位
        • 进位标志(CF,Carry Flag)、符号标志(SF,Sign Flag)、溢出标志(OF,Overflow Flag)
  • cmp指令执行完成后,PC寄存器会自动自增,开始执行下一条jne指令
    • jne = jump if not equaljne指令会查看对应的零标志位,如果为0,会跳转到42的位置
      • 42对应的是汇编代码的行号,也是else条件里面的第一条指令
      • 当发生跳转
        • PC寄存器就不再自增,而是被直接设置成42,CPU再把42地址里的指令加载到指令寄存器来执行
        • 跳转到执行地址为42的指令,实际是一条mov指令
          • 该指令将2设置到另一个32位整型的寄存器地址,相当于一个赋值操作
        • 然后PC寄存器里的值继续自增,执行leave指令
  1. 如果r == 0条件满足,在赋值的mov指令执行完成后,有一个jmp无条件跳转指令,跳转到49(leave指令)

for

1
2
3
4
5
6
7
8
9
10
// test.c

int main()
{
int a = 0;
for (int i = 0; i < 3; i++)
{
a += i;
}
}
1
2
$ gcc -g -c -std=c99 for.c
$ objdump -d -M intel -S for.o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......
int main()
{
......
for (int i = 0; i < 3; i++)
b: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0
12: eb 0a jmp 1e <main+0x1e>
{
......
for (int i = 0; i < 3; i++)
1a: 83 45 f8 01 add DWORD PTR [rbp-0x8],0x1
1e: 83 7d f8 02 cmp DWORD PTR [rbp-0x8],0x2
22: 7e f0 jle 14 <main+0x14>
24: b8 00 00 00 00 mov eax,0x0
}
}
29: 5d pop rbp
2a: c3 ret
  1. 循环是用1e地址上的cmp比较指令和jle条件跳转指令来实现的
  2. jle跳转的地址,是jle指令前面的地址14(向前跳转

参考资料

深入浅出计算机组成原理