软硬链接

介绍

软链接

通过下图可以看出软链接和原始文件是两个独立的文件,因为软链接有着自己的inode编号:

具有独立的 inode ,也有独立的数据块,它的数据块里面保存的是指向的文件的路径,公用 inode

硬链接

通过下图可以看出硬链接和原始文件是同一个文件,因为二者的inode编号是相同的,并且创建完硬链接后改变了原始文件的引用计数

观察 inode 编号可以发现,软硬链接的区别:是否具有独立的Inode。

软链接具有独立的Inode:可以被当作独立的文件看待。

如果想删除一个软链接或者硬链接,可以使用删除命令rm,也可以使用unlink命令,例如删除上面的硬链接:

软硬链接的使用场景 

如果对一个文件既创建了软链接,也创建了一个硬链接,那么删除原文件时,软链接将失效,但是硬链接不会:

此时再访问软链接指向的文件中的内容就会无效,但不会影响硬链接

因为对于存在硬链接的文件来说,删除原文件就是减少其引用计数,只要引用计数不为0,那么该文件就不会被认为失效,而创建硬链接会增加原文件的引用计数,所以此时删除原文件就只是让原文件的引用计数从2变为1,从而保证文件还在硬盘上存在

从上面删除文件的例子可以看出,使用硬链接可以做到对原文件的备份

我们新建一个目录,引用计数是2,新建一个普通文件,引用计数为1

empty目录里面有两个隐藏文件,其中一个是 表示当前路径,文件名不同,inode相同

在empty里面再新建一个目录dir,引用计数变为3,新建目录dir里面有隐藏文件 .. 表示上级路径

和empty的inode相同,相当于硬链接

linux不允许对目录新建硬链接(会形成环状路径)

下面看一下软链接的应用场景:

软链接就像windows下的快捷方式,路径直接跳转

当前目录下有一个test.cc文件,g++编译形成可执行程序,直接运行可以看到运行结果

但是上面的运行需要带./限定才能正常运行a.out文件,在linux命令行与环境变量提到之所以需要./作为限定是因为Linux默认查找可执行文件的路径是/bin路径下,而当前a.out文件并不在该目录。当时解决这个问题的办法就是将a.out移动到/bin路径下

实际上,/bin也是一个软链接,该链接指向的原文件是/usr/bin

在软链接部分,就可以通过为a.out创建软链接,再将软链接移动到/bin路径下即可执行a.out文件:此时的软链接指向的原文件需要使用绝对路径

这时候直接写a.out不用带./就可以运行test.txt程序了

通过上面的例子可以看出,软链接的作用主要是相当于一个快捷方式 ,软链接里面保存的是与文件所处路径的映射关系

动静态库

创建静态库与使用

我们自己封装了两个简单的库,stdio,string库,为了将我们的库给别人使用,并且不暴露源码,我们可以将这两个库制作成静态库

制作静态库的两种方式

方式一:将静态库放到/usr/lib64目录下,将头文件放到/usr/include目录下 (安装到系统里)

制作步骤如下:

1.将需要打包为静态库的.c文件编译生成.o文件


2.使用ar -rc lib库名.a 指定的.o文件生成静态库,注意静态库的名称一定要以lib开头,后缀为.a


3.将头文件使用cp命令拷贝到/usr/include目录下使用cp命令将静态库拷贝到/usr/lib64目录下。


这一步仅仅只是完成静态库的安装,如果直接编译要生成可执行程序的文件会出现链接报错

4.编译要生成可执行程序的文件时使用gcc 文件名 -l+库名称(去掉lib和.a)

方法二: 通过编译器选项指定静态库路径,并且使用当前目录下的头文件

默认情况下,gcc不会在当前目录查找需要的静态库,也不会在指定目录中自动查找需要的静态库,所以需要指定静态库路径和静态库名称

当前目录下存在静态库、头文件和用于生成可执行程序的源文件: 

使用下面的指令指定静态库所在位置:

gcc 文件名 -L路径 -l静态库(去掉lib和.a)

 方式3:通过编译器选项指定头文件和静态库文件的位置

使用Makefile自动化生成静态库和.o文件

使用下面的指令指定头文件所在路径和静态库所在路径

gcc 文件名 -I.h所在位置 -L静态库路径 -l静态库名称(去掉lib和.a)

运行结果如下: 

创建动态库与使用 

创建动态库的命令不再是ar而是直接使用gcc,但是在生成动态库前必须保证.o文件具有绝对地址,即对.o文件的生成方式需要改变:使用-fPIC选项生成带有与位置无关码的.o文件,即:

运行结果如下

动态库前两种创建方式与静态库一样

第三种创建方式与静态库不同

 gcc编译动态库时,并没有把代码加载到程序里,静态库加载到程序里面了,直接运行即可。

动态库没有加载到程序里,运行时找不到 libmystdio ,系统找动态库默认从lib64找

如何给系统指定路径,查找自己的动态库:

1.拷贝到系统默认路径下(与静态库使用第一种方法相同)

2.在系统路径,建立软链接

3.linux系统中,OS查找动态库,环境变量,LD_LIBRARY_PATH

4. ldconfig⽅案:配置/ etc/ld.so.conf.d/ ,ldconfig更新  (系统级别)

动静态库同时使用的细节

1.同时存在动静态库时,gcc/g++默认使用动态库

如果想使用静态库,编译时应该带上 -static 

2.如果强制静态链接,必须提供对应的静态库

3.如果只提供静态库,但是连接方式是动态链接的,gcc/g++只能针对.a局部性采用静态链接

动态库的加载

先探讨⼀下编译和链接的整个过程,来更好的理解动静态库的使用原理

ELF的形成与加载

编译的过程其实就是将我们程序的源代码翻译成CPU能够直接运行的机器代码。在编译之后会⽣成两个扩展名为 .o 的文件,它们被称作目标文件

目标文件是⼀个⼆进制的文件,文件的格式是 ELF ,是对⼆进制代码的⼀种封装

ELF文件

以下四种都是ELF文件:

1.可重定位文件(xxx.o文件) 2.可执行程序  3.共享目标文件(.so文件)  4.内核转储(core dumps)

ELF文件由以下四部分组成:

ELF头(ELF header) :描述⽂件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位文件的其他部分。
• 程序头表(Program header table) :列举了所有有效的段(segments)和他们的属性。表⾥
记着每个段的开始的位置和位移(offset)、⻓度,毕竟这些段,都是紧密的放在⼆进制⽂件中,需要段表的描述信息,才能把他们每个段分割开。
• 节头表(Section header table) :包含对节(sections)的描述。
• 节(Section ):ELF⽂件中的基本组成单位,包含了特定类型的数据。ELF⽂件的各种信息和数据都存储在不同的节中,如代码节存储了可执⾏代码,数据节存储了全局变量和静态数据等

最常见的节:

代码节(.text) : 用于保存机器指令,是程序的主要执行部分

数据节(.data) :保存已初始化的全局变量和局部静态变量

链接就是将一个一个的相同属性的section合并

对任何一个文件,文件的内容就是一个巨大的“一维数组”,标识文件任何一个区域,用偏移量+大小的方式

动态库的加载

使用动态库的可执行程序在调用动态库中的方法时需要知道动态库的地址,动态库还未加载到内存之前,先使用一些内容进行占位,等到执行到指定的动态库代码再加载动态库,此时就形成了动态库的虚拟地址和物理地址映射关系,根据这个虚拟地址替换掉进程中调用动态库代码的占位内容即可。这个过程也被称为地址重定位

看似上面的思路好像没问题,实际上,虚拟地址空间的代码区是不可写的,也就是说,如果进程的代码加载到虚拟地址空间就不无法再更改其中的内容,那么此时又是如何做到使用动态库加载到内存之后的虚拟地址替换进程调用动态库代码的位置的内容

其实,进程调用动态库代码的位置的内容并不是直接写动态库的地址,而是写入一个GOT表的地址,这个表中存储的就是指定动态库和对应的虚拟地址的映射关系,进程在调用动态库代码的位置此时只需要写上调用的是GOT表中的哪一个动态库的下标即可,剩下的就交给GOT表来进行,即当动态库加载到内存后,虚拟地址填充到GOT表指定动态库对应下标即可。这也就是所谓的「生成与位置无关码」

所以,一个动态库之所以可以只加载一次而可以被任何进程所调用,本质就是因为这个GOT表,只需要知道这个GOT表的地址和对应库的下标,即可调用对应动态库中的内容

重谈地址空间--可执行程序,加载问题

可执行程序是有地址的

CPU要执行进程中的代码,就需要知道对应代码的地址,所以在磁盘的可执行程序中,尽管其未加载到内存,但是在编译链接时就已经形成了地址,使用下面的指令对main程序进行反汇编可以看到每一个步骤对应的虚拟地址

注意,不是物理地址,因为此时可执行程序还没有被加载到内存,只有被加载到内存后,才有物理地址。程序在加载到内存之前只有虚拟地址逻辑地址,只有在加载到物理内存后才会被分配对应的物理地址

objdump -S指令显示目标文件的详细信息

平坦模式:逻辑地址=起始地址+偏移量 

平坦模式(Flat Mode)是指在计算机系统中的一种内存管理模式,其中整个地址空间被看作是单一的、连续的线性空间。在这种模式下,所有代码和数据都位于一个大的、平坦的地址范围内,没有分段或分区的概念。这种模式简化了编程模型,使得编译器和程序员不需要处理复杂的段选择符或偏移量计算

ELF在没有加载到内存的时候就已经按照[000...000,FFF...FFF](虚拟地址)进行编址了 

 结论:编译器编译,就已经形成虚拟地址了

当可执行程序加载到内存之后,其ELF中的LOAD部分的内容就会分别被加载到指定的区域,例如.text的内容被加载到代码区.data的内容被加载到数据区等,这个过程就完成了虚拟地址空间的初始化

但是只有初始化还不够,为了保证物理地址和可执行程序的虚拟地址可以匹配,此时就需要页表进行对应的映射

上面整个过程完成,一个可执行程序就从硬盘加载到内存,变为了一个可以被CPU调度的进程

接着,CPU要执行这个进程,PC寄存器就需要找到第一条指令的地址(即找到入口地址),这个地址在ELF头中可以看到Entry point address字段:

但是这个地址依旧是虚拟地址,所以依旧需要使用页表进行映射,对应着的就是反汇编代码中的<_start>地址(此处<_start>相当于关于C语言函数栈帧:main函数被其他函数调用的__tmainCRTStartup())

所以,不论是进程还是CPU的PC寄存器,二者访问到的都是虚拟地址,但是这个虚拟地址要和物理地址在页表中建立映射关系。同时CPU内部还有一个寄存器,称为CR3寄存器,其中存储的就是页表本身的物理地址,这个寄存器是操作系统本身使用的。有了CR3寄存器后,就需要一个硬件配合其完成查表的工作,这个硬件称为MMU,也是在CPU内部。还有一个寄存器EIP,将pc指针中的虚拟地址,通过MMU查表转化为物理地址

虚拟空间是操作系统,CPU,编译器协作下的产物,通过上面的过程,再次思考为什么需要有虚拟地址和虚拟地址空间:编译器在编译代码时不再需要考虑物理内存,完成操作系统和编译器进行解耦合。

理解虚拟地址空间的区域划分

前面提到,虚拟地址空间初始化时会由ELF文件中的内容对指定区域进行初始化,但是并没有看到ELF文件中存在对栈、堆和共享区进行初始化的部分,这些部分如何进行的初始化就是下面需要讨论的问题

实际上,虚拟地址空间中还存在一个结构,称为vm_area_struct,即虚拟区域结构,其对应的部分源码如下:

struct vm_area_struct {struct mm_struct * vm_mm;    /* The address space we belong to. */unsigned long vm_start;        /* Our start address within vm_mm. */unsigned long vm_end;        /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;
};

真正的栈、堆和共享区都是vm_area_struct结构对象,有着自己的vm_startvm_end用于标记区域的开始和结束,每一个vm_area_struct结构对象通过链表进行链接。所以,CPU在访问栈、堆和共享区时实际上访问的也是对应的vm_area_struct对象的虚拟地址,在页表中也存在着这些虚拟地址和物理地址的映射

有了上面这种思想,当一个可执行程序有很多内容时,操作系统可以考虑先加载一部分的Section形成vm_area_struct对象,再根据需要加载后面的Section,这也就实现了Section的懒加载

所以,如果有多个动态库需要加载,本质上就是创建一个vm_area_struct结构对象链接到指定的区域

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/912761.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/912761.shtml
英文地址,请注明出处:http://en.pswp.cn/news/912761.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3D 商品展示与 AR 试戴能为珠宝行业带来一些便利?

对于珠宝行业而言&#xff0c;长久以来&#xff0c;如何让消费者在做出购买决策之前&#xff0c;便能真切且直观地领略到珠宝独一无二的魅力&#xff0c;始终是横亘在行业发展道路上的一道棘手难题。而 3D 互动营销的横空出世&#xff0c;恰似一道曙光&#xff0c;完美且精准地…

电子电气架构 --- SOVD功能简单介绍

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

【Java编程动手学】 Java中的运算符全解析

文章目录 一、引言二、算术运算符1、基本概念2、具体运算符及示例 三、关系运算符1、基本概念2、具体运算符及示例 四、自增减运算符1、基本概念2、具体运算符及示例 五、逻辑运算符1、基本概念2、具体运算符及示例 六、位运算符1、基本概念2、具体运算符及示例 七、移位运算符…

【前端】1 小时实现 React 简历项目

近期更新完毕。仅包括核心代码 目录结构 yarn.lock保证开发者每次能下载到同版本依赖&#xff0c;一般不需要特别留意 package.json 是 Node.js 项目、前端项目、npm/yarn的配置文件。 Dockerfile 是用来 定义 Docker 镜像构建过程的文本文件。它是一份脚本&#xff0c;告诉 …

python中的pydantic是什么?

Pydantic 是 Python 中一个用于数据验证和设置管理的库&#xff0c;主要通过 Python 类型注解&#xff08;Type Hints&#xff09;来定义数据结构&#xff0c;并自动验证输入数据的合法性。它广泛应用于 API 开发&#xff08;如 FastAPI&#xff09;、配置管理、数据序列化等场…

腾讯云市场目前饱和度

首先我需要理解市场饱和度的概念。市场饱和度通常指一个产品或服务在潜在市场中的渗透程度&#xff0c;高饱和度意味着市场增长空间有限&#xff0c;低饱和度则表明还有较大发展潜力。 从搜索结果看&#xff0c;腾讯云目前在中国云服务市场排名第三&#xff0c;市场份额约为15%…

EDR、NDR、XDR工作原理和架构及区别

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; EDR、NDR、XDR是网络安全中关键的检测与响应技术&#xff0c;它们在覆盖范围、数据源和响应机制上有显著差异。以下是它们的工作原理和架构详解&#xff1a; --- ### &#x1f50d; 一、EDR&#xff0…

vue3 + luckysheet 实现在线编辑Excel

效果图奉上&#xff1a; 引入的依赖&#xff1a; "dependencies": {"types/jquery": "^3.5.32","types/xlsx": "^0.0.36","jquery": "^3.7.1","xlsx": "^0.18.5",}在index.html中…

Linux下MinIO分布式安装部署

文章目录 一、MinIO简单说明二、MinIO分布式安装部署1、关闭SELINUX2、开启防火墙2.1、关闭firewall&#xff1a;2.2、安装iptables防火墙 3、安装MinIO4、添加MinIO集群控制脚本4.1添加启动脚本4.2添加关闭脚本 5、MinIO控制台使用 一、MinIO简单说明 1、MinIO是一个轻量的对…

Codeforces Round 980 (Div. 2)

ABC 略 D 这个过程一定是由1向后跳的过程中穿插有几次向前一步一步走。直到跳到一个位置后再把前面所有没有走过的位置倒序走一遍。总分就等于最大位置的前缀和-前面所有起跳位置和。前缀和固定我们只需要求到每个位置的最小起跳和即可。对于这个向后跳和向前走的过程我们可以…

Langchain实现rag功能

RAG&#xff08;检索增强生成&#xff09;的核心是通过外部知识库增强大模型回答的准确性和针对性&#xff0c;其工作流程与优化策略如下&#xff1a; 一、RAG 核心流程 ‌知识库构建‌ ‌文档加载与分割‌&#xff1a;将非结构化文档&#xff08;PDF、Markdown等&#xff09;…

算法笔记上机训练实战指南刷题

算法笔记上机训练实战指南刷题记录 文章目录 算法笔记上机训练实战指南刷题记录模拟B1001 害死人不偿命的(3n1)猜想B1011 AB 和 CB1016 部分ABB1026 程序运行时间B1046划拳B1008数组元素循环右移问题B1012 数字分类B1018 锤子剪刀布A1042 Shuffling Machine 每天两题&#xff0…

MYSQL基础内容

一、介绍 1.不用数据库&#xff1a;使用IO流对数据进行管理 2.使用数据库&#xff1a;使用SQL语句对开发的数据进行管理&#xff0c;能储存上亿条数据 3.MYSQL&#xff1a; 是流行的关系型数据库管理系统之一&#xff0c;将数据保存在不同的数据表中&#xff0c;通过表与表之…

音视频会议服务搭建(设计方案)-01

前言 最近在做音视频会议系统服务搭建的工作任务&#xff0c;因为内容过多&#xff0c;我会逐篇分享相关的设计方案、开发思路、编程语言、使用的组件集合等等。如果你也有大型音视频会议系统搭建架构的需求&#xff0c;希望这些可以对你有所帮助。 EchoMeet 音视频会议系统架构…

刷leetcode hot100/准备机试--图

图的基础知识【这部分建议使用acm模式】 图论理论基础 | 代码随想录 存储&#xff1a; 一般有邻接表【适合稀疏图】【数组 链表 】和邻接矩阵【适合稠密图】存储方式 注意邻接表 和 邻接矩阵的写法都要掌握&#xff01; 邻接矩阵 n个节点&#xff0c;申请n*n或者&#xf…

无代码自动化测试工具介绍

无代码自动化测试工具允许用户无需编写代码即可创建和运行测试,通过拖拽式界面或录制回放等可视化界面进行操作。 这些工具利用图形用户界面和预定义命令来创建测试,使非编程人员也能进行自动化测试。 无代码自动化测试工具使团队能够: 使用直观的拖拽界面开发和执行自动化…

python学习打卡day58

DAY 58 经典时序预测模型2 知识点回顾&#xff1a; 时序建模的流程时序任务经典单变量数据集ARIMA&#xff08;p&#xff0c;d&#xff0c;q&#xff09;模型实战SARIMA摘要图的理解处理不平稳的2种差分 n阶差分---处理趋势季节性差分---处理季节性 建立一个ARIMA模型&#xf…

分布式锁的实现方式:使用 Redisson 实现分布式锁( Spring Boot )

Redisson提供了分布式和可扩展的Java数据结构&#xff0c;包括分布式锁的实现。 1. 添加依赖 在pom.xml中添加Redisson依赖&#xff1a; <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId>…

Web基础关键_004_CSS(二)

目 录 一、样式 1.行内样式 2.内部样式 3.外部样式 二、选择器优先级 1.非关系选择器 2.关系选择器 三、属性 四、盒子模型 五、元素 1.块级元素 2.行内元素 3.行内块级元素 4.元素类型转换 六、浮动 七、定位 1.静态定位 2.相对定位 3.绝对定位 4.固定定位 …

数据使用权与所有权分离:能否诞生“数据租赁”市场

——首席数据官高鹏律师数字经济团队创作&#xff0c;AI辅助 数据如矿藏&#xff0c;开采需“契约” 想象一座蕴藏着无尽资源的数字矿山&#xff1a;企业或个人拥有数据的“所有权”&#xff0c;如同手握矿脉的产权&#xff0c;但若无法高效挖掘其价值&#xff0c;矿石终将沉…