程序的装载和链接

      今天跑去计算机学院上OS课的时候了解了下程序的装载和链接,虽然很理论,但还是决定记下来,再加些自己的理解。
回来第一件想弄清楚的就是逻辑地址,虚拟地址和物理地址。我去网上查了一下,在http://bbs.chinaunix.net/thread-2083672-1-1.html看到了解释,我觉得解释的很好,就copy下了((*^__^*) )
---------------------------------------------------------

物理地址(physical address)


用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。

 

虚拟内存(virtual memory)

这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;
之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。打住了,这个问题再说下去,就收不住了。

 

逻辑地址(logical address)


Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。
——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些。

线性地址(linear address)或也叫虚拟地址(virtual address)
跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。

CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。
--------------------------------------------------------

下面就是就说重点了(*^__^*)

程序的装入


       最早的时候是没有物理地址和逻辑地址之分的,意思就是只有物理地址,程序的操作都是直接对物理地址进行操作的,也叫绝对地址,所以程序的载入方式叫做绝对载入方式,这种时候程序都是在单道环境下跑的;后来出现了多道程序,怎么办呢?假设有A,B,C三个模块都要装载,A的0号地址对应物理地址的0号,那么B和C的0号地址怎么办呢(因为程序里面给出的都是绝对地址)??于是就出现了可重定位的装入方式可重定位:把装入时对目标程序中指令和数据修改过程称为可重定位。其实也就是一个逻辑地址转换成物理地址的过程,这样A的地址还是从0,A的末地址可能是100,B的起始地址在加载的时候就变成了101,然后里面对特定地址的操作都改了,比如在原来08处,指令是movax,1000,那对应过去就是在0x109处执行该指令。这样虽然是解决了程序的逻辑地址转化成物理地址,但是问题又出现了,就是在程序运行的时候我们要修改指令和数据怎么办呢??上面的加载方式一旦到了内存里面就是变成了“绑定”的程序,移动都不能移动。这就又出现了动态运行时候加载程序方式:动态运行时的装入程序把程序装载到内存里后,不直接修改装入模块中的地址,而是真正到执行的时候才进行地址的转化。因此在执行到要修改程序指令或者数据之前都是相对地址,不会影响程序的移动。

 

 

程序的链接


静态链接方式:比如我们需要CALL函数A,B,C长度为L,M,N,那么链接的时候在内存里就是安装地址(假设起始地址是0)0,L,L+M这样整合起来形成可执行文件,从这以后这个可执行文件也变成了“绑定文件”,你就不能拆开它了,要注意的是刚才的装入内存是为了生成!比如程序中的有些库函数就是在链接的时候的做静态链接。这样对程序的提升性实在是太差了,一个模块要是想升级下,整个可执行文件都得大动干戈,而且还很浪费磁盘空间。总之就是用静态链接的形式生成可执行文件的话,在载入内存之前是把所有要用到的东西都整合到一起,等到一切准备就绪再去执行!

装入时的动态连接:得到用户所编译后的目标模块后,在装入内存的时候是边装入边链接的,比如在执行A模块中需要B模块了,那就去动态地寻找B模块,与前面一股脑儿都整合到一起可好多了,起码从程序的更新升级变好了很多,我想程序的C模块升级下,不需要对整个可执行文件进行调整,只需要修改C模块,因为这时候各个模块都是相互独立的。在动态链接的时候内存中需要C模块的时候自动载入,同样不需要的时候想丢就丢。但就是可能在实际程序运行的时候有些模块是可能会运行,所以也就有很多无谓的装入。

运行时动态链接:这个才是现在的主流,很多.dll文件都是这样,在程序运行的时候需要了什么模块就去找什么模块,叫OS把它装入内存,这个与与上面的装入时的动态链接可有区别,上面的那个是在装入程序的时候去加载所需模块,但实际上程序运行的时候所需的模块比装入的模块少多了,因为上面装入的时候是可能需要的模块。但是采用运行时动态加载就指定了所需什么模块就加载什么模块。节省了装入的时间,也节省了磁盘空间。

标签:OS