当前位置: 首页 > article >正文

软硬链接与动静态库

1、软硬链接

(1)软链接

①如何建立

代码:ln -s  原文件名  软链接文件名

②特征

可以发现软链接文件与原文件的inode编号是不同的,这说明两者是两个不同的文件。软链接的内容其实是目标文件所对应的路径字符串,类似于windows下的快捷方式。

③使用场景

在项目打包时,一般分为bin、conf、log等目录,bin目录下一般是存放可执行程序。如果我们要执行bin目录下的可执行程序,那么就要如下写代码:

我们的做法是在proc项目目录下,创建一个软链接:

所以说,软链接其实就是一种快捷方式,其内部存的是目标文件的路径字符串。

(2)硬链接

①如何建立

用代码: ln 目标文件 硬链接文件名 

②特征

观察硬链接和目标文件的inode编号,发现其是一样的,说明硬链接和目标文件是同一个inode编号映射的不同文件名。而在权限属性后有一个数字,表示的是硬链接数。当创建一个硬链接后,目标文件和硬链接文件的硬链接数都变为2了。

③目录文件的硬链接数

硬链接数其实是一个inode内部的引用计数,当该引用计数为0时系统则会删除该文件。当创建一个空目录时,其默认硬链接数是2.(示例为newdir)

这是因为在目录内部,默认会有目录的硬链接".";而在创建新目录后,上级目录的硬链接数会加一,因为新目录内部有上级目录的硬链接“..”。根据该特性,我们可以借此数当前目录的子目录个数=硬链接数-2。

系统禁止我们对目录创建硬链接,目的是为了避免形成路径环绕。(系统的. ..会形成路径环绕,但其本身能够处理,所以不会造成影响)

④使用场景

以上的. ..就是使用场景之一,是为了方便用户进行路径切换;

第二个使用场景就是文件备份。例如在某公司的网盘系统,用户A上传一个视频,该视频被很多用户已经上传过了,那么再上传时,系统就不会再将A上传的视频再下载一份,而是直接在A的网盘系统中用一个硬链接将本来已有的视频备份,这就叫快速上传功能。

2、动静态库

(1)静态库

①本质

静态库本质就是将库中的源代码编译成为.o目标文件,并打包成一个库文件。静态库的在linux系统中的后缀是.a(在windows中后缀是.dll)。静态库在编译时直接被复制到文件中。

②实验

先将多个.c文件编译成.o文件:

再将.o文件打包成静态库:

注意这里的名字取得不好,所以后面进行重命名了,命名为libmyc.a;而在编译时只用中间的myc就行了。

我们的做法是,构建一个mylib的目录,该目录的结构如下:

此时编译就可以通过了:

运行a.out:

注意test.c的代码:

#include"mystdio.h" 
#include "mymath.h"
//双引号表示从当前路径下找

//#include<mystdio.h>
//#include <mymath.h>
//尖括号表示头文件从系统中找

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    myFILE* fd=my_fopen("log.txt","w");
    const char* buffer="test text:";
    my_fwrite(fd,buffer,strlen(buffer));
    my_fflush(fd);
    my_fclose(fd);
    int x=1,y=2;
    fprintf(stdout,"%d + %d = %d\n",x,y,Add(x,y));

    return 0;
}

③代码解释

a、-I(大写i) 指定用户自定义的头文件路径;头文件是一个手册,告诉用户怎么使用函数,是函数的声明。包含头文件时用<>表示从系统路径中找;用""则表示从当前路径下找,如果-I不写的话,那么一定要写成""的形式,否则在系统路径中找不到自己下载或书写的库。

b、-L指定用户自定义库文件路径;.o文件提供实现。

c、-l(小写L)指定确定的第三方库名称

为什么平时使用gcc编译时,没有加这些选项?

因为默认gcc是可以认识C标准库的,而我们自己写的或者下载别人的库叫第三方库,gcc默认是不认识的。

用ldd查看可执行程序a.out依赖的库,发现并不是自己打包的静态库,而是系统自带的动态库。原因是因为这些依赖的库都只是动态库,而静态库已经把自己的代码拷贝到可执行程序里面了。这里该可执行程序既依赖了动态库,又依赖了静态库,一般来说,可执行程序依赖的库有动态链接的优先动态链接,如果特定的库只提供静态库会把静态库拷贝到可执行程序,也就是说一个可执行程序依赖的库可以是动静态库混杂的现象。gcc默认是动态链接的,但有些库只提供了静态库,那么就只能静态链接(将静态库中内容拷贝到程序中);有静态库有动态库时。默认动态链接,除非在编译时加上-static 强制静态链接。

④作用

静态库的使用是为了提高开发效率。(动态库也是这个作用)

(2)动态库

①本质

动态库本质也是将库中的源代码编译成为.o目标文件,并打包成一个库文件。动态库的在linux系统中的后缀是.so(在windows中后缀是 .lib)。动态库会在程序运行时加载到内存中。

②实验

编译成.o文件:(要带上fPIC)

将.o文件打包成动态库:

将动态库放入mylib目录下

编译、运行,发现a.out正常编译但无法正常运行,且其依赖的动态库找不到:

③代码解释及动态库的使用方式

解释:上述编译的时候,指定了编译器编译时寻找动态库的路径,所以编译成功了;而动态库是在进程启动后动态加载到内存中的,而在运行时,并未指定寻找动态库的路径,系统则会从默认的搜索路径 /lib64 去寻找, 所以找不到所依赖的动态库。

动态库如何使用?

方法一:将打包形成的动态库安装到系统中(放到系统的 /lib64/ 下)

此时可正常运行:

方法二:在系统路径 /lib64/ 下建立一个指向自己的动态库的软链接

结果a.out还是运行不了,这是因为命名少了一个.so

方法三:更改环境变量LD_LIBARARY_PATH

这种方法是内存级的,重启系统后会丢失该环境变量信息。

方法四:修改.bashrc配置文件,让环境变量永远生效,这里不演示了。

方法五:新增动态库搜索的配置文件 /etc/ld.so.conf.d 

配置文件查看,并新建一个配置文件:

在配置文件中加入路径(这里是/home/zwh/mylib/mylib/lib):

再加载一下

此时重新进入该系统也能正常运行。

3、虚拟地址空间详述与动态库加载

(1)可执行程序与虚拟地址空间

.c等文件在编译成功后形成可执行程序,是ELF格式的,ELF是可执行程序的头部,涵盖了可执行程序的属性,也就是说该格式可以获取可执行程序的各属性。编译后的可执行程序,其内部有很多行汇编语句,每条汇编语句都有他的地址。

编译时设定地址采用的是平坦模式,也就是按照0000...000~FFFF...FFF,从基地址为0开始编址。而这时的地址,可以理解成虚拟地址,(本质是逻辑地址,但含义类似)。这里的编址方式是相对编址。

当可执行程序运行时,系统为可执行程序创建进程。进程=内核树结构+代码与数据。该创建过程,是先建构内核数据结构,再将代码与数据加载到内存中。内核数据结构有 task_struct,虚拟地址空间mm_struct,以及页表。因为可执行程序的格式是ELF,所以很容易通过ELF+加载器,将程序的各部分起始地址、结束地址拿到,用这些地址来建构虚拟地址空间mm_struct,并将页表的虚拟地址部分填好。与此同时,在CPU内部有一个寄存器pc指针,其中存储的是下一次执行代码的虚拟地址,在这个过程中会将main函数的入口地址放入pc指针。

然后将程序的代码和数据加载到内存中,此时就获取到了他们在内存中的物理地址,然后将这些物理地址填入页表。

当cpu开始运行之后,通过pc中的虚拟地址,结合页表找到物理地址,从而开始执行代码。

以上的过程可以看出,虚拟地址空间的概念,不是OS独有的,而是OS、编译器、加载器共有且统一的。

(2)动态库加载

在上面内存中的代码执行时,遇到动态库中的函数时,则会先判断库有没有被加载,如果没有,则会从磁盘中将动态库加载到内存。动态库是.o文件的集合,其内部是已经编译过的代码,也就有其虚拟地址,这里的地址也是相对地址(相对于某个起始值的偏移)。加载到内存之后,也就有了其物理地址。

此时将其虚拟的相对地址,放入虚拟地址空间mm_struct中的共享区部分,此时其在mm_struct中的虚拟地址是起始位置+相对地址(偏移量)。然后将此时的虚拟地址填入页表左侧,而在内存中的真实物理地址填入右侧。在可执行程序的代码中,调用动态库中的函数,就是通过偏移量call到共享区里去寻找对应的方法,然后当函数执行完返回的时候又会回到代码区执行后续代码。这样的函数调用就是代码区与共享区指令来回执行的过程。

所以在编译动态库时,我们加上-fPIC选项,其中fPIC叫做位置无关码。

系统是如何判断动态库是否加载进来的?

对每个加载在内存中的动态库先描述再组织,形成一个个的结构体,这些结构体彼此相连形成链表;判断需要的动态库是否需要加载只要查询这个链表就可以做到。


http://www.kler.cn/a/381098.html

相关文章:

  • 用Python PySide6 复刻了两软件UI 做下练习
  • 题解 洛谷 Luogu P1135 奇怪的电梯 广度优先搜索 BFS C/C++
  • 32. 线程、进程与协程
  • 程控电阻箱应用中需要注意哪些安全事项?
  • Pytorch | 从零构建ParNet/Non-Deep Networks对CIFAR10进行分类
  • 维克日记:私密写作新选择,轻松记录生活点滴
  • [N-155]基于springboot,vue宿舍管理系统
  • Java项目实战II基于Spring Boot的交通管理在线服务系统设计与实现(开发文档+数据库+源码)
  • VSCode Markdown pdf导出修改字体、行距等
  • MySQL之JDBC入门详解
  • MySQL初学之旅(1)配置与基础操作
  • 大数据-205 数据挖掘 机器学习理论 - 线性回归 最小二乘法 多元线性
  • Vue3版本的uniapp项目运行至鸿蒙系统
  • 数据结构(8.7_3)置换——选择排序
  • 【P2-8】ESP8266 WIFI模块在STA+AP模式下相关指令及注意事项
  • RDD转换算子:重分区算子:【repartition、coalesce】
  • [C++ 核心编程]笔记 4.2.5 深拷贝与浅拷贝
  • Hive学习笔记
  • SQL入门的基础知识
  • SQL 像英语是个善意的错误
  • openapi回调地址请求不通过
  • 医院信息化与智能化系统(17)
  • iOS 再谈KVC、 KVO
  • 【Web自动化】探索Selenium与WebDriver的核心原理
  • Python OpenCV 图像改变
  • AI大模型赋能医学诊疗与药学服务——课题基金申请辅导项目成功举办