【Linux】软硬链接 && 动静态库(很详细)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、软硬链接

1.1、见一见

1.2、特征

1.3、什么是软硬链接,有什么用?(场景)

为什么有软链接?

软链接有什么用?

硬链接有什么用?

二、动态库和静态库

2.1、静态库与动态库

2.2、静态库的知识

建立静态库的命令——ar

使用第三方静态库

动态库 VS 静态库

2.3、动态库的知识

生成动态库

使用第三方动态库

那么可执行程序运行时,怎么让操作系统找到动态库呢?

方法一(拷贝)

方法二(软链接)

方法三(命令行导入环境变量)

方法四(修改.bashrc配置文件,让环境变量永久生效)

方法五(新增动态库搜素的配置文件)

附加问题:动态库加载 --- 可执行程序和地址空间

理解动态库加载

我们来看一下动态库具体是如何加载的

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、软硬链接

1.1、见一见

  • ln -s:ln ---> link(链接);-s ---> soft(软)   后者链接前者
  • 两个文件都是独立的文件,因为都有各自的inode编号
  • 软连接的文件删除对目标文件没影响,但是目标文件删除之后,软连接的文件就没用了
  • 删除连接文件,可以 rm 或者 unlink 后面跟要删除的连接文件名,这两个命令可以删除连接文件

  • 2是文件(inode)的属性,对应的是文件的引用计数
  • 硬链接数:文件名和inode编号的映射关系有几个

创建一个目录,目录的引用计数是2,为什么呢?
任何一个目录下都有一个点(当前目录)和两个点(回退上级目录);目录这个文件名和inode编号有一个映射关系,一个点表示当前路径,那么一个点的inode编号与目录的inode编号一样,相当于一个点就是目录的重命名。

1.2、特征

  1. 软链接是一个独立的文件,因为有独立的inode编号。软连接的内容:目标文件所对应的路径字符串。(类似于windows中的快捷方式)
  2. 硬链接不是一个独立的文件,因为你没有独立的inode编号,你用的是目标文件的inode。硬链接是什么?硬链接就是一个文件名和inode的映射关系,建立硬链接,就是在指定的目录下,添加一个新的文件名和inode编号的映射关系!
  3. 属性中有一列硬链接数。

文件的磁盘引用计数:有多少个文件名字符串通过inode编号指向inode。

定位一个文件,只有两种方式:

  1. 通过路径;----> 软链接
  2. 直接找到目标文件的inode。----> 硬链接

不过这两种方式最终都是要通过inode编号来找到文件的。

1.3、什么是软硬链接,有什么用?(场景)

为什么有软链接?

  1. Linux系统里路径具有唯一性;(软链接的内容:目标文件所对应的路径字符串。)
  2. 系统对路径进行路径解析,进而找到目标文件。(多了一个找到目标文件的方式)

软链接有什么用?

类似于windows的快捷方式。

硬链接有什么用?

  1. 构建Linux的路径结构,让我们可以使用一个点和两个点来进行路径定位;
  2. 一般用硬链接来做文件备份。

文件名是固定的。所有的系统指令在设定的时候,几乎都能知道一个点和两个点是干什么的。

任何一个目录,刚开始新建的时候,引用计数一定是2.

目录A内部,新建一个目录,会让A目录的引用计数自动+1,一个目录内部有几个目录:A的引用计数-2。

acm

下面解释一下文件的三个时间:

  • Access 最后访问时间
  • Modify 文件内容最后修改时间
  • Change 属性最后修改时间

二、动态库和静态库

2.1、静态库与动态库

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文 件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个 过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
ldd a.out
// 作用:帮我们去找对应的可执行程序所依赖的库

gcc -c mymath.c    
// -c选项,后面没有跟指定的目标文件名,就生成同名的 mymath.o 文件
// 目标文件:里面放的是方法的实现
  • 头文件是一个手册,提供函数的声明,告诉用户怎么用。
  • .o提供实现,我们只需要补上一个main()函数,调用头文件提供的方法,然后和.o进行链接,就可以形成可执行。

2.2、静态库的知识

建立静态库的命令——ar

所谓的库文件,本质,就是把所有的.o文件(目标文件:方法的实现)打包。

ar -rc libmyc.a *.o              
// 把所有的.o文件打包,形成一个文件libmyc.a的静态库
  • replace:比如libmy.c文件中打包了100个.o文件,其中有几个.o文件更新了,需要替换
  • creat:不存在libmy.c文件,就创建
  • 库的名字一般都是以 lib 开头,myc才是库的真正的名字,.a静态库的后缀

使用第三方静态库

gcc main.c -I ./mylib/include/ -L ./mylib/lib -l myc
  • -I:include的意思,动态的由程序员自己去指定一个头文件的路径
  • -L:动态的由程序员自己去指定一个库文件的路径;但是库有非常多个
  • -l:由程序员指定一个具体的库名字

如果你上面的命令当中没有直接包含你写的头文件的话,gcc编译器默认是在系统的头文件目录里去找对应的头文件,不会去找你写的头文件。

所谓的安装和卸载对应的库,包括安装和卸载对应的可执行程序,本质就是把对应的可执行程序、头文件和库文件拷贝到系统指定的目录下。

  • #include<stdio.h>  左右尖括号:是在系统的头文件目录下去找stdio.h
  • #include "stdio.h" 双引号:优先在系统的头文件路径下找,找不到就在当前的路径下去找对应的头文件

动态库 VS 静态库

  • 默认链接的是动态库。
  • 如果你没有使用 -static,并且只提供.a为后缀的静态库文件,那么只能静态链接当前的.a库文件,其它库正常动态链接。
  • -static的意义是什么呢?必须强制的。将我们的程序进行静态链接,这就要求我们链接的任何库都必须提供对应的静态库版本。
gcc main.c -I ./mylib/include/ -L ./mylib/lib -l myc -static
  • -static:静态链接;没有-static的选项时,优先使用动态库,没有动态库时,才使用静态库;
  • 静态库:将库(库里面包含了需要的目标文件,目标文件就是方法的实现)里面需要的函数的实现拷贝到可执行程序中。

ar:建立静态库的命令
静态库还用ar命令来形成,因为动态库用的比例远远大于静态库,所以编译器gcc就可以直接形成动态库,不需要专门再去形成了。
结论:gcc不仅仅可以用来形成可执行程序,还可以用来形成动态库

 gcc *.o -o libmyc.so 
  • 我们想要将所有的.o目标文件形成了一个libmyc.so可执行程序,但是这行代码是错误的;
  • 库的源代码里面不能出现main()函数,如果将库里面写一个main()函数打包给用户,那么用户不管怎么写代码都会出现main()冲突的问题。

2.3、动态库的知识

所谓的库文件,本质,就是把所有的.o文件(目标文件:方法的实现)打包。

生成动态库

  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so
// 我们想要形成库,不要形成可执行程序:
gcc -shared -o libmyc.so *.o

使用第三方动态库

gcc main.c -I ./mylib/include/ -L ./mylib/lib -l myc

执行上面的命令,我们已经提供了第三方库的名称,但是为什么执行可执行程序时,依然找不到目标文件呢?

  • 那是因为你只告诉了gcc/g++编译器,却没有告诉操作系统。上述操作只是让我们的程序和动态库在链接的时候,产生了关联。
  • 在可执行程序运行期间,也需要让操作系统帮我们把对应的动态库加载进来,才能运行。
  • 静态库为什么没有这个问题?编译期间,已经将库中的代码拷贝到我们的可执行程序内部了!加载和库就没有关系了。

系统在运行期间,会自动帮我们加载对应的动态库,它的查找路径默认是 ll /lib64;编译时和与运行时,默认都会在这个路径下找。

那么可执行程序运行时,怎么让操作系统找到动态库呢?

方法一(拷贝)
cp mylib/lib/libmyc.so /lib64

将自己写的动态库拷贝到默认路径下,但是这种方式不好。

sudo rm /lib64/libmyc.so

删除掉刚拷贝到系统路径下的动态库。

方法二(软链接)
sudo ln -s /home/song/111/roommate/mylib/lib/libmyc.so /lib64/libmyc.so

建立软连接,后者链接前者,因为是在lib64(系统的库文件路径)目录下建立新文件,所以要sudo提升权限。

方法三(命令行导入环境变量)
  • 系统当中存在一个环境变量 LD_LIBRARY_PATH ----> 加载库路径;
  • LD_LIBRARY_PATH:系统当前动态库的搜索路径;
  • LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/song/111/roommate/mylib/lib 在环境变量中添加动态库的路径。
方法四(修改.bashrc配置文件,让环境变量永久生效)
  • 修改bachrc配置文件,让环境变量永久生效;
  • bachrc配置文件改动之后,想让配置文件立马生效:source ~/.bachrc
方法五(新增动态库搜素的配置文件)

系统的配置路径下,有一个/etc/ld.so.conf.d目录,在这个目录当中,存在很多系统级别的配置文件;你可以把你写的动态库所对应的路径添加到配置文件里,将配置文件在系统层面上加载一下,那么配置文件就永久生效了。

  1. 因为/etc/ld.so.conf.d这个目录是root的,所以切换到root账号下;
  2. cd /etc/ld.so.conf.d  切换到该目录下;
  3. touch bit_111.conf 新建一个文件,必须以.conf结尾;
  4. vim bit_111.conf 打开文件,将你写的动态库的路径粘贴到该目录下,保存退出;
  5. 在root账号下,执行ldconfig命令,将/etc/ld.so.conf.d该目录下的配置文件在系统层面上生效。

执行ldconfig命令,将/etc/ld.so.conf.d目录下新增动态库搜索的配置文件,在系统层面上生效。

ld:load  so:库  conf:配置文件  d:目录

  • 没有 -static 选项,优先选择动态库,没有再使用静态库
  • 如果有 -static 选项,就只要静态库,动态库不要

附加问题:动态库加载 --- 可执行程序和地址空间

理解动态库加载

可执行程序和动态库本质都是磁盘当中的文件;我们要编译,要运行可执行程序都得先找到我们对应的文件;要找文件,就必须知道文件的路径。

静态库不需要考虑库的加载,因为在代码编译期间,就已经将静态库当中对应的方法加载到程序当中了;所以我们只需要考虑可执行程序加载到内存的情况了。库的加载与静态库无关。

因为编译期间并没有将动态库的方法加载到可执行程序中,只是在链接时,让可执行程序和动态库产生关联。

如果加载到更多的进程,而这些进程也是需要使用这个库文件的话,库文件是不需要加载多份的,只需要在内存中加载一份库文件就可以了,其它进程只需要通过页表与这份库文件建立映射关系就可以了。

可执行程序没有加载到内存中,也没有变成进程,也没有形成进程地址空间,那么能不能出现目标地址?
结论:objdump -S xxx:代码xxx转到反汇编。可执行程序内部就包含了地址。

代码中的各种变量、函数名等各种被定义的的变量,代码被翻译成二进制代码的时候,对应的变量名和函数名就不在了,变成了地址。

  • 我们一般在Linux中形成的可执行程序的格式,叫做ELF格式的可执行程序。
  • 代码编译成二进制格式之后,二进制是有自己固定的格式的,固定格式中有elf可执行程序的头部,头部有很多可执行程序的属性。
  • 各个区域的起始和结束地址的信息,都是从elf格式的可执行程序的头部信息里来的。
  • 加载器对可执行程序进行扫描,获得各个区域的起始和结束地址的信息。
  • CPU在读取时,CPU内部存在一个PC指针的寄存器,它保存的是正在执行当前指令的下一条指令的地址。

mm_struct是结构体对象(虚拟地址空间),里面存放了多个成员变量,那么这些成员变量的起始和结束地址是从哪来的呢?

OS不能给我们的地址空间来分配初始值,因为OS不知道各个区域的起始和结束地址;而我们的可执行程序本身就有各个区域的起始和结束地址,只有可执行程序自己知道各个区域的范围,所以给地址空间初始值,是由可执行程序来提供的。

我们可以先不在内存当中加载对应可执行程序的代码和数据,但我可以把程序线性当中的构建出来的各个区域的起始和结束的虚拟地址,包括main()函数的入口地址(这些地址信息是从elf格式的可执行程序的头部信息中的来的),来填充结构体。堆和栈是由动态运行的,由OS帮我们指定。

虚拟地址空间的概念,不是OS独有的,而是,要有OS支持,编译器支持,加载器支持。是一套设计标准。

我们来看一下动态库具体是如何加载的

  1. 在可执行程序加载到内存之前,加载器会读取可执行程序相关的虚拟地址信息,初始化地址空间,并且初始化CPU,让CPU中的PC寄存器保存我们整个程序的main函数的入口地址;
  2. 将程序加载到内存时,那么每一个代码就存在了物理地址,虚拟和物理地址都有,就可以通过页表填充映射关系了;
  3. 当代码按行执行到调用myc动态库中的myAdd()函数的方法时,OS申请物理内存,将myc动态库加载进来,只要myc加载到内存中,那么库的起始地址就有了,要使用库,就得把库映射到我们的虚拟地址空间;
  4. 操作系统在当前进程的堆和栈之间,申请一段虚拟地址空间(myc库),通过页表将库的虚拟和物理地址建立映射关系;
  5. 在正文部分调用库的myAdd()函数,跳转到虚拟地址的库当中,找到虚拟地址中的myAdd()函数的虚拟地址(库的起始地址 + myAdd()函数的地址(偏移量)),通过页表找到函数的物理地址,执行完函数后,拿到结果,再跳转回来,PC寄存器在顺便保存下一条指令的地址,执行后续的代码。

库被映射到虚拟地址空间的什么位置,重要吗?

不重要,只要偏移量没变,就不影响代码的运行。

OS管理已经被加载的库:先描述,再组织。

你怎么知道一个库有没有被加载?

访问库时,拿着库的名字,在库的描述结构体链表里遍历,如果发现库确实没有被加载,可执行程序的链接里有库的路径,找到库文件,把库的二进制代码加载进来,并创建描述该库的结构体,入链表。


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

  • 39
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值