Linux内核分析-1:计算机程序的一生

首先,组装过计算机的童鞋应该知道,计算机是由CPU,内存,硬盘,主板,电源组成的,当然,玩游戏的童鞋还会念念不忘显卡(比如GTX980战术核显卡)

只要有了这些东西,计算机就可以跑起来了。

然而这些东西又是如何协同工作而让程序运行的呢?

首先,我们得知道一个叫做操作系统的东西,本质上,它也是一个程序,抽象上,它可以被看作成一个环境。这个环境保证了各个程序可以正常工作,正常休息,正常学习(喂,这就不正常了吧!)

而我们程序员只要用标准的语法来告诉操作系统我们要计算机做什么工作就可以了,比如,这里有一段C语言代码:

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

 

这个程序在计算机上是如何工作的呢?我们对其进行反汇编,得到汇编代码:

技术分享

化简之后:

g:    

    pushl    %ebp

    movl    %esp, %ebp

    movl    8(%ebp), %eax

    addl    $3, %eax

    popl    %ebp

    ret

f:

    pushl    %ebp

    movl    %esp, %ebp

    subl    $4, %esp

    movl    8(%ebp), %eax

    movl    %eax, (%esp)

    call    g

    leave

    ret

main:

    pushl    %ebp

    movl    %esp, %ebp

    subl    $4, %esp

    movl    $8, (%esp)

    call    f

    addl    $1, %eax

    leave

    ret

 

我们首先来看到Main函数入口点的两条汇编代码:

Pushl %ebp

Movl %esp,%ebp

Pushl指令是把ebp寄存器的值压在栈上,把这个值给保存起来,而movl指令则是把esp上的值放在了ebp,使ebp得到了一个新的值,这个新的值等于esp的值。

所以我们知道esp和ebp一开始都相等,也就是说以此就建立了一个程序的独立存储堆栈(PS:我们可以把ebp看成堆栈的开始,esp看成堆栈的末尾,esp增长,则代表栈增长,反之则减少)

 

通俗一点来说,就是ebp和esp他们决定开一个新坑。。。

好了,开了新坑以后,就要填坑了,后面的C代码调用了函数f(8),与之对应的是后面的三条汇编代码:

Subl $4,%esp

Movl $8,(%esp)

Call f

首先,计算机把传入的参数8压到了栈上面,要完成这项操作,首先必须先扩展一下栈空间,就像前面提到的,使esp增长,怎么增长呢,首先让esp寄存器指向的地址减去4,再把8填到这个扩展的空间中。

之后,调用Call,也就是调用f函数,Call这句话相当于保存现在的上下文然后改变eip指向的地址(要执行的代码段的位置改变了)。

于是eip的当前地址被压到了栈中,然后eip的地址就指向了函数f,函数f又做了和main函数开始部分相同的工作,通俗来说,它也开辟了一个独立堆栈存储空间(新坑),于是乎,函数f也坐起了浩浩荡荡的填坑工作。

在C语言中,我们看到f函数调用了g函数,与main函数调用f函数类似,首先计算机扩展了栈空间,然后把x压入到推栈中,但是与上一个不同的是计算机并没有直接把堆栈中的值压到堆栈中,而是靠eax寄存器做了一次中转:

movl    8(%ebp), %eax

movl    %eax, (%esp)

虽然过程不同,但结果一样,之后调用Call,eip再一次跳转到g函数那里,开新坑还有填坑:

pushl    %ebp

movl    %esp, %ebp

movl    8(%ebp), %eax

addl    $3, %eax

我们从第三条汇编代码开始,也就是将f函数传给g的参数x取出并放在了eax寄存器上,第四条addl语句执行了x+3

之后popl %ebp,也就是把栈顶的值弹到ebp上,我们知道ebp是栈的开始,ebp的值改变也就意味着跳转到了一个新的栈上。

而这个新的栈正是函数f的独立存储堆栈。

Ret语句相当于弹出栈顶的数值到eip上,而这个弹出的值正是函数f调用g函数之后的地址。

于是程序又回到了函数f中,并得到了返回值(存在了eax寄存器中):

Leave

这条语句代表的含义是

movl    %esp,     %ebp

popl    %ebp

返回到上一个独立存储堆栈中。

Ret之后eip又指向main函数调用f函数的下一行代码。也就是x+1,由于eax负责保存返回后的函数的结果,所以有:

Addl $1,%eax

之后main函数返回,之后啊。。我编不下了。。。

计算机的工作,也可以说是操作系统的工作就是不断地执行一个循环,这个循环保存上一个循环的上下文,数据,然后为当前循环开辟一个上下文和存储空间,让当前的循环做事,如果当前循环又开始一个新的循环那么就会重复这项工作,如果这项循环完成了,并没有开启新的循环,计算机就跳转到上一个循环上去。如果用一个轮子比喻会比较贴切一些,这个轮子既可以往前转也可以向后转。

不过,我更喜欢计算机程序的构造与解释所阐述的,程序员就像一个魔法师,他们写出符文来变出各种各样的魔法,而计算机就是一个可以实现无限梦想的魔杖。

blackerXHunter

原创作品转载请注明出处

《Linux内核分析》MOOC课程,http://mooc.istudy.163.com/course/USTC-1000029000

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。