您的位置:首页 > 理论基础

《深入理解计算机系统》读书笔记3---关于程序运行的思考

2015-08-31 16:49 316 查看
这里先申明几点:

(1)不考虑内存区里的堆这样的malloc空间,以及链接过程需要的内存空间。

(2)不考虑多线程时候在内存里怎么分配。

(3)不讨论太多的函数嵌套函数的细节。

(4)不考虑最开始的载入过程,比如双击.exe将程序加载进入内存,而双击这个命令又是怎样传递的不讨论。

前提知识:

(1)一个函数里面的程序代码以及全局变量还有static变量会分配到内存里面的--只读的代码和程序区

而局部变量会分配到内存里面的---用户栈区域

(2)CPU里面有8个通用寄存器,最后两个都是指向内存里面的用户栈的,分别是%ebp和%esp

程序计数器PC,则是寄存器%eip,指向当前运行的语句(内存中的只读代码和程序区)。

注:后面用程序区简称只读代码和程序区,用栈区简称用户栈区域。

再说一下%ebp和%esp

%ebp是帧指针,永远指向最顶层栈帧的开头---帧。

%esp是栈指针,永远指向栈顶。

每当发生函数调用时,就会新生成一个栈帧,最顶端的栈帧以两个指针界定,帧指针%ebp,栈指针%esp。

%ebp指向栈帧开头,那么栈帧开头那个内存空间里面又放着什么呢?

假设函数A调用函数B,在调用之前,%ebp指向A的栈帧的开头,这时调用B,那么就为B新生成一个栈帧,这时%ebp就会指向B的栈帧的开头,

但是如果B执行完,我们是要回到A的哦,而且考虑到%ebp的作用(后面会讲到),这时%ebp应该恢复为指向A的栈帧的开头啊,

所以我们就用B的栈帧开头里面的空间来存A的栈帧开头的地址。

简单地说就是,%ebp存的是地址,地址指向栈帧开头---帧,所以叫帧指针。

而帧也是一个指针,存的是调用自己的函数的栈帧的帧。

那么整个流程可以描述为这样,这是我读汇编代码总结的。

我写了一段代码,C和汇编如下:







首先运行某二进制文件的程序,那么

(1)将该存在于硬盘上的二进制代码加载到内存中去。

程序代码,比如某个.c文件里面的每个定义的函数,以及一些全局变量,都载入程序区保存。

(2)最开始从main开始,将它的帧的地址载入CPU存入%ebp帧指针。

依次按照局部变量出现顺序以及类型,分配适当的栈空间,这样栈顶不断下移,每次下移就将此时栈顶的地址载入CPU存入%esp栈顶指针。

(3)将main函数的第一条语句(即movl $1, 28(%esp))地址,载入CPU中的PC,即%eip,程序开始执行。

执行完一条之后如果没有跳转命令,那么是PC自动下移,执行main中下一条语句。

如果下一条语句是对于某一个函数的调用,那么进入(4)

(4)非常类似于(2)只不过多了一个保存返回地址的过程。

将函数调用完成后的下一条指令(这里是红色部分)的地址入栈,表示返回地址,即程序从调用的函数返回后又从哪里开始执行。

入栈返回地址之后,上一个栈帧结束。

接着就入栈下一个栈帧的开头即帧了,入栈的内容就是上一个栈帧的%ebp,

然后又将%ebp赋值为当前帧的地址。

然后按照按照局部变量出现顺序以及类型,分配适当的栈空间,这样栈顶不断下移,每次下移就将此时栈顶的地址载入CPU存入%esp栈顶指针。

(5)将f()的第一条语句地址,载入CPU的PC,f()开始执行。

一直到f()执行完毕。

然后就是图中的leave指令,它完成的内容就是,先将栈顶指针,指向帧,然后将帧弹出,这样%ebp恢复到main的帧,而栈顶也到了返回地址处。

然后是ret语句对应的操作,也就是根据(4)中的返回地址,载入PC,继续执行main函数中调用f()后的下一条语句。

其中(2)、(4)以及(5)中的leave都是关于栈的操作,都是函数调用时的上下文操作。

(3)以及(5)中的ret部分都是程序真正送到CPU里面执行的语句。

注意:

(1)在上述过程中,我没有严格区分函数中的形式参数变量,还有在内部定义的局部变量。

形式参数变量必须每个都在栈区分配空间。

内部定义的局部变量有的直接在通用寄存器使用即可,如果太多也需要在栈中申请空间来保存。

而CPU在访问栈中的那些变量时是使用基址+偏移地址的方法来访问的。

基址就是帧的地址,即表示该函数调用的栈帧的最开始那个地址,也就是%ebp的内容。

而不同的偏移地址就代表了不同的变量。

(2)C语言中的函数返回return语句,并不对应于刚刚说的,leave和ret指令。

return是函数返回值,一般是将返回的值送入到通用寄存器%eax中,对应于图中的红框部分。

l

当然还有几个问题没有搞明白:

(1)那是谁又去调用main的呢?不同的main在栈区又是怎么分配的呢?

(2)被调用函数,对于调用它的函数里面的局部变量是怎么个访问方法啊?

(3)对于全局变量以及函数名的地址,在生成机器代码的时候根本不知道会被载入程序区的哪个位置,那么是怎么来表示不同的全局变量以及函数的起始地址的呢?

猜想也是用的是相对地址吧。但是并没有一个地方来存放那个基地址啊?

(4)怎么实现从main开始的,是不是汇编-->机器码的时候会把main放在最开始还是怎样?

(5)CPU按照PC里面的地址,指向内存中的程序代码,而程序指令由1-15个字节不等,那么是怎么区分不同的指令呢?也就是说读的时候怎么知道读多长才将目前这个完整的指令读进来了呢?见P109页那个第6行的指令

回答:

(1)是一个.exe就启动一个进程,一个进程逻辑上独占虚拟存储器。至于不同进程之间,后面的问题了。

(2)没有办法直接使用,必须传递指针或者引用才行。因为另一个函数里面的变量,对于被调用的函数来说是undeclear的。

(3)由P12可知,逻辑上来说,是1个进程单独享有整个虚拟地址空间,而程序区是从0x08048000开始的。

先放定义的函数的代码,然后放全局变量代码,每个全局变量以及每个函数就会有一个自己对应的地址。

而这个地址怎么再变成真正的物理地址,以及进程间的内存使用,就涉及到后面的虚拟地址的内容以及进程之间的互操作了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: