计算机是如何工作的(Linux)

------------------------------------计算机是如何工作的--------------------------------

        如今计算机已经充斥了现代人们生活的方方面面,在给人们带来巨大的便利的同时,不禁要问“计算机是如何工作的”呢?说实话,这是一个很大的命题,整个计算机需要多种机制的相互配合才能完成各种各样的工作。但是我们可以去繁为简,目前市面上的计算机都是“冯·诺依曼体系结构”。冯氏计算机最核心的思想就是:程序存储、顺序执行。其程序当作数据来对待,一律用二进制数表示。硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成。而该体系的实现中,最重要的是对内存的分配与管理,这得益于栈与堆结构的应用。

       其中,栈因其“先进后出”的特点,很好地支持了函数间的相互调用。这样CPU对指令的工作就是简单的三个阶段(取指令、指令译码、执行指令),而不必去考虑更多的事情。

 

该实验就是来分析一个简单明晰的函数调用的过程。

 

下面开始实验!

1在Code文件夹下创建一个c代码文件:a.c


2写一段最简单的c代码


3利用指令gcc –S –o a.s a.c -m32将其转变为汇编代码


4查看汇编代码发现及其冗长复杂,其实所有以“.”开头的都是编译器加上的内容


5删除所有以“.”开头的内容,得到真正的汇编代码



接下来我们来分析这段汇编码

 

          首先,我们知道,栈空间在内容中的增长是由高地址向低地址进行的。


          首先从main开始执行,第一句是pushl %ebp,将ebp 的值保存起来,以便以后修改ebp仍然可以恢复原来的值。(其实每一个ebp存放的都是其所在函数的父函数的ebp的地址,即每个ebp都指向上一个ebp,这样可以完成栈桢的恢复工作。这一点将在后面看到。)


         将ebp与esp对齐,这就是新的栈底(main函数的栈)。


         将esp下移4个字节,即一块内存空间,这块预留的区域用于存放局部变量等。

其实,(1)(2)(3)合起来就是一个开栈的过程,每一次发生函数调用的时候都要开栈,因此,在汇编码中,每个函数的开头都是这三句汇编指令(具体数值会因栈的大小不同而略有区别)。


         将226放入esp所指的内存空间内。这个226即为f的参数。



         call指令相当于两个过程:将eip的值入栈 + jmp指令。所以,此处入栈的eip值的实质就是f的返回地址(用fret表示),然后跳转执行f的代码。



         与之前main的情形类似,这是f的开栈过程和参数的传递。

         其中,语句movl   8(%ebp), %eax中8是ebp移动过的距离。因此可以看出,c 程序中函数参数的传递是通过栈来完成的,也因此,每个函数栈都是紧挨着向下增长的。

         在此过程中,寄存器eax用来暂存参数的值,因此,此时eax=226。



         这时又是call指令,此处将g的返回地址入栈。



         将ebp与esp对齐。

         之后是

              movl   8(%ebp), %eax

              addl    $15, %eax

        这里做了个加法运算,加法的两个参数分别是226(即8(%ebp)处的值)与15,即226+15=241。其结果是存在eax中的,因此,此时eax=241。



        g的功能执行完毕,开始退栈,先popl %ebp,使得ebp回到上一个栈的栈底位置。这就是为何要使得每个ebp存放的都是其所在函数的父函数的ebp的地址的原因了!



        ret指令相当于popl %eip,将代码的执行交还给其父函数,使父函数继续执行。因为pop指令,所以esp也自然向上移动了一个内存空间,这样ebp与esp就又指回到了父函数的栈空间,这种设计非常准确、整齐、优雅!



        leave指令相当于mov ebp, esp + pop ebp。就是个退栈的过程。

        此处多说一下,由此可以注意到,内存的管理中,只注重内存中数据结构的逻辑,如此处的退栈过程,仅注重了栈结构的退出,并不理会每个内存单元的存储数据的值。下次再开栈的时候,数据会自动覆盖。但是,在此处,我们可以明显看到,该内存区域的数据发生了改动,而且还未被新的覆盖,这样数据在内存中就形成了残留,若有黑客对内存进行任意读取的话,则很容易读取到这些残留,从而可能获取到一些私密的结果。因此,从安全角度讲,这样的设计会导致内存窥视的发生。


        之后是

               ret

        返回到main中继续执行。


        之后是

              addl    $252, %eax

              leave

              ret

       这时的加法是252+%eax,而此时eax=241(即刚才运算的结果),因此252+241=493,加法结束后,eax=493,即最终的计算结果为493,保存在eax中。


       通过这个实验,我们可以清楚地看到函数调用过程中,内存单元发生的变化。从而,理解程序执行的过程,这样从微观上明白计算机工作的过程。当然,这只是整个计算机工作过程中的一个片断,整个工作其实相当复杂庞大,希望大家能进行更深入的研究。

 

 

-----------------------------END-------------------------------

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

------------------------------------------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值