本文已收录至《Linux知识与编程》专栏!
作者:ARMCSKGT
演示环境:CentOS 7
软硬链接和动静态库
前言
当我们安装一款软件成功后,桌面会多一个图标,如果我们点击图标就能启动软件,但那个图标就是软件本身吗?显然不是!图标只是软件启动程序的一个快捷方式,在Linux中可以通过软硬链接来创建类似于Windows下的软件快捷方式,Windows下创建快捷方式一般相当于Linux中的软连接;当Windows中的一个软件启动之后会加载各种文件,一般我们看到的软件启动程序都很小,但是软件安装包却很大,例如游戏,其实软件中大部分都是库文件,关于Linux下的库,本节将会详细为您介绍!
正文
软硬链接
关于软硬链接,其中涉及Linux文件系统的知识,如果有疑问,请先移步:Linux文件系统概述
原理
软链接
软链接又称为符号链接,软链接是一个单独的文件,启动这个软链接程序会跳转启动这个软链接指向的程序,既然是一个单独的文件,那么软链接文件就拥有独立的inode和文件属性,软链接的主要存储内容是源文件的地址,所以软连接一般很小且依赖源文件!
因为软连接非常依赖源文件,所以源文件一旦不存在或被源文件路径出错,再次启动该软链接(快捷方式)则会出现 No such file or directory 的错误提示,表示系统没有找到对应文件!
当我们删除软链接文件时,并不会对源文件产生任何影响,就像我们删除Windows桌面上的快捷方式并不是清除了该软件一样,想删除该软件必须删除该软件所在的安装目录!
硬链接
硬链接与软链接不同,硬链接没有独立的inode和文件属性,其只是在目录中创建了与源文件inode和硬链接名 的映射关系,并在源文件的文件属性中引用计数进行+1,表示增加了一个硬链接!
当我们删除一个硬链接时,文件系统会判断该链接的inode源文件ret_count计数是否为0,如果减去1后引用为0则删除该文件,否则只是删除该目录下的一个硬链接映射关系且源文件引用计数-1,所以删除硬链接文件,只有删除最后一个硬链接才是真正删除文件,否则也不会对源文件产生影响!
所以,硬链接并不在意源文件,因为只要硬链接存在,文件就存在,而软链接的源文件被删除则软链接失效!
普通文件可以建立硬链接和软链接,但是目录只能建立软链接!
当我们新建一个文件时,其默认硬链接数为1,而目录的默认硬链接数为2
为什么新目录的硬链接数为2? 因为新目录与父级目录建立了硬链接
在Linux系统中, . 表示本目录,**. .**表示上级目录!
所以在父级目录下,每创建一个新目录,其硬链接数就会+1,因为新目录与父级目录构建的新的硬链接关系,但是Linux系统不允许用户手动为目录创建硬链接,因为这样会破坏文件系统的树结构,严重时可能会变成图,使得文件系统崩溃!
硬链接就像是浅拷贝,只拷贝了inode。
使用
生成软链接文件:
bash$ ln -s 源文件路径 软链接文件名
注意:可以对目录进行软链接,其方式与普通文件进行软链接方式相同!
生成硬链接文件:
bash$ ln 源文件路径 软链接文件名
虽然软硬链接在使用上没有差别,但是底层差别还是非常大的!
取消链接:
- 删除链接文件
bash$ rm 链接文件
- unlink取消链接
bash$ unlink 链接文件
注意:普通文件的首个标识符为 -,软链接文件标识符为 l,而硬链接本质上是源文件的别名,所以也是普通文件 -!
文件时间
每一个文件从创建之日起就有三个时间(简称为 ACM 时间):
- 访问时间 Access :最后一次查看内容的时间,如何判定取决于系统
- 修改属性时间 Change:最后一次修改文件属性的时间
- 修改内容时间 Modify:最后一次修改文件内容的时间
注意: 因为modify是文件属性,modify修改了change也会修改;更改文件内容与查看文件内容不一样,区别于access和modify,查看文件改变access,修改文件改变modify,Linux系统并非每次查看都会改变文件的access时间(因为修改access涉及文件属性的修改需要读取硬盘),有一定的策略,这样可以减少与硬盘的交涉提高效率。
我们可以通过 stat 命令查看文件属性和ACM时间:
bash$ stat 文件名
动静态库
库介绍
原来在学习C语言时,写一下学校课程设计可能会需要函数的声明和定义进行分离,这样可以让代码显得不凌乱!
其实图中的test.cpp就可以充当一个C/C++的函数库,但是我们在使用时需要结合头文件test.h一起使用!
但是在实际的项目开发过程中,一个保密的项目是不能透露库中的源代码的,我们只想留下 头文件 声明函数接口,而具体实现放在一个保密的库中(别人可以使用,但是无法获取函数实现),此时我们就需要引入动静态库了!
说明: 一般系统已经预装了C/C++头文件和动态库文件,头文件提供方法说明,库提供方法实现,头和库是有对应关系的,要组合在一起使用;头文件是在预处理阶段就引入的,链接的本质其实就是链接库;引入库文件一方面是为了提高开发效率(代码复用性等),另一方面可以保密!
无论是在Linux系统中还是Windows系统中,都存在库,分为动态库 和静态库,虽然不同环境下的后缀有所不同,但其工作原理是一致的!
- Linux 系统中:.a 后缀为静态库,.so 后缀为动态库
- Windows 系统中:.lib 后缀为静态库,.dll 后缀为动态库
在Linux系统中,库命名一般为:libstdc++.so.6 ,去掉前缀 lib 和后缀 .so.6 ,剩下的就是库名 stdc++ 。
我们可以使用命令查看Linux环境中的库文件:
bash# 查看C库 $ find /usr/lib64/libc* # 查看C++库 $ find /usr/lib64/libstdc* # 查看所有库 $ find /usr/lib64/lib
该图只截取了一部分,实际上有很多库!
在我们写C/C++程序时,离不开库函数,比如我们使用的各种STL容器,库中只会提供给我们头文件开放接口进行使用,并不会让我们看到具体实现 ,其具体实现已经打包成库(二进制文件),在平时使用时,编译器默认使用动态库而非静态库,动态库在日常使用具有较大优势 !
如果我们想指定使用静态库编译,在gcc/g++下,只需要加上 -static 选项即可,但是前提是Linux环境中已经安装C/C++静态库,一般服务器和虚拟机是没有安装的!
C/C++静态库的安装(非root用户需要sudo提权):
bash# 安装C静态库 yum install -y glibc-static # 安装C++静态库 yum install -y libstdc++-static
动静态库比较:
区别 静态库 动态库 空间大小 占用空间较大 占用空间小 调用方式 函数实现在程序中,直接在程序中执行 通过地址转到动态库中执行动代码 加载速度 库函数加载到程序中,空间占用大 多个程序共享一个已加载的动态库,空间占用小 依赖性 直接在程序中运行 依赖于动态库 补充: 动态库是在函数调用出留下地址链接,在调用该函数时转到内存中的动态库在指定的函数位置执行代码;而静态库是将函数实现直接拷贝到调用出,运行时直接执行!
简言之,如果没有库文件,那么你在开发时,需要自己手动将 printf/cout 等高频函数编写出来,因此库文件可以提高我们的开发效率,比如 Python 中就有很多现成的库函数可以使用,效率很高!
一般库分为三种:
- 第一方库:语言自带的库
- 第二方库:操作系统提供的库
- 第三方库:我们自己制作的库或网上下载的库
静态库
静态库制作
准备库文件和头文件
add.hpp
cpp#pragma once int add(int a,int b);
add.cpp
cpp#include "add.hpp" int add(int a,int b) { return a+b; }
sub.hpp
cpp#pragma once int sub(int a,int b);
add.cpp
cpp#include "sub.hpp" int sub(int a,int b) { return a-b; }
main.cpp
cpp#include <iostream> #include "add.hpp" #include "sub.hpp" using namespace std; int main() { cout<<add(1,2)<<endl; cout<<sub(1,2)<<endl; return 0; }
编译链接形成二进制程序:
bash$ g++ main.cpp add.cpp sub.cpp -o exe -std=c++11
注意:声明与定义分离的情况下,需要指明链接的函数实现文件
接下来开始打包成静态库,主要分为两步:
- 1.将源文件进行 预处理->编译->汇编,生成可链接的二进制 .o 文件
bash$ g++ -c add.cpp sub.cpp
执行完毕后生成与文件名相同的 .o 二进制链接文件
- 2.通过 ar指令 将 .o 文件打包为静态库
bash#格式:ar -rc { lib静态库名.a } {链接文件1.o 链接文件2.o ...} $ ar -rc libmymath.a add.o sub.o # 也可以简写为:ar -rc libmymath.a *.o
注意:实际的静态库名为 mymath 但是gcc/g++编译器会识别静态库自动去掉头(lib)尾(.a),按照规范我们必须这样写!
此时就成功生成了静态库文件!
ar指令除了可以打包链接文件成为库文件,还可以查看库文件中的打包内容:
bash$ ar -tv {静态库文件.o}
静态库的使用
我们自己指针的库是第三方库,在使用上区别与第一方和第二方库区别很大!
此时如果我们直接编译程序,是无法编译的!
我们将上面使用和生成的文件进行规整:
将头文件放在_hpp目录,库文件放在lib,链接文件放在_o,实现文件放在_cpp,留下main.cpp在本目录下,这样在本目录下就既没有头文件也没有实现文件了,此时我们进行静态编译!
1.直接使用静态库
bash# 格式:g++ 代码文件.cpp -I 头文件夹路径 -L 库文件夹路径 -l 静态库名(去掉头尾) $ g++ main.cpp -o static_exe -I ./_hpp -L ./lib -l mymath -static #静态链接 $ g++ main.cpp -o exe -I ./_hpp -L ./lib -l mymath #动静混合 # gcc使用方法与此相同
使用第三方库与使用标准库和系统库不同,使用标准库和系统库都是系统级的,gcc/g++ 默认找的就是 stdc/stdc++ 库,而第三方库则需要我们自己指定!
2.将头文件和静态库文件安装至系统库下
Linux系统C/C++头文件路径:/usr/include/
Linux系统库路径:/lib64/
bash# 注意:非root用户需要sudo提权 $ cp ./_hpp/*.hpp /usr/include/ $ cp ./lib/*.a /lib64/
将库和头文件安装到系统目录下后,只需要指定库名即可完成编译链接!
bash$ g++ main.cpp -o exe -l mymath #动静混合 $ g++ main.cpp -o static_exe -l mymath -static #静态链接
注意:这里极其不提倡这种私自安装外部库的方式,将自己写的文件安装到系统目录下是一件危险的事(导致系统环境被污染),如果不得已,用完后记得手动删除!关于静态链接
静态链接,是在链接时将调用函数处的函数调用替换为函数执行代码(二进制代码),可执行程序从根本上脱离了库的限制,在同系统的机器上无论是否有静态库都可以运行,不依赖任何静态库!
静态库所体现的优点是运行比较快,但静态链接形成的可执行程序本身就有静态库中对应方法的实现(将代码拷贝到可执行程序中),静态链接的程序,非常占用资源,内存中会程序多个重复的代码(可执行程序体积变大加载占用内存),也占用磁盘资源,如果是给用户用的软件那么下载周期就会变长,占用网络资源!
注意:如果要使用静态链接需要加上 -static 否则编译器将使用静态库进行动态库和静态库的混合链接!
动态库
动态库制作
我们准备与静态库制作相同的五个文件
打包为动态库分为两步:
- 1.编译源文件,生成二进制可链接文件.o同时形成与位置无关码
bash# 格式:gcc -c -fPIC {实现文件.cpp} $ g++ -c -fPIC add.cpp sub.cpp # gcc 使用方式与g++相同
同样的,会形成 .o 链接文件!
说明:fPIC这个选项是形成.o文件同时形成与位置无关码,动态库中里面的代码地址排布与静态库是有区别的,动态库经常使用,所以gcc/g++自带打包动态库功能。
- 2.打包链接文件形成动态库
bash# 格式:g++ -shared { 链接文件.o } -o { libmymath.so } g++ -o libmymath.so *.o -shared # gcc 使用方式与此相同
说明:shared表示形成(打包成)动态库或共享文件
动态库的使用
同样的,我们先规整文件到不同的文件夹
我们使用静态库的方法去编译可执行程序:
bash$ g++ main.cpp -o exe -I ./_hpp -L ./lib -l mymath
之所以会运行失败,是因为当前只告诉了编译器动态库的位置,没有告诉 OS!
出现这种情况,可以使用 ldd 指令查看程序的链接情况:
bash$ ldd 可执行程序
动态库处,显示状态为没有链接,未发现动态库!
这也说明,我们不能盲目进行链接,在此之前我们需要让操作系统知道库的位置,有三种方法:
- 1.修改临时环境变量: 添加动态库路径至 LD_LIBRARY_PATH 环境变量中;因为环境变量 LD_LIBRARY_PATH 是程序在进行动态库查找时的默认搜索路径。
bash$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/动态库目录详细(绝对)路径/
注意: 更改环境变量只是临时方案,重新登录bash后就失效了,需要重新配置
2.将动态库的软链接文件存入系统目录中
我们将动态库建立软链接放入系统库目录中(普通用户需要sudo提权)
bash# ln -s /动态库绝对路径/ /lib64/动态库名称(与软链接的动态库名称相同) $ ln -s /home/ARMCSKGT/演示文件夹/lib/libmymath.so /lib64/libmymath.so
注意: 创建软连接文件时,需要使用绝对路径;这种方式的修改是永久有效的,但是我们不建议使用,如果非必要建议用后删除,避免冲突和污染系统环境。
- 3.更改配置文件中的信息
系统的第三个动态库查找路径就是前往动态库配置目录的各配置文件提供的路径中查找,我们可以写一个自己配置文件,里面写入动态库的路径,放入该目录中!
bash$ touch mylib.conf #创建一个配置文件 $ echo /home/ARMCSKGT/演示文件夹/lib/ > mylib.conf #将动态库绝对路径写入配置文件 $ mv mylib.conf /etc/ld.so.conf.d/ #将配置文件放入系统目录/etc/ld.so.conf.d/中 $ ldconfig #重新加载系统配置文件
说明: mv和ldconfig操作在普通用户操作下需要sudo提权
注意: 这种方式的修改也是永久有效的,如果非必要建议用后删除,避免冲突和污染系统环境。关于动态链接
动态库的链接:将可执行程序中的外部符号替换为库中的具体地址,在程序执行时直接将库地址加载到内存中,只要我们把库加载到内存,映射到进程的地址空间中后,我们的代码执行库中的方法,就依旧还是在自己的地址空间内进行函数跳转,当库因为程序调用已经在内存中,另一个程序使用时就直接在页表中映射然后在共享空间中调用即可!
动态库的方法只需要在内存中存在一份就可以被所有进程使用了,库的加载也不一定是全部加载到内存,使用什么加载什么就行,具体根据操作系统的策略就行操作。
程序在链接动态库函数时,是通过 动态库起始地址 + 所链接函数偏移量 的方式进行链接访问的,而这个偏移量就是 fPIC 与位置无关码!
地址就两类:绝对编址 and 相对编制
静态链接时,将可链接的二进制文件加载至程序中,直接通过 绝对地址 进行链接,如果函数被调用了多次,就会导致代码冗余等问题。
动态链接采用 相对地址 的方式进行链接,同一个函数的 动态库起始地址 + 所链接函数偏移量 值相同,代码只需要加载一份,并且可以任意位置进行函数调用(与位置无关)。动态库必定面临一个问题:不同的进程,运行程度不同,需要使用的第三方库不同,注定了每一个进程的共享空间中的空闲位置是不确定的
动态库中的地址,绝对不能确定,去使用绝对编址,动态库中的所有地址都是偏移量,默认从0地址开始,当一个库真正被映射到地址空间时,他的起始地址才能真正确定,动态库在进程的地址空间中随便加载,与我们加载到地址空间的什么位置毫无关系!
补充
当同时存在动态库和静态库时,编译器默认采用动态库!
如果我们想指定生成静态库程序,只需要加上 -static 选项即可:
bash$ g++ [代码文件.cpp] -static #如果是第三方库需要指定头文件,库路径和库名称
如果只提供静态库,但编译时不加-static,编译时会将静态库进行静态链接(结合具体的情况)(将可以动态链接的库动态链接,然后将不能动态链接的静态库中代码拷贝到文件中),可以动态链接的库进行动态链接!
静态链接生成的程序比动态链接大得多,并且内含静态库的动态链接程序,也比纯粹的动态链接程序大,说明程序不是 非静即动,可以同时使用动态库与静态库!
最后
以上就是 【Linux软硬链接和动静态库】 的全部内容了,本节我们结束了如同快捷方式一样的软硬链接,让我们可以对目标建立链接快速访问,同时学习了程序的运行需要库的加持,动静态库的区别,学习了制作库,最后介绍了库的链接原理,相信本次的介绍可以帮助各位开发者!
本次 <Linux软硬链接和动静态库> 就先介绍到这里啦,希望能够尽可能帮助到大家。
如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
🌟其他文章阅读推荐🌟
Linux<文件系统概述> -CSDN博客
Linux<重定向和缓冲区理解> -CSDN博客
Linux<文件理解和系统调用> -CSDN博客
Linux<进程控制> -CSDN博客
Linux<进程地址空间> -CSDN博客
🌹欢迎读者多多浏览多多支持!🌹