实验:计算机是怎样工作的?

实验一 ID SA12226267

首先,概述一下编译链接的过程:C语言的编译链接过程是将程序源代码转换成可以运行的可执行文件,需要进行编译和链接。编译就是把文本形式源代码(高级语言指令)转换为功能等效的汇编代码。链接是把目标文件、操作系统的启动代码和用到的库文件链接最终生成二进制可执行代码的过程。完整过程可以描述如下():

C源程序--->预处理(.c)--->编译(.s)--->汇编(.o)--->链接(可执行文件)

源代码Example.c

int g(int x)
{
	return x + 3;
}
  
int f(int x)
{
	return g(x);
}
 
int main()
{
	return f(8) + 1;
}
进行预处理,预处理后的文件为Example.cpp ,gcc -E Example.c  -o Example.cpp,用vim Example.cpp可看到预处理后的文件

# 1 "Example.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "Example.c"
int g(int x)
{
 return x + 3;
}

int f(int x)
{
 return g(x);
}

int main()
{
 return f(8) + 1;
}
然后是编译成汇编代码 gcc -x cpp-output -S -o Example.s Example.cpp ,其中参数-x说明根据指定的步骤进行工作,cpp-output指明从预处理得到的文件开始编译,-S说明生成汇编代码后停止工作。生成的汇编代码如下;

	.file	"Example.c"
	.text
	.globl	g
	.type	g, @function
g:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	movl	8(%ebp), %eax
	addl	$3, %eax
	popl	%ebp
	.cfi_def_cfa 4, 4
	.cfi_restore 5
	ret
	.cfi_endproc
.LFE0:
	.size	g, .-g
	.globl	f
	.type	f, @function
f:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$4, %esp
	movl	8(%ebp), %eax
	movl	%eax, (%esp)
	call	g
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	f, .-f
	.globl	main
	.type	main, @function
main:
.LFB2:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$4, %esp
	movl	$8, (%esp)
	call	f
	addl	$1, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE2:
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
	.section	.note.GNU-stack,"",@progbits

 

汇编生成目标代码 gcc -x assembler -c Example.s -o Example.o

最后生成可执行文件gcc -o Example.o Example


涉及的几个寄存器有ebp,esp,eip,其中ebp和esp分别指向当前栈的栈底和栈顶,eip是指令寄存器,存储着CPU要执行的下条指令的地址。

此外,还需要注意几个汇编指令:

call 指令:执行call 指令时:会把当前eip的值压栈保存,并使得eip等于被调用函数的起始地址。

leave指令:执行leave指令,等于下面两条指令:

movl  %ebp , %esp    //使栈顶指针指向栈基指针

pop  %ebp       //使得栈基指针恢复为前一次保存的ebp的值

ret指令:等于 pop  %ebp   即恢复ebp的值。


从main函数开始分析:

pushl    %ebp
movl     %esp, %ebp

显示ebp入栈(保存之前的ebp),然后使ebp指向与esp相同,此时栈情况如下:

 

调用函数之前,要将参数(本例是8)压栈,

subl  $4, %esp
movl $8, (%esp)


这两条指令完成参数入栈的作用,此时,栈的情况如下图:

然后是call f;

call f 的过程包括将eip入栈,即保存返回地址,然后将函数f的地址给eip,然后开始执行函数f

执行函数f的过程,首先仍然是保存旧栈,

pushl %ebp
movl %esp, %ebp

subl    $4, %esp
movl    8(%ebp), %eax
movl    %eax, (%esp)


这个过程是参数x(这里是8)入栈,执行完后


接着是call g,

然后开始执行g,同理,先保存ebp,

pushl %ebp
movl %esp, %ebp

movl    8(%ebp), %eax
addl    $3, %eax

这两条指令进行计算,计算结果放入eax寄存器,内存不发生变化

popl    %ebp
ret

完成计算后,出栈

此时,返回到f中,

leave
ret

其中,leave等价于movl %ebp , %esp;popl %ebp;

ret 相当于popl %eip

再次回到main中。

addl    $1, %eax

该指令进行计算,结果仍然存在eax中。

leave
ret

函数执行完毕。
关于计算机单任务与多任务的运行:

单任务运行,CPU通过读取EIP寄存器的值得值下一条指令的内存地址,然后读取指令并执行。整个过程中,始终由EIP保存将要执行的下一条指令的地址。函数调用过程中,如上面分析,需要保存现场,执行完后要返回,主要是通过EBP,ESP等寄存器实现的。

多任务情况要复杂一些,依靠进程调度、中断机制等实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值