多文件和静态/动态链接以及虚拟内存管理

2024-05-26 21:12

本文主要是介绍多文件和静态/动态链接以及虚拟内存管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

多目标文件链接

//stack.c
char stack[512];
int top =-1;
void push(char c){stack[++top] = c;
}char pop(void){return stack[top--];
}int is_empty(void){return top == 1;
}// main.c
#include <stdio.h>
int a,b = 1;
int main(){
push('a');
push('b');
push('c');
while(!is_empty())putchar(pop());putchar('\n');return 0;
}

通过 readelf -a main命令可以看到

  • main的.bss段合并了 main.o和stack.o的.bss段,包含了变量a和stack;
  • main的.data段合并了main.o和 stack.o的.data段,其中包含了变量b和top;
  • main的.text段合并了main.o和 stack.o的.text段
image-20240525234842552

PS: GDB如何调试多个文件的code

//main.c 和 stack.c 
(gdb)list stack.c:1 
(gdb)b stack.c:10     

定义和声明

extern和static

  • 用extern声明的函数名具有external linkage
  • 用static声明的函数名具有internal linkage
  • 函数默认是extern的

凡是被多次声明的变量或函数,必须有且只有一个声明是定义,如果有多个定义,或者一个定义都没有,链接器就无法完成链接。

变量声明和函数声明有一点不同,函数声明的 extern关键字可以省略,而变量声明如果不写 extern意思就完全变了

用 static关键字声明具有 Internal Linkage,保护了函数的内部状态,是一种封装

头文件

通过宏定义避免硬编码

//stack.h 
#ifdef STACK_H 
#define STACK_H void push(char); char pop(void); int is_empty(void); 
#endif//main.c 
#include "stack.h"          

对于用角括号包含的头文件**,gcc首先查找**-I**选项指定的目录,然后查找系统的头文件目录);

而对于用引号包含的头文件**,gcc首先查找包含这个头文件的当前文件所在的目录,然后查找**-I选项指定的目录,然后查找系统的头文件目录

则可以用gcc- c maln.c编译,gcc会自动在main.C所在的目录中找到stack. h。假如把 stack.h移到一个子目录下

image-20240525235130253

则需要用gcc- c main.c -Istack编译,用-I选项告诉gcc头文件要到子目录 stack里找

在#include预处理指示中可以使用相对路径,例如把上面的代码改成#include “stack/stack,h”,那么编译时就不需要加-Istack选项了,因为是main.c要包含头文件,gcc会自动在main.c所在的目录中查找,而头文件相对于main.c所在目录的相对路径正是 stack/ stack.h

PS:gcc -E可以产生预编译文件

避免头文件被重复包含的方法为header guard

写.C文件和头文件时一般来说应遵循以下原则:

  • C文件中可以有变量或函数定义,而.h文件中应该只有变量或函数声明而没有定义。
  • 不要把一个C文件包含到另一个C文件中。

静态库

把一组代码编译成一个库,很多项目中复用

例如将stack.c文件拆分为四个文件,main.c保持不变

image-20240525235204471
gcc -c stakc/stack.c stack/push.c stack/pop.c stack/is_empty.c
ar rs libstack.a stack.o push.o pop.o is_empty.o
# r表示将文件打包进libstack.a中,s表示为静态链接库
# 等价于
ar r libstack.a stack.o push.o pop.o is_empty.o
ranlib libstack.a
# 链接libstack.a main.c
gcc mian.c -L. -lstack -Istack -o main

-L选项告诉编译器去哪里找需要的库文件L.表示在当前目录找。-lstack选项告诉编译器要链接 libstack库-I选项告诉编译器去哪里找头文件

编译器默认会找哪些目录,用-print-search-dirs选项查看一下

gcc -print-search-dirs            

在处理-lstack选项时,gcc首先到-L选项指定的目录下查找,看有没有共享库Iibstack.so,如果有就链接它,否则再找有没有静态库 Iibstack,a,如果有就链接它,如果还是没有,就到默认搜索路径下按同样的步骤查找。

gcc在链接时优先考虑共享库,其次才是静态库,如果希望gcc只考虑静态库,可以指定-static选项。

main.c只调用了push这一个函数,所以链接生成的可执行文件中也只有push而没有pop和 is_empty。链接器从静态库中只取出需要的目标文件来做链接,不需要的目标文件可以不链接

共享库

组成共享库的目标文件和一般的目标文件有所不同,在编译时要加-fPIC选项,即位置无关编码

gcc -c -fPIC stakc/stack.c stack/push.c stack/pop.c stack/is_empty.c

指令中凡是用到stack和top变量的地址都用0x0表示,以备在重定位时修改。

image-20240526001510554

原来指令中的0x0被改成了0x804a010和0x804a040,这样做了重定位之后,各段的加载地址就定死了,因为在指令中使用了绝对地址

image-20240526001534124

和先前的结果不同,指令中的0x0(%ebx)被修改成-0xc(ebx)和-0x8(%ebx),而不是修改成绝对地址。所以共享库各段的加载地址并没有定死,可以加载到任意位置。因为指令中的地址都是相对于ebx的,没有使用绝对地址,只要根据实际的加载情况修改ebx就可以了,这就是位置无关代码的特点.

image-20240526001605212

对比前后的指令差异

image-20240525235450330

-0xc(%ebx)这个地址并不是变量top的地址,这个地址的内存单元中又保存了另外一个地址,而它才是变量top的地址。指令mov -0xc(%ebx),%eax是从地址ebx-12取出变量top的地址传给eax,而指令mov (%eax),%eax才是从top的地址取出top的值传给eax。指令lea 0x1(%eax),%edx是把top的值加1存到edx中。lea指令算出第一个操作数所代表的地址,但并不访问内存,而是直接把这个地址传给第二个操作数。我们知道x86的内存寻址方式涉及加法和乘法运算,lea指令只是利用寻址电路做加法和乘法运算,而不是真的寻址,

image-20240526001319076

将main.c文件和共享库链接

image-20240525235509342

用Ldd命令査看可执行文件依赖于哪些共享库:

image-20240525235515592

动态链接器在那些目录搜索共享库?

  1. 首先在环境变量LD_ LIBRARY_PATH保存的路径中查找
  2. 然后从缓存文件/etc/ld.so. cache中查找这个缓存文件是由 ldconfig命令读取配置文件/etc/ld.so.conf生成的
  3. 如果上述步骤都找不到,则到默认的系统库文件目录中查找,先是/usr/ib然后是/Lib。

最常用的方法。把lsibtack.so所在目录的绝对路径(比如/home/ akaedu/somedir)添加到配置文件/etc/ld.so.conf(该文件中每个路径占一行),然后运行ldconfig命令:

image-20240525235606585

再查看动态库

image-20240525235617265

函数的动态链接

和链接静态库的情况不同,push函数的指令没有链接到可执行文件中,而且call 86483d8-push@pLt>这条指令调用的也不是push函数的地址,而是plt段里的地址。PLT是 Procedure Linkage Table的缩写,.plt段里保存的也是指令,和.text一起合并到 Text Segment

image-20240525235657934

共享库命名

按照共享库的命名惯例,每个共享库有三个文件名:real name、 soname和 linker name

真正的库文件(而不是符号链接)的名字是 real name,包含完整的共享库版本号,例如上面的 libcap.so.1.10、libc-2.8.90.so等

soname是符号链接的名字,只包含共享库的主版本号

但对于依赖libcap.So.1的程序来说,真正的库文件不管是 Libcap.S0.1.16还是Libcap.so.1.11都可以用,所以使用共享库可以很方便地升级库文件而不需要重新编译程序,这是静态库所没有的优点。注意libc的版本编号有一点特殊,libc-2.8.90.s0的主版本号是6而不是2或28

linker name仅在编译链接时使用,gcc的-L选项应该指定 linker name所在的目录。有的 linker name是库文件的一个符号链接,有的 linker name是一段链接脚本。

虚拟内存管理

ps //查看进程
cat /porc/29977/maps //查看进程地址空间
image-20240525235734531

堆空间的地址上限(0x09497000)称为 Break,堆空间要向高地址增长就要抬高 Break,映射新的虚拟内存页面到物理内存,这是通过系统调用brk实现的, malloc函数也是调用brk向内核请求分配内存的。

操作系统虚拟内存控制机制的作用

(1)可以控制物理内存的访问权限

物理内存本身是不限制访问的,任何地址都可以读写,而操作系统要求不同的页面具有不同的访问权限,这是利用CPU模式和MMU的内存保护机制实现的。错误的指令或恶意代码的破坏能力受到了限制,最多使当前进程因段错误而终止,不会影响到整个系统的稳定性。

(2)使每个进程有独立的地址空间

不同进程中相同的VA被MMU映射到不同的PA,因此在某一个进程中访问任何虚拟地址都不可能访问到属于另外一个进程的物理内存页面,并且每个进程都认为自己独占0x0000000 xbffffffff 整个用户地址空间。独立地址空间的好处是:任何一个进程由于执行了错误指令或恶意代码而导致的非法内存访问都不会意外改写其他进程的数据,也不会影响其他进程的运行;链接器和加载器的实现也比较容易,不必考虑各进程的地址范围是否冲突

image-20240525235841597

两个进程都是bash进程, Text Segment是一样的,并且 Text Segment是只读的,不会被改写,因此操作系统安排两个进程的TextSegment共享相同的物理页面。由于每个进程都有自己的一套VA到PA的映射表,在一个进程中通过VA只能访问到属于自己的物理页面,而不会访问到其他进程的物理页面。

(3)VA到PA的映射会给分配和释放内存带来方便

物理地址不连续的几块内存可以映射成虚拟地址连续的一块内存。比如要用 malloc分配一块很大的内存空间,虽然有足够多的空闲物理内存,却没有足够大的连续空闲内存,这时就可以分配多个不连续的物理页面而映射到连续的虚拟地址范围。

image-20240525235849454

(4)一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存,虚拟内存管理机制使这种情况下各进程仍然能够正常运行

进程访问的是虚拟内存页面,这些页面的数据可以保存在物理页面中,也可以临时保存在磁盘上而不占用物理页面,可以在磁盘上开一个分区或者建一个文件专门用于临时保存虚拟内存页面的数据,这称为交换设备( Swap Device)。启用了交换设备之后,系统中可分配的内存总量等于物理内存的大小与交换设备的大小之和

这篇关于多文件和静态/动态链接以及虚拟内存管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1005605

相关文章

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、

IDEA中的Kafka管理神器详解

《IDEA中的Kafka管理神器详解》这款基于IDEA插件实现的Kafka管理工具,能够在本地IDE环境中直接运行,简化了设置流程,为开发者提供了更加紧密集成、高效且直观的Kafka操作体验... 目录免安装:IDEA中的Kafka管理神器!简介安装必要的插件创建 Kafka 连接第一步:创建连接第二步:选

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

.NET利用C#字节流动态操作Excel文件

《.NET利用C#字节流动态操作Excel文件》在.NET开发中,通过字节流动态操作Excel文件提供了一种高效且灵活的方式处理数据,本文将演示如何在.NET平台使用C#通过字节流创建,读取,编辑及保... 目录用C#创建并保存Excel工作簿为字节流用C#通过字节流直接读取Excel文件数据用C#通过字节

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖