重定位概念:程序在装入内存时将逻辑地址转换为物理地址。
重定位分类—
静态重定位:在程序装入内存时就将逻辑地址转换为物理地址
优势:操作简单,不需要额外的机构或操作。
劣势:程序一旦装入后地址就不可再改变,程序也不可以再移动,不利于内存空间的有效使用。各个程序很难共享同一副本。
动态重定位:在程序运行期间,需要某一内存数据时才进行重定位。
优势:程序占用的内存空间可变,能提高内存的利用效率。比较容易实现不同程序对同一副本的共同使用。
劣势:需要额外的硬件支持,增加了成本。
链接概念:各个模块独自编写、编译,然后由链接器将各个模块链接在一起,以解析模块间的访问和对库的访问。
静态链接:在程序装入内存之前完成链接工作,静态链接库里的指令最后都被包含在最终的EXE文件。
动态链接:动态链接库文件不会被包含在EXE文件里,EXE文件动态的引用动态链接库。
细分:
装载时动态链接:提前就已经知道程序要调用哪些模块,在编译时就在相应地方存放链接这些模块的信息,运行时对应去调用。
运行时动态链接:提前无法预知,完全等运行时决定调用哪个函数。
—————————————————————————————————————————————————————————
程序和数据装入内存时需对目标程序中的地址进行修改。这种把逻辑地址转变为内存的的过程叫重定位。
对程序进行重定位的技术按重定位的时机可分为两种:静态重定位和动态重定位。
1.静态重定位
静态重定位是在目标程序装入内存时,由装入程序对目标程序中的指令和数据的地址进行修改,即把程序的逻辑地址都改成实际的地址。对每个程序来说,这种地址变换只是在装入时一次完成,在程序运行期间不再进行重定位。
优点是:无需增加硬件地址转换机构,便于实现程序的静态连接。在早期计算机系统中大多采用这种方案。
缺点是:(1)程序的存储空间只能是连续的一片区域,而且在重定位之后就不能再移动。这不利于内存空间的有效使用。(2)各个用户进程很难中的同一程序的副本。
2.动态重定位
动态重定位是在程序执行期间每次访问内存之前进行重定位。这种变换是靠硬件地址变换机构实现的。通常采用一个重定位,其中放有当前正在执行的程序在内存空间中的起始地址,而中的代码在装入过程中不发生变化。
优点是:(1)程序占用的内存空间动态可变,不必连续存放在一处。(2)比较容易实现几个进程对同一程序副本的共享使用。
缺点是:需要附加的硬件支持,增加了机器成本,而且实现存储管理的软件比较复杂。
现在一般计算机系统中都采用动态重定位方法。
链接:
一、分别编译与链接(Linking)
大多数高级语言都支持分别编译,程序员可以显式地把程序划分为独立的模块或文件,然后每个独立部分分别编译。在编译之后,由链接器把这些独立的片段(称为编译单元)“粘接到一起”。(想想这样做有什么好处?)
在C/C++中,这些独立的编译单元包括obj文件(一般的源程序编译而成)、lib文件(静态链接的函数库)、dll文件(动态链接的函数库)等。
静态链接方式:在程序执行之前完成所有的组装工作,生成一个可执行的目标文件(EXE文件)。
动态链接方式:在程序已经为了执行被装入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝。
二、静态链接库与动态链接库
先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。
静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终的EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。
采用动态链接库的优点:(1)更加节省内存;(2)DLL文件与EXE文件独立,只要输出接口不变,更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性。
三、认识动态链接库
动态链接是相对于静态链接而言的。所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。一般情况下,如果一个应用程序使用了动态链接库,Win32系统保证内存中只有DLL的一份复制品
动态链接库的两种链接方法:
(1) 装载时动态链接(Load-time Dynamic Linking):这种用法的前提是在编译之前已经明确知道要调用DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含DLL函数的代码;当程序执行时,调用函数的时候利用链接信息加载DLL函数代码并在内存中将其链接入调用程序的执行空间中(全部函数加载进内存),其主要目的是便于代码共享。(动态加载程序,处在加载阶段,主要为了共享代码,共享代码内存)
(2) 运行时动态链接(Run-time Dynamic Linking):这种方式是指在编译之前并不知道将会调用哪些DLL函数,完全是在运行过程中根据需要决定应调用哪个函数,将其加载到内存中(只加载调用的函数进内存),并标识内存地址,其他程序也可以使用该程序,并用LoadLibrary和GetProcAddress动态获得DLL函数的入口地址。(dll在内存中只存在一份,处在运行阶段)
上述的区别主要在于阶段不同,编译器是否知道进程要调用的dll函数。动态加载在编译时知道所调用的函数,而在运行态时则必须不知道。
重定位————————————————————————————————————————————————
连续分配方式,是指为一个用户程序分配一个连续的内存空间。
----- 可重定位分区分配
1、动态重定位的引入
在连续分配方式中,必须把一个系统或用户程序装入一连续的内存空间。如果在系统中只有若干个小的分区,即使它们的容量总和大于要装入的
程序,但由于这些分区不相邻接,也无法把该程序装入内存。这种不能被利用的小分区称为“零头”或“碎片”。
当内存中出现几个互不邻接的小分区,它们单独的容量不能满足作业的大小,但它们的容量总和大于作业的要求时,若想把作业装入,可采用的
一种方法是:将内存中的所有作业进行移动,使它们全部相邻接,这样,即可把原来分散的多个小分区拼接成一个大分区,这时就可把作业装入该区。
拼接(紧凑/紧缩)-- 这种通过移动内存中作业的位置,然后把原来多个分散的空闲小分区拼接成一个大分区的方法,称为“拼接”或“紧凑”。
所谓拼接是指移动内存(存储器)中所有已分配区到内存的一端,使本来分散的小空闲区连成一个大的空闲区。
---- 由于经过紧凑后的某些用户程序在内存中的位置发生了变化,此时若不对程序和数据的地址加以修改(变换),则程序必将无法执行。
为此,在每次“紧凑”后,都必须对移动了的程序或数据进行重定位。
---- 拼接时机(什么时候拼接):
第一种方案是在某个分区回收时立即进行拼接,这样在内存中总是只有一个连续的空闲区。但由于拼接很费时间,拼接频率过高会使系统开销加大。
第二种方案是当找不到足够大的空闲区且空闲区的总容量可以满足作业要求时进行拼接。拼接的频率比第一种要小得多,但空闲区的管理稍微复杂一些。
2、动态重定位的实现
---- 在动态运行时装入的方式中,作业装入内存后的所有地址都仍然是相对地址(逻辑地址),将相对地址转换为物理地址的工作,被推迟到程序指令
要真正执行时进行。为使地址的转换不会影响到指令的执行速度,必须有硬件地址变换机构的支持,即需在系统中增设一个重定位寄存器,用来存放
程序(数据)在内存中的起始地址。程序在执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。
---- 地址变换过程是在程序执行期间,随着对每条指令或数据的访问自动进行的,故称为动态重定位。
---- 当系统对内存进行了“紧凑”而使若干程序从内存的某处移至另一处时,不需对程序做任何修改,只需用该程序在内存的新起始地址,去置换原来的
起始地址即可。
3、动态重定位分区分配
---- 动态重定位分区分配算法与动态分区分配算法基本上相同,差别仅在于:在这种分配算法中,增加了紧凑功能。
---- 通常,在找不到足够大的空闲分区来满足用户需求时进行紧凑。
算法流程图如下:
4、分区的存储保护
---- 存储保护是为了防止一个作业有意或无意地破坏或其他作业。常用的存储保护方法有界限寄存器和存储保护键方法。
---- 采用界限寄存器方法实现存储保护又有两种方式:
1)上、下界寄存器方法。采用上、下界寄存器分别存放作业的结束地址和开始地址。在作业运行过程中,将每一个访问内存的地址都同这两个
寄存器的内容进行比较。在正常情况下,这个地址应大于下界寄存器,且小于上界寄存器的内容,如超出这个范围便产生保护性中断。
2)基址、限长寄存器方法。采用基址和限长寄存器分别存放作业的起始地址及作业的地址空间长度。当作业执行时,将每一个访问内存的
相对地址和这个限长寄存器比较,如果超过了限长,则发出越界中断信号,并停止作业的运行。
---- 存储保护键方法是给每个存储块分配一个单独的保护键,它相当于一把锁。存储块不同于分区,一个分区由若干个存储块组成,每个存储块
大小相同,一个分区的大小必须是存储块的整数倍。此外,进入系统的每个作业也被赋予一个保护键,它相当于一把钥匙。
当作业运行时,检查钥匙和锁是否一致,如果二者不匹配,则系统发出保护性中断信号,并停止作业的运行。
————————————————————————————————————————————
我们大家在编程过程中对“链接”这个词并不陌生,链接所解决的问题即是将我们自己写的代码和别人写的库集成在一起。链接可以分为静态链接与动态链接,下文将分别讲解这两种方式的特点与其区别。
- 静态链接
- 特点:在生成可执行文件的时候(链接阶段),把所有需要的函数的二进制代码都包含到可执行文件中去。因此,链接器需要知道参与链接的目标文件需要哪些函数,同时也要知道每个目标文件都能提供什么函数,这样链接器才能知道是不是每个目标文件所需要的函数都能正确地链接。如果某个目标文件需要的函数在参与链接的目标文件中找不到的话,链接器就报错了。目标文件中有两个重要的接口来提供这些信息:一个是符号表,另外一个是重定位表。
- 优点:在程序发布的时候就不需要的依赖库,也就是不再需要带着库一块发布,程序可以独立执行。
- 缺点:
-
- 程序体积会相对大一些。
- 如果静态库有更新的话,所有可执行文件都得重新链接才能用上新的静态库。
- 动态链接
- 特点: 在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。
- 优点: 多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝。
- 缺点: 由于是运行时加载,可能会影响程序的前期执行性能。
- 库
上面的文章多次提到库(lib)这个概念,所谓的库就是一些功能代码经过编译连接后的可执行形式。
大家在Windows平台上见到的.dll文件和linux平台下so动态库都输入库。
库也有静态lib和动态lib之分:
-
-
- 静态lib将导出声明和实现都放在lib中。编译后所有代码都嵌入到宿主程序。
- 动态lib相当于一个h文件,是对实现部分(.dll文件)的导出部分的声明。编译后只是将导出声明部分编译到宿主程序中,运行时候需要相应的dll文件支持。
-