Linux相关概念和易错知识点(21)(软硬链接、动静态库)
目录
1.软硬链接
简单地说,对一个文件进行链接就是针对该文件创建快捷方式。不过软硬链接有所区别。
(1)软链接
使用ln -s (源文件) (快捷方式)可以创建一个软链接。
软链接文件有独特的标识,最前面的标识符不是目录的d,也不是普通类型文件的-,而是l
针对软链接文件,对它进行访问操作,会自动根据软链接文件里面保存的路径直接访问目标文件。我们所有访问这个软链接文件的操作都会转为访问目标文件(vim、cat等),这是文件类型的特性。除此之外,软链接文件具有文件的一切属性(inode、权限、修改时间等),它是一个完整的、独立的文件,只不过具有快捷方式的功能,可以帮我们在其它地方快速访问文件。
软链接的内容是目标文件的路径,这就意味着如果目标文件的路径修改,这个软链接文件的内容无法指向指定文件,就会失效。
要特别注意软链接是通过路径来定位目标文件的,需要保证文件名、路径所途径的目录相同才能定位,而不是通过inode进行定位的。
我们把文件还原回来,软链接文件的内容又能链接上了,因此还能恢复软链接功能,而不是那种一旦失效就永久失效的文件。
(2)硬链接
软链接本质上就是通过文件和文件的映射来实现创建快捷方式的操作的。硬链接直接从inode入手。
①实现方式及其功能
使用ln (源文件) (快捷方式)可以创建一个硬链接。
我们已经知道目录文件的内容就是文件名和inode的映射表。一个文件名对应一个inode,这个inode一般情况下只会在系统中出现一次,引用计数就是1。但硬链接本质就是在当前目录中新增一组新的文件名和inode的映射关系。相当于一个文件有了两个文件名。重命名过程就是依赖硬链接完成的。
硬链接是采用inode定位目标文件的,因此无论目标文件跑到哪,硬链接都能定位上。
硬链接通过inode引用计数的方式创建快捷方式,除了inode精准定位以外,还带来了一个很厉害的功能,就是无消耗备份。
文件的inode属性在底层是保存在硬盘中的,当打开文件后读取inode,并会将新的inode属性写回去。其中inode属性就包含自己的引用计数。如果引用计数为0,就说明没有文件名指向inode,也就是说此时文件已经失效了,系统这时候就会删除文件。如果一个文件引用计数为2,那么当我们对文件进行删除时,系统不会删除文件,而是只会对文件inode的引用计数减1。这就意味着,当我们创建了一个硬链接时,可以认为对文件创建了一层备份,当误删文件时,由于引用计数的特性,文件会被保存下来。
②硬链接在目录中的运用
硬链接我们可以说是天天见。
这下我们能够进一步理解.和..的原理了。当我们创建目录时,这个目录里面就自动创建了两个硬链接,一个是指向本级目录,一个是指向上级目录。
我们还能由此解释,为什么创建目录时引用计数一来就是2
我们也能解释根目录是如何特殊处理cd ..命令的了,它只需要将根目录的上级目录硬链接自己就行了。所以我们在根目录cd上级目录还是根目录。但是有个细节需要注意,根目录的..会特殊处理,不会增加inode的引用计数,我们了解即可。
由于Linux存储结构是树形的,而目录的硬链接恰好会破坏树状结构。
其实我们想想,目录和.本质上已经构成了环状结构,只不过紧挨着两级的环状结构退化成了线性结构。Linux为防止我们造出环状路径,让树状结构更混乱,因此Linux不允许我们针对目录文件进行硬链接,有且仅有.和..是硬链接。因此要对目录创建快捷方式,我们只能用软链接,软链接本质上只是个带路径并特殊处理的文件,所以软链接针对目录文件没有任何问题。
③计算子目录数量
我们借助.和..自动创建的特性,可以通过引用计数推断子目录的数量,毕竟每创建一个子目录,其父目录就会多一个硬链接..。
只要子目录下的..目录指向自己,都要加引用计数。软链接对应目录的上级目录一般不增加引用计数。
注意根目录也遵循这条规则,但根目录的..目录也指向自己,如果增加引用计数,那就不符合规则了,所以系统会特殊处理,其..不会增加引用计数。
2.动静态库
(1)动态库
①动态链接和静态链接
之前我介绍过动静态链接的区别,读者可自行查看。
简单来说,动态库就是.so文件,如果可执行程序采用动态链接,可执行程序必须要和.so联合才能正常执行(头文件不需要,预处理时就已经展开)。
静态库就是.a文件,如果可执行程序采用静态链接,只需要可执行程序就能正常执行(所有库方法都存入可执行程序里了)。
这次我将侧重于动静态库的模拟实现,进一步加深我们对动静态库的理解。
②动态库的实现
第一步,就是先将库的源文件变成.o文件,gcc -fPIC。这样生成的.o才能用于.so的生成
第二步,将所有的相关的.o文件都打包成.so文件。这些.o文件里面对应的就是函数具体实现,-shared选项告诉编译器不要形成可执行程序。
注意生成动态库的名字必须满足前缀lib,后缀.so,这会影响后续的识别
第三步,生成可执行程序,注意-I(大写的i),-L和-l(小写的L)三个选项的含义,定位到具体的头文件目录才能进一步查找(预处理)。定位到具体的目录和.so文件才知道调用的动态库(编译、链接)。
③系统查找动态库问题
按照上述步骤,我们本应能正常执行可执行程序了,但事实上还不行,因为ldd test看出我们的动态库并没有地址指向,这也就意味着当执行时系统不知道到哪里去找动态库的实现。
可是我们不是已经-L和-l定位了文件吗?
这是因为-L和-l是在gcc编译和链接时使用的,而我们是想让这个可执行程序变成一个进程,执行它时,系统找的时候会按照自己的默认路径去找,这和我们在gcc使用的-L和-l没有任何关系,所以是找不到我们的可执行程序的。从另一个角度上讲,动态库的存在就是为了脱离可执行程序,交由系统管理和调用,因此动态库的地址是不能存到在可执行程序里面的,这会增强耦合度。是系统在执行我们的程序时,按照默认的路径去找要调用的动态库,再正常执行。
因此,我们要解决的就是动态库的配置问题了,要让系统能够找到动态库。
④解决系统查找动态库问题
既然知道可执行程序和动态库解耦合,动态库全权交由系统管理和调用,因此唯一解决系统查找动态库的问题的方法就是让系统默认查找路径能够找到动态库。以下方法均遵循这个思路。
a.拷贝动态库到系统默认路径下
将.so文件放在系统/lib64或者/lib(根据系统决定,可自行尝试),这样系统就能查找到动态库,正确的加载动态库并执行。
b.在系统层面进行软链接
利用ln -s 在/lib64或/lib里面创建软链接,本质原理还是让动态库保存在系统查找的必经之路上。
unlink (完成文件名)可以删掉软链接
c.修改环境变量
Linux系统中,OS查找动态库依赖环境变量提供的路径,OS按理来说是根据LD_LIBARY_PATH环境变量中查找,我们可以配置环境变量用于修改系统查找.so的默认路径。
但在Ubuntu下面,大多数人遇到的是这种情况。
这需要我们先进行一些设置操作,在~/.bashrc最底行添加下面的语句,保证该环境变量能被使用。
export
LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:./lib:/lib:/lib64:/usr/lib:/usr/lib64:/usr/local/lib
在这之后,我们每次登录就都能看到LD_LIBARY_PATH的值,当然我们也可以对它进行修改以满足我们寻找动态库的需求。注意LD_LIBARY_PATH里面的值是系统会默认查找的路径,但不是意味着只会到LD_LIBARY_PATH指定的路径查找,这只是我们添加系统默认查找路径的方法之一。我们可以通过修改.bashrc里面的路径,来保障系统会默认来指定的位置查找动态库。这就意味着我们不需要将这个动态库安装到/lib或者/lib64里面,相对而言比较灵活。
d.全局配置路径
系统默认的查找路径还会从/etc/ld.so.conf.d目录读取,我们可以使用root身份,新建.conf文件,名字随便取保证后缀即可。使用echo强制在里面写读取路径(路径指向.so对应目录即可)。ldconfig更新配置文件使其生效,之后我们就可以正常执行文件而不需要将动态库安装到/lib或者/lib64目录下了。
以上4种方式提供了动态库的查找问题,前两种的核心思想是想办法安装在系统中,后两种则是侧重于修改查找路径使得系统能够找到它。
⑤动态库和头文件存放位置对编译过程的影响
a.动态库
上面就解决可执行程序的执行已经讲解了存放动态库到系统目录/lib或者/lib64的重要性。将动态库安装在系统中,除了系统能够找到动态库,正常执行可执行程序之外,我们的编译过程gcc可以省去-L这个选项,可以不再指定目录进行编译链接。
但特别注意,-l(小写L)这个选项不能省去(就算一个目录里就只有一个动态库,这是统一规定),因为编译过程要面对的动态库很多,编译器在编译时只知道声明而不知道定义在哪,找不到我们的动态库,所以只有显式指定。执行可执行程序时不需要指定动态库名字、只需要路径的原因是可执行程序已经有了动态库的名字了,只需要路径就能找到。理解两者的差异,搞清楚为什么要这么做很重要。
b.头文件
/usr/include/是系统的头文件目录,如果我们将头文件放到该目录下,预处理时,使用<xxx.h>就能够找到,如果放在源文件目录下,需要用"xxx.h","xxx.h"会先在当级目录查找,再到系统查找,而<xxx.h>只会在系统中查找。除此之外,"xxx.h"在查完系统目录后还能在制定目录下查找,就是-I(大写的i)对应目录。
注意,头文件的展开是预处理干的事,预处理之后头文件就没意义了,所以上述注意事项是在gcc/g++编译时需要注意的。当生成可执行程序之后,头文件在不在、在哪都不会影响可执行程序的执行。
(2)静态库
①和动态库的区别
理解了动态库,静态库相对而言就比较简单了,大部分过程都是相似的。动态库是将.so安装在系统目录中或者让系统能够找到它,在执行程序时调用它,动态库不存放在可执行程序中,因此可执行程序相对较小。
而静态库文件是.a文件,其命名规则是前缀lib,后缀.a,在编译后会将整个静态库文件的内容拷贝到可执行程序中。因此,gcc编译后的.a和头文件存不存在、在哪都不会影响程序的执行,一个可执行程序可以独立运行,但代价是可执行程序相对较大,很多函数的实现都放在里面。
在gcc编译过程中,同样需要面临静态库文件的定位问题,这和动态库的gcc定位问题解决方法一致,都是使用-I(大写i)、-L、-l(小写L)辅助定位头文件和静态库。
②静态库打包
我们将所有的库.c编译成.o,这样我们拿着所有的.o打包成一个静态库,和动态库思路一致。
静态库打包使用指令ar -rc(存在就replace,不存在就create)lib.a (前缀必须是lib,后缀必须是.a) 1.o 2.o 3.o,之后我们将库文件和对应头文件交给别人,他们就能正常使用我们的库了,而不会知道源文件是什么样子。
注意事项和动态库的差不多,相对而言静态库在操作上还要简单一些。
③静态编译
在编译代码时,若我们同时提供动静态库,gcc/g++默认使用动态库。我们只有提供选项-static强制静态链接,同时必须提供静态库,不然会报错。但如果我们只提供静态库,但链接方式是动态链接(只要没有指定静态链接,都默认动态链接)的话,gcc和g++没得选,会针对.a采用局部性静态链接。
原文地址:https://blog.csdn.net/SGlow0708/article/details/143697116
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!