本篇文章主要是围绕动静态库的原理与制作进行展开讲解的。其中涉及到了inode的概念引入和软硬连接的讲解。会结合实际操作对这些抽象的概念进行解释,希望会对你有所帮助。
文章目录
一、inode 概念
二、软硬链接
2、1 软连接
2、2 硬链接
三、动静态库概念
3、1 静态库制作
3、2 静态库的使用
3、2、1 加载到系统的文件中
3、2、2 指定路径直接使用
3、3 动态库制作
3、4 动态库的使用
3、4、1 指定路径直接使用
3、4、2 环境变量:LD_LIBRARY_PATH
3、4、3 系统文件:/etc/ld.so.conf.d/
四、总结
🙋♂️ 作者:@Ggggggtm 🙋♂️
👀 专栏:Linux从入门到精通 👀
💥 标题:动静态库💥
❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️
一、inode 概念
一个文件里面没有任何内容,文件的大小就是0吗?实际上并不是。我们知道,文件不仅仅要保存其内容,属性也是必不可少的。属性保存在哪里呢?
Inode(Index Node)是文件系统中的一个重要概念,用于存储和管理文件的元数据。在UNIX和类UNIX操作系统中,如Linux,每个文件都与一个唯一的inode相关联。
Inode 包含了以下文件的元数据信息:
- 文件类型:指示文件的类型,如常见的普通文件、目录、符号链接等。
- 文件权限:描述了对文件的访问权限,包括拥有者、所属组和其他用户的读、写、执行权限。
- 文件大小:表示文件占用的磁盘空间大小。
- 日期和时间戳:记录了文件的创建时间、最近修改时间和最近访问时间。
- 硬链接计数:记录有多少个硬链接指向该文件。硬链接是指多个文件名指向同一个inode的情况。
- 文件数据块的物理地址:指示文件数据在磁盘上的存储位置。
通过使用inode,操作系统可以有效地管理文件系统中的文件。例如,当需要读取或写入文件时,操作系统可以根据文件的inode查找文件的数据块的物理位置,以快速定位并访问文件内容。
需要注意的是,inode与文件名是独立的。文件名与inode之间的对应关系由文件系统的目录结构来维护。当文件被打开或者通过文件名访问时,操作系统会根据文件系统的目录结构使用inode来定位文件。
二、软硬链接
2、1 软连接
软连接(Symbolic Link)是一个指向另一个文件或目录的链接。它是一个特殊类型的文件,其中包含了所指向文件或目录的路径信息。软连接可以跨越不同的文件系统,甚至可以指向不存在的文件或目录。删除软连接不会影响被链接的文件或目录本身,而只会删除软连接本身。下面我们看一个实际的例子。
如上图,我们在上图的路径中有一个可执行程序 mytest。假设我们想在其他路径下执行该程序,一种办法就是使用 相对路径/绝对路径 找到该可执行程序。具体如下图:
但是每次都需要加上路径去执行该程序是不是优点太过繁杂。那有没有其他的便捷的方法呢?这里就可使用到软连接:ln -s ./test_8_19/bin/mytest mytest。具体如下图:
对这种方法有一种莫名熟悉的感觉。在windows下,不就是在桌面创建快捷方式嘛!!!
软连接的主要特点和用途包括:
- 文件间的共享:通过创建软连接,多个文件可以引用同一个文件,减少存储空间的占用。
- 简化路径:软连接可以提供简洁的路径名,使得访问文件更加方便。
- 跨文件系统的链接:软连接可以跨越不同的文件系统进行链接,增强了灵活性。
- 动态更新:当原始文件或目录发生改变时,软连接也会实时更新,保持链接的有效性。
2、2 硬链接
硬连接(Hard Link)是一个直接指向目标文件或目录的链接。硬连接与原始文件或目录没有区别,它们共享同一个索引节点,指向同一个磁盘区域,从而形成了相同的文件内容和属性。删除硬链接不会影响原始文件或目录,因为硬链接实际上是原始文件或目录的另一个名称。
如上图,我们使用link指令创建了一个硬链接。我们发现他们的inode竟然相同。那么创建硬链接,并不是真正的创建文件。而是在目录下,建立了文件名与指定inode的映射关系而已!通俗理解,就是给指定文件起别名。
硬链接的主要特点和用途包括:
- 文件备份:通过创建硬链接,可以在不占用额外存储空间的情况下,生成与原始文件内容完全相同的备份文件。
- 文件共享:多个硬链接可以引用同一个文件,可以在不同位置使用相同的文件。
- 快速访问:由于硬链接实际上是同一个文件,所以可以通过多个链接快速访问文件,提高效率。(隐藏文件 . ..)
需要注意的是:
- 软连接可以跨越不同的文件系统进行链接,而硬连接只能在同一文件系统中创建链接。
- 删除原始文件并不会立即影响已经创建的硬链接,因为硬链接与原始文件共享相同的磁盘空间,只有当所有链接都被删除后,才会真正释放磁盘空间。
- 软连接可以指向不存在的文件或目录,而硬连接必须指向已存在的文件或目录。
三、动静态库概念
3、1 静态库制作
静态库是一种将一组预编译的目标文件(.o)打包成一个单独的文件的技术。它的主要作用是将代码模块化并提供给其他开发者使用,以便在编译阶段将这些模块链接到他们的程序中。
静态库的形成: 静态库是由多个编译好的目标文件组成的,这些目标文件包含了被编译源代码的函数和数据。当我们将这些目标文件打包成一个单独的库文件时,就形成了静态库。通常,静态库的文件扩展名是
.a
(在Windows上也可以是.lib
)。形成静态库的过程包括:
- 预处理:处理源代码中的宏定义、条件编译等预处理指令。
- 编译:将预处理后的源代码编译成汇编代码。
- 汇编:将汇编代码转换成机器码,并生成目标文件(通常是
.o
文件)。- 链接:将多个目标文件链接在一起形成静态库文件。
如下图,我们经过编译生成了目标文件(.o)。
我们再对 .o 文件打包生成静态库:ar -rc libhello.a mymath.o myprint.o。
上述打包的过程就是生成静态库的过程。通俗来讲,静态库就是对.o 文件进行打包形成的。
我们所生成静态库的名字前缀必须是lib,后缀必须是.a 。这个就是规定。
为了方便使用,我们将静态库和头文件统一放到一个目录下。具体操作如下:
libhello.a:mymath.o myprint.o ar -rc libhello.a mymath.o myprint.o mymath.o:mymath.c gcc -c mymath.c -o mymath.o myprint.o:myprint.c gcc -c myprint.c -o myprint.o .PHONY:hello hello: mkdir -p hello/lib mkdir -p hello/include cp -rf *.h hello/include cp -rf *.a hello/lib .PHONY:clean clean: rm -rf *.a *.o hello
3、2 静态库的使用
我们再把打包静态库和头文件目录(hello)拷贝到上级目录下一个文件中进行调用使用。
我们直接编译main.c 文件,可以吗?看下图:
是不可以的。编译时会自动在本目录下查找头文件。如果找不到,就回去系统的文件中查找。该目录下确实是没有,所以试过报错的。那怎么才能使用呢?
3、2、1 加载到系统的文件中
gcc 编译时,头文件的默认搜索路径是:/usr/include。库文件的默认搜索路径是:/lib64。当我们把自己所写的头文件和静态库加载到系统默认的搜索路径后,就会自动找到我们所引入的头文件和库函数。具体操作如下:
但是此时我们进行编译,还是会进行报错。如下图:
这是为什么呢?我们自己写的库属于第三方库,在编译时必须需要指定库的名字。具体如下:
虽然这样可以使用静态库,但是不支持这种做法。这样就有可能会污染系统的文件。
3、2、2 指定路径直接使用
加载到系统的文件中不是一种很优的方法。哪还有其他方法吗?答案有的。我们在编译时直接告诉编译器路径:gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello。具体如下图:
3、3 动态库制作
我们知道,静态库在编译过程中,目标文件被编译链接生成的可执行程序中包含所有函数和数据。可执行程序所占用内存比较大。当程序运行时,所有的代码和数据都被加载到内存中,并占用固定的内存地址。这种方式是静态链接,使得可执行文件的体积较大,且不具备代码共享的特性。而动态库并不是这样的,且动态库的生成与静态库也有所区别。我们接着往下看。
动态库(Dynamic Link Library,简称DLL)是一种可执行文件,包含可以被多个程序同时调用的函数和数据。与静态库相比,动态库提供了更为灵活和高效的代码共享方式。
形成动态库的过程主要包括以下几个步骤:
编写动态库代码:根据需求编写所需的函数和数据,并将其封装在一个动态链接库项目中。
编译动态库代码:使用合适的编译器将动态库代码编译成二进制形式,生成具有扩展名为.dll(在Windows系统中)或.so(在Linux系统中)的文件。
链接动态库:将动态库文件与主程序进行链接,使主程序能够使用库中的函数和数据。在这个过程中,主程序并不会把动态库的内容复制到自己的代码中,而是在运行时通过动态链接来加载和使用库中的函数和数据。
通过上图我们发现,在生成.o文件时多出了-fPIC选项。-fPIC选项是什么意思呢?
gcc的-fPIC选项是用于生成可重定位目标文件(Position Independent Code,PIC)的编译选项。通过使用该选项,生成的目标文件可以在内存中的任何位置加载和执行,而不需要进行修改或重新链接。
在编译过程中,目标文件只包含程序所需的函数和数据的引用信息,真正的函数和数据则通过动态链接库(Dynamic Linking Library,DLL)提供。在程序运行时,操作系统会将需要的函数和数据从动态链接库中加载到内存,并进行地址重定向。这种方式使得可执行文件的体积较小,且不同程序之间可以共享同一个动态链接库。
而-fPIC选项则是在编译过程中产生与位置无关的代码,主要用于动态链接库的创建。使用该选项可以确保生成的目标文件能够适应不同的内存布局和地址空间。具体来说,-fPIC选项会通过使用相对寻址(relative addressing)的方式替代绝对寻址(absolute addressing),使得目标文件中的函数和数据可以在不同的内存地址加载和执行。
当我们有了目标文件(.o)后,再看如下图生成动态库:
3、4 动态库的使用
为了同时生成静态库和动态库,我们再次进行对makefile文件进行改写,代码如下:
.PHONY:all all:libhello.so libhello.a libhello.so:mymath_d.o myprint_d.o gcc -shared mymath_d.o myprint_d.o -o libhello.so mymath_d.o:mymath.c gcc -c -fPIC mymath.c -o mymath_d.o myprint_d.o:myprint.c gcc -c -fPIC myprint.c -o myprint_d.o libhello.a: mymath.o myprint.o ar -rc libhello.a mymath.o myprint.o mymath.o:mymath.c gcc -c mymath.c -o mymath.o myprint.o:myprint.c gcc -c myprint.c -o myprint.o .PHONY:output output: mkdir -p output/lib mkdir -p output/include cp -rf *.h output/include cp -rf *.a output/lib cp -rf *.so output/lib .PHONY:clean clean: rm -rf *.o *.a *.so output
3、4、1 指定路径直接使用
我们同样是先把打包动静态库和头文件目录(hello)拷贝到上级目录下一个文件中进行调用使用。动态库的直接指定路径使用与静态库相似。我们看如下实例:
我们知道,动态库和静态库真是的名字去掉前后缀后都是hello。那我们直接使用-lhello,指定的是动态库还是静态库呢?也就是默认情况下是动态链接呢?还是静态链接呢?我们看选图:
我们发现,默认情况下是动态链接。但是并没有找到动态库!且生成的可执行程序也不能执行。这又是为什么呢?原因是我们需要把动态库加载到内存中后,可被多个进程使用(因此也被称为共享库)。但是我们只是告诉gcc动态库所在的路径了,并没有告诉操作系统动态库在哪里!
3、4、2 环境变量:LD_LIBRARY_PATH
在Linux下,默认查找共享库的环境变量是LD_LIBRARY_PATH。LD_LIBRARY_PATH是一个包含目录路径的环境变量,用于告诉动态链接器(ld.so)在哪些目录中搜索共享库文件。
当程序需要加载共享库时,动态链接器会按照以下顺序进行搜索:
- 优先搜索程序内部指定的路径。
- 如果没有找到,接下来会搜索LD_LIBRARY_PATH中指定的路径。
- 如果还是没有找到,最后动态链接器会按照一定的默认规则搜索系统预定义的路径,如
/usr/lib
、/lib
等。通过设置LD_LIBRARY_PATH环境变量,可以临时修改共享库的搜索路径。例如,可以使用以下命令来设置LD_LIBRARY_PATH环境变量:
export LD_LIBRARY_PATH=/path/to/shared/libs
该命令将/shared/libs目录添加到共享库搜索路径中。在当前的终端会话中,程序运行时将会优先搜索该路径下的共享库。
但是LD_LIBRARY_PATH是一个临时的环境变量设置,只对当前终端会话有效。如果希望永久修改共享库的搜索路径,可以考虑修改系统范围内的配置文件,如/etc/ld.so.conf.d/目录下的配置文件,并执行相应的更新操作,例如使用ldconfig命令。稍后我们也会详细介绍。
现在以我自己为例子来添加动态库到共享库的环境变量中。具体如下图:
其实我们也不难发现,每个路径都是用 :来进行分割的。我们添加成功后,我们再次执行a.out 时,系统就回根据环境变量自动找到改动挑库所在的位置。运行如下图:
3、4、3 系统文件:/etc/ld.so.conf.d/
在Linux系统中,/etc/ld.so.conf.d/目录是用来配置共享库搜索路径的。共享库在运行时被程序动态链接使用,这些库存储在特定的路径下。ld.so是动态链接器(loader)的一部分,它负责在运行程序时定位和加载所需的共享库。
在/etc/ld.so.conf.d/目录中,可以创建不同的配置文件,每个文件对应一个共享库搜索路径。这些配置文件通常以.conf为后缀名。通过在这些配置文件中添加共享库的路径,可以告诉动态链接器在特定的目录中搜索共享库。
当系统启动或需要加载共享库时,动态链接器会读取这些配置文件,并根据其中的路径进行查找。如果某个共享库存在于指定的路径中,那么它将被加载到内存中供程序使用。使用/etc/ld.so.conf.d/目录可以方便地管理共享库路径的配置。可以在不同的配置文件中分别设置不同的共享库路径,这样可以根据需要独立地管理和更新路径的配置,而不会影响到其他配置文件。
我们再来看一下具体的操作实例。我们在 /etc/ld.so.conf.d/ 下创建一个test.conf 文件。然后把我们动态库所在的路径编辑加入test.conf 文件中。
我们再看就可以找到动态库了。
四、总结
动态库和静态库是两种代码库的形式,它们主要用于程序的模块化开发和代码共享。它们具有以下不同之处:
静态库(Static Library): 静态库在编译时会被完全链接到可执行文件中,使得可执行文件包含了所有需要的库函数和数据。使用静态库的主要优点是程序的独立性,无需依赖外部库文件即可运行。静态库适用于一些较小的应用,或者需要独立部署的情况。
动态库(Dynamic Library): 动态库在程序运行时由操作系统进行加载和链接,而不是在编译时完全链接到可执行文件。程序在运行时只需要动态库的引用并调用相应函数即可。使用动态库的主要优点是节约系统资源和提高可维护性。多个程序可以共享同一个动态库,减少了内存占用和可执行文件的大小。
为什么要有动态库和静态库呢?主要原因包括:
代码共享:将常用的功能进行封装成库,可以被多个应用程序共享使用,避免重复编写相同的代码,提高了代码的复用性和开发效率。
系统资源的优化:动态库的方式可以在程序运行时动态加载和链接,节省了内存的占用空间,提高了系统资源的利用效率。
可维护性:使用库的方式可以使得程序模块化,便于维护和更新。当库需要升级或修复BUG时,只需替换库文件而无需修改引用该库的程序。