操作系统是如何实现动态链接库被多个进程共享的

时间:2022-11-08 15:48:25
第一个程序A,链接了共享库比如 la.so,这时A的可执行文件和共享库 la.so都会被读进内存;如果第二个程序B也链接了 la.so,这时B的可执行文件被读入内存,系统是怎么知道 la.so已经在内存中了呢(是否在内核或其他地方具有某种记录)?
另外系统共享库的数据部分由进程独享,而代码部分可以多个进程共享,理论上这时只要把进程B的虚拟地址空间映射到 la.so的指令部分存放的物理内存就可以了,系统是怎么记录 la.so 的物理内存地址呢? 这个过程是由加载器还是动态链接器或者其他的成分来完成呢?

18 个解决方案

#1


《深入解析Windows操作系统-Windows Internals》?

#2


对于我们的程序而言,有一个程序 A和另外一个程序 B 的区别。
而对于操作系统而言,只有一个运行的程序,就是他自己,包含程序 A 和程序 B。

#3


引用 2 楼 Saleayas 的回复:
对于我们的程序而言,有一个程序 A和另外一个程序 B 的区别。
而对于操作系统而言,只有一个运行的程序,就是他自己,包含程序 A 和程序 B。

我觉得这个说法不合适吧,操作系统不存在所谓“一个运行的程序”吧,操作系统只不过是负责组织各个部分的代码和数据,将指令和数据放在合适的位置,并且设置CPU的寄存器,让CPU获取指令并且执行而已。但是对于共享库而言,关于共享库的很多书和资料,都不厌其烦地提及GOT和PLT,但是对于共享库如何实现共享的具体机制,没有太多的说明(包括比较有名的《程序员的自我修养》),我觉得这可能与操作系统内核相关,但是关于具体的操作系统内核的书籍(例如《深入理解linux内核》之类,对于这个问题也没有提及。感觉这方面的资料很少,就算是想粗略了解都找不到相关信息。

#4


引用 1 楼 zhao4zhong1 的回复:
《深入解析Windows操作系统-Windows Internals》?

赵四老师,向您请教一个问题。我看《程序员的自我修养》中关于动态链接的章节提到,对于定义在共享模块的全局变量(例如gloabl),如果被主模块(即可执行程序)引用的话,“程序的主模块的代码不是地址无关代码”,“它引用这个全局变量的方式跟普通的数据访问的方式一样,编译器会产生这样的代码”,movl $0x01 xxxxxxxx。xxxxxxxx就是全局变量的地址。“由于可执行文件在运行时并不进行代码重定位,所以变量的地址必须在链接过程中确定下来”,因此,链接器会在.bss段创建一个全局变量的副本。解决办法是,共享库内部定义的变量,当做主模块定义的变量处理,即在GOT中间接跳转,将GOT中的全局变量的地址指向主模块。
这段话有些不理解。什么叫做“可执行文件在运行时并不进行代码重定位”?如果在主模块中调用共享库的函数,可执行文件不进行重定位,怎么知道函数的地址呢?我认为可执行文件也是通过GOT在加载时重定位调用外部模块的函数,而且这种做法对于上述问题也是可行的。不知道您怎么理解这段话?

#5


操作系统是如何实现动态链接库被多个进程共享的理解讨论之前请先学会如何 观察

参考Linux或Windows下加载可执行文件相关代码片断。

推荐使用WinHex软件查看硬盘或文件或内存中的原始字节内容。

VMMap 是进程虚拟和物理内存分析实用工具。 http://technet.microsoft.com/zh-cn/sysinternals/dd535533

LInux下使用gdb,Windows下使用WinDbg

不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!

有人说一套做一套,你相信他说的还是相信他做的?
其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗?

#6


《Windows PE权威指南》

#7


dll ,so 和可执行程序一样拥有重定位表
对于 dll,so 中通用的程序,实现上,操作系统通常安排在 
不同可执行程序的,同一内存空间,
如果某个可执行程序,和这个dll,so 的地址安排,有地址冲突,则对这个可执行程序(的某一个执行过程)进行地址重定位
使得 此进程的 dll,so地址空间和 其他进程的 地址空间有所不同。虽然地址空间不同,
但是操作系统本身加载 这个dll,so 的位置(系统地址)始终不变,
只是对此进程的地址映射(用户地址)有所不同。

当然,如果 系统加载的 dll,so 过多,操作系统也可以采取别的策略。
为每个进程单独加载 这个 dll,so 不过此时的  dll,so 就不再共享了,显然要更加浪费内存了.

#8


现代操作系统,用户进程在地址空间上,是互相隔离的。
每个用户进程,都有单独的地址空间,所以同一个 dll,so 可以加载在, 每个进程的同一位置的地址空间
如果因为种种原因不能实现加载在同一地址空间,
则操作系统会对某些进程,重定位此dll,so的地址空间。
所以 dll 和 so 只需要加载一次,
只是对每个进程,视情况而定,能统一则不必重定位(照抄一下重定位表),
否则就重定位一下(建立一个新的重定位表)。

#9


系统会有一个单独的加载器,将动态库加载到内存中,当某个可执行文件运行到需要动态库的时候,再将动态库映射到进程空间内

#10


引用 楼主 shixiazuike1991 的回复:
第一个程序A,链接了共享库比如 la.so,这时A的可执行文件和共享库 la.so都会被读进内存;如果第二个程序B也链接了 la.so,这时B的可执行文件被读入内存,系统是怎么知道 la.so已经在内存中了呢(是否在内核或其他地方具有某种记录)?
另外系统共享库的数据部分由进程独享,而代码部分可以多个进程共享,理论上这时只要把进程B的虚拟地址空间映射到 la.so的指令部分存放的物理内存就可以了,系统是怎么记录 la.so 的物理内存地址呢? 这个过程是由加载器还是动态链接器或者其他的成分来完成呢?

操作系统对物理内存的管理当然是在内核当中了,系统会维护每一个物理内存页面(通常大小为 4K)分配到哪里在做什么的。对于文件映射过的内存,内核当然可以知道这个文件有没有在物理内存之中。通过查找内核的记录也可以很容易确定 la.so 的物理地址呢。
虚拟地址到物理地址的映射只能是内核才能完成。所以动态库加载时的共享主要是靠内核。加载器、连接器这些只能告诉内核这是一个共享库,并不能负责完成内存的共享功能。

#11


引用 3 楼 shixiazuike1991 的回复:
《程序员的自我修养》《深入理解linux内核》

推荐 《Linux内核源代码情景分析》  尤其关注一下 CPU 硬件提供的内存分页机制,操作系统也只是把相关的数据结构提供给 CPU,实现过程还是靠 CPU 来完成的。

#12


引用 4 楼 shixiazuike1991 的回复:
Quote: 引用 1 楼 zhao4zhong1 的回复:

《深入解析Windows操作系统-Windows Internals》?

赵四老师,向您请教一个问题。我看《程序员的自我修养》中关于动态链接的章节提到,对于定义在共享模块的全局变量(例如gloabl),如果被主模块(即可执行程序)引用的话,“程序的主模块的代码不是地址无关代码”,“它引用这个全局变量的方式跟普通的数据访问的方式一样,编译器会产生这样的代码”,movl $0x01 xxxxxxxx。xxxxxxxx就是全局变量的地址。“由于可执行文件在运行时并不进行代码重定位,所以变量的地址必须在链接过程中确定下来”,因此,链接器会在.bss段创建一个全局变量的副本。解决办法是,共享库内部定义的变量,当做主模块定义的变量处理,即在GOT中间接跳转,将GOT中的全局变量的地址指向主模块。
这段话有些不理解。什么叫做“可执行文件在运行时并不进行代码重定位”?如果在主模块中调用共享库的函数,可执行文件不进行重定位,怎么知道函数的地址呢?我认为可执行文件也是通过GOT在加载时重定位调用外部模块的函数,而且这种做法对于上述问题也是可行的。不知道您怎么理解这段话?

重定位是由于程序运行时被加载到的地址和编译链接时指定的地址不同时才可能会发生的,由于可执行程序通常是第一个被加载的模块,加载的时候整个虚拟内存空间几乎都是空的,所以一定可以把它加载到链接的时候指定的那个地址上,因此也就不需要重定位,既然不需要重定位就没有必要把它编译成位置无关的代码了。
至于说程序中调用其它动态模块的函数,那是另外一个故事了,在 windows 中这个是通过导入表来完成的,在 linux 中有动态链接机制,他们都是在加载的时候通过符号查找来确定地址的,并不是通过重定位...         重定位只是解决模块内部引用自己的全局变量导致的问题。

#13


操作系统是如何实现动态链接库被多个进程共享的

#14


引用 7 楼 lm_whales 的回复:
dll ,so 和可执行程序一样拥有重定位表
对于 dll,so 中通用的程序,实现上,操作系统通常安排在 
不同可执行程序的,同一内存空间,
如果某个可执行程序,和这个dll,so 的地址安排,有地址冲突,则对这个可执行程序(的某一个执行过程)进行地址重定位
使得 此进程的 dll,so地址空间和 其他进程的 地址空间有所不同。虽然地址空间不同,
但是操作系统本身加载 这个dll,so 的位置(系统地址)始终不变,
只是对此进程的地址映射(用户地址)有所不同。

当然,如果 系统加载的 dll,so 过多,操作系统也可以采取别的策略。
为每个进程单独加载 这个 dll,so 不过此时的  dll,so 就不再共享了,显然要更加浪费内存了.

并不是加载地址不同就叫重定位了。 是因为加载地址不同,导致模块内代码访问全局变量时的地址不正确,需要对这部分代码进行修改,这个修改过程才叫重定位。
可执行文件通常不需要重定位表,不过新的 windows 为了安全,使用了一个 ASLR 技术让每次加载的基地址都不同,要使用这个功能就需要重定位了。
so 文件如果编译的时候加位置无关选项,也可以不使用重定位表,不进行重定位。

#15


实际上重定位只是对绝对地址来说的,如果某个指令采用相对地址,根本就不需要重定位
因为加载位置不同,所以绝对地址需要重定位
绝对地址,
通常是函数,全局变量。(提供给外部使用的接口函数,一般都要重定位,其它函数看调用指令,有的需要,有的不需要),
内部采用绝对地址的符号,也可能需要重定位。

引用 14 楼 adlay 的回复:
Quote: 引用 7 楼 lm_whales 的回复:

并不是加载地址不同就叫重定位了。 是因为加载地址不同,导致模块内代码访问全局变量时的地址不正确,需要对这部分代码进行修改,这个修改过程才叫重定位。
可执行文件通常不需要重定位表,不过新的 windows 为了安全,使用了一个 ASLR 技术让每次加载的基地址都不同,要使用这个功能就需要重定位了。
so 文件如果编译的时候加位置无关选项,也可以不使用重定位表,不进行重定位。


所以重定位功能可以解决地址冲突问题,而固定重定位符号的位置,
则可以统一 dll, so 在不同进程的地址。
所以不管重定位这个功能有没有使用,这都和重定位,重定位表相关。

其中 DLL中的全局变量,除非设置在共享内存段,对于每个进程都是独立的。
因为Windows 的dll 中
全局变量,在没有设置在 共享内存段中的情况下,一定不会各个进程共享。

#16


引用 15 楼 lm_whales 的回复:
实际上重定位只是对绝对地址来说的,如果某个指令采用相对地址,根本就不需要重定位
因为加载位置不同,所以绝对地址需要重定位
绝对地址,
通常是函数,全局变量。(提供给外部使用的接口函数,一般都要重定位,其它函数看调用指令,有的需要,有的不需要),
内部采用绝对地址的符号,也可能需要重定位。

所以重定位功能可以解决地址冲突问题,而固定重定位符号的位置,
则可以统一 dll, so 在不同进程的地址。
所以不管重定位这个功能有没有使用,这都和重定位,重定位表相关。

其中 DLL中的全局变量,除非设置在共享内存段,对于每个进程都是独立的。
因为Windows 的dll 中
全局变量,在没有设置在 共享内存段中的情况下,一定不会各个进程共享。


实际上重定位只是对绝对地址来说的,如果某个指令采用相对地址,根本就不需要重定位
因为加载位置不同,所以绝对地址需要重定位
----------------------------------------------
正确


绝对地址,
通常是函数,全局变量。
------------------------------------------------------
一般来说编译器产生的模块内部函数调用,跳转都是使用的偏移,也就是相对地址
全局变量会用绝对地址来访问, 因为在 32 位 x86 的 CPU 指令里面还没有通过相对地址来访问数据的方式.  gcc 通过一个编译选项可以支持: 编译的时候计算好访问全局变量的指令的地址和说访问变量地址之间的差值,运行的时候先调用一个函数来获得当前指令的地址,然后加上编译时计算的值来获得全局变量的地址进行访问,当然这样效率会低很多。 在 x64 的 CPU 指令里面已经增加了一种新的寻址方式,叫 RIP 相对寻址 来解决类似的问题。


(提供给外部使用的接口函数,一般都要重定位,其它函数看调用指令,有的需要,有的不需要),
内部采用绝对地址的符号,也可能需要重定位。

所以重定位功能可以解决地址冲突问题,而固定重定位符号的位置,
则可以统一 dll, so 在不同进程的地址。
所以不管重定位这个功能有没有使用,这都和重定位,重定位表相关。

其中 DLL中的全局变量,除非设置在共享内存段,对于每个进程都是独立的。
因为Windows 的dll 中
全局变量,在没有设置在 共享内存段中的情况下,一定不会各个进程共享。
----------------------------------------------------------------------------------------------------------------------------
不管是数据段还是代码段,不管设置了共享没有,DLL 一开始在物理内存中都是共享的。
对于数据段,在你第一次写一个全局变量的时候,操作系统才会判断,如果这个段是共享的,直接写,写了后所有进程中看到这个变量的值都改变了。如果不是共享的,在写入的时候操作系统才会把这一个内存页拷贝一份,然后修改拷贝的那一份物理内存的内容,并修改进程的虚拟内存映射关系,让它映射到拷贝的这块新物理内存上。这就是所谓的 Copy-On-Write 技术。
而且注意,修改变量时拷贝并不是拷贝整个数据段,而是以内存页为单位的,大小通常为 4K。

代码段的处理方式没有什么不同,如果你进行了写入(下钩子修改代码的),操作系统也会在写入的时候拷贝一份出来。但是代码段的修改毕竟比数据段的修改少得多,所以代码段获得共享的机会比数据段高。但是,如果对代码段进行重定位,那就意味着修改代码段,也就意味着操作系统会拷贝,也就没有共享了。

#17


再说明一下,操作系统管理文件到内存的映射,并不是只有 so 或 dll 这种共享库才会共享物理内存的。
可执行文件也一样,如果用同一个可执行文件起了多个进程,这几个进程就可以共享相同的部分,像代码段这些。
so,dll 叫共享库,可以在不同执行文件的进程*享,并不是因为他们什么特殊的地方,操作系统都是统一对待的。只是操作系统映射的时候以文件为单位而已,把 不同执行文件 中相同的部分提取出来,做成一个单独的文件他们就获得了共享的机会。

#18


该回复于2015-12-28 12:44:27被管理员删除

#1


《深入解析Windows操作系统-Windows Internals》?

#2


对于我们的程序而言,有一个程序 A和另外一个程序 B 的区别。
而对于操作系统而言,只有一个运行的程序,就是他自己,包含程序 A 和程序 B。

#3


引用 2 楼 Saleayas 的回复:
对于我们的程序而言,有一个程序 A和另外一个程序 B 的区别。
而对于操作系统而言,只有一个运行的程序,就是他自己,包含程序 A 和程序 B。

我觉得这个说法不合适吧,操作系统不存在所谓“一个运行的程序”吧,操作系统只不过是负责组织各个部分的代码和数据,将指令和数据放在合适的位置,并且设置CPU的寄存器,让CPU获取指令并且执行而已。但是对于共享库而言,关于共享库的很多书和资料,都不厌其烦地提及GOT和PLT,但是对于共享库如何实现共享的具体机制,没有太多的说明(包括比较有名的《程序员的自我修养》),我觉得这可能与操作系统内核相关,但是关于具体的操作系统内核的书籍(例如《深入理解linux内核》之类,对于这个问题也没有提及。感觉这方面的资料很少,就算是想粗略了解都找不到相关信息。

#4


引用 1 楼 zhao4zhong1 的回复:
《深入解析Windows操作系统-Windows Internals》?

赵四老师,向您请教一个问题。我看《程序员的自我修养》中关于动态链接的章节提到,对于定义在共享模块的全局变量(例如gloabl),如果被主模块(即可执行程序)引用的话,“程序的主模块的代码不是地址无关代码”,“它引用这个全局变量的方式跟普通的数据访问的方式一样,编译器会产生这样的代码”,movl $0x01 xxxxxxxx。xxxxxxxx就是全局变量的地址。“由于可执行文件在运行时并不进行代码重定位,所以变量的地址必须在链接过程中确定下来”,因此,链接器会在.bss段创建一个全局变量的副本。解决办法是,共享库内部定义的变量,当做主模块定义的变量处理,即在GOT中间接跳转,将GOT中的全局变量的地址指向主模块。
这段话有些不理解。什么叫做“可执行文件在运行时并不进行代码重定位”?如果在主模块中调用共享库的函数,可执行文件不进行重定位,怎么知道函数的地址呢?我认为可执行文件也是通过GOT在加载时重定位调用外部模块的函数,而且这种做法对于上述问题也是可行的。不知道您怎么理解这段话?

#5


操作系统是如何实现动态链接库被多个进程共享的理解讨论之前请先学会如何 观察

参考Linux或Windows下加载可执行文件相关代码片断。

推荐使用WinHex软件查看硬盘或文件或内存中的原始字节内容。

VMMap 是进程虚拟和物理内存分析实用工具。 http://technet.microsoft.com/zh-cn/sysinternals/dd535533

LInux下使用gdb,Windows下使用WinDbg

不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!

有人说一套做一套,你相信他说的还是相信他做的?
其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗?

#6


《Windows PE权威指南》

#7


dll ,so 和可执行程序一样拥有重定位表
对于 dll,so 中通用的程序,实现上,操作系统通常安排在 
不同可执行程序的,同一内存空间,
如果某个可执行程序,和这个dll,so 的地址安排,有地址冲突,则对这个可执行程序(的某一个执行过程)进行地址重定位
使得 此进程的 dll,so地址空间和 其他进程的 地址空间有所不同。虽然地址空间不同,
但是操作系统本身加载 这个dll,so 的位置(系统地址)始终不变,
只是对此进程的地址映射(用户地址)有所不同。

当然,如果 系统加载的 dll,so 过多,操作系统也可以采取别的策略。
为每个进程单独加载 这个 dll,so 不过此时的  dll,so 就不再共享了,显然要更加浪费内存了.

#8


现代操作系统,用户进程在地址空间上,是互相隔离的。
每个用户进程,都有单独的地址空间,所以同一个 dll,so 可以加载在, 每个进程的同一位置的地址空间
如果因为种种原因不能实现加载在同一地址空间,
则操作系统会对某些进程,重定位此dll,so的地址空间。
所以 dll 和 so 只需要加载一次,
只是对每个进程,视情况而定,能统一则不必重定位(照抄一下重定位表),
否则就重定位一下(建立一个新的重定位表)。

#9


系统会有一个单独的加载器,将动态库加载到内存中,当某个可执行文件运行到需要动态库的时候,再将动态库映射到进程空间内

#10


引用 楼主 shixiazuike1991 的回复:
第一个程序A,链接了共享库比如 la.so,这时A的可执行文件和共享库 la.so都会被读进内存;如果第二个程序B也链接了 la.so,这时B的可执行文件被读入内存,系统是怎么知道 la.so已经在内存中了呢(是否在内核或其他地方具有某种记录)?
另外系统共享库的数据部分由进程独享,而代码部分可以多个进程共享,理论上这时只要把进程B的虚拟地址空间映射到 la.so的指令部分存放的物理内存就可以了,系统是怎么记录 la.so 的物理内存地址呢? 这个过程是由加载器还是动态链接器或者其他的成分来完成呢?

操作系统对物理内存的管理当然是在内核当中了,系统会维护每一个物理内存页面(通常大小为 4K)分配到哪里在做什么的。对于文件映射过的内存,内核当然可以知道这个文件有没有在物理内存之中。通过查找内核的记录也可以很容易确定 la.so 的物理地址呢。
虚拟地址到物理地址的映射只能是内核才能完成。所以动态库加载时的共享主要是靠内核。加载器、连接器这些只能告诉内核这是一个共享库,并不能负责完成内存的共享功能。

#11


引用 3 楼 shixiazuike1991 的回复:
《程序员的自我修养》《深入理解linux内核》

推荐 《Linux内核源代码情景分析》  尤其关注一下 CPU 硬件提供的内存分页机制,操作系统也只是把相关的数据结构提供给 CPU,实现过程还是靠 CPU 来完成的。

#12


引用 4 楼 shixiazuike1991 的回复:
Quote: 引用 1 楼 zhao4zhong1 的回复:

《深入解析Windows操作系统-Windows Internals》?

赵四老师,向您请教一个问题。我看《程序员的自我修养》中关于动态链接的章节提到,对于定义在共享模块的全局变量(例如gloabl),如果被主模块(即可执行程序)引用的话,“程序的主模块的代码不是地址无关代码”,“它引用这个全局变量的方式跟普通的数据访问的方式一样,编译器会产生这样的代码”,movl $0x01 xxxxxxxx。xxxxxxxx就是全局变量的地址。“由于可执行文件在运行时并不进行代码重定位,所以变量的地址必须在链接过程中确定下来”,因此,链接器会在.bss段创建一个全局变量的副本。解决办法是,共享库内部定义的变量,当做主模块定义的变量处理,即在GOT中间接跳转,将GOT中的全局变量的地址指向主模块。
这段话有些不理解。什么叫做“可执行文件在运行时并不进行代码重定位”?如果在主模块中调用共享库的函数,可执行文件不进行重定位,怎么知道函数的地址呢?我认为可执行文件也是通过GOT在加载时重定位调用外部模块的函数,而且这种做法对于上述问题也是可行的。不知道您怎么理解这段话?

重定位是由于程序运行时被加载到的地址和编译链接时指定的地址不同时才可能会发生的,由于可执行程序通常是第一个被加载的模块,加载的时候整个虚拟内存空间几乎都是空的,所以一定可以把它加载到链接的时候指定的那个地址上,因此也就不需要重定位,既然不需要重定位就没有必要把它编译成位置无关的代码了。
至于说程序中调用其它动态模块的函数,那是另外一个故事了,在 windows 中这个是通过导入表来完成的,在 linux 中有动态链接机制,他们都是在加载的时候通过符号查找来确定地址的,并不是通过重定位...         重定位只是解决模块内部引用自己的全局变量导致的问题。

#13


操作系统是如何实现动态链接库被多个进程共享的

#14


引用 7 楼 lm_whales 的回复:
dll ,so 和可执行程序一样拥有重定位表
对于 dll,so 中通用的程序,实现上,操作系统通常安排在 
不同可执行程序的,同一内存空间,
如果某个可执行程序,和这个dll,so 的地址安排,有地址冲突,则对这个可执行程序(的某一个执行过程)进行地址重定位
使得 此进程的 dll,so地址空间和 其他进程的 地址空间有所不同。虽然地址空间不同,
但是操作系统本身加载 这个dll,so 的位置(系统地址)始终不变,
只是对此进程的地址映射(用户地址)有所不同。

当然,如果 系统加载的 dll,so 过多,操作系统也可以采取别的策略。
为每个进程单独加载 这个 dll,so 不过此时的  dll,so 就不再共享了,显然要更加浪费内存了.

并不是加载地址不同就叫重定位了。 是因为加载地址不同,导致模块内代码访问全局变量时的地址不正确,需要对这部分代码进行修改,这个修改过程才叫重定位。
可执行文件通常不需要重定位表,不过新的 windows 为了安全,使用了一个 ASLR 技术让每次加载的基地址都不同,要使用这个功能就需要重定位了。
so 文件如果编译的时候加位置无关选项,也可以不使用重定位表,不进行重定位。

#15


实际上重定位只是对绝对地址来说的,如果某个指令采用相对地址,根本就不需要重定位
因为加载位置不同,所以绝对地址需要重定位
绝对地址,
通常是函数,全局变量。(提供给外部使用的接口函数,一般都要重定位,其它函数看调用指令,有的需要,有的不需要),
内部采用绝对地址的符号,也可能需要重定位。

引用 14 楼 adlay 的回复:
Quote: 引用 7 楼 lm_whales 的回复:

并不是加载地址不同就叫重定位了。 是因为加载地址不同,导致模块内代码访问全局变量时的地址不正确,需要对这部分代码进行修改,这个修改过程才叫重定位。
可执行文件通常不需要重定位表,不过新的 windows 为了安全,使用了一个 ASLR 技术让每次加载的基地址都不同,要使用这个功能就需要重定位了。
so 文件如果编译的时候加位置无关选项,也可以不使用重定位表,不进行重定位。


所以重定位功能可以解决地址冲突问题,而固定重定位符号的位置,
则可以统一 dll, so 在不同进程的地址。
所以不管重定位这个功能有没有使用,这都和重定位,重定位表相关。

其中 DLL中的全局变量,除非设置在共享内存段,对于每个进程都是独立的。
因为Windows 的dll 中
全局变量,在没有设置在 共享内存段中的情况下,一定不会各个进程共享。

#16


引用 15 楼 lm_whales 的回复:
实际上重定位只是对绝对地址来说的,如果某个指令采用相对地址,根本就不需要重定位
因为加载位置不同,所以绝对地址需要重定位
绝对地址,
通常是函数,全局变量。(提供给外部使用的接口函数,一般都要重定位,其它函数看调用指令,有的需要,有的不需要),
内部采用绝对地址的符号,也可能需要重定位。

所以重定位功能可以解决地址冲突问题,而固定重定位符号的位置,
则可以统一 dll, so 在不同进程的地址。
所以不管重定位这个功能有没有使用,这都和重定位,重定位表相关。

其中 DLL中的全局变量,除非设置在共享内存段,对于每个进程都是独立的。
因为Windows 的dll 中
全局变量,在没有设置在 共享内存段中的情况下,一定不会各个进程共享。


实际上重定位只是对绝对地址来说的,如果某个指令采用相对地址,根本就不需要重定位
因为加载位置不同,所以绝对地址需要重定位
----------------------------------------------
正确


绝对地址,
通常是函数,全局变量。
------------------------------------------------------
一般来说编译器产生的模块内部函数调用,跳转都是使用的偏移,也就是相对地址
全局变量会用绝对地址来访问, 因为在 32 位 x86 的 CPU 指令里面还没有通过相对地址来访问数据的方式.  gcc 通过一个编译选项可以支持: 编译的时候计算好访问全局变量的指令的地址和说访问变量地址之间的差值,运行的时候先调用一个函数来获得当前指令的地址,然后加上编译时计算的值来获得全局变量的地址进行访问,当然这样效率会低很多。 在 x64 的 CPU 指令里面已经增加了一种新的寻址方式,叫 RIP 相对寻址 来解决类似的问题。


(提供给外部使用的接口函数,一般都要重定位,其它函数看调用指令,有的需要,有的不需要),
内部采用绝对地址的符号,也可能需要重定位。

所以重定位功能可以解决地址冲突问题,而固定重定位符号的位置,
则可以统一 dll, so 在不同进程的地址。
所以不管重定位这个功能有没有使用,这都和重定位,重定位表相关。

其中 DLL中的全局变量,除非设置在共享内存段,对于每个进程都是独立的。
因为Windows 的dll 中
全局变量,在没有设置在 共享内存段中的情况下,一定不会各个进程共享。
----------------------------------------------------------------------------------------------------------------------------
不管是数据段还是代码段,不管设置了共享没有,DLL 一开始在物理内存中都是共享的。
对于数据段,在你第一次写一个全局变量的时候,操作系统才会判断,如果这个段是共享的,直接写,写了后所有进程中看到这个变量的值都改变了。如果不是共享的,在写入的时候操作系统才会把这一个内存页拷贝一份,然后修改拷贝的那一份物理内存的内容,并修改进程的虚拟内存映射关系,让它映射到拷贝的这块新物理内存上。这就是所谓的 Copy-On-Write 技术。
而且注意,修改变量时拷贝并不是拷贝整个数据段,而是以内存页为单位的,大小通常为 4K。

代码段的处理方式没有什么不同,如果你进行了写入(下钩子修改代码的),操作系统也会在写入的时候拷贝一份出来。但是代码段的修改毕竟比数据段的修改少得多,所以代码段获得共享的机会比数据段高。但是,如果对代码段进行重定位,那就意味着修改代码段,也就意味着操作系统会拷贝,也就没有共享了。

#17


再说明一下,操作系统管理文件到内存的映射,并不是只有 so 或 dll 这种共享库才会共享物理内存的。
可执行文件也一样,如果用同一个可执行文件起了多个进程,这几个进程就可以共享相同的部分,像代码段这些。
so,dll 叫共享库,可以在不同执行文件的进程*享,并不是因为他们什么特殊的地方,操作系统都是统一对待的。只是操作系统映射的时候以文件为单位而已,把 不同执行文件 中相同的部分提取出来,做成一个单独的文件他们就获得了共享的机会。

#18


该回复于2015-12-28 12:44:27被管理员删除