一、前言
在编程中,我们有时会使用到静态库和动态库,而静态库是链接到程序之中的,基本上静态库的优化与进程类似。但动态库是加载在内存空间中的,是在运行时链接的。它的优化方式比较不同,本文我们就简单地讲述一下动态库如何优化
二、动态库
对于 动态库 而言,它仅包括 2 个段:
只读的代码段
可修改的数据段。
堆段 和 栈段 只有进程中才有。所以,如果你在动态库的函数里分配了一块内存,这段内存将被算在调用该函数的进程的 堆段 中。
系统在处理 动态库 的 代码段、数据段 时和 程序 的 代码段、数据段 一样:
代码段 由于其内容是对每个进程都是一样的,它在系统中是唯一的,系统只为其分配一块内存,多个进程之间共享。
数据段 由于其内容对每个进程是不一样的,所以在链接到进程空间后,系统会为每个进程创建相应的数据段。
2.1、BSS段
在 进程中 的 BSS 会被开辟在 堆段 中。但在 动态库 中则是另外开辟一段 内存段 来存放这些数据,我们看下面的代码:
/* test.c */
#include
#include
int bss[1024*128]={0};
void funca()
{
printf("bss:%p\n",bss);
return;
}
/* main.c */
#include
#include
#include
#include
#include
#include
#include
int funca();
int main()
{
int pid=getpid();
printf("pid:%d\n",pid);
funca();
pause();
return 0;
}
动态库编译: arm-linux-gnueabihf-gcc -shared -fPIC test.c -o libtest.so
主程序编译:arm-linux-gnueabihf-gcc -L./ -ltest main.c -o main
编译完成后记得把 动态库和主程序 一起拷贝到嵌入式设备上,其中具体的动态库查找路径相信各位读者都有些许了解,我们运行后得到下面的结果:
运行结果
smaps.png
可以看到,我们的进程中有专门的内存段来存放 动态库 的数据,可以看到其中有 2 个段各自占据了 1 个物理页
再看看如果在进程中引用动态库中的数据的情况,代码如下:
/* test.c */
#include
#include
int bss[1024*128]={0};
void funca()
{
printf("bss:%p\n",bss);
return;
}
/* main.c */
#include
#include
#include
#include
#include
#include
#include
extern int bss[1024*128];
int funca();
int main()
{
int pid=getpid();
printf("pid:%d\n",pid);
funca();
pause();
return 0;
}
运行结果
smps
我们发现 地址不在动态库的段中,而是在程序的堆栈里面。这样来看,即使我们没使用该数据,但还是会占用我们的物理页。所以我们尽量少引用动态库中的全局变量。一旦引用,不论其是否使用,都将会占用物理内存。同时还会增加系统启动时,内存复制的代价,会导致性能的下降。
2.1、动态库数据段对进程的影响
上一小节说了,如果全局变量声明在共享库中,在进程中使用,则该变量被复制到进程的数据段, 同时修改使用该变量的指向。 那么接下来看看 动态库 的 数据段 会对 进程 的 数据段 产生什么影响?
我们通过 3 个实例来验证这个问题,每个实例都分别给出:代码、运行结果、文件大小、堆段信息、动态库数据段信息
声明在bss段,但在进程中使用
/* test.c */
#include
#include
int bss[1024*8]={0};
void funca()
{
printf("dynamic bss = %p\n",bss);
return ;
}
/* main.c */
#include
#include
#include
#include
#include
#include
#include
extern int bss[1024*8]; //32KB
int* funca();
int main()
{
int pid=getpid();
int i = 0;
funca();
for(i = 0; i < 1024*8; i++)
bss[i] = 99;
printf("program bss[0] = %d\n",bss[0]);
printf("pid:%d bss:%p\n",pid,bss);
pause();
return 0;
}
运行结果
文件大小
堆段信息
动态库数据段信息
我们可以看到,在 动态库的数据段 中并没有占用内存,仅仅是在 进程 的 堆段 中占用内存。
声明在bss段,但在进程中不使用
/* test.c */
#include
#include
int bss[1024*8]={0};
void funca()
{
printf("dynamic bss = %p\n",bss);
return ;
}
/* main.c */
#include
#include
#include
#include
#include
#include
#include
extern int bss[1024*8]; //32KB
int* funca();
int main()
{
int pid=getpid();
int i = 0;
funca();
printf("pid:%d bss:%p\n",pid,bss);
pause();
return 0;
}
运行结果
文件大小
堆段信息
动态库数据段信息
该实例在主程序中引用了 动态库的数据,但没有修改数据的内容。可以看到其结论与第一个实例相同,也就是说明 在动态库中的数据一旦被主程序引用,那么主程序就会在堆段开辟内存并将动态库中的内容拷贝到堆段内存中
声明在data段
/* test.c */
#include
#include
int bss[1024*8]={1};
void funca()
{
printf("dynamic bss = %p\n",bss);
return ;
}
/* main.c */
#include
#include
#include
#include
#include
#include
#include
extern int bss[1024*8]; //32KB
int* funca();
int main()
{
int pid=getpid();
int i = 0;
funca();
printf("program bss[0] = %d\n",bss[0]);
printf("pid:%d bss:%p\n",pid,bss);
pause();
return 0;
}
运行结果
文件大小
堆段信息