您的位置:首页 > 其它

程序结构与进程结构

2015-07-19 18:00 232 查看
学习编程,但是对其根源从未探索,总觉得心里空荡荡的,有时候对根源大的探索是进一步的学习。

在程序写好后,我们进行编译,之后生成一个可执行文件,Linux下为ELF文件,window下会经常看到是一个后缀为.exe的文件,其实为PE文件。那这些文件中结构怎样,定义的变量还有语句是怎么在这个可执行文件中存储的?

我们编译源文件的时候,经过预编译->编译->汇编->链接。最终完成一个可执行文件的生成。

预编译大家可能都熟悉,就是将程序中的注释删掉,宏定义替换,处理条件编译,将include的文件插入到该文件的位置等等。这个是最简单的过程。这一步可利用下面的命令实现,查看预编译后的文件是什么:

gcc -E test.c -o test.i


那么编译阶段进行什么呢?这个稍微有些复杂,编译器进行词法分析->语法分析->语义分析->中间语言生成->目标代码生成与优化。经过这一步生成汇编代码文件。汇编文件可由下面命令实现:

gcc -S test.c -o test.s
or
gcc -S test.i -o test.s
生成汇编文件后,接下来就是要进行汇编,汇编器将生成的汇编语言转换成可执行的机器码,每一条汇编指令对应一条机器指令。利用下面的语句可以完成:

as test.s -o test.o
or
gcc -c test.s -o test.o
最后一步是链接,可能有些疑惑,上面通过汇编已经翻译成机器语言,是可以直接执行的,为什么还要链接呢?这是由于程序在执行的时候会用到一些库文件,这些库包括静态库与动态库文件,链接的过程就是将这些库文件与上面的汇编结果进行综合。具体命令如下:

ld *.o -o test -start-group -lgcc -lgcc_eh -lc-end-group
经过链接最终生成可执行文件test,我们通常的编译过程就一句命令解决:

gcc test.c -o test
那编译完成的文件,是怎么组成?下图显示了详细的过程:



从上面的图中可以看出,已经初始化过的全局变量与局部静态变量存储在数据区,未初始化的全局变量与局部静态变量存储在.bss区。局部用户定义的变量存储在堆栈区。然后程序语句也就是后面可执行的指令存储在代码区。然后程序在32位机器上执行的时候的映射关系如图后半部分所示。

程序的格式知道了,那么程序运行时候,此时就成为了操作系统一个进程(ps:运行的程序就叫进程),这个过程是怎么进行的呢?下面的图显示其过程:



首先可执行文件将其每个部分映射到虚拟内存空间内,32位可以有4GB空间映射,范围为(0x00000000~0xffffffff),64为可以有17179869184GB的空间可映射,范围为(0x0000000000000000~0xffffffffffffffff)。这里我们以32为系统为例,讲解系统怎么装载程序:

32为系统的可映射的虚拟空间的大小为4GB,其中操作系统内核占去了1GB的空间,剩下的为用户程序可映射的空间,大约有3GB,在windows下操作系统占用2GB的空间,剩下2GB为用户程序所占用的空间,可以通过修改操作系统盘根目录下的Boot.ini,可以将用户空间改到3GB。

装载开始的时候,先创建虚拟地址空间,然后读取可执行文件的头,建立虚拟空间与可执行文件的映射关系。比如上面图中在虚拟内存中建立的.text段,Linux中称为虚拟内存区域(VMA)。最后一步就是将CPU指令寄存器设置成可执行文件的入口。

上面的步骤完成后,只是完成了到虚拟内存空间的映射,注意这里是“虚拟”,也就是说是假的,不是真真的内存,程序的数据还没有真真地进入内存中。这里需要利用CPU的内存管理单元(MMU)进行虚拟内存到物理内存的映射。

这个过程是怎么进行的呢?

从上面图中可以看出,可执行文件的入口地址是0x08048000。CPU开始认为这个就是内存里面的真实的地址,结果发现内存里面此处的数据为空!这个时候就会产生一个页面错误消息。这时候操作系统会调用相应的页错误例程处理这个错误,然后虚拟空间与可执行文件之间的映射就起到非常重要的作用,首先找到空页面所在的虚拟内存区(VMA),计算出相应页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,建立物理页面与虚拟页的映射关系,然后再执行进程,进程就从错误的位置开始重新执行程序。

有了上面的了解,程序运行时的环境如下:



进程在操作系统的结构大致如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: