多进程引用动态链接库中的全局变量问题

多进程引用动态链接库中的全局变量问题

 

现象描述

前提:存在一个动态库libvlan.so,存在一个应用console。

做法:在console中调用libvlan.so中的两个APIs,分别是设置和获取动态库中一个全局变量的值。

结果:在console中的操作没有问题,设置和获取都正常;但在动态库中打印该全局变量,其值并没有改变。

 

结论:动态库中的全局变量,在被其他进程使用时,会拷贝一份,所以多个进程使用同一个动态库中的全局变量也不会相互影响。也不会改变动态库中该变量的值。类似于fork的COW技术。

 

网络资料

现有liba.so中定义一个全局变量char buf;

libb.so 中的函数print会将buf进行输出。

进程A和进程B使用-lb进行编译链接,并在进程A和B中分别对buf进行初始化,如strcpy(buf, "A"); strcpy(buf, "B");

进程A和进程B在初始化后分别通过dlopen的方式加载liba.so并调用其中的print函数。

输出结果是A中输出A,B中输出B(当然A在初始化buf后sleep一段时间再调用print函数的,而B初始化后直接调用print,这样保证如果只有一份buf则两者输出结果肯定是后初始化的那个值)。

这是否能确定不同进程加载的动态链接库中的全局变量分别有一份拷贝,即使共享库在内存中只有一份,但是全局变量还是会有多份的,共享库共享的只是代码部分?

对这种应用方式不太确定。不知是否是这样,测试程序看来是没问题。

 

简单的说,你的结论是对的。
虽然我没有看过加载器的代码,但我看过内核动态加载module的代码,它们的原理是一样的,所以我推测加载器会是这样做的:
1. 将代码段以SHARED模式mmap代码段,于是所有application共享一份代码段
2.以PRIVATE模式mmap数据段,于是当有application试图修改数据段时,就会在它自己的地址空间内生成一份拷贝

LZ可以试验将这个全局变量初始化,然后A进程起来后先打印,然后修改,修改完毕后再起B进程,再打印。B进程打印的值应该任然是初始化的那个值。我想这个测试跟我A修改再sleep再启B测试的效果是一样的。

那就是动态库中的全局变量在不同的执行程序中是独立的。

还有一个问题是为什么libb.so中可以使用liba.so中的定义的全局变量但却不能使用a.c中定义的全局变量。a.c 直接编译成执行程序。动态库中使用的符号会去该应用加载的所有动态库中查找但却不会在可执行程序本身里面查找?

进程间是相互独立的,其实完全可以看成A、B两个进程各自有一份单独的liba.so和libb.so,相应的动态库的代码段和数据段都是各个进程各自有一份的。
然后在这个基础上,由于代码段是不会被修改的,所以操作系统可以采用copy on write的优化技术,让两个进程共享同一份物理内存。这是属于在不改变系统行为的基础上,为了节省内存的优化技术。

这是可以的,只不过在链接你的a.c的时候需要一些额外的选项。不同的操作系统是不同的,我记得Linux上是-Wl,--export-dynamic

 

 

为什么动态库中的全局变量是可以用的?

之前在linux下做过一个测试:

写一个so,该so中有一个全局变量。so中的代码在运行时会修改该全局变量的值。然后,有多个程序都需要该so,而且这些应用程序都启动了。此时,很显然so只被加载了一份,那么,当这么多程序在运行调用该so时,该so中的全局变量的值会被覆盖来覆盖去么?

答案是不会。这是测试的答案。

现在知道原理了,尽管这是windows via C/C++中解释的windows的做法,但是我想linux也是这么类似处理的。

windows 使用memory map来加载exe和dll。当一个exe/dll有多个instance要启动时,实际在windows paging file(包括RAM和swap文件)中,exe和牵涉到的dll,只有一份。这样可以节省内存使用,也可以提高性能。

也就是说,如果是exe,虽然每个instance都有自己独立的地址空间,但是地址空间映射到storage的时候,他们映射的都是同样的地方。这样就带来问题了:exe/dll中的全局变量和静态变量怎么办?每个instance都有可能会修改这些变量。

windows 的做法是,该存放全局变量和静态变量的page,设定copy on write protect attribute。所以,当任何一个线程尝试修改这些page中的内容时,windows负责分配一个新的page出来,然后修改该线程的地址空间,将这个新分配的page的地址设置上去,从此以后,该线程修改这个全局变量或是静态变量,操作的就是这个新分配的page了。这样,多个实例就不会出现全局变量或静态变量互相覆盖的问题了。

参考windows via C/C++ P593,有详细说明

 

如果某动态库中有一个全局变量,程序a使用的这个动态库,程序b也使用了这个动态库,那么程序a和b中的全局变量是一个吗?也就是说,进程间使用动态库时,共享全局变量吗?答题是:是一个,共享,但写时拷贝,给程序员的感觉是:不共享。

写时复制的一个应用是:在调试器中实现断点支持。例如:在默认情况下,代码页面在起始时都是只能执行的(即:只读的),然而,如果一个程序员在调试一个程序时设置了一个断点,则调试器必须在代码中加入一条断点指令。它是这样做的:首先将该页面的保护模式改变为PAGE_EXECUTE_READWRITE,然后改变指令流。因为代码页面是所映射的内存区的一部分,所以内存管理器为设置了断点的那个进程创建一份私有拷贝,同时其它进程仍然使用原先未经修改的代码页面。
写时复制是“延迟计算(lazy evaluation)”这一计算技术(evaluation technique)的一个例子,内存管理器广泛地使用了延迟计算的技术。延迟计算使得只有当绝对需要时才执行一个昂贵的操作——如果该操作从来也不需要的话,则它不会浪费任何一点时间。
POSIX子系统利用写时复制来实现fork函数,当一个UNIX应用程序调用fork函数来创建另一个进程时,新进程所做的第一件事是调用exec函数,用一个可执行程序来重新初始化它的地址空间。 在fork中,新进程不是拷贝整个地址空间,而是通过将页面标记为写时复制的方式,与父进程共享这些页面。如果子进程在这些页面中写入数据了,则生成一份进程私有的拷贝。如果没有写操作,则2个进程继续共享页面,不会执行拷贝动作。不管怎么样,内存管理器只拷贝一个进程试图要写入数据的那些页面,而不是整个地址空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值