从一段代码的汇编看计算机的工作原理

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

大家都知道,现在的计算机主要遵循的是所谓的“冯诺依曼框架”。那什么是冯诺依曼框架呢,其实就是计算机通过总线从内存中读取一条条的程序和数据,将它们存储在自己的寄存器中一条条地执行,如下图所示。

  

  而今天,我们将通过汇编一个具体的C程序来探讨计算机工作的流程。

首先写下这么一段C程序:

 1 //linux.c
 2 int g(x)
 3 {
 4     return x+3;
 5 }
 6 int f(x)
 7 {
 8     return g(x);
 9 }
10 int main()
11 {
12     return f(10)+1;
13 }

  在Linux的环境中输入如下指令:

gcc –S –o linux.s linux.c -m32

  然后打开linux.s,就可以看到我们汇编后的代码(直接上截图了)

 

   将里面以“.”开头的行去掉(这是为链接用的),得到汇编后的代码:

 1 g:
 2     pushl   %ebp
 3     movl    %esp, %ebp
 4     movl    8(%ebp), %eax
 5     addl    $3, %eax
 6     popl    %ebp
 7     ret
 8 f:
 9     pushl   %ebp
10     movl    %esp, %ebp
11     subl    $4, %esp
12     movl    8(%ebp), %eax
13     movl    %eax, (%esp)
14     call    g
15     leave
16     ret
17 main:
18     pushl   %ebp
19     movl    %esp, %ebp
20     subl    $4, %esp
21     movl    $10, (%esp)
22     call    f
23     addl    $1, %eax
24     leave
25     ret

  接下来我们来分析一下改程序具体的流程。

  程序一开始,CPU的IP寄存器指向汇编代码的第18行,假设堆栈在内存中的地址分别为0,1,2,3……堆栈基指针寄存器(EBP)和堆栈顶指针寄存器(ESP)均指向堆栈段0处。

  第18~21行首先为main函数开辟新的内存区域,之后将传的参数10入栈,此时堆栈段如下所示:

  

  然后程序调用call 函数,将IP入栈,IP指向代码第9行f处。

  在f函数的代码处,首先为f函数开辟新的内存区域,接着将传入的参数10赋值给EAX,并将EAX入栈,此时堆栈段内存如下图:

  程序在此调用call进入g函数。在g函数中,同样先是开辟内存空间,然后将参数传给EAX,并将EAX的值加上3。

  之后将EBP出栈,并调用ret命令。此时IP重新指向f函数call之后的命令,堆栈内存的情况如下:

  

  之后就是不断的调用leave与ret命令,跳出当前的内存区域,回到上一级函数的内存区域中,并将EAX的值加3,直到跳出main函数,至此程序结束。

  从上面的分析中,我觉得可以归纳出以下几点:

  1.计算机的运行流程确是遵循冯诺依曼框架,CPU将内存中的代码和数据读取到自己的寄存器中,再根据一条条命令调用寄存器进行进一步的操作。

  2.在进入每一个程序之前,CPU都会将上一级的EIP和EBP压栈,相当于为新的函数重新开辟了一段新的内存空间,直到退出函数的时候才将它们出栈。

  3.CPU的各个寄存器都有不同的分工,如EIP指向要执行的代码,EAX存储返回值等。它们贯穿于整个程序执行流程,自己写程序时一般不要轻易改动。

  

时间: 2024-08-02 02:55:16

从一段代码的汇编看计算机的工作原理的相关文章

从汇编看计算机的工作过程

本周学习了Linux内核分析第一课,老师通过讲解一个简单的c程序使我了解了计算机(特别是堆栈部分)工作过程. 下面通过一个简单的例子来分析一下: 下面是c程序: int g(int x) { return x + 6; } int f(int x) { return g(x); } int main(void) { return f(5) + 2; } 使用下面命令反汇编c程序得到汇编程序 gcc –S –o main.s main.c -m32 首先从main函数开始分析,可以发现每个函数中前

分析汇编代码理解计算机如何工作

周玉勇 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 把C代码反汇编成汇编代码,再来分析计算机如何工作 下面是C代码 C代码编译成汇编的代码 简化的汇编代码 1 g: 2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 addl $6, %eax 6 popl %ebp 7 ret 8 f: 9 pushl %ebp 10 mov

从汇编看for和while循环的效率

这事情得从C语言开始说起,先编写下面这两段代码: 1. #include<stdio.h> int main(void) {     int i;     for(i = 0; i < 10; i++) {     }     return 0; } 2. #include<stdio.h> int main(void) {     int i = 0;     while (i < 10) {         i++;     }     return 0; } 然后

编程如何驱动计算机完成工作

1.计算机的工作原理 要想理解编程如何驱动计算机完成工作,就要理解计算机是如何工作的. 1.1 现代计算机的几个构成部件 中央处理单元(CPU,central processing unit),承担计算机绝大部分的运算和控制工作,负责处理程序. 随机存取内存(RAM,random access memory),存储程序和文件的工作区. 外围设备,如键盘.鼠标.触摸屏.监视器,提供人与计算机之间的交互. 1.2 CPU的日常工作 CPU从内存中获取并执行一条指令,然后再从内存中获取并执行下一条指令

都怪当初看了这6段代码,造成了如今的深度学习!

回忆当初我们看到的这6段代码,造就了我们如今的深度学习,这6段代码的创作者及其取得 的这些辉煌成就的那些故事.这些故事都对应的有简单的代码示例,小伙伴们在FloydHub 和 GitHub 上找到相关代码. 提示一下:要运行 FloydHub 上的代码,需要确保你的电脑上已经安装了Floyd命令工具,并且复制代码保存到本地计算机.在本地计算机示例项目中初始化命令行界面之后,您就可以运行以下命令在 FloydHub 上启动项目: 最小二乘法 所有的深度学习算法都始于下面这个数学公式(我已将其转成

理解计算机的工作方式——通过汇编一个简单的C程序并分析汇编代码

Author: 翁超平 Notice:原创作品转载请注明出处 See also:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  本文通过汇编一个简单的C程序,并分析汇编代码,来理解计算机是如何工作的.整个过程都在实验楼上完成,感兴趣的读者可以通过上面给出的课程链接自行动手学习.以下是实验过程和结果. 一.操作步骤 1.首先在通过vim程序建立main.c文件.代码如下: 图1 2.使用如下命令将main.c编

从一段代码看fork()函数及其引发的竞争

首先来看一段从<UNIX环境高级编程>中摘录的一段非常有意思的代码.借此我们再来谈谈fork()函数的一些问题. #include "apue.h" static void charatatime(char*); int main(void) { pid_t pid; if((pid=fork())<0){ err_sys("fork error"); }else if(pid==0){ charatatime("output from

c#转Java ,如何折叠一段代码使整个代码看起来简洁

Java netBeans/Eclips 如何折叠一段代码使整个代码看起来简洁 最近刚用Java,以前写C#的时候,通过region操作可以使一段代码折叠起来,使整段程序缩成一行,看起来清爽了许多,现在用netBeans, 发现自带的代码折叠功能只能折叠一个整个的method,不能选择一段进行折叠,请问能实现类似于C#的功能吗?如 #region 很长的代码 #endregion NetBeans内Editor设置了类似的功能. 只需要在模块开始注释以//<editor-fold>开始, 在模

从汇编层面看函数调用的实现原理

本文是<go调度器源代码情景分析>系列 第一章 预备知识的第6小节. 前面几节我们介绍了CPU寄存器.内存.汇编指令以及栈等基础知识,为了达到融会贯通加深理解的目的,这一节我们来综合运用一下前面所学的这些知识,看看函数的执行和调用过程. 本节我们需要重点关注的问题有: CPU是如何从调用者跳转到被调用函数执行的? 参数是如何从调用者传递给被调用函数的? 函数局部变量所占内存是怎么在栈上分配的? 返回值是如何从被调用函数返回给调用者的? 函数执行完成之后又需要做哪些清理工作? 解决了这些问题,我