马哲骅 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

计算机是如何工作的

我们平时使用的计算机属于“冯·诺依曼结构计算机”,这种计算机将程序存储在内存中,CPU从内存中不断地取指令并执行指令。CPU能够执行的指令是二进制指令,为了便于理解,人们发明了汇编指令,它是对机器指令的简单翻译,但是增强了可读性。我们平时所使用的编程语言多属于高级语言,它具有良好的可读性,却不能被计算机直接执行,因此,高级语言通常都要经过编译或解释说明等过程,转换为计算机能够执行的机器语言。下面我们以一个简单的C程序为例,分析计算机如何执行指令。

C程序代码参考截图一。将其命名为main.c,然后执行一条命令生成对应的汇编代码:

gcc -S -o main.s main.c -m32
copy

其中-S选项表示在生成汇编代码后停止编译过程,-o选项表示输出编译结果,即输出汇编代码至main.s文件中,-m32则表示生成32位的汇编代码。

生成的main.s文件较复杂,我们把其中以句点"."开始的行全部删去,这些行是链接文件时需要使用的,与程序本身关系不大。留下的汇编代码就是C程序转换后获得的代码了,参考截图二。可以看到汇编代码中也有三个函数,分别对应于C代码中的main(), f(), g()函数。

要说明汇编语言的执行过程,还需要引入另外一个重要的计算机概念——栈。它是一个“先进后出”的数据结构,栈底由寄存器ebp指示,栈顶由寄存器esp指示,栈在内存中向下增长,即每压入一个元素,esp的值减小,每弹出一个元素,esp的值增大。

现在我们来分析汇编代码的执行过程,不妨假设程序开始时esp和ebp两个寄存器指向同一初始位置,以序号0表示,序号延栈增长的方向增大,即压入一个元素后esp指向位置1,弹出一个元素后esp指向位置0,以此类推。要注意相邻两个序号的物理地址相差4而不是相差1。

pushl %ebp
copy

esp的值首先减4,然后将ebp的值存储在esp指示的位置,则栈中序号为1的位置存储了ebp的当前值(0),ebp仍指向位置0,esp指向位置1;

movl %esp, %ebp
copy

此时ebp与esp都指向位置1;

subl $4, %esp
copy

esp指向位置2;

movl $12, (%esp)
copy

栈的位置2存储立即数12;

call f
copy

首先将eip压入栈中,eip此时的值是下一条将要执行的指令的位置,我们不妨以行号代表,即将行号23存入栈中位置3,此时esp指向位置3,ebp指向位置1;然后将函数f的起始位置赋给寄存器eip,即下一条指令为f的第一条指令;

pushl %ebp
copy

栈中位置4存储ebp的值(位置1),esp指向位置4;

movl %esp, %ebp
copy

esp和ebp都指向位置4;

subl $4, %esp
copy

esp指向位置5;

movl 8(%ebp), %eax
copy

将ebp之前两个位置处的值给寄存器eax,即将位置2处存储的值12给eax;

movl %eax, (%esp)
copy

将eax中的值存储到esp指向的位置,即栈中位置5存储12;

call g
copy

首先将eip压入栈中,即将行号15存入栈中位置6,此时esp指向位置6,ebp指向位置4;然后将函数g的起始位置赋给寄存器eip,即下一条指令为g的第一条指令;

pushl %ebp
copy

栈中位置7存储ebp的值(位置4),esp指向位置7;

movl %esp, %ebp
copy

esp和ebp都指向位置7;

movl 8(%ebp), %eax
copy

将ebp之前两个位置处的值给寄存器eax,即将位置5处存储的值12给eax;

addl $6, %eax
copy

eax中的值加上6,变为18;

popl %ebp
copy

将栈顶数据弹出到ebp中,即将栈中位置7的值“位置4”赋给ebp,同时esp指向位置6;

ret
copy

将栈顶数据弹出到eip中,即将栈中位置6的值“行15”赋给eip,同时esp指向位置5,下一条指令变为第15行处的指令;

leave
copy

这条指令首先将ebp的值赋给esp,即esp和ebp都指向位置4,然后将栈顶元素弹出到ebp中,即将栈中位置4的值“位置1”赋给ebp,同时esp指向位置3;

ret
copy

将栈顶数据弹出到eip中,即将栈中位置3的值“行23”赋给eip,同时esp指向位置2,下一条指令变为第23行处的指令;

addl $1, %eax
copy

将寄存器eax中的值加1,变为19;

leave
copy

首先将ebp的值赋给esp,即esp和ebp都指向位置1,然后将栈顶元素弹出到ebp中,即将栈中位置4的值“位置0”赋给ebp,同时esp指向位置0;

ret
copy

将栈顶数据弹出到eip中,即将栈中位置0的值赋给eip,同时esp指向位置-1,下一条指令变为程序开始前的指令。

总结

以上我们逐条分析了汇编程序的运行过程,尤其是栈的变化情况。通过这种分析我们可以看到计算机运行的基本机制:计算机将编辑好的程序存储与内存中,然后逐条执行程序代码,利用寄存器和栈结构来存储中间结果,直至程序执行完毕。

最新评论
暂无评论~