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

【Linux】动静态库的制作与原理

库的核心意义是 “通过封装与复用,解决软件开发的效率、质量、兼容性问题”,而库的 “多” 则是 “功能细分、平台差异、语言生态、场景需求” 共同驱动的结果。没有任何一个库能覆盖所有领域、所有平台、所有需求,因此需要不同的库供开发者选择使用

1.静态库的制作与使用原理

1.2静态库的制作

  • 静态库(.a):a 是 archive(归档)

静态库本质上是多个目标文件(.o 文件)的归档集合,它由编译器将多个目标文件打包成一个 .a 文件,程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库

我们想提供方法给别人用:
1.把源文件直接给他,这样会泄漏代码
2.将源代码打包成库=头文件.h+库(由.o目标文件组成)
头文件是 “说明书”,告诉用户如何使用库;库文件是 “实体”,提供实际的功能实现。

  • makefile自动化构建工具

实现了静态库的 “编译 - 打包 - 清理 - 整理” 完整流程

   //定义一个名为 static-lib 的变量,值为 mymath.a(目标静态库的文件名)1 static-lib=mymath.a2 //用 $(static-lib) 引用该变量,方便统一修改目标文件名。3 $(static-lib):mymath.o4     ar -rc $@ $^5 mtmath.o:mymath.c6     gcc -c $^//-c:只编译不链接,生成目标文件(.o)7 8 .PHONY:clean9 clean:10     rm -rf *.o *.a mylib                                                    11 12 .PHONY:output//声明 output 是伪目标(其他可以自定义执行)与clean相同13 output:14     mkdir -p mylib/include15     mkdir -p mylib/lib16     cp *.h mylib/include17     cp *.a mylib/lib
  • 命令:ar -rc
    ar 是归档工具(用于创建静态库)
    -r(replace):如果静态库已存在,替换其中的目标文件。
    -c(creat):创建静态库(若不存在)。
    $@:自动替换为当前目标名(即 mymath.a)。
    $^:自动替换为所有依赖文件(即 mymath.o)。
    作用:将xxx.o可执行文件(可多个) 打包成 mymath.a 静态库。
  • 执行结果
    在这里插入图片描述

1.3静态库的使用

  • 模拟使用场景
    当前目录下新创建一个目录当作用户,来使用我们所作的静态库
    在这里插入图片描述
    安装的本质就是把头文件和库文件拷贝到系统里路径里,删除就是在特定路径中删除

  • 头文件和库的查找
    在这里插入图片描述

  • gcc 文件名 -I 头文件路径 :

在这里插入图片描述
加上之后头文件找得到了,但是库文件找不到在链接阶段报错
在这里插入图片描述
用gcc -c选项可以证明前编译的前三个阶段,预处理-编译-汇编,能顺利完成

  • -L与-l选项
    如图就是一个利用自己静态库运行程序的完整过程,-I,-L,-l三选项都带上
    在这里插入图片描述
    -L指系统在默认系统路径和当前路径找不到就去指定路径找库文件
    -l后紧跟库名称,去掉前后缀lib和.a/.so

  • 如果想方便查找,可以给头文件和库在系统路径下建立软链接,但一般是给头文件所在的路径建立软链接,因为有可能要链接多个头文件,每个都建立软链接太麻烦,这样找头文件时带一个路径就行

注意: gcc 的 -l 选项有固定的命名规则:它会自动在指定的库名前加 lib 前缀,后缀加 .a(静态库)或 .so(动态库)。如果静态库文件没有 lib 前缀(例如我的库文件是 mymath.a 而非标准命名的 libmymath.a),无法直接使用 -l 选项

关于gcc查找习惯:
gcc的默认查找路径为当前路径或系统路径/usr/lib或者/usr/local/lib,我们平时在vim上写C或C++代码,gcc不需要加路径因为这些语言的库都存在于系统路径中,gcc找得到。
-l是直接带库文件名称,不需要头文件名称是因为代码中已包含头文件,所以使用第三方库必须指明名称

  • gcc的静态链接

在这里插入图片描述
1。gcc默认链接动态库,有动连动,只有静连静,ldd +static会强制链接静态库,动静态库也可以混合链接
2.看出链接信显示.so为系统动态库文件,但我们只提供了静态库,不加-static不显示但其实已经链接上可以用了。
file指令查看:
动态链接:输出包含 dynamically linked
静态链接:输出包含 statically linked
ldd指令查看:
动态链接:会列出依赖的 .so 动态库
静态链接:输出类似 not a dynamic executable 或仅显示系统级动态库
在这里插入图片描述

2.动态库的使用与制作原理

2.2动态库的制作

  • 生成可执行文件要带-fPIC选项,其余和静态库没区别
    在这里插入图片描述
    2.gcc默认可以编成动态库,不需要rc指令
    在这里插入图片描述

  • gcc无法编译形成可执行程序,因为可执行形成动态库所需的可执行程序中没有main函数,它们只是作为被调用的接口来使用而不是main函数程序的入口,-shared是编译成动态库要带的选项

  • *.o表示所有形成动态库所需要的可执行程序,*为通配符

  • 发现动态库默认带x权限,可执行权限就是文件是否会以可执行程序加载到内存里,动态库有可执行权限因为程序运行时要加载到内存里,会跳转到其中执行,虽然没有main函数但有函数方法,被调用也是可执行程序;动态库中的函数方法不能单独执行,需要被调用
    静态库没有可执行权限,因为链接时就将其代码加载到可执行程序中去了 动静态库都不能带main函数

  • makefile

加入生成动态库后的自动构建工具

dy-lib=libmymethod.so
static-lib=mymath.a
//一次性构建出动态库(.so)和静态库(.a)。
.PHONY:all
all:$(dy-lib) $(static-lib)$(static-lib):mymath.oar -rc $@ $^
mtmath.o:mymath.cgcc -c $^$(dy-lib):mylog.o myprint.ogcc -shared -o $@ $^
mylog.o:mylog.cgcc -fPIC -c $^
myprint.o:myprint.cgcc -fPIC -c $^.PHONY:clean
clean:rm -rf *.o *.a mylib *.so.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.a mylib/libcp *.so mylib/lib

在这里插入图片描述

2.3动态库的加载

  • 找不到动态库的原因
    在这里插入图片描述
    已经将动态库在哪告诉编译器了,但编译器生成二进制文件后,但加载可执行程序到内存中运行时与编译器无关了,所以在这个阶段找不到编译器,动态库的路径也要告诉加载器

解决加载找不到动态库的方法:
1.拷贝到系统默认的库路径/lib64/usr/lib64/

实际情况多用这种方式,因为使用的库大多为成熟的库,可以直接安装到系统。可用cp -r选项(可能破坏包管理的完整性、版本冲突风险),推荐使用系统包管理器安装(首选)

2.在系统默认路径/lib64/usr/lib64/下建立软链接
在这里插入图片描述
3.将自己的库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH(用来搜索用户自定义库路径的)
在这里插入图片描述

4./etc/ld.so.conf.d建立自己的动态库路径的配置文件,然后重新ldconfig
在这里插入图片描述
可在该目录中建立自己的配置文件,将需要的动态库路径写入其中,可补充动态库搜索路径,在可执行程序加载时系统就能直接从中找到

  • 动态库是怎么被加载的:

1.动态库和可执行文件都存储在磁盘中,程序运行时会把动态库加载到物理内存中,当某些进程需要链接时,动态库会通过页表将其地址映射到对应进程的虚拟空间中,可以与不同进程建立映射关系。
-所以执行任何代码,都是在进程地址空间中执行。
物理地址是执行的载体,虚拟地址是管理的核心,最终被 CPU 执行的指令,确实存储在物理内存中,但整个过程中,进程的逻辑、操作系统的调度、MMU 的翻译,都以虚拟地址为 “沟通语言”—— 物理地址只是 MMU 和物理内存之间的 “底层协议”,进程和用户完全无需关心。
2.系统在运行中一定存在多个动态库,操作系统管理起来一定是先描述再组织,熟知所有库的加载情况

  • 结论
    1.动态库就是共享库,动态库在加载之后会被所有系统共享
    2.核心是物理内存的共享(避免重复加载浪费内存),而非虚拟地址的共享(每个进程的虚拟地址空间独立)。这一机制依赖操作系统的分页内存管理、页表映射和写时拷贝(COW) 技术,本质是 “不同进程的虚拟地址,通过页表映射到同一块物理内存”。

1.分页内存管理:物理内存与虚拟内存的 “最小共享单位”
操作系统会将物理内存和进程虚拟地址空间都划分为大小相等的 “页”,内存的分配、映射和共享都以 “页” 为单位。动态库被加载时,会被拆分为多个 “物理页”(例如 1MB 的libc.so会拆分为 256 个 4KB 页);多个进程共享这些物理页,只需让它们的页表指向这些页即可,无需重复加载
2.动态库的代码段和只读数据段(物理内存只存一份);可写数据段不共享(每个进程COW 生成私有副本)。

3.加深进程地址空间的理解

3.2程序没有加载前的地址

程序编译好后内部有地址的概念:
1.编译 / 汇编阶段会完成部分地址转换(相对偏移),而非最终地址。链接(静态)后,大部分符号会被替换为确定的虚拟地址;动态链接的符号(确定在动态库中的偏移量),会在运行时确定地址。
2.程序内部编译后确定地址采用平坦模式,平坦模式是一种简单的内存管理方式,用单一连续地址空间管理所有数据(不分各种数据段),适用于资源有限的场景。

这些地址都被称为逻辑地址,每个程序都存有自己的入口地址,入口地址是逻辑地址而不是物理地址

3.3程序加载后的地址

1.程序中的地址都是进程地址空间中一一对应的虚拟地址,体现了编译器和操作系统的相互协调,编译器在最开始将符号转成地址时就考虑到程序加载后在进程地址空间中的情况。
2.可执行程序加载进内存后会将逻辑地址转化为虚拟地址,按需对语句分配对应的物理地址。进程在执行指令时,首先获取其虚拟地址,通过页表查询其对应的物理地址,但可执行程序一般不会全部加载到内存,所以可能造成缺页中断(若页表中存在到物理地址的映射直接执行),这时将对应的程序部分加载到内存中,分配物理内存重新建立映射关系,此时就能通过页表找到对应的物理地址执行代码
3.CPU读取到的指令,内部可能有数据,也可能有地址(虚拟地址),再重复上述过程就能通过虚拟地址找到物理地址执行代码

3.4理解逻辑地址、虚拟地址、物理地址的区别与关系

1.逻辑地址
定义:程序在编译、汇编、链接阶段由编译器 / 链接器生成的地址,是程序 “自己看到的地址”,表现为相对于程序自身地址空间的偏移量。
特点:
与物理内存无关,仅用于程序内部的指令和数据引用(例如代码中&a获取的地址,函数调用时的跳转地址)。
不唯一:不同程序可能使用相同的逻辑地址(因为各自相对于自身的偏移可能相同)。

2.虚拟地址
定义:现代操作系统中,通过虚拟内存机制为每个进程提供的 “抽象地址空间” 中的地址,是 “操作系统给程序画的内存地图上的地址”。
特点:
每个进程拥有独立的虚拟地址空间(例如 32 位系统通常为0~4GB),进程间地址隔离(同一虚拟地址在不同进程中对应不同物理内存)。
不直接对应物理内存,需要通过MMU(内存管理单元) 转换为物理地址后才能访问硬件内存。

简述mmu与页表关系:
MMU:是CPU 内部的硬件电路(物理组件),由逻辑门、寄存器等硬件构成,负责执行具体的地址转换操作(通过硬件逻辑快速完成)。
页表:是操作系统维护的软件数据结构(存储在内存中),本质是一张 “虚拟地址→物理地址的映射表”,记录了虚拟页和物理页的对应关系,以及内存访问权限(如只读、可执行)等信息。
简言之:页表存映射,MMU 用映射,两者协同完成虚拟地址到物理地址的转换,是现代虚拟内存机制的核心。

3.物理地址
定义:计算机硬件(内存条)中真实的内存单元地址,是 “硬件能直接识别的地址”,对应内存芯片中的实际存储单元。
特点:
唯一且固定:整个系统的物理地址空间由硬件内存容量决定(例如 8GB 内存的物理地址范围是0~0x200000000)。
CPU 最终访问内存时必须使用物理地址,虚拟地址必须经过转换才能得到物理地址。

4.三者的关系
1.编译 / 链接阶段:生成逻辑地址
编译器将源代码转换为机器码时,为变量、函数等分配逻辑地址。链接器将多个目标文件合并时,会调整逻辑地址,使其在程序的 “虚拟地址空间雏形” 中保持唯一性(例如将不同文件的逻辑地址映射到统一的虚拟地址范围)。
2.加载阶段:逻辑地址→虚拟地址
程序加载到内存时,操作系统会为其分配虚拟地址空间,逻辑地址被 “映射” 到虚拟地址空间中的某个位置。此时程序运行时看到的地址就是虚拟地址(逻辑地址已融入虚拟地址空间)。
3.运行阶段:虚拟地址→物理地址
程序执行时,CPU 发出的内存访问请求使用虚拟地址。MMU(内存管理元)通过查询操作系统维护的页表(记录虚拟地址与物理地址的映射关系),将虚拟地址转换为物理地址,最终访问硬件内存。

总结:
逻辑地址是程序 “编译时的地址”,是虚拟地址的前身;
虚拟地址是操作系统 “抽象的地址”,用于隔离和管理内存,需要转换为物理地址;
物理地址是硬件 “真实的地址”,是内存访问的最终目标。

3.5动态库的地址

动态库都会映射到进程地址空间中,加载的动态库多了,在共享区中具体映射到哪里不确定,所以动态库加载到固定的地址是不可能的,动态库可以在虚拟内存中任意位置加载
这就要求:编译时动态库自己的内部函数不能采用绝对地址编址,只表示每个函数在库中的偏移量即可,以每个动态库的起始位置为基准!加载后是绝对虚拟地址:偏移量 + 进程分配的基地址,得到全局虚拟地址,位于进程的共享区

fPIC:产生位置无关码
通过上述分析,在制作动态库生成可执行文件时要带上fPIC。
静态库不需要是因为静态库在编译链接阶段会被完整 “嵌入” 到可执行文件中,其最终地址在链接时就已确定

http://www.xdnf.cn/news/19318.html

相关文章:

  • 第三十二天:数组
  • 刷算法题-数组-02
  • 关于Ctrl+a不能全选的问题
  • Wi-Fi技术——OSI模型
  • VS安装 .NETFramework,Version=v4.6.x
  • React Hooks useMemo
  • [强网杯2019]随便注-----堆叠注入,预编译
  • centos7挂载iscis存储操作记录
  • postman 用于接口测试,举例
  • postman带Token测试接口
  • DAY50打卡
  • Redis 持久化 AOF 与 RDB 的区别
  • Ruoyi-vue-plus-5.x第二篇MyBatis-Plus数据持久层技术:2.1 MyBatis-Plus核心功能
  • audioLDM模型代码阅读(五)—— pipeline
  • Python学习大集合:基础与进阶、项目实践、系统与工具、Web 开发、测试与运维、人工智能(视频教程)
  • 电力电子技术知识学习-----晶闸管
  • VSCode中使用Markdown
  • 从零开始学炒股
  • cordova+umi 创建项目android APP
  • PythonDay42
  • KNN算法常见面试题
  • C数据结构:排序
  • 第25章学习笔记|额外的提示、技巧与技术(PowerShell 实战版)
  • Qt Core 之 QString
  • PyTorch 张量(Tensor)详解:从基础到实战
  • 【深度学习】配分函数:近似最大似然与替代准则
  • python复杂代码如何让ide自动推导提示内容
  • 编写Linux下usb设备驱动方法:disconnect函数中要完成的任务
  • More Effective C++ 条款20:协助完成返回值优化(Facilitate the Return Value Optimization)
  • 每日算法题【栈和队列】:栈和队列的实现、有效的括号、设计循环队列