操作系统:动静态库
目录
1.动静态库
1.1.如何制作一个库
1.2.静态库的使用和管理
1.3.安装和使用库
1.4.动态库
1.4.1.动态库的实现
1.4.2.动态库与静态库的区别
1.4.3.共享动态库给系统的方法
2.动态链接
2.1.操作系统层面的动态链接
1.动静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静 态库
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文 件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个 过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
1.1.如何制作一个库
库(Library)是一种预先编写好的可重用代码集合,它包含了一组函数、类、数据结构或其他可执行代码的集合。库的目的是为了提供一些常用的功能,以便开发人员可以在自己的程序中直接调用这些功能,而不需要从头开始编写代码。
在我们实际开发的场景下,当我们写好一个库时,我们往往是将源代码(.c文件)经过编译后的文件(.o文件)和头文件一起打包给库的使用者,也就是我们写好的库是不包含源代码的。
文件编译
一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤:
- 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
- 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
- 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
- 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。
库中包含着.h文件和.o文件
接下来我们写一个demo并把它打包成库
我们以实现计算器这个库
一般来说我们要编译这个test.c这个可执行文件,需要将三个.c文件同时编译使用
gcc test.c add.c sub.c
但是这样子就需要将源代码给到别人,而开发中的源代码一般是闭源的,所以我们需要先将这几个实现.h文件的.c文件编译成.o文件
gcc -c add.c sub.c // 生成对应的.o文件
那么我们现在开始创建库的雏形,只给.h文件和.o文件(这个图add.c应该为add.h)
用户在使用时通过先将自己的测试文件转换为.o文件然后再一起编译这几个.o文件
gcc test.o add.o sub.o
假设在实际开发中,有10000个.o文件那这样子编译会不会容易漏掉,所以这时候我们需要打包文件,生成静态库
// 通过这条指令我们打包了一个静态库mycal ar -rc libmycal.a *.o
如图所示我们就打包好了一个静态库
一般来说我们通常通过makefile来打包好静态库
static-lib=libmycal.a$(static-lib):add.o sub.oar -rc $@ $^%.o:%.cgcc -c $<.PHONY:clean clean:rm -f *.o
1.2.静态库的使用和管理
我们在1.1.中制作了一个mycal的静态库,那么从库的使用角度出发我们怎么来使用这个库呢?
首先当我们写了一个库给他人使用时,此时作为第三方库,gcc无法认识这个库,需要链接指定的库
这时我们需要连接上这个静态库,输入链接指令
// gcc 需要编译的文件名 -l库名称 -L 库所在的路径 gcc test.c -lmycal -L.
注意这里的 -l 直接连接对应的库名称,且库名称需要去掉 lib 和 .a
这样我们就完成了静态库的使用了!!!
值得注意的是:一个可执行文件,可以实现动静态库的混合链接,如何连接时加入 -static 选项,表示只使用该文件编译时需要的静态库。
我们在lib文件夹中发现.h文件和.a库放在一起,在实际开发场景中,可能存在大量的这些文件,为了便于管理,我们通常是将他们分组存放后,然后打包挂到云端提供给他人使用
回到最初的起点,我们开始准备打包
这里是makefile内的代码块
static-lib=libmycal.a$(static-lib):add.o sub.oar -rc $@ $^%.o:%.cgcc -c $<.PHONY:output output:mkdir -p mycal_lib/includemkdir -p mycal_lib/lib cp -f *.h mycal_lib/includecp -f *.a mycal_lib/lib.PHONY:clean clean:rm -f *.o *.a output
接着我们make编译一下
这时就回到了我们之前讲的需要管理的场景,我们在makefile里写了一个脚本output
形成了一个目录,我们查看一下
这样子就实现了我们对这个库进行管理了
tar czf mycal_lib.tgz mycal_lib
最后的最后我们打包好这个文件就可以挂载服务器端让他人下载使用了
1.3.安装和使用库
书接上文,假设我们从网上下载一个mycal_lib这个库,我们需要如何加载进我们的库环境中呢?
首先需要解压这个包,获得库,接着就是将库安装到开发环境中!!!(本质上是拷贝)
安装开发环境的本质就是将第三方库提供的.h、.a文件分别放到系统中的指定位置,一般是拷贝到对应的include、lib文件夹里
那么当我们运行时,gcc默认就会在系统的开发环境中来寻找我们download的库和头文件了,使用时就只需要连接需要的库即可
gcc test.c -lmycal
如果我们不安装到系统里,我们需要怎么使用这个库呢?
这时我们需要输入指令来寻址(因为头文件和库文件在mycal_lib中,gcc找不到)
// -I 新增头文件搜索路径 -l为连接的库名称 -L 新增库文件的搜索路径
gcc test.c -I mycal_lib/include -lmycal -L mycal_lib/lib
那么这样子我们就完成了,对下载库使用的学习了
1.4.动态库
因为动态库的制作和静态库的制作思路一致都是形成.o文件然后打包,但是会有一些细节上的不同,接下来我们来实现一下动态库
1.4.1.动态库的实现
首先是编译文件时和打包指令不同
// 编译成.o文件时需要添加 -fPIC
gcc -fPIC -c add.c sub.c
// 打包成库时用gcc指令
gcc -shared -o libmycal.so *.o
我们只需要修改一下makefile就也可以通过脚本实现之前讲的内容
dy-lib=libmycal.so$(dy-lib):add.o sub.ogcc -shared -o $@ $^%.o:%.cgcc -fPIC -c $<.PHONY:output
output:mkdir -p mycal_lib/includemkdir -p mycal_lib/lib cp -f *.h mycal_lib/includecp -f *.so mycal_lib/lib.PHONY:clean
clean:rm -f *.o *.so output
实现的部分我们不再赘述了
1.4.2.动态库与静态库的区别
那我们通过使用静态库的方式来调用一下这个动态库
我们发现就算我们声明了路径,链接了调用的库,最后可以正常生成这个a.out文件但是我们运行时发现 -> 报错:加载共享库,无法打开这个共享库文件
这里我们解释一下: 静态库在编译后,可执行程序和静态库打包在一起相当于加载进了整个文件中,运行期间不需要找到静态库。而运行动态库是和可执行程序分离的,所以需要将动态库加载到内存中。
可执行程序 和 动态库 同时存在于内存中才可以实现动态库!!!
我们发现这时系统找不到动态库的位置,因为我们之前只给了test.c寻找到了动态库的位置,但是系统还是不知道动态库在哪里,所以需要我们把动态库的位置共享给系统。
1.4.3.共享动态库给系统的方法
- 将头文件和库文件添加到系统默认的区域,也就是拷贝到相应的include和lib
// 拷贝库中的所有.h文件到系统默认的include中 sudo cp mycal_lib/include/*.h /usr/include/ // 拷贝.so库文件 sudo cp mycal_lib/lib/*.so /lib64/
- 建立软连接,动态库会在pwd中寻找库
ln -s mycal_lib/lib/libmycal.so libmycal.so
动态库在运行时会查找当前路径是否能找到这个动态库,所以我们可以建立软连接来实现!
这时候我们可以思考一下:我们能不能把这个软连接安装到系统里面,而不是安装整个整个库呢?也是可以的!
- 添加至环境变量LD_LIBRARY_PATH中(内存级别,退出shell会被清空)
// 通过pwd找到动态库所在的路径添加到环境变量里 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/Czh_Linux/test/mycal_lib/lib
- 更改系统的配置文件(不会因为重启shell而被清空)
系统中存在一些管理动态库的配置文件
// 文件的路径 /etc/ld.so.conf.d/
接着我们就需要写入进这个目录里面,在里面创建一个自己的动态库配置文件
// 需要sudo权限或者root touch /etc/ld.so.conf.d/my_so.conf
接着在这个my_so.conf文件里面写入你库的地址,进入动态库的地址用pwd打印出地址然后拷贝进这个文件中。
// 直接拷贝这一段话到你创建的.conf文件中 /home/Czh_Linux/test/mycal_lib/lib
这时已经完成了写入动态库信息到配置文件中!!!
// 如果没有生效就需要刷新一下 sudo ldconfig
2.动态链接
在Linux中可执行文件一般以ELF的格式出现,符号表中存放着调用某些库的方法的地址,那也就是当可执行文件加载进内存时,进入内存的只有文件中对应库方法的地址,而没有库中的实现,也就对应了我们之前没有将动态库加载进内存中会出现报错!
所以进行动态链接的过程就是将对应库的方法的地址存放在可执行文件的符号表中,再配合着将对应库加载进内存中!
我们接着思考,当程序加载进内存中时是以什么形式出现的呢?从汇编语言中我们就能看出大部分的函数变量加载进内存时,最终是以“地址”的形式加载进内存的。那么这个问题引申下去就是如何对代码进行编址呢?
在编译器中,也是通过“虚拟地址空间”编址,虚拟地址空间可以看成是一种编译标准
文件通过编译后在形成进程加载进内存之前,代码和数据就已经具有了自己的地址(虚拟地址),大多数情况叫做处于磁盘文件的“逻辑地址”,核心是:基地址 + 偏移量
一般情况下:设定基地址为0,然后偏移量来进行定位区分(Linux的平坦模式)
在计算机中对程序进行编址时,一般是两种方式:绝对编址(以基地址为标准) 和 相对编址(以其他个体地址为标准)
库当中形成地址一般是通过相对编址,记录函数间的偏移量
假如:Entry的main函数地址为0x1234,进入main函数后遇到的第一个地址为0x1236那么他们的偏移量固定为(0x1236 -0x1234)
未来库在内存中的任意位置加载,那么库内部函数的相对编址始终不变,所以无论是加载在内存的某个区域,只要找到Entry我们都能通过不变的“地址”来使用库中的所有的函数。
这就是 fPIC 与位置无关码
2.1.操作系统层面的动态链接
我们知道了ELF文件(编译后的代码)也实现了类似操作系统中的虚拟地址空间,也就是操作系统和磁盘通过这个虚拟地址空间进行代码的交互,又动态库在内存中会映射在pcb的虚拟内存的共享区内,这样子就穿起来了操作系统和磁盘内的ELF可执行文件(这个不就是进程运行文件的动态链接吗)
但是我们感觉还是有一点抽象,究竟代码的地址和进程的地址是如何联系起来的呢?
我们之前强调了相对编址这个概念,在ELF的符号表中存储着各个调用库的函数的地址(只要知道main函数入口地址),我们可以计算出来他们相对的偏移量,如果main为a,printf为b,那么偏移量就为b-a,这是从库角度出发。当我们从文件的代码区出发,运行代码的本质就是地址的跳转,但是代码区相当于只存储了代码的声明,实现在库中,所以我们就需要代码区进行到某个函数时跳转到对应共享区的部分,通过偏移量,映射到内存中代码的实现!
接着我们再换一个角度,从进程的调度中出发
CPU在进行进程调度时也是通过访问虚拟地址来实现的,也就是在我们所说的代码区和共享区进行跳转通过CPU中的指令寄存器。我们知道代码在内存中拥有自己的物理地址,而CPU指令寄存器读入代码虚拟地址通过页表映射找到物理地址,因此CPU可以实现某一行代码的功能。
- 操作系统可以读取编译后的ELF程序的入口Entry地址(虚拟地址),这个地址被操作系统用于进程pcb对应的正文代码的main函数的入口,接下来操作系统将这个入口地址加载到指令寄存器中。
- 从main函数还是执行,一步一步调用不同的代码,读取对应的虚拟地址,然后通过页表映射到物理内存,实现库方法和代码的运行,接着继续跳转回CPU,读取下一条虚拟地址
- 如此往复这个循环,最终完成我们进程的调度
找代码是通过物理地址,代码之间跳转通过虚拟地址。每个程序加载进物理内存中,每个变量、函数都有自己的物理地址和虚拟地址。
核心:
- 只要获得起始地址再配合偏移量就能实现库中的任何方法
- 通过虚拟地址映射到物理地址来实现代码的定位
- 操作系统和编译器用同样的虚拟地址空间标准,实现CPU进行虚拟地址之间的转化
相关文章:

操作系统:动静态库
目录 1.动静态库 1.1.如何制作一个库 1.2.静态库的使用和管理 1.3.安装和使用库 1.4.动态库 1.4.1.动态库的实现 1.4.2.动态库与静态库的区别 1.4.3.共享动态库给系统的方法 2.动态链接 2.1.操作系统层面的动态链接 1.动静态库 静态库(.a)&…...

车载电子电器架构 —— 局部网络管理汇总
车载电子电器架构 —— 局部网络管理汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…...

网络安全 | 什么是DDoS攻击?
关注WX:CodingTechWork DDoS-介绍 DoS:Denial of Service,拒绝服务。DDoS是通过大规模的网络流量使得正常流量不能访问受害者目标,是一种压垮性的网络攻击,而不是一种入侵手段。NTP网络时间协议,设备需要…...
[Godot] 3D拾取
CollisionObject3D文档 Camera3D文档 CollisionObject3D有个信号_input_event,可以用于处理3D拾取。 Camera3D也有project_position用于将屏幕空间坐标投影到3D空间。 extends Node3D#是否处于选中状态 var selected : bool false #摄像机的前向量 var front : V…...

知识融合:知识图谱构建的关键技术
目录 一、引言二、知识图谱基础2.1 知识表示三元组属性图 2.2 知识抽取实体抽取关系抽取属性抽取 三、知识融合的核心问题3.1 实体识别与链接实体识别实体链接 3.2 重复实体合并方法示例 3.3 关系融合挑战方法示例 四、知识融合技术深度解析4.1 基于规则的方法规则设计原则规则…...

外贸建站:WordPress搭建外贸独立站零基础自建站完整教程(2024)
对于做外贸来说,拥有自己的外贸独立网站真的非常重要。在外贸领域,如今各平台竞争激烈,规则多,成本高,价格战、政策变化快,还存在封店风险等等因素。在这种情况下,拥有外贸独立站就能很好规避上…...

【教程】Kotlin语言学习笔记(五)——Lambda表达式与条件控制
写在前面: 如果文章对你有帮助,记得点赞关注加收藏一波,利于以后需要的时候复习,多谢支持! 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 第五章 《L…...

C++的并发世界(三)——线程对象生命周期
0.案例代码 先看下面一个例子: #include <iostream> #include <thread>void ThreadMain() {std::cout << "begin sub thread:" << std::this_thread::get_id()<<std::endl;for (int i 0; i < 10; i){std::cout <&…...

SAD法(附python实现)和Siamese神经网络计算图像的视差图
1 视差图 视差图:以左视图视差图为例,在像素位置p的视差值等于该像素在右图上的匹配点的列坐标减去其在左图上的列坐标 视差图和深度图: z f b d z \frac{fb}{d} zdfb 其中 d d d 是视差, f f f 是焦距, b b…...

基于DWT(离散小波变换)的图像加密水印算法,Matlab实现
博主简介: 专注、专一于Matlab图像处理学习、交流,matlab图像代码代做/项目合作可以联系(QQ:3249726188) 个人主页:Matlab_ImagePro-CSDN博客 原则:代码均由本人编写完成,非中介,提供…...

【威胁情报综述阅读3】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense
【威胁情报综述阅读1】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense: A Survey and New Perspectives 写在最前面一、介绍二、网络威胁情报挖掘方法和分类A. 研究方法1) 第 1 步 - 网络场景分析:2) 第 2 步 - 数据…...

在编程中使用中文到底该不该??
看到知乎上有个热门问题,为什么很多人反对中文在编程中的使用? 这个问题有几百万的浏览热度,其中排名第一的回答非常简洁,我深以为然: 在国内做开发,用中文写注释、写文档,是非常好的习惯&…...
PyQt6从入门到放弃
PyQt6从入门到放弃 安装PyQt6 pip install PyQt6# 查看QT和PyQT的版本 from PyQt6.QtCore import QT_VERSION_STR from PyQt6.QtCore import PYQT_VERSION_STR print(QT_VERSION_STR) print(PYQT_VERSION_STR)PyQt6模块 PyQt6类由一系列模块组成包括QtCore、QtGui、QtWidgets…...
PhpWord导入试卷
规定word导入格式 1、[单选题][2024][一般]题目1 A.选项1 B.选项2 C.选项3 D.选项4 答案:D 试题图片(上传多媒体图片): 分数:2 答案解析: 2、[多选题][2024][困难]题目2 A.选项1 B.选项2 C.选项3 D.选项4 E…...
C# 运算符重载 之前的小总结
C# 中支持运算符重载,所谓运算符重载就是我们可以使用自定义类型来重新定义 C# 中大多数运算符的功能。运算符重载需要通过 operator 关键字后跟运算符的形式来定义的,我们可以将被重新定义的运算符看作是具有特殊名称的函数,与其他函数一样&…...

XenCenter 2024 创建一个虚拟机
前言 实现,创建一个虚拟机,内存,cpu,磁盘,名称,网卡,配置 Xen Center 2024 download 创建虚拟机 选择系统类型 定义虚拟机名称 选择ISO镜像库 选择主服务器 分配虚拟机内存,cpu资源…...
tomcat 知多少
Tomcat的缺省端口: 默认端口为8080,可以通过在tomcat安装包conf目录下,service.xml中的Connector元素的port属性来修改端口。 tomcat 常见 Connector 运行模式(优化): 这三种模式的不同之处如下: BIO : 一…...

【详细讲解语言模型的原理、实战与评估】
🌈个人主页:程序员不想敲代码啊🌈 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家🏆 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提…...

Predict the Next “X” ,第四范式发布先知AIOS 5.0
今天,第四范式发布了先知AIOS 5.0,一款全新的行业大模型平台。 大语言模型的原理是根据历史单词去不断预测下一个单词,换一句常见的话:Predict the Next “Word”。 当前对于行业大模型的普遍认知就是沿用这种逻辑,用大…...
PCL使用4PCS配准
一、代码 C++ #include <pcl/registration/ia_fpcs.h> // 4PCS算法 #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> #include <pcl/io/ply_io.h> #include <boost/thread/thread.hpp> #include…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决
问题: pgsql数据库通过备份数据库文件进行还原时,如果表中有自增序列,还原后可能会出现重复的序列,此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”,…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...
MeanFlow:何凯明新作,单步去噪图像生成新SOTA
1.简介 这篇文章介绍了一种名为MeanFlow的新型生成模型框架,旨在通过单步生成过程高效地将先验分布转换为数据分布。文章的核心创新在于引入了平均速度的概念,这一概念的引入使得模型能够通过单次函数评估完成从先验分布到数据分布的转换,显…...
宠物车载安全座椅市场报告:解读行业趋势与投资前景
一、什么是宠物车载安全座椅? 宠物车载安全座椅是一种专为宠物设计的车内固定装置,旨在保障宠物在乘车过程中的安全性与舒适性。它通常由高强度材料制成,具备良好的缓冲性能,并可通过安全带或ISOFIX接口固定于车内。 近年来&…...
数据库优化实战指南:提升性能的黄金法则
在现代软件系统中,数据库性能直接影响应用的响应速度和用户体验。面对数据量激增、访问压力增大,数据库性能瓶颈经常成为项目痛点。如何科学有效地优化数据库,提升查询效率和系统稳定性,是每位开发与运维人员必备的技能。 本文结…...