计算机是怎样工作的-函数调用堆栈机制
简介:解释单任务计算机(不考虑中断)的函数调用堆栈机制,并在此基础上讨论分析多任务计算机是怎样工作的
实验过程:
从c源代码到运行可执行程序一般要经过以下几个阶段:
预处理->编译->汇编->链接
源程序:example.c
int g(int x){ return x+3; } int f(int x){ return g(x); } int main(void){ return f(8)+1; }
1.预处理:将c源文件预处理生成example.cpp文件
2.编译:将预处理好的example.cpp文件编译生成example.s汇编代码
3.汇编:将汇编代码example.s文件编译为目标文件example.o
4.链接:将目标文件example.o与所需的所有附加的目标文件连接起来,生成可执行文件example(本实验未包含静态和动态链接库这样的附加的目标文件)
实验分析:
查看example.s文件,分析main()函数的执行过程:
伪代码:
g: .LFB0: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl $3, %eax popl %ebp ret f: .LFB1: pushl %ebp movl %esp, %ebp subl $4, %esp movl 8(%ebp), %eax movl %eax, (%esp) call g leave ret main: .LFB2: pushl %ebp movl %esp, %ebp subl $4, %esp movl $8, (%esp) call f addl $1, %eax leave ret
在main函数段的部分,首先构造堆栈框架
pushl %ebp
movl %esp, %ebp
堆栈变化
接着将要调用的函数参数压栈
subl $4, %esp movl $8, (%esp)
堆栈变化
然后调用函数f()
call f
堆栈的变化
程序代码段的变化
f()函数段部分,类似的构建堆栈
pushl %ebp
movl %esp, %ebp subl $4, %esp
堆栈变化
接着,将参数8存入寄存器eax,并将该值存入esp所指向的内存单元
movl 8(%ebp), %eax movl %eax, (%esp)
堆栈变化
然后调用g()函数
call g
堆栈变化 程序代码段变化
函数g()部分,类似的构造堆栈,将参数8存储到eax,执行加3运算后在存入eax作为函数的返回值,最后将ebp出栈,返回到f()
pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl $3, %eax
popl %ebp
ret
堆栈变化: 构造堆栈 ebp出栈 执行ret 弹出地址
程序代码段变化
返回到f()段后,执行将栈返回并返回main()函数
leave
ret
堆栈变化: 执行leave后 执行ret后 程序代码段变化
回到main()函数段后,执行将eax中的值11执行加1运算后存入eax作为main()函数的返回值,接着将栈返回并退出
堆栈返回
实验总结:
对于单任务计算机,通过eip找到程序代码段顺序执行每一条指令,当遇到控制语句(如跳转指令jmp)以及过程调用(如本实验中的call g)时会改变eip的值,使eip跳转到不同的位置继续顺序的执行程序代码段的每一条指令。不同的函数具有不同的栈空间,当执行有关堆栈操作的指令时(如本实验中的push等),相应函数的堆栈会发生相应的变化。
对于多任务计算机,通常利用时间片轮转的机制,系统的调度进程转依次调度执行就绪队列中进程。每个进程都有自己独立的进程空间,所以在进程切换时,会首先进行进程上下文切换,接着就如同单任务计算机一样执行当前的进程