Linux基础IO--软硬链接与动静态库
目录
1、软硬链接
1.1、基本认知
对文件进行软硬链接非常简单,只需要通过 ln -s 或 ln 对文件进行链接即可,生成的链接文件类型为 l(普通文件为 -,目录文件为 d)
对文件
myproc进行软链接,生成软链接文件my-sort,其中软链接文件名可以自定义
ln -s myproc my-sort

生成硬链接文件就更简单了,对文件
myproc进行硬连接,生成硬连接文件my-hard,其中硬链接文件名也可以自定义
ln myproc my-hard

注意: 可以对目录进行软链接,但不能对目录进行硬链接,具体原因后面再解释
生成的软硬链接文件如何使用呢?
- 像源文件一样使用即可,结果一模一样(因为当前软硬链接的都是同一个源文件)

虽然此时的软硬链接执行结果一致,但这两种链接方式在本质上有很大区别
- 软链接文件的
inode编号与源文件不同(独立存在),软连接文件比源文件小得多,并且 软连接文件->源文件,软连接的产生了一个新文件,软连接文件内容是链接文件的地址。 - 硬链接文件与源文件共用一个
inode编号(对源文件其别名),硬链接文件与源文件一样大,并且硬链接文件与源文件的链接数变成了2
软链接文件依赖于源文件,而硬链接文件是源文件的别名
当我们将源文件删除后,软连接失效;硬链接仍然有效,不过硬链接数变为了 1
同样是对源文件进行链接,为何两种链接方式差别如此大呢?这就不得不谈一下它们的实现原理了
1.2、实现原理
软链接又称为符号链接,它是一个单独存在的文件,拥有属于自己的 inode 属性及相应的文件内容,不过在软连接的 Data block 中存放的是源文件存放的地址,因此软连接很小,并且非常依赖于源文件。

因此如果源文件被删除了,那么在执行软连接文件时,其中的地址就是一个无效地址(目标文件已丢失),此时就会报错 No such file or directory
假设只是单纯的删除软连接文件,那么对源文件的内容没有丝毫影响,就好比 Windows 桌面上的快捷方式,有的人以为将快捷方式(软链接)文件删除了,就是在 “卸载” 软件,其实不是,如果想卸载软件,直接将其源文件相关文件夹全部删除即可。
硬链接并非创建一个相同的文件进行链接,而是在源文件所目录下的 【inode编号 与文件名对应表中】,新增 【inode 编号与硬链接文件名】的映射关系,并将 inode 结构体中的引用计数 +1,表示当前已成功硬链接上了一个文件。
当删除当前 inode 对应文件时,会 先判断 ref_count 是否为 1,如果是,才会将文件内容及其属性真正删除,否则删除的只是 文件名 与 inode 编号的映射关系
这也就解释了为什么删除源文件后,硬链接文件不受任何影响,仅仅只是 硬链接数 - 1,同理,删除硬链接文件,也不会影响源文件
为什么新建目录的硬链接数为
2?
- 因为一个目录在新建后,其中默认存在两个隐藏文件:
.与.. - 其中
.表示当前目录,..表示上级目录
Linux 中的目录结构为多叉树,即当前节点(目录)需要与父节点(上级目录)、子节点(下级目录)建立链接关系,并且还得知道当目录的地址,否则就会导致切换目录时出现错误
为了避免因用户的误操作而导致的目录环状问题,规定用户不能手动给目录建立硬链接关系,只能由 OS 自动建立硬链接,比如新目录后,默认与上级目录和当前目录建立硬链接文件,在当目录下创建新目录后,当前目录的硬链接数 + 1

小技巧:将目录的硬链接数 - 2,就可以知道该目录中有多少个目录了
ls -d //只查看目录文件

1.3、应用场景
软链接可以当作快捷方式使用,比如快速运行一个藏的很深的可执行程序
而硬链接一是可以用来当作目录移动的工具,二是可以用来给重要的源文件起别名并使用,一旦发生删除等不可逆行为时,可以确保源文件的安全
注意: 硬链接并不是将源文件直接进行备份,而是新建立 inode 编号与硬链接文件名的映射关系,同时 struct inode 中的引用计数 ref_count++,只有当 ref_count == 1 时才会真正删除文件内容及属性,否则都只是在取消映射关系和 ref_count--。
1.4、取消链接
取消链接的方式有两种:
- 直接删除链接文件
- 通过
unlink取消链接关系
1.5 为什么目录不能硬链接:
-
防止循环引用:
-
如果允许硬链接目录,可能会形成循环(例如,目录 A 链接到目录 B,目录 B 又链接回目录 A),这会导致程序像
find等工具无法正确处理,可能会进入死循环。
-
-
破坏目录结构:
-
目录在文件系统中是树状结构,允许硬链接会让目录结构变得复杂,不再是树形的,而是可能变成一个图,导致文件系统变得混乱。
-
-
防止错误操作:
-
目录的操作(如删除)需要谨慎。如果允许硬链接目录,删除一个目录时可能会影响其他目录,导致文件系统出现错误或者数据丢失。
-
2、动静态库
接下来学习动静态库的相关内容,了解程序运行时是如何调用资源的
2.1、认识库
常见的库文件:stdio.h、stdlib.h、string.h 等
库分为 动态库 和 静态库
Linux 中,.a 后缀为静态库,.so 后缀为动态库
Windows 中,.lib 后缀为静态库,.dll 后缀为动态库
虽然不同环境下的后缀有所不同,但其工作原理是一致的
库命名
比如 libstdc++.so.6
去掉前缀跟后缀,最终库名为 stdc++
查看当前环境中的库文件
find /usr/lib64/libc*

C++ 中具体库文件可以这样查看
find /usr/lib64/libstdc*

无论是 C语言 还是 C++,在编写程序时,一定离不开库文件,比如之前模拟实现的 FILE 类型,就位于 stdio.h 这个库中,动态库优势比静态库明显,因此在编译代码时,默认采用动态链接的方式,如果想指定为静态链接编译,只需要在 gcc/g++ 语句后面加上 -static 即可(前提是得有静态库)
一般云服务默认只会存在动态库,静态库需要单独安装

关于动静态库的优缺点可以看看下面这个表格
注意: 静态库是将所需要的函数代码拷贝到源文件中直接使用,而动态库是通过动态链接的方式,进行函数链接使用
2.2、库的作用
所以,库文件到底有什么用?
- 提高开发效率
系统已经预装了 C/C++ 的头文件和库文件,头文件提供说明,库文件提供方法的实现
- 头和库是有对应关系的,需要组合使用
- 头文件在预处理阶段就已经引入了,链接的本质就是在链接库
简言之,如果没有库文件,那么你在开发时,需要自己手动将 printf 等高频函数编写出来,因此库文件可以提高我们的开发效率,比如 Python 中就有很多现成的库函数可以使用,效率很高
语法提示是如何做到的?
安装开发环境
实际上是在安装编译器、开发语言配套的库和头文件
编译器的 语法提示功能来源于头文件(语法提示其实就是搜索)我们在写代码时,开发环境是怎么知道语法错误或其他错误的?
编译器有命令行模式,还有其他自动化模式,编写代码时,不断进行主动编译,排查错误
3、制作静态库
现在有一些简单的计算 demo 函数,能满足整型的 +- 计算,将这些代码作为库进行打包
myadd.h
#pragma once
//声明功能
int add(int x, int y);
myadd.c
#include "myadd.h"
//定义功能
int add(int x, int y)
{
return x + y;
}
mysub.h
#pragma once
//声明功能
int sub(int x, int y);
mysub.c
#include "mysub.h"
//定义功能
int sub(int x, int y)
{
return x - y;
}
主函数中将对这些自定义的库函数进行调用
test.c
#include <stdio.h>
#include "myadd.h"
#include "mysub.h"
int main()
{
printf("2 + 3 = %d\n", add(2, 3));
printf("18 - 6 = %d\n", sub(18, 6));
return 0;
}
3.1、静态库的打包
静态库的打包主要分为以下两步:
1.将源文件进行 预处理->编译->汇编,生成可链接的二进制 .o 文件
2.通过指令将 .o 文件打包为静态库
将文件编译为 .o 二进制文件
gcc -c myadd.c mysub.c

将所有的 .o 文件打包为一个静态库(库名自定义)
其中的 mycalc 为库名
ar -rc libmycalc.a *.o

ar是GNU提供的归档工具,常用来将目标文件打包为静态库
我们还可以使用ar反向查看静态库中的具体文件
ar -tv 静态库文件
获得静态库后,就可以进行使用了
注:此时的 .h、.c、.o 文件位于 myinclude 文件夹中,而静态库文件 .a 位于 mylib 文件夹中
3.2、静态库的使用
方法一:通过指定路径使用静态库
如果直接编译程序,会出现编译失败的情况,因为编译器不认识第三方库(需要提供第三方库的路径及库名)

第一方库:语言提供
第二方库:操作系统提供
第三方库:other 提供的库,比如当前我们直接打包的静态库
对于自己写的的第三库的使用,需要标注三个参数:
-I 所需头文件的路径需要将所需头文件的路径加上,此处为./myinclude-L 所需库文件的路径这里加的是库文件的路径,也为./mylib-l 待链接静态库名所需要链接的静态库名字,这里为mycalc
不要知道头文件的名字是因为在源文件已经有了名字。
将选项加上后重新编译
gcc -o myproc test.c -I./myinclude -L./mylib -lmycalc

为什么编译
C/C++代码时,不需要指定路径?
- 因为这些库都是系统级的,
gcc/g++默认找的就是stdc/stdc++库
方法二:将头文件和静态库文件安装至系统目录中
除了这种比较麻烦的指定路径编译外,我们还可以将头文件与静态库文件直接安装在系统目录中,直接使用,无需指定路径(需要指定静态库名)
所谓的安装软件,就是将自己的文件安装到系统目录下
sudo cp ./myinclude/*.h /usr/include/
sudo cp ./mylib/*.a /lib64/

注意: 将自己写的文件安装到系统目录下是一件危险的事(导致系统环境被污染),用完后记得手动删除
4、制作动态库
除了可以制作静态库外,我们还可以制作动态库,这里用的例子和上面一样
4.1、动态库的打包
动态库不同于静态库,动态库中的函数代码不需要加载到源文件中,而是通过 与位置无关码 ,对指定函数进行链接使用
动态库的打包也同样分为两步:
- 编译源文件,生成二进制可链接文件,此时需要加上
-fPIC与位置无关码 - 通过
gcc/g++直接目标程序(此时不需要使用ar归档工具)
将源文件编译为 .o 二进制文件,此时需要带上 fPIC 与位置无关码
gcc -c -fPIC *.c

将所有的 .o 文件打包为动态库(借助 gcc/g++)
gcc -o libmycalc.so *.o -shared

获得动态库后,就可以进行使用了
注:此时的 .h、.c、.o 文件位于 myinclude 文件夹中,而动态库文件 .so 位于 mylib 文件夹中
4.2、动态库的链接与使用
像使用静态库一样使用动态库(指定路径及库名),编译成功,但运行失败!
为什么会出现这种问题?因为当前只告诉了编译器动态库的位置,没有告诉 OS
通过 ldd 查看程序链接情况:
运行时,OS 是如何链接动态库?
- 环境变量 LD_LIBRARY_PATH (默认没有这个环境变量),将第三方动态库路径添加至此环境变量中(临时方案)
- sudo 在 /lib64/ 目录下建立动态库的软链接
- 更改配置文件 /etc/ld.so.conf.d 这个目录中都是各种动态库配置文件,创建文件 xx.conf 至目录中(文件中存储的是第三方动态库的路径)ldconfig 令配置文件生效
以上三种方式都可以正常使用动态库,下面就来逐个进行尝试
方法一:通过环境变量解决
添加动态库路径至 LD_LIBRARY_PATH 环境变量中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/Yohifo/linux/Explore/file/blog/Test_5_3/mylib

环境变量 LD_LIBRARY_PATH 是程序在进行动态库查找时的默认搜索路径
注意: 更改环境变量只是临时方案,重新登录后会失效
方法二:将动态库的软链接文件存入系统目录中
sudo ln -s /home/Yohifo/linux/Explore/file/blog/Test_5_3/mylib/libmycalc.so /lib64/libmycalc.so

注意: 创建软连接文件时,需要使用绝对路径!
方法三:更改配置文件中的信息
echo /home/Yohifo/linux/Explore/file/blog/Test_5_3/mylib > Yohifo.conf sudo mv Yohifo.conf /etc/ld.so.conf.d/ sudo ldconfig

注意: 后两种方法都可以做到永久生效(因为存入了系统目录中),但在使用完后最好删除,避免污染系统环境
4.3、动态库的链接原理
1. 静态链接与动态链接的地址访问方式
-
静态链接:在程序编译时,所有的函数和变量会被复制到最终的可执行文件中,并且在链接时使用的是 绝对地址。这意味着函数和数据在内存中的位置在编译时就已经确定。
-
如果多个地方调用了同一个函数,函数代码会被多次复制,导致代码冗余。
-
-
动态链接:动态库在程序运行时被加载,函数调用使用的是 相对地址。通过动态库的 起始地址 + 偏移量 访问特定的函数或变量。
-
同一份动态库只会被加载一次,程序中的多个调用会直接引用这份已加载的库,避免了代码冗余。
-
2. 位置无关代码(PIC)
-
位置无关代码(PIC) 是指代码在编译时不依赖于固定的内存地址。即使动态库被加载到不同的内存地址,代码依然能够正确运行。通过使用相对地址(而非绝对地址),动态链接的代码可以在任意地址加载,并且不会影响功能。
-
fPIC(position-independent code)是编译时的一个标志,表示编译器生成的位置无关代码,确保动态库能够在任何地址被加载并正确工作。
-
3. 动态链接的优势
-
动态链接的最大优势是节省内存,因为多个程序可以共享同一个动态库,库只需加载一次。即使多个程序使用同一个函数,动态链接也只需要一次加载,并且使用相对地址调用,避免了代码冗余和浪费。

更多推荐



所有评论(0)