Linux操作系统:软硬链接与动静态库
目录
前言:
一、硬链接
二、软链接
三、静态库
四、动态库
总结:
前言:
我们上一篇文章已经讲完了文件系统,并把我们之前所学的进程,与文件系统关联了起来。而本节课我们将为大家带来有关于软硬链接与动静态库的有关知识点。
一、硬链接
我们经过文件系统的相关学习后,就应该知道了真正找到磁盘上文件的并不是文件名,而是inode!
其实在Linux上也可以让多个文件名对应于同一个inode,我们在当前目录下有一个file.txt文件:
随后,输入以下命令:ln file.txt File,让后者对前者进行硬链接:
可以看见,新形成的File文件,与file.txt的inode号码,是一模一样的,并且权限后面的数字,由1变成2了:
此时,File与file.txt文件其实是等价的,所有硬链接地位平等,没有“原始文件”和“链接”之分。
二者这种状态被称为:硬链接!
我们给file.txt文件中写入hello world,其实就等于向File进行写入,因为二者的inode是一样的。
而数字1变为2,其实就相当于是一个引用计数,代表指向该inode文件的文件名的数量,这里有file.txt与File两个文件名,他们都与921122这个inode形成了映射关系,所以就是2
在我们进行硬链接之前,只有file.txt与该inode映射,所以是1。当我们把file.txt删除后,该引用计数也变为了1:
其实,上述过程就相当于进行了一次重命名操作。在我们执行重命名这个操作时,就相当于是进行了一次硬链接,我们对新的文件名进行一个硬链接,随后删除旧的文件名。
所以我们在linux下可以怎么对文件进行备份呢?
没错,就是进行硬链接就行了。
二、软链接
输入以下命令:
ln -s File file.txt ,新建一个名为file.txt的文件,对File文件进行软连接:
可以看见 ,我们的原文件File与软链接形成的新文件,二者的inode是不一样的,新文件后面的->指向的是他的原文件。所以软链接形成的是一个全新的文件,而这个文件中,保存的是原文件的路径!!
说到路径,同学们想起来了什么吗?
没错,在上节课中,我们曾经提到了路径解析,如果我们有了一个文件的路径,可以通过文件解析的操作,找到他的inode与block,也就是说,我们可以通过软链接来访问到我们的File文件。
其实,在Windows中,软链接就相当于是他的一个快捷方式 ,我们在linux中也可以近似理解。
我们知道了关于引用计数的这个概念,那么我想问一下,当我们新建一个目录文件log,他的引用计数又是多少呢?目录不也是文件的一种吗?
可以看见,他的引用计数一开始就是2,为什么呢?
我们知道,每一个目录中,都存在两个被隐藏的文件: . 与..
.指向的是该目录自己,..指向的是该目录的上级目录。
我们可以通过cd ..,来达到返回上级目录的效果,这个是怎么做到的呢?
答案还是:硬链接!
没错,这个其实也是一种硬链接,也就说明了为什么我们新建一个目录,它的引用计数就为2的原因。
我们都知道,目录其实也是文件的一种,那么目录是否存在软硬链接呢?
我们可以看见,当我们想要尝试对目录文件log/进行硬链接时,被提示不能进行硬链接。而我们进行软链接时,却显示成功了。
为什么存在这样的差异呢?
因为要防止目录循环,避免文件系统陷入无限递归的混乱状态,直白点说,就是防止出现循环目录。
目录在文件系统中是一个“文件名 → inode”的映射表。如果允许目录硬链接,可能会出现以下情况:
-
目录
A
包含子目录B
,而B
又硬链接回A
,形成A → B → A
的循环。 -
当工具(如
find
、rm -r
)遍历目录时,会陷入无限循环,导致系统崩溃或资源耗尽。
而软链接的本质是路径跳转,不会破坏文件系统的拓扑结构,它存储的是目标目录的路径字符串,访问时,内核会解析路径,按正常目录层级遍历,不会形成循环依赖。所以我们就可以对目录进行软链接而不是硬链接。
但我们前面不是说. 与..它本质上也是硬链接吗?
哈哈,这就是允许州官放火,不允许百姓点灯了。
.
(当前目录)和 ..
(父目录)本质上确实是硬链接,但它们是由文件系统内核直接管理的特殊硬链接,所以,一般来说这两个文件都会被隐藏起来。
三、静态库
那么,静态库是指的什么呢?
:程序在编译链接的时候,直接把库的代码链接到可执行文件中,程序运行的时候就不再需要静态库了。
由于这种方式,导致程序大小会比较大,但是运行速度会相对较快,因为运行时已经不需要再去找静态库了。
那我们如何生成一个静态库呢?
假设我们有以下四个文件:
首先,我们需要编译源文件为目标文件.o:
-c
表示“只编译不链接”,生成 test1.o
和 test2.o
。
随后,我们使用rs命令打包库:
ar
:归档工具,用于创建静态库。
rcs
参数:
r
:替换已存在的成员。c
:创建库(如果不存在)。
s
:写入索引(加快链接速度)。
libtest.a
:静态库文件名(约定以 lib
开头,.a
结尾)。
test1.o test2.o
:要打包的目标文件。
于是,一个静态库文件就打包好了,如果我们想要使用这个静态库的话:
假设当前目录下,有一个 main.c
调用 test1
和 test2
的函数:
我们只需要输入:
gcc main.c -L. -ltest -o main
命令就可以生成main的可执行文件了。
-L.
:告诉编译器在当前目录查找库。
-ltest
:链接 libtest.a
(省略 lib
和 .a
)。
关于静态库的使用,还有多种方法,我们这里就不再赘述。有兴趣同学可以自己下来深入去了解一下。
四、动态库
.so
文件)是程序运行时才加载的库文件。与静态库不同,动态库不会在编译时被直接嵌入到可执行文件中,而是在程序启动或运行时动态加载。
也就是说,我们使用gcc命令时要指明动态库,运行程序时,也要指明动态库!
因为在我们运行程序时,程序的代码会被加载到内存上,随后要使用到库的代码了,就需要把库也加载到内存上(运行时指明了动态库),如果此时,有其他程序也会用到该动态库的代码,会把该程序的PCB内的虚拟地址空间部分的页表进行映射,实现共享动态库。
其实就是两个进程的PCB都映射到了该内存地址,于是都能使用同一份代码,这也就是为什么我们说它节省空间的原因。
那么我们应该怎么生成一个动态库呢?
同样是以上四个文件,我们要生成一个动态库,就需要先对其源文件生成位置无关代码,这是动态库必需的,因为库可能被加载到不同的内存地址:
我们使用-fPIC
随后生成动态库(.so
文件):
-shared
:告诉 gcc
生成的是动态库。
-o libtest.so
:指定输出的库文件名(通常以 lib
开头,.so
结尾)。
如果我们想要编译程序并链接动态库
可以使用如下命令:
gcc main.c -L. -ltest -o main
-L.
:告诉编译器在当前目录查找库文件。
-ltest
:链接 libtest.so
(编译器会自动补全 lib
和 .so
)。
想要运行程序的话,动态库就麻烦了很多。由于动态库是运行时加载的,系统需要知道去哪里找 .so
文件。有几种方法:
方法 1:临时设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./main
LD_LIBRARY_PATH
是一个环境变量,告诉系统去哪里找动态库。
当然这个方法只在当前终端有效,关闭后失效。
方法 2:永久配置
将 .so
文件复制到系统库目录(如 /usr/local/lib
),然后更新动态库缓存:
sudo cp libtest.so /usr/local/lib/ sudo ldconfig
ldconfig
:更新系统的动态库缓存,使新库生效。
方法 3:编译时指定 rpath
gcc main.c -L. -ltest -Wl,-rpath=. -o main
-Wl,-rpath=.
:告诉程序运行时在当前目录找 .so
文件。
这个方法适合我们平常调试代码,避免频繁修改环境变量。
总结:
本篇文章为大家带来了软硬链接与动静态库的相关概念,如果有疑问的欢迎评论区或者私信指正交流!!
希望对大家有所帮助,谢谢!