您的当前位置:首页正文

【Linux】软硬链接、动静态库

2024-11-08 来源:个人技术集锦

1. 软链接

1.1. 概念

1.2. 特点

  1. 独立文件:软链接文件在文件系统中是一个独立文件,拥有自己的inode编号和属性。

1.3. 应用场景

  1. 动态库版本管理:对于共享库,可以使用软链接来管理不同版本动态库之间的切换。如:创建一个指向最新版本动态库的软链接,当库升级时,仅需要更新软链接指向新版本的动态库。

2. 硬链接

2.1. 概念

  1. 硬链接概念:指多个文件名指向同一个物理文件的链接关系。这些链接在文件系统中具有相同的inode编号,但可以位于不同的目录中。

建立硬链接,并没有新建文件,而是为文件创建了新的文件名,硬链接不是一个独立的文件,因为inode与目标文件的inode相同。

  1. 硬链接本质是在指定目录下,插入新的文件名和inode之间的映射关系,并让inode中的硬链接计数++。

ln 源路径 硬链接路径

2.2. 硬链计数

一、硬链接计数的概念

  1. 概念:在linux中,每个inodo中都有一个硬链接计数,它表示有多少个文件名指向这个inode。

二、硬链接计数的变化场景

  1. 创建文件:当创建一个新文件时,这个文件的硬件计数默认为1,因为此时只要一个文件名指向这个inode。

  2. 创建硬链接:当使用ln创建一个文件的硬链接,不会创建一个新的文件,只会为该文件创建一个新的文件名,并增加inode中的硬链接计数。

  3. 删除文件:当删除一个文件时,会删除指向该文件的文件名,并减少inode中的硬链接计数,如果此时硬链接计数等于0,文件才会被删除,系统回收该inode及其所占的磁盘空间。

三、硬链接计数的限制

  1. 不能跨文件系统:硬链接只能在同一文件系统内创建,不能跨文件系统或分区,因为inode只在分区内唯一。

  • Linux中,每个目录都包含两个特殊的目录 “.“和”. .”,".“代表当前目录,”. ."代表上级目录,它们在目录被创建时就自动生成,并作为硬链接存在。

2.3. 特点

  1. 共享inode:硬链接与原始文件共享同一个inode编号,意味着它们指向同一个物理文件的数据块,对物理文件所做的任何更改都将反映在所有硬链接上。

  2. 增加访问路径:创建硬链接实际上是为文件增加了一个新的访问路径或文件名。

  3. 无差别访问:无论通过哪个文件名访问文件,都指向同一个inode,即:指向同一个文件内容。因此硬链接与原始文件无差别,二者等价。

  4. 节省空间:由于硬链接共享相同的inode和数据块,因此它们不会占用额外都磁盘空间。

  5. 删除与移动:如果删除目标文件或硬链接,只要inode中的硬链接计数不为0,文件仍可以被硬链接访问,直到硬链接计数=0,文件才被真正删除。

2.4. 应用场景

硬链接应用场景如下:数据备份、数据共享、数据恢复、版本控制、自动同步等。

3. 动静态库

  1. 库文件:将多个对象文件(.o文件)打包在一起的文件。

如果我希望其他人能够使用我编写的方法,通常的方法是提供一组同名方法.o文件(预编译的对象文件),然后将所有.o文件打包形成一个库文件,和相应的同名方法.h头文件。即:库文件 + 头文件。

3.1 库存在的原因

  1. 提高开发效率。

库底层封装了重复的代码和常用的功能,使得开发者在编写项目时可以直接使用这些现成的代码,而无需从头开始编写,这大大减少了开发的时间,提高了开发效率。

  1. 隐藏源代码。

这意味着用户只能看到库提供的接口(通过头文件.h),而无法直接访问或修改库中的源代码,这有助于保护开发者的知识产权和代码实现。

3.2. 静态库制作与使用

#include"my_add.h"

int my_add(int a, int b)
{
    return a + b;                                                                    
}
#include"my_sub.h"

int my_sub(int a, int b)
{
    return a - b;                                                                    
}

3.2.1 打包

ar [options] archive-file object-files

  1. archive-file是静态库名(lib库名.a);object-files是要添加到库中的对象名(.o文件)。

  2. 常见的选项

-c:创建库文件,如果库已存在,则会被覆盖。

-r:向库文件中添加.o文件,如果.o文件已在库中存在,则会被替换。

-t:列出库文件中包含的.o文件列表。

-v:在执行过程中显示详细的信息。

第一步:编译形成 .o 文件。

第二步:使用ar命令,将所有.o文件进行打包,形成静态库文件。

第三步:将库进行标准化。

  1. 库的标准化:通常将头文件或库文件分别放在不同的文件夹中,可以提高项目的可维护性和组织性。

libmyc.a:my_add.o my_sub.o  //第二步:使用ar命令,将所有.o文件进行打包,形成静态库文件
    ar -rc $@ $^

%.o:%.c  //第一步:编译形成 .o 文件
    gcc -c $<
 
.PHONY:clean  
clean:
    rm -rf *.a *.o mylib mylib.tgz
  
.PHONY:output  //第三步:将库进行标准化
output:   
    mkdir -p mylib/include
    mkdir -p mylib/lib 
    cp -rf *.h mylib/include/
    cp -rf *.a mylib/lib/
    tar czf mylib.tgz mylib   

3.2.2. 使用

#include"my_add.h"
#include"my_sub.h"
 
int main()
{
    int a = 4, b = 3;
    printf("%d + %d = %d\n", a, b, my_add(a, b));
    printf("%d - %d = %d\n", a, b , my_sub(a, b));
    
    return 0;                                                                            
}
  1. -I(i的大写):用来指定编译器搜索头文件的额外路径。
  1. -L:用来指定链接器搜索库文件的额外路径。

当链接器在链接中需要找到某个库文件(.so、.a),它先会在标准的位置(系统默认的库路径)中查找,如果查找不到,链接器就会使用-L指定的路径进行搜索。

  1. -l(L的小写):用来指定链接器在链接过程中要链接的库。

这些选项分别用于控制编译和链接过程中的头文件、库文件的搜索路径和库文件的选择。

3.3. 动态库制作与使用

3.3.1. 打包

第一步:使用-fPIC选项,编译形成 .o 文件。

  • -fPIC:用于指示编译器生成与位置无关的代码,无论代码被加载到内存的哪个位置,它都能正确运行,而不依赖于它在编译或加载时的具体地址。这种特性通过使用相对寻址,而不是绝对寻址来实现的。这对于创建共享库是至关重要的,因为共享库可以在进程地址空间的任何位置被加载。

第二步:使用-shared,将所有.o文件进行打包,形成动态库文件。

  • -shared:告诉编译器gcc生成一个共享库(.so或.dll文件)。

第三步:将库进行标准化。

libmyc.so:my_add.o my_sub.o  //第二步:使用-shared,将所有.o文件进行打包,形成动态库文件
    gcc -shared -o $@ $^

%.o:%.c  //第一步:使用-fPIC选项,编译形成 .o 文件
    gcc -c -fPIC $<
 
.PHONY:clean  
clean:
    rm -rf *.so *.o mylib mylib.tgz
  
.PHONY:output  //第三步:将库进行标准化
output:   
    mkdir -p mylib/include
    mkdir -p mylib/lib 
    cp -rf *.h mylib/include/
    cp -rf *.so mylib/lib/
    tar czf mylib.tgz mylib   

3.3.2. 使用

现象:程序运行时,OS加载动态库文件myc失败,即:系统找不到这个文件。

4. 解决’动态库查不到’的4种方法

4.1. 库安装

  1. 将库或其他软件安装到系统中,本质是把对应的文件,拷贝到系统默认的搜索路径中。

  2. 在64位系统,系统中库默认的搜索路径为/lib64、/usr/lib64;在32系统,系统中库默认的搜索路径为/lib、/usr/lib。

4.2. 软链接

4.3. /etc/ld.so.conf.d配置文件

  1. /etc/ld.so.conf.d目录下的配置文件,用来指定额外的库文件的搜索路径,以便动态链接器能够在运行时找到并加载这些库文件。

4.4. LD_LIBRARY_PATH环境变量

  1. LD_LIBRARY_PATH:是一个环境变量,在linux中,为动态链接器指定额外的库搜索路径。

5. 动静态链接的选择

  1. 动静库同时存在:gcc默认会使用动态库进行动态链接;若想要进行静态链接,必须加上-static选项。

  1. 只存在静态库:可执行程序只能进行静态链接,程序不一定整体是静态链接的。

可执行程序只能进行静态链接,指的是对于特定库的链接方式: 当系统中只存在某个库的静态版本,任何尝试链接到这个库的程序都必须使用使用静态链接,因为动态链接器在运行时无法找到这个库的动态版本。

程序不一定整体是静态链接的,取决于它所依赖的所有库,以及编译链接时的选项: 一个程序可能依赖于多个库,此外,即使程序使用了静态链接来链接某些库时,它仍可能依赖于动态链接的库(C、C++标准库或系统库),这些库在OS级别上提供,且以动态链接的形式存在,以便在多个程序之间共享。

  1. 只存在动态库:只能进行动态链接,若非得进行静态链接,编译器或链接器会报错。

6. 理解动态库加载

6.1. 站在OS角度理解

一、动态库概念

  1. 动态库:也称为共享库,是一种包含代码和数据,可以在多个程序之间共享的文件,存放在磁盘上。

与静态库不同,静态库在程序编译时会被完全复制到可执行文件中,而共享库则在程序运行时被加载到内存中,如果多个程序使用同一个共享库,OS会让这些进程共享内存中的同一份库代码和数据,即:动态库的代码和数据在内存中只存在一份。

  1. 管理:系统中可以同时存在多个已经被加载的库,OS需要管理它们,先描述(包含了加载地址等信息)、再组织。

二、动态库加载的过程

  1. 检查依赖:程序启动时,动态链接器会检查该程序依赖的所有动态库。

  2. 搜索路径:动态链接器会在预设的库搜索路径中查找所需的动态库文件。

  3. 加载与映射:第一次加载、后续加载。

  1. 优点:节省内存、易于更新、提高了程序的性能和安全性。

6.2. 编址

可编址的范围:32位平台,[0, 2^32] -> [0, 4GB] ,64位平台,[0, 2^64] -> [0, 16GB]。

  1. 绝对编址:在编译和链接过程中,符号的地址是固定的,即:已经确定了符号的实际的物理内存地址。这种方式要求程序运行时,必须加载到特定的物理地址处,否则无法正确的运行。

在计算机早期,程序使用绝对编址,因为当时系统比较简单,程序通常使用直接映射到物理内存的绝对编址,而没有复杂的内存管理和保护机制,如:虚拟内存、页表等。

  1. 相对编制:也成为逻辑地址、虚拟地址。在编译和链接过程中,符号的地址是不固定的,而是相对于某个基地址的偏移量。这种方式允许程序在加载时动态确定实际地址,从而实现位置无关代码。

现代计算机系统广泛使用相对编址,因为这提供了更好的灵活性和安全性。

  • 当基地址确定后,OS会使用可执行程序中各个区域的虚拟地址,来初始化进程地址空间、页表。每个区域在进程地址空间都有一个相对应的虚拟地址范围。在程序被加载到内存中,OS会自动分配物理内存,并构建页表来建立虚拟地址和物理地址之间的关系。

6.3. 理解动态链接和加载问题

6.3.1. 一般程序的加载

一、一般程序加载的过程

  1. 读取可执行文件,并解析文件中各个段的信息:OS先从存储介质(如硬盘)中读取可执行文件(程序代码、数据以及依赖的库),然后OS会解析可执行文件中的各个段(如代码段、数据段、堆栈段等)的信息。这些段包含了程序运行所需的所有指令和数据。

  1. 初始化数据段和未初始化数据段、构建页表。

三、CPU执行程序的过程

6.3.2. 动态库的加载

显示全文