CPU体系架构-ARM/MIPS/X86

转自:

http://nieyong.github.io/wiki_cpu/

 

CPU体系架构-ARM/MIPS/X86

第一部分:从寄存器、寻址方式、汇编指令等方面总结了ARM/MIPS/X86的异同

第二部分:分析了和CPU关系最近的几个硬件模块,依次是存储管理模型、Cache、MMU和DMA

第三部分:

操作系统-任务调度/内存管理/文件系统/IO系统/协议栈

第一部分:操作系统5大系统的基本概念

参考书籍

  1. 《程序员的自我修养-链接、装载与库》 俞甲子等著
  2. 《微机系统原理与接口技术》 李广军等主编
  3. 《ARM嵌入式系统开发-软件设计与优化》 Andrew N.Sloss等著 沈建华 译
  4. 《See MIPS Run》 Dominic Sweetman著
  5. 《VxWorks设备驱动开发详解》 曹桂平等著
  6. 《大话处理器》 杨万木
  7. 《嵌入式系统linux内核开发实战指南:ARM平台》 王洪辉 著
  8. 《Linux设备驱动程序》 Jonathan Corbet等著 魏永明等译

网络资料

  1. 《ARM与X86》 弯曲评论 王齐著
  2. 《The MIPS Cache Architecture》 comcat 著
  3. 《MIPS异常和中断处理机制研究v1.0.pdf》沈浩 著
  4. 《讲解MMU的好文章.doc》 网络资料
  5. 《arm920t的mmu和cache.pdf》 亚嵌教育
  6. 《Cache Memory》 弯曲评论 王齐等注

 

 

CPU体系架构-函数调用实例

 

在看过了上面的几节之后,在潜意识中你想记住的东西肯定很多了。这个时候,你需要静下心来休息一下在沉淀一下。

"Now is a good point to take a break to let this information sink in."

下面,我们就看看C语言撰写的程序,在不同的CPU架构下,生成的汇编语言是怎么样的,各有什么特点,这和前面介绍的各种CPU架构的知识是如何联系的。如果你觉得还不够,也很高兴一起来探讨一下在不同的CPU架构下,函数的调用时如何实现的,以及各有什么特点。

反汇编文件

测试用的C源码如下,main.c文件:

  1. #include <stdio.h>  
  2.   
  3. int add(int a,int b)  
  4. {  
  5.     return a+b;  
  6. }  
  7.   
  8.   
  9. int main(int argc,char** argv)  
  10. {  
  11.     int a,b,c;  
  12.   
  13.     a = 1;  
  14.     b = 2;  
  15.   
  16.     c = fn(a,b);  
  17.   
  18.     return c;  
  19. }  

 

下面为编译以及反汇编的过程。当然,ARM和MIPS的编译和反汇编是使用的交叉编译工具链。例如在我的平台下,对应的命令就是ccarm,objdumparm和ccmips,objdumpmips。

#gcc -o main.o -c main.c
#objdump -d main.o>main.a

编译成x86下面的main.o文件,注意,没有链接。然后反汇编为main_x86.a如下所示。这里需要指出的是,下面的汇编语言并不是我们在《微机原理》课本上学习到的x86的汇编(我们称为intel汇编),而叫做AT&T汇编。那么,什么是AT&T汇编呢?

在将Unix移植到80386处理器上时,Unix圈内人士根据Unix领域的习惯和需要而定义了AT&T汇编。而GNU主要是在Unix领域活动,因此GNU开发的各种系统工具继承了AT&T的386汇编格式,这也是我们使用GNU工具进行反汇编的时候,看到的汇编语言。 
  1. main.o:     file format elf32-i386  
  2.   
  3.   
  4. Disassembly of section .text:  
  5.   
  6. 00000000 <add>:  
  7.    0:   55                      push   %ebp  
  8.    1:   89 e5                   mov    %esp,%ebp  
  9.    3:   8b 55 0c                mov    0xc(%ebp),%edx  
  10.    6:   8b 45 08                mov    0x8(%ebp),%eax  
  11.    9:   01 d0                   add    %edx,%eax  
  12.    b:   5d                      pop    %ebp  
  13.    c:   c3                      ret      
  14.   
  15. 0000000d <main>:  
  16.    d:   8d 4c 24 04             lea    0x4(%esp),%ecx  
  17.   11:   83 e4 f0                and    $0xfffffff0,%esp  
  18.   14:   ff 71 fc                pushl  -0x4(%ecx)  
  19.   17:   55                      push   %ebp  
  20.   18:   89 e5                   mov    %esp,%ebp  
  21.   1a:   51                      push   %ecx  
  22.   1b:   83 ec 24                sub    $0x24,%esp  
  23.   1e:   c7 45 f0 01 00 00 00    movl   $0x1,-0x10(%ebp)  
  24.   25:   c7 45 f4 02 00 00 00    movl   $0x2,-0xc(%ebp)  
  25.   2c:   8b 45 f4                mov    -0xc(%ebp),%eax  
  26.   2f:   89 44 24 04             mov    %eax,0x4(%esp)  
  27.   33:   8b 45 f0                mov    -0x10(%ebp),%eax  
  28.   36:   89 04 24                mov    %eax,(%esp)  
  29.   39:   e8 fc ff ff ff          call   3a <main+0x2d>  
  30.   3e:   89 45 f8                mov    %eax,-0x8(%ebp)  
  31.   41:   8b 45 f8                mov    -0x8(%ebp),%eax  
  32.   44:   83 c4 24                add    $0x24,%esp  
  33.   47:   59                      pop    %ecx  
  34.   48:   5d                      pop    %ebp  
  35.   49:   8d 61 fc                lea    -0x4(%ecx),%esp  
  36.   4c:   c3                      ret      

 

编译成mips下面的main.o文件,没有链接。然后反汇编为main_mips.a:

  1. main.o:     file format elf32-bigmips  
  2.   
  3. Disassembly of section .text:  
  4.   
  5. 0000000000000000 <add>:  
  6.    0:   27bdfff8    addiu   $sp,$sp,-8  
  7.    4:   afbe0000    sw  $s8,0($sp)  
  8.    8:   03a0f021    move    $s8,$sp  
  9.    c:   afc40008    sw  $a0,8($s8)  
  10.   10:   afc5000c    sw  $a1,12($s8)  
  11.   14:   8fc20008    lw  $v0,8($s8)  
  12.   18:   8fc3000c    lw  $v1,12($s8)  
  13.   1c:   00000000    nop  
  14.   20:   00431021    addu    $v0,$v0,$v1  
  15.   24:   03c0e821    move    $sp,$s8  
  16.   28:   8fbe0000    lw  $s8,0($sp)  
  17.   2c:   03e00008    jr  $ra  
  18.   30:   27bd0008    addiu   $sp,$sp,8  
  19.   
  20. 0000000000000034 <main>:  
  21.   34:   27bdffd8    addiu   $sp,$sp,-40  
  22.   38:   afbf0024    sw  $ra,36($sp)  
  23.   3c:   afbe0020    sw  $s8,32($sp)  
  24.   40:   03a0f021    move    $s8,$sp  
  25.   44:   afc40028    sw  $a0,40($s8)  
  26.   48:   afc5002c    sw  $a1,44($s8)  
  27.   4c:   24020001    li  $v0,1  
  28.   50:   afc20010    sw  $v0,16($s8)  
  29.   54:   24020002    li  $v0,2  
  30.   58:   afc20014    sw  $v0,20($s8)  
  31.   5c:   8fc40010    lw  $a0,16($s8)  
  32.   60:   8fc50014    lw  $a1,20($s8)  
  33.   64:   0c000000    jal 0 <add>  
  34.   68:   00000000    nop  
  35.   6c:   afc20018    sw  $v0,24($s8)  
  36.   70:   8fc20018    lw  $v0,24($s8)  
  37.   74:   03c0e821    move    $sp,$s8  
  38.   78:   8fbf0024    lw  $ra,36($sp)  
  39.   7c:   8fbe0020    lw  $s8,32($sp)  
  40.   80:   03e00008    jr  $ra  
  41.   84:   27bd0028    addiu   $sp,$sp,40  
  42.     ...  

 

编译成arm下的main.o,没有链接。然后反汇编为main_arm.a:

  1. main.o:     file format elf32-littlearm  
  2.   
  3. Disassembly of section .text:  
  4.   
  5. 00000000 <add>:  
  6.    0:   e1a0c00d    mov r12, sp  
  7.    4:   e92dd800    stmdb   sp!, {r11, r12, lr, pc}  
  8.    8:   e24cb004    sub r11, r12, #4    ; 0x4  
  9.    c:   e24dd008    sub sp, sp, #8  ; 0x8  
  10.   10:   e50b0010    str r0, [r11, -#16]  
  11.   14:   e50b1014    str r1, [r11, -#20]  
  12.   18:   e51b3010    ldr r3, [r11, -#16]  
  13.   1c:   e51b2014    ldr r2, [r11, -#20]  
  14.   20:   e0833002    add r3, r3, r2  
  15.   24:   e1a00003    mov r0, r3  
  16.   28:   ea000009    b   2c <add+0x2c>  
  17.   2c:   e91ba800    ldmdb   r11, {r11, sp, pc}  
  18.   
  19. 00000030 <main>:  
  20.   30:   e1a0c00d    mov r12, sp  
  21.   34:   e92dd800    stmdb   sp!, {r11, r12, lr, pc}  
  22.   38:   e24cb004    sub r11, r12, #4    ; 0x4  
  23.   3c:   e24dd014    sub sp, sp, #20 ; 0x14  
  24.   40:   e50b0010    str r0, [r11, -#16]  
  25.   44:   e50b1014    str r1, [r11, -#20]  
  26.   48:   e3a03001    mov r3, #1  ; 0x1  
  27.   4c:   e50b3018    str r3, [r11, -#24]  
  28.   50:   e3a03002    mov r3, #2  ; 0x2  
  29.   54:   e50b301c    str r3, [r11, -#28]  
  30.   58:   e51b0018    ldr r0, [r11, -#24]  
  31.   5c:   e51b101c    ldr r1, [r11, -#28]  
  32.   60:   ebfffffe    bl  0 <add>  
  33.   64:   e1a03000    mov r3, r0  
  34.   68:   e50b3020    str r3, [r11, -#32]  
  35.   6c:   e51b3020    ldr r3, [r11, -#32]  
  36.   70:   e1a00003    mov r0, r3  
  37.   74:   ea00001c    b   78 <main+0x48>  
  38.   78:   e91ba800    ldmdb   r11, {r11, sp, pc}  

 

不同CPU架构下汇编语言的特点

代码长度

首先,我们看同一个C语言程序,在不同的处理器下,代码长度。代码长度为反汇编文件的第一列,可以看到,在x86下是0x4c+1个字节,在MIPS下是0x84+4个字节,在ARM下是0x78+4个字节。为什么这样呢?

首先看的是CISC和RISC的区别 CISC架构下,指令的长度不是固定的,而RISC为了实现流水线,每条指令的长度都是固定的。看到反汇编文件的第二列,x86的指令中,最短为一个字节,例如push,pop,ret等指令,最长为7个字节,例如movl指令。而对于ARM和MIPS,所有的指令长度都固定为4个字节。这也是ARM和MIPS的机器代码长度要明显比x86长的原因。

另外一个原因,就是CISC可能为某个特殊操作实现了一条指令,而RISC处理器则需要用多条指令组合来完成该操作。最明显的就是出入栈操作。

然后我们看MIPS和ARM的区别 ARM的代码长度比MIPS代码长度稍稍小,这也验证了人们常说的MIPS是纯粹的RISC架构,而ARM则在RISC的基础上吸收了CISC中的某些优点。我们还是要看看,ARM是因为吸收了哪些CISC的特点,才做到代码长度的减少的呢?最明显的是出入栈,在ARM中实现了多寄存器load/store指令,请注意main_arm.a文件的0x4,0x2c,0x34,0x78行的stmdb和ldmdb指令。那么,这和CISC有什么关系呢?仔细想想,这就是CISC中的为了某个特殊的操作而实现一条指令的思想。另外,多寄存器的load/store指令破坏了RISC中指令的执行周期必须是单周期的规定,这一点,也是人们指责ARM不是纯粹的RISC架构的证据之一。

另外还有就是ARM实现了条件标志,实现条件执行,这样可以减少分支指令的数目,提高代码的密度。不过在我们这个实例中没有这方面的应用。

出栈入栈

入栈指将CPU通用寄存器的值放入栈(存储器)中保存起来。出栈则是指将栈中的值恢复到CPU中相应通用寄存器。

在x86下面,有专门的出栈和入栈指令pop和push。出栈和入栈在栈中的位置是当前堆栈指针sp所指向的位置。在MIPS和ARM下面,没有专门的指令,所以入栈和出栈首先使用的是load/store指令将数据放入堆栈或者弹出堆栈,然后使用add/stub指令修改堆栈指针的值。比较特殊的是ARM有特殊的多寄存器load/store指令来快速的完成出入栈。由此可见,在MIPS和ARM下面,出入栈都是使用帧指针或者堆栈指针的相对位置,在执行之后,并不会影响堆栈指针sp的值。

栈的生长 栈的生长指堆栈指针sp的变化。例如,在函数调用的时候,需要一次性的分配被调用函数的栈空间大小。在x86,ARM,MIPS下都是使用的对堆栈指针寄存器直接加减的办法。而且,在MIPS和ARM下,也只有这种办法可以改变堆栈指针SP的值。而在x86下,除了直接加减的办法之外,出入栈操作指令pop和push还可以改变sp的值。

不同CPU架构下函数调用的特点

C语言函数的调用,请参考C语言-Stack的相关内容。涉及堆栈帧(stack frame),活动记录(active record),调用惯例(call convention)等相关概念。建议参考《程序员的自我修养-链接、装载与库》的第10.2节-栈和调用惯例。

毫无疑问,这里都是使用的C语言的默认调用惯例cdecl。cdecl调用管理的特点如下:

  • 调用的出栈方为函数调用方;
  • 参数的传递为从右向左入栈;
  • 名字修饰为下划线+函数名;

首先我们给出一个cdecl调用惯例的模型,不针对任何处理器架构,然后我们再看看不同的处理器架构下cdecl调用惯例有什么样的特点。下图为cdecl调用惯例的一般情况示意图。

下面分析cdecl调用惯例中,调用函数和被调用函数分别负责活动记录(堆帧栈)中的什么操作呢?首先是被调用函数,在函数的入口,需要做以下操作:

  • 需要将返回地址入栈,例如MIPS下的 sw \(ra,28(\)sp) 一句,就是将函数的返回地址入栈。对于x86架构,由于call命令会自动将返回地址入栈,所以在函数入口处没有返回地址入栈的指令;
  • 需要将调用函数的帧指针入栈,然后设置本身自己函数的帧指针。在x86下,是 push %ebp;mov %esp,%ebp两句,而在MIPS下,是sw \(s8,24(\)sp);move \(s8,\)sp;两句。这里也可以看出x86的拥有专门入栈指令的特点push的特点;
  • 调整堆栈指针的大小,升栈到函数需要的堆栈大小。

上面是被调用函数需要负责的事情,那么调用函数需要负责什么呢?主要有一下几点:

  • 准备好函数调用参数;
  • 调用指令call跳转到被调用函数。

X86的函数调用

这个调用中没有调整堆栈指针的操作,因为不需要用到额外的堆栈空间。所以,只有上面提到的第一点和第二点。

  1. push   %ebp  
  2. mov    %esp,%ebp  
  3. ......  
  4. pop    %ebp  
  5. ret      

 

MIPS的函数调用

MIPS的堆栈,在被调用函数中有8bytes的升栈操作,下面的ARM中也是一样的。

  1. addiu   $sp,$sp,-8  
  2. sw  $s8,0($sp)  
  3. move    $s8,$sp  
  4. ......  
  5. move    $sp,$s8  
  6. lw  $s8,0($sp)  
  7. jr  $ra  
  8. addiu   $sp,$sp,8  

 

ARM的函数调用

相比于MIPS架构,一条stmdb和ldmdb就可以完成多个寄存器的存储(store)和加载(load)。

  1. mov r12, sp  
  2. stmdb   sp!, {r11, r12, lr, pc}  
  3. sub r11, r12, #4    ; 0x4  
  4. sub sp, sp, #8  ; 0x8  
  5. ......  
  6. b   2c <add+0x2c>  
  7. ldmdb   r11, {r11, sp, pc}  

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值