实验一:计算机是怎样工作的
一、实验目的
请使用Example的c代码分别生成.cpp,.s,.o和ELF可执行文件,并加载运行,分析.s汇编代码在CPU上的执行过程。
通过实验解释单任务计算机是怎样工作的,并在此基础上讨论分析多任务计算机是怎样工作的。
二、实验步骤
1. Example中的c代码
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8)+1;
}
2. C代码的预处理(.cpp)文件生成
# 1 "main.c"
# 1 ""
# 1 ""
# 1 "main.c"
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8)+1;
}
指令:-gcc -E -o main.cpp main.c
执行程序的预处理阶段,预处理器根据以字符“#”开头的命令,修改原始的C程序。预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令,主要处理的规则有:
将所有的“#include”删除,并且展开所有的宏定义;
处理所有的条件预编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”;
处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件;
删除所有的注释“//”和“”;
删除行号和文件名标识,比如#2“hello.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号;
保留所有的#pragma编译器指令,因为编译器需要使用它们。
3. C代码的汇编程序(.s)生成
.file "main.c"
.text
.globl g
.type g, @function
g: //程序指针跳到g函数
pushl �p //栈底指针ebp入栈
movl %esp, �p //将栈顶指针esp的值放入ebp里
movl 8(�p), �x //将ebp的值加8,并把结果付给eax累加器
addl $3, �x //把eax的值再加3
popl �p
ret //相当于popl %eip
.size g, .-g
.globl f
.type f, @function
f: //程序指针跳到f函数
pushl �p //栈底指针ebp入栈
movl %esp, �p //将栈顶指针esp的值放入ebp里
subl $4, %esp //栈顶指针esp减去4,即先预留出4字节空间
movl 8(�p), �x //将ebp的值加8,并把结果付给eax累加器
movl �x, (%esp) //把当前eax的值,即8压入堆栈
call g //相当于pushl %eip;movl $g的地址,%eip
leave //相当于movl �p,%esp;popl �p
ret
.size f, .-f
.globl main
.type main, @function
main: //程序指针首先指向main函数,即先执行main函数
pushl �p //栈底指针ebp入栈
movl %esp, �p //将栈顶指针esp的值放入ebp里
subl $4, %esp //栈顶指针esp减去4,即先预留出4字节空间
movl $8, (%esp) //将8压入堆栈
call f //调用f函数
addl $1, �x //eax寄存器中的值+1
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5"
.section .note.GNU-stack,"",@progbits
执行指令:gcc -x cpp-output -S -o main.s main.cpp
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件。
汇编指令入栈出栈的顺序如下图所示:
(1)先执行main函数到call f;
(2)再执行f函数到调用g函数开始处
f中调用call g:
(3)执行g函数
pushl �p; movl %esp,�p
movl 8(�p),�x; addl $3,�x; popl �p; ret
(4)程序从f返回接着执行f的leave
(5)接着执行main函数剩下的部分
addl $1,�x; leave; ret
3. C代码的可重定位目标程序(.o)生成
汇编器是将汇编代码编程极其可以执行的指令。
执行指令:gcc -x assembler -c main.s -o main.o
4. ELF可执行文件生成
链接器的作用就是合并上述阶段生成的多个main.o文件,得到“main.out”,即最终的可执行文件。
执行指令:gcc -o main main.o
三、计算机是怎样工作的
1. 单任务计算机
单任务计算机一次只能执行一条指令,如上述实验分析可知,在单任务计算机中指令的执行顺序就是汇编程序中指令入栈出栈的顺序来执行的。在本实验中,程序先从main函数开始执行,每个函数都有自己的函数栈,在需要调用其它函数的时候,在main函数栈中保存现存的所有信息,包括累加器等寄存器的值,然后基栈指针ebp就开始指向被调用函数的开始处,开始维护一个新的函数栈,当被调函数执行完之后,堆栈指针ebp和esp就又回到main函数中被挂起的位置继续执行,直到整个程序结束。
2. 多任务计算机
多任务计算机是可以并行执行多个任务,其实多任务计算机也是单个处理器在工作,只是在宏观上好似多个任务并行执行的,这是由于CPU的分时操作实现的,CPU在时间片结束的瞬间在不同任务之间快速切换,即不同任务的指令在交替地执行着,但是对于每一个任务来说,指令的执行顺序和入栈出栈的顺序都是单任务计算机的指令执行过程是相似的。