记得在学ARM编程时候学过如何基本的ARM汇编操作,也涉及到一些非常基础的Linux下的汇编指令形式。但都未真正深入了解,也没写过多少汇编代码,除了能够认识代码的含义以外,真正上手写一些汇编代码进行问题的处理我还在学习中。
谈及计算机是如何工作的,似乎在每一本计算机有关的书上都会出现一个名字,那就是冯诺依曼。我记得这也是机房墙上必帖的人物头像,和图灵站在一块。
这篇文章并不过多的论述计算机组成原理,只是简单的阐述一下到目前为止我所能记得的领悟的计算机工作的过程。
书上有谈到冯诺依曼结构是通用的计算机结构,它也被称作普林斯顿结构。它只有一个主存储器,主存储器中可以存放的是数据也可以是指令。并且它只有一种访问主存储器的指令。CPU通过总线与存储器进行信息交换。这种结构事实上是不高效的,因为数据和指令都杂合在一起。所以,对应的比较好的设计就是哈佛结构,哈佛结构是指令与数据分开存储的体系结构。它有存放指令和数据的分离的地址空间和访问指令,所以这里就有两类总线,数据总线和指令总线。因为进行了职责分离,因此哈佛结构的计算机处理器具有更高的数据吞吐率。
计算机工作的原理其实就在于对数据的处理,而对数据的处理通过调用指令的方式进行。我们平时操作计算机,可以通过键盘,通过鼠标操作,对应的是计算机电路0,1的电位变化。而如何解释操作则是由操作系统来进行处理,总而言之,计算机工作过程都被进行了抽象与封装,而操作系统是对底层的第一层封装。最基础的计算机由存储器,输入输出设备,屏幕,主板,CPU等硬件组成。如何驱动这些硬件则是由软件来进行。
编程是在操作系统的基础上进行的通过代码的方式进行操作再由编译器进行编译来执行。
在Linux下我们进行了反汇编来测试了一下C代码如何转换成汇编代码的过程。
我们在实验楼这个平台进行,C代码是简单的三个函数(一个主函数和两个被调用函数):
在Linux下用GCC进行反汇编:
gcc -S -o main.s main.c -m32
则可以得到main.c的汇编级代码:
以点开头的是注释类所以删去了大部分的这样的语句,以上截图是为全部的汇编代码。
首先从main:开始看起:
pushl %ebp
ebp是基础栈指针,假想为0,此时栈为空,esp和ebp都指向栈底,这条指令等价于:
subl $4, %esp
movl %ebp,(%esp)
即:将esp指向的位置向下减去4,再把esp对应处的值设为ebp的位置,假设为0;
movl %esp, %ebp
是将ebp指针值改为esp指针的值,即esp再次和ebp指向同一个位置;
subl $4, %esp
movl $8, (%esp)
这两条指令等价于pushl $8, esp指针向下移动4个字节;再把esp指向的寄存器处值设为8;
call f
调用函数f,这条指令等价于
pushl %eip(*)
movl f %eip(*)
意思是先暂存eip的值,eip是指向要调用指令的位置,在图中是第24行,存在寄存器中,然后对eip进行赋值为f的入口地址,执行f函数的代码;
pushl %ebp
esp向下走4个字节,并把ebp指针值压栈
movl %esp, %ebp
将ebp的值设置为何esp相同
subl $4,%esp
esp向下走4个字节
movl 8(%ebp), %eax
eax寄存器的值等于ebp+8,即eax现在指向数字8
movl %eax, (%esp)
将esp位置的值设为eax指针值
call g
等价于
pushl %eip(*)
movl g %eip(*)
调用函数g, 将eip的值存在寄存器,图中是16行,再对eip进行赋值,进入g函数入口;
pushl %ebp
esp向下进4个字节并把ebp指针值放进来
movl %esp, %ebp
使得ebp=esp
movl 8(%ebp), %eax
令eax=ebp+8,向上走8个字节指向存储着eax值,这个eax指向的是数字8
addl $10,%eax
把eax指向的值与10相加得到18
popl %ebp
等价于movl (%esp), %ebp
addl $4, %esp
然后:
ret
等价于 popl %eip(*)
跳转到16行进行执行
leave
等价于
movl %ebp, %esp
popl %ebp
再弹出eip跳转到第24行暂存的eax值为18,再与3相加,再leave,再ret,ebp与esp均回到初值。