静态库和动态库

了解静态库和动态库的概念

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库

动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码

在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个 过程称为动态链接(dynamic linking)

动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

1.站在开发者角度看待

开发者需要将自己开发的方法提供给其他人,这就需要其将源代码形成库和头文件,库里面是具体的方法,头文件则是这些方法的声明。这些库里存放的文件都是以xxx.o的形式存在,只需要我们用户在最后链接的时候链接库文件就可以成功调用这些方法了。

静态库

首先,我们先来认识静态库是如何链接的?我们应该如何调用第三方静态库?(下面分别是mymath.h、mymath.c、main.c)

首先我们需要让我们所编写的mymath.c文件形成mymath.o文件然后使用ar -rc命令将mymath.o形成目标库。(ar -rc的功能是创建或更新静态库,将目标文件打包形成静态库。如果目标库存在-r表示更新目标库;如果目标库不存再-c表示创建目标库)

现在我们模拟第三方库位于lib/mymathlib中,头文件位于lib/include中。应该如何才能让我们的程序成功链接?

首先我们必须得指明头文件和库文件在哪个目录下!但并没有成功,这是由于我们没有指明库的名称,系统不知道链接哪一个库,尽管目标目录下只有一个库!

-I 和 -L 是两个常用的选项,分别用于指定头文件搜索路径和库文件搜索路径。

头文件不需要指明头文件名(在引用文件中已经指出头文件名称),库则需要指出库名称(去掉lib和.a,纯库名)。-l(Link)表示跟的库名

如果我们将库放入到系统默认的库搜索路径下,我们不用再去指定头文件搜索路径和库搜索路径。但是,我们仍然还是需要指明我们使用的三分库名称。

(1) 第三方的库,往后使用的时候必定需要gcc -l

(2) 根据我们程序中的myerrno,我们理解了errno的本质。它是一个用于记录系统调用和库函数调用中发生的最近一次错误的全局变量。其本质是一个线程安全的、可修改的整形变量,用于传递程序中出现的错误信息。并且可以通过errno的值来判断出错的类型。

(3) ldd + 可执行文件可以看到所链接的动态库。gcc链接程序是默认动态链接的(如果有动态库)。如果只提供静态库,那就只能静态链接。ldd看不到静态链接的库名,因为其编译时直接嵌入到可执行程序中

注意:如果gcc带-static,那么就只能静态链接静态库!

(4) 如果系统中需要链接多个库,则gcc可以链接多个库

注意:如果链接时候我们不像指明-I -L -l,那我们将其拷贝到系统默认搜索路径能解决吗?不是!我们仍然需要指明库名,头文件还是不知晓需要链接哪一个库。将第三方库拷贝到系统默认搜索路径下这个行为其实就是安装第三方库!

如果我们的main文件需要多个第三方头文件,我们可以在当前路径建立第三方头文件目录的软连接,这样就不需要带一大串的绝对路径了或相对路径!(不建议自己建立软连接)

动态库

现在我们编写了两个.h文件和对应的.c文件,我们要将其编译成.o文件后形成动态库。

我们和之前一样需要先将.c文件进行编译得到.o文件,但与之前不一样的是需要添加选项-fPIC(产生位置无关码)。

将所有的.o文件集合起来形成库,-shared告诉gcc我们需要形成的是动态库而不是可执行文件!

为什么动态库需要带可执行权限,而静态库没有?

编译时候会将静态库拷贝到可执行文件,静态库本身不会加载到内存。动态库会和可执行程序产生关联,当运行到调用动态库的代码时候会跳转到动态库中,因此动态库必须被加载到内存!(库像可执行文件一样被系统加载到程序中)

我们按照之前使用静态库的经验来使用以下动态库,我们发现我们能够编译链接成功!

但是,我们运行我们的可执行文件的时候出现了问题,它无法运行!并且,它显示无法找到我们编写的动态库!!!

我们不能只告诉编译器动态库在哪,我们还得告诉加载器动态库在哪!(如果加载时候未指明路径,系统在加载时候会去默认搜索路径寻找目标库)

如何让可执行文件找到动态库?

1.将其拷贝到系统的默认搜索路径 /lib64 /usr/lib64

2.在系统默认搜索路径 /lib64 /usr/lib64下建立软链接

同时还有一点就是注意环境变量LD_LIBRARY_PATH,debian中环境变量LD_LIBRARY_PATH默认为空,需要我们手动设置//lib64。

3.将自己的库所在的路径添加到系统的环境变量LD_LIBRARY_PATH

4./etc/ld.so.conf.d建立自己的动态库路径的配置文件然后重新ldconfig即可。(运行可执行文件时候不需要加库名字,因为在编译的时候就指定了库名!)

注意:实际情况我们使用的三方库都是比较成熟的,基本采用直接安装到系统方式!!!(也就是第一种方式)

A.动态库在进程运行时是要被加载的!

B.常见的动态库被很多的可执行程序(动态链接的)所使用,因此动态库也可以称为共享库!

从上面两点我们可以推出动态库在系统中加载后会被所有进程共享!(一次加载即可,操作系统不会做浪费时间空间的事情!!!)

动态库是如何被加载的?

结论:建立映射,从此往后,我们执行的任何代码,都是在自身的进程地址空间内执行!!

事实:系统在运行中,一定会存在多个动态库!操作系统需要先描述在组织!什么库被加载,什么库没有被加载(库的使用情况),操作系统都非常清楚!

那么对于像errno这种库,它记录一个进程最后出错时的错误码,但这个库被很多进程使用,这会导致一个进程的错误码被其他进程所使用吗?

库是位于共享区,这是用户空间。当用户进行写入的时候,目标库为只读权限,内存中目标库的引用加一,因为权限问题出发缺页中断此时就会发生写时拷贝。

关于地址

1.程序没有加载前的地址(程序)

程序编译好之后,内部有地址的概念!(编译器要考虑操作系统)并且划分的是虚拟地址(为了区分称为逻辑地址!)。(为了更好地加载到进程地址空间)逻辑地址的偏移量直接等于虚拟地址。(Linux平坦内存模型)

2.程序加载后的地址

当可执行程序加载到内存的时候,每一个指令都会有自己的物理地址。(未加载时的逻辑地址,加载到内存后成为了物理地址)

如何找到第一条指令并执行呢?

在编译的时候就会形成一个程序的入口地址(entry),注意因为是编译期间形成的,所以不是物理地址而是逻辑地址。因此当可执行程序加载到内存后,CPU的pc会获得入口地址开始执行(前文提到过编译器为了考虑操作系统,逻辑地址的偏移量直接等于虚拟地址)

PCB总是首先需要创建的,CPU从入口地址(程序当中地址)执行时(在此之前会发生进程地址空间的创建并初始化代码段、数据段、bss段、堆、栈等区域。刚开始的时候进程的虚拟地址并不会都映射到真正的物理地址,而是采取按需分页的方法),如果目标指令未被加载到内存就会发生缺页中断,此时建立从虚拟地址到物理地址的映射,这样就找到了对应的代码。CPU内读到的指令,内部可能有数据也可能有地址(可能需要跳转到某个区域执行指令),这些地址是什么类型的地址?虚拟地址!如果通过页表访问不存在,缺页中断即可。

3.动态库的地址

共享库非常大,具体映射到了哪里?例如调用了printf,那么其在编译的时候printf就变成了一串逻辑地址(可以匹配到虚拟地址)。那么在进程地址空间中,共享库就必须被加载到之前提到的这个虚拟地址。如果不放在这个地址,那在pc跳转的时候就无法找到printf对应的指令执行。按照上面的说法,动态库会映射到固定位置,但多个动态库的先后加载顺序是不确定的,无法保证每个库都加载到固定位置(进程地址空间中)。因此,动态库加载到固定地址空间是不可能的。

为了适应上述问题,库可以在虚拟内存(地址空间)中任意加载!如何做到?

让库中的函数不采用绝对地址编址,而是采用相对地址(偏移量)。因此动态库需要被记录起始地址,然后根据偏移量寻找到我们printf对应的指令继而执行。因此fPIC(产生位置无关码)其实是直接用偏移量对库中函数进行编址!

总结:记住:今天的努力,是明天从容的底气。 你读的每一页书,写的每一行代码,思考的每一个问题,都在悄悄塑造更好的自己。坚持学习,不是为了瞬间的辉煌,而是为了在机会来临时,能够稳稳接住它。

相关推荐
HLC++12 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室13 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy15 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕16 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫17 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生18 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1119 小时前
linux基础IO
linux·运维·服务器
BelongPanda21 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯21 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983421 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器