本文目标:
⭐认识动态静态库,学会结合gcc选项,制作动静态库⭐
⭐了解动态库加载过程⭐
库的一些概念:
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
动静态库的本质:
(.o文件:二进制目标文件)
如果一个工程,被分成了头文件、和好几个工程项目,比如我们使用C语言实现一个简单的计算器,包含加减乘除,然后把加减乘除分别分开写,比如是Add.c一个工程项目,Sub.c一个工程项目等等,再使用main.c来调用它们。这样一来,就需要所有的工程项目的".o文件"链接起来。于是,随着工程项目越来越大,".o"文件也越来越多,于是聪明的工程师们就将这些".o"文件打包,变成一个库文件!因此,库的本质,就是一个文件,是包含了许多常用的".o"文件的库文件!
静态库
站在制作者的角度:生成静态库
生成静态库的方法,就是将所有的".o"文件打包,下面是演示的代码:
使用自动化构建工具Makefile将所有.o文件打包:
libmymath.a:my_add.o my_sub.o ar -rc$@$^ my_add.o:my_add.c gcc-c my_add.c -o my_add.o my_sub.o:my_sub.c gcc-c my_sub.c -o my_sub.o ar:archive,归档的意思,就像我们打包zip一样。 -rc:rc表示(replace and create),意思是如果在我们的静态库文件libmymath.a里面,没有某个.o文件,比如没有add.o,那么就会create一个,添加进去。如果有了,就会替换掉。
make一下,最后就会形成我们需要的libmymath.a的文件,也就是生成了一个静态库!但此时,还不能发给用户去使用,因为里面并没有头文件。因此,我们需要在库文件里面,也添加头文件,并且把.o文件和头文件分别放在库文件里面不同的目录路径下。
1 libmymath.a:my_add.o my_sub.o 2 ar -rc$@$^3 my_add.o:my_add.c 4gcc-c my_add.c -o my_add.o 5 my_sub.o:my_sub.c 6gcc-c my_sub.c -o my_sub.o 789 .PHONY:output 10 output: 11mkdir-p mylib/include //创建一个目录路径mylib,里面有include目录,用来存放头文件 12mkdir-p mylib/lib //在mylib里,创建lib目录,用来存放库文件 13cp-f *.a mylib/lib //将Makefile的当前路径下的所有.a的文件拷贝到lib里面 14cp-f *.h mylib/include //将Mkafile的当前路径下的所有.h文件拷贝到include目录里面 1516 .PHONY:clean 17 clean: 18rm-f *.o libmymath.a
现在的库文件,就真正地形成了,然后接下来就是将库文件打包,交付就行了。
站在使用者的角度:使用第三方静态库
指令:gcc main.c -L. -lmymath 其中的main.c就是用户的主函数的工程文件,-L 带上库路径,-l (小写的 L )带上库名 -I (大写的 i )带上头文件的路径
-L 指定库路径
-l 指定库名
-I 指定头文件路径
需要注意的是:带上的库名,注意要去掉头尾,即lib和.a/.so需要去掉,才是库名。
我们在平时用C语言和C++语言写代码的时候,并不需要指明库名称,就能使用C的库或C++的库,那是因为gcc或g++,本身就是用来写C/C++的,已经默认加上了库名了。
gcc -o mymath main.c -I./mylib/include -L./mylib/lib -lmymath
当然,测试目标文件生成后,静态库mylib,其它的所有的.c,.h,.o文件删掉,程序照样可以运行,因为这些文件,已经链接在目标文件里面了。
形成一个可执行程序,可能不仅仅只依赖一个库!而gcc的库默认是动态链接,但是当提供的是静态库时,并不能说gcc不会使用静态库,事实证明,不管是动态还是静态,都可以使用。因此,gcc默认动态库,是建议的意思,对于一个指定的库,是动态还是静态,取决于我们使用的库的什么库。如果在使用的若跟个库里面,只要有一个是动态库,那么,这个可执行程序就是动态链接的!
为了简化使用库的操作,我们可以将库安装在系统头文件里,安装的本质就是拷贝,也就是拷贝到系统头文件里面。
sudo cp mylib/include/* /usr/include/ 将库文件里面的头文件全部拷贝到到系统头文件中
sudo cp mylib/lib/*.a /lib64/ 将libmymath.a静态库拷贝系统的库文件中
此时,就能简化使用的操作,当然,即使已经安装到系统里面了,但是gcc只会认识C语言库,第三方的库它能找到,但是不认识,因此在使用的时候,必须带上库名。
gcc main.c -lmymath
总结:制作者角度:生成静态库,就将需要的.o文件全部归档,并且头文件也一并带上,然后打包交付即可。使用者角度:在使用第三方库的时候,需要-L带上库的路径,-I头文件的路径和-l库的名称,注意库的名称是去掉lib和后缀之后的。不建议将自己写的第三方库安装到系统库中。
动态库
站在制作者的角度:生成动态库
动态库也是库,跟静态库差不多,也是.o文件归档,带上相应的头文件。其中的区别就是多加了一个选项:-fPIC。-fPIC的作用是在生成.o文件的时候,产生位置无关码,然后再多加了一个选项:-shared。-shared的意思是表示生成共享库格式。
gcc -fPIC -c my_add.c my_sub.c 生成.o文件
gcc -shared -o libmymath.so my_add.o my_sub.o 归档
接下来的库还不能直接使用,因为需要带上头文件。因此我们创建一个目录,用来存放库和头文件,与生成静态库的步骤一致。
mkdir mylib //创建新目录
mkdir mylib/include //在mylib里面,创建include目录,存放头文件
mkdir mylib/lib //在mylib里面,创建lib目录,存放libmymath.so动态库
mv libmymath.so mylib/lib/ //将libmymath.so动态库移到lib里面
cp *.h mylib/include/ //将头文件拷贝到include目录里面
至此,就能将mylib打包,交付!
站在使用者角度:使用第三方动态库
操作的方法与静态库的一样:
gcc -o mymath main.c -Imylib/include/ -Lmylib/lib/ -lmymath
可此时我们执行程序,会发现执行失败,理由是找不到这个动态库。
前面我们在gcc编译的时候,虽然已经加上了路径和库的名字,但是那个是给gcc说的,是告诉gcc的,gcc才能把代码编译起来,编译完后,动态库就跟gcc没关系了。
然而现在是需要执行这个程序,对于动态库来说,这个可执行程序是需要找到它在哪,因此程序需要允许起来的时候,OS和shell也要知道库在哪!而此时我们写的库,没有在系统路径下,所以OS找不到。
解决找不到动态库的问题:
有时候OS和shell会在环境变量里面找,也会在系统路径里面找。因此:
方法一:在环境变量里面添加库:更改 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wjmhlh/lesson23/mylib/lib
此时,就可以正常运行了!但是这个方法有个缺点,自定义的环境变量添加到系统环境变量里,只在本次登录有效,等退出再登录的时候,就再次找不到了!
方法二:拷贝到系统目录下,拷贝.so文件到系统共享库路径下, 一般指/usr/lib。这个方案跟静态库的一样,就不演示了,使用的时候记得带上-l库名。
方法三:配置文件:
在系统的路径里,有一个目录,/etc/ld.so.conf.d/,叫做conf,conf目录里面有一些文件,这些文件的作用是在进行动态库的搜索时,可以定义conf配置文件的方式,来让OS找到我们要的动态库。配置的方法很简单,只需要在conf里面创建一个目录,然后在目录里面写上我们的动态库的路径即可。接着使用指令:ldconfig,将conf目录更新一下,就完成啦!
步骤指令:
① cd /etc/ld.so.conf.d/ 进入conf系统目录
② sudo touch my_test 在conf里面创建一个目录
③ sudo vim my_test.conf 进入创建的目录,然后将动态库的路径写入
④ sudo ldconfig 更新目录
⑤ cd - 回退到我们用户目录
这样,程序就可以正常执行了,并且永久有效。
方法四:软链接
通过软链接,类似于快捷方式,就可以了。
格式:ln -s 动态库的路径 动态库
ln -s /home/wjmhlh/lesson23/mylib/lib/libmymath.so libmymath.so
也可以建立在系统目录下:
sudo ln -s /home/wjmhlh/lesson23/mylib/lib/libmymath.so /lib64/libmymath.so
使用外部库:
系统中其实有很多库,它们通常由一组互相关联的用来完成某项常见工作的函数构成。比如用来处理屏幕显示情况的函数(ncurses库)
动态库的加载
静态库一般不考虑加载。一个程序在编译完成后,还没加载到内存时,就已经有了自己的代码区等,此时程序所用到的静态库被拷贝到代码区里面!此时的静态库的代码数据已经称为了这个程序的代码数据的一部分了,因此静态库不需要考虑加载问题。
动态库加载与访问加载过程:
与位置无关码:就是用特定的参照系来进行定位某一个人或物对应所处的位置,这种相对静止的方式就叫做与位置无关。
动态库不会像静态库一样,直接拷贝在可执行程序的代码区里面,而是动态库里指定的函数的地址,写入到可执行程序中,而这地址,暂且只需知道它是start:偏移地址,是起始地址+偏移地址的地址。
假设我们要访问C语言库中的printf函数,在可执行程序中有这个函数的偏移地址,但是这个地址属于外部地址,而这个printf函数是属于libc.so的,因此就能立马识别到这个库,然后操作系统暂时不执行代码,开始加载库,加载到内存,加载后,把这块内存通过页表,映射到虚拟空间的共享区,接着就会立马确定了这个库的起始地址!此时,因为我们要访问的printf所在的地址空间中,已经有了偏移量,然后可以去访问共享区,找到动态库的起始地址,然后拿着这个起始地址+偏移量,在共享区的libc.so这个库里面找到这个函数,然后调用,调用完之后返回代码区继续往后执行!
理顺过程:在可执行程序里面遇到了printf,OS就识别到了printf是属于libc.so库的,然后加载库,拿到起始地址,接着拿着起始地址+偏移地址在共享区中的库里面找到这个函数调用,最后返回到代码区中继续执行下面的代码。
⭐本文结束!如果对您有帮助,可以点个赞和关注!下一篇预告:进程间通讯!⭐