文章目录
- 1. 动静态库前置知识
- 1.1 动静态库知识回顾
- 1.2 什么是动静态库
- 2. 动静态库
- 2.1 站在库的制作者的角度
- 2.2 站在库的使用者的角度
- 2.3 动态库是怎么被加载的(原理)
- 序:上一篇文章我们从认识到理解,从理解到实现场景,我们知道了什么是软硬链接、数据交换的基本单位—页、操作系统是如何管理内存的还有深入清楚了一个被打开的文件在内核部分是如何被修改文件内容的,而本篇文章将从什么是动静态库,如何连接动静态库,以及动态库在系统内核部分是如何被访问的!!!
1. 动静态库前置知识
1.1 动静态库知识回顾
Linux: .so(动态库).a(静态库)
Windows: .dll(动态库) .lib(静态库)
编译型语言:开发包必然是下载安装对应的头文件和库文件
其中方法的实现就是在库当中
库其实就是把源文件(.c)经过一定翻译,然后打包给你提供一个头文件就行,不用你提供太多源文件
头文件提供你方法的声明,库文件提供你方法的实现+你的代码 == 你的软件(库文件:不做重复工作)
问题一:那么我们的.o文件和库是如何连接的?
动态连接–>动态库(注:动态库不能缺失,否则会导致很多程序无法运行)
静态连接–>静态库
1.2 什么是动静态库
动态库和静态库
动态库因为是共享库,能有效节省资源(磁盘空间、内存空间、网络空间)【优点】,动态库一但缺失,会导致很多程序无法进行【缺点】
静态库,不依赖于库,程序可以独立运行【优点】,体积大,比较消耗资源【缺点】,如果非要静态连接,可以加-static
2. 动静态库
2.1 站在库的制作者的角度
lib.XXX.a—动态链接
lib.XXX.so—静态链接将我们的方法提供给别人使用:将源代码包装成库和.h文件给别人
问题一:一个程序是如何连接静态库的?
makefile如下:
dy-lib=libmethod.so
static-lib=libmymath.a
.PHONY:all
all: $(static-lib) $(dy-lib)
$(static-lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
$(dy-lib):myprint.o mylog.o
gcc -shared -o $@ $^ //其中的-shared选项表示要形成动态库
myprint.o:myprint.c
gcc -fPIC -c $^ //-fPIC表示与地址无关码,在后面一章地址程序空间第二讲中会详细说明
mylog.o:mylog.c
gcc -fPIC -c $^
.PHONY:clean
clean:
rm -rf *.a *.o *.so mylib
.PHONY:output
output:
mkdir -p ./mylib/include
mkdir -p ./mylib/lib
cp mymath.h ./mylib/include
cp libmymath.a ./mylib/lib
cp myprint.h ./mylib/include
cp mylog.h ./mylib/include
cp libmethod.so ./mylib/lib
我们能直接gcc吗?在程序中包含静态库头文件,然后调用静态库中的函数,能直接gcc,就编译通过,并且执行吗?
如图:
答案显然是不行的!!!当我们gcc时,gcc没办法通过这个头文件找到这个静态库的头文件的位置,所以程序中的头文件无法被识别!!!
这样是不是就是说,我们要在gcc的时候,将静态库的头文件的绝对或相对路径告诉gcc,才能使编译通过?我们可以试试,如果要告诉程序我们的静态库的头文件在哪,可以加个“-I +[头文件的绝对或相对路径]”选项来告诉gcc我们的静态库的头文件在哪!!!
如图:
当我们加了“-I”选项后,我们发现之前找不到头文件的报错消失了!!!但是新的问题又出现了!!!即使,gcc已经找到了静态库的头文件的位置了,但gcc没办法调用静态库中的函数!!!所以我们还要告诉gcc这个静态库的方法调用的库的绝对或相对路径!!!如果要告诉程序我们的静态库在哪,可以加个“-L + [.so库绝对或相对路径]”
如图:
我们发现,即使我们告诉了gcc,我们的头文件的路径和静态库的路径,我们依然没办法将他编译出来,这是为什么?答案就在于gcc即使找到了静态库的目录,gcc也不知道该调用哪一个静态库!!!所以我们的最后一步就是告诉gcc我们要在哪个路径下调用哪个静态库!!!,我们可以使用“-[静态库的名字(去掉前缀lib和后缀.so)]”来告诉gcc我们要调用的静态库的名字!!!
如图:
终于,我们成功调用了我们自己制作的静态库!!!总结:
我们调用静态库时要给出调用头文件的路径和调用静态库的路径和静态库的名字!!!在这里大家可能会有个疑问,那就是为什么我们给出头文件的路径时,为什么不要表明我们要调用哪个头文件,其实在程序中我们已经说明了我们要调用哪一个头文件了,所以就不需要再去表明了!!!
补充:
A. 第三方库,在往后使用的时候,必定是要用gcc -l的
B. 深刻理解errno的本质
C. 如果系统中只提供静态链接,gcc则只能对该库进行静态链接
D. 如果系统中需要链接多个库,则gcc可以链接多个库
2.2 站在库的使用者的角度
问题一:一个程序是如何连接动态库的?
有了之前静态库的链接方式,我们也同样按照那样的顺序去链接试试,结果如下:
第一步:直接gcc
第二步:gcc并加上库头文件的路径
第三步:gcc加上库头文件的路径和动态库的路径和动态库名字
问题来了!为什么我们明明加上了库头文件的路径和动态库的路径和动态库名字,而且编译成功了,但是为什么不能正常运行???当我们编译时,我们告诉了编译器这个动态库的路径和名字,但是当我们编译完后,要执行这个可执行程序时,我们的加载器根本就不知道这个动态库在哪!!!当我们使用命令"ldd+[可执行程序]"时,我们发现这个动态库确实是链接上去了,但是该动态库的指向确实“not found”,这也说明我们的动态库的路径并没有被加载器知道!!!
所以,我们就要让加载器知道我们的动态库在哪!!!
解决加载找不到动态库的方法:
A. 拷贝到系统默认但库路径/usr/lib64/
B. 在系统默认的库路径/usr/lib64/下建立软连接
C. 将自己的库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH中
D. 在目录/etc/ld.so.conf.d中建立自己的动态库路径的配置文件,然后ldcconfig应用即可实际上,我们用的库都是别人的成熟的库,都是采用直接安装到系统的方式!!!(例如:ncurses----基于终端的库)
这里以第四种方法(也是比较方便的方法)来加载动态库为例:
在系统路径下的/etc/ld.so.conf.d目录中,我们可以建立自己的动态库路径配置文件,先创建一个conf文件,然后将动态库的绝对路径放入该文件中,最后再ldcnfig进行应用,我们就可以使用动态库链接的可执行文件了!!!如图:
动态库在运行时,是要被加载的(静态库没有)
常规的动态库被所有的可执行程序(动态链接的),都要使用,动态库----共享库
所以动态库在系统加载之后,会被所有进程共享。
2.3 动态库是怎么被加载的(原理)
当我们执行可执行程序时,当我们要执行动态库中的方法时,动态库会被操作系统加载到物理内存中,然后通过页表映射,映射到进程地址空间当中的共享区内!!!,正文代码就会去自己的进程地址空间的共享区中找到对应的方法,然后调用,将结果返回正文区。当有多个进程访问同一个动态库时,也是通过映射的方法映射同一个物理内存中的动态库,然后在自己的进程地址空间的共享区内调用方法并返回!!!这就是为什么动态库也叫做共享库!!!
结论:建立映射,从此往后,我们执行的任何代码,都是我们进程地址空间中进行执行!!!
事实:系统在运行中,一定会存在多个动态库,所以操作系统要将这些动态库给管理起来—先描述,再组织,所以在操作系统重,所有库的加载情况,操作系统都非常清楚!!!
问题一:如果此时,这个动态库中有一个全局变量errno,但多个进程一起报错,需要返回一个errno值时,岂不是会互相影响吗?
不会互相影响,当一个动态库被多个进程使用时,该动态库是被不同的进程加载到了他们自己的进程地址空间当中的,此时,该动态库的引用计数会加加,当要改变errno的值时,就会引发缺页中断,从而发生写实拷贝!!!此时各个进程的errno值是属于自己进程的,所以各个进程之间不会相互影响!!!
总结:
通过本篇文章我们知道了什么是动静态库以及动静态库的格式,以及如何连接动静态库,动态库和静态库的链接有什么区别,以及动态库在系统内核部分是如何被访问的!!!