可执行文件的装载

本文深入解析了程序装载的两种主要方式:静态装入与动态装入。详细介绍了动态装入中的覆盖装入与页映射机制,以及Linux内核如何处理ELF格式文件的装载过程。涵盖了模块划分、页错误处理及虚拟存储机制等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

装载的方式

  • 静态装入

静态装入:将程序运行所需的指令和数据全部装入内存中。

  • 动态装入

   很多情况下所需要的内存数量大于物理内存的数量,这可以通过添加内存的方式解决,但是内存较为昂贵,所以就希望通过不增加内存的方式来解决。又根据程序局部性原理:程序运行时,在某一时间段只是执行一小部分数据,我们可以将常用数据部分留到内存,不常用的数据可以存放到磁盘,等需要执行时再拷贝到内存中。这就是动态装入的原理。

   动态装入有两种方式:

  1.  覆盖装入
  2.  页映射

   覆盖装入

   覆盖装入需要程序员手工对将程序进行模块划分,并通过编写辅助代码来管理(也称覆盖管理器,由于所占空间很小,所以常驻内存),某一时间将某一模块拷入内存,某一时间将某一模块进行替换。这些模块所占用的都是同一内存空间,只是谁被调用谁使用。

         在程序员进行模块划分时,特别要注意各模块间的依赖关系

          如图组织关系:

 

        模块main依赖于A 和 B,A依赖于C 和 D,同时B依赖于 E。这种组织结构可以看成树状结构。

        值得注意的是:

  1.  这个树状结构任意模块到模块main(即树根)都叫做调用路径。而当某一模块被调用时整个调用路径模块必须都在内存中。比如调用C时A、main模块必须都在内存中。
  2.  禁止跨树间调用。比如:模块A不得调用模块B、模块E。假使有一模块F都被模块A和模B依赖,则可以将模块F并入模块main中。

       覆盖载入是较为早期的动态装入方式,但在使用上较为复杂,且每个模块都需要经过覆盖管理器,如果某一模块不在内存还需要从磁盘中拷入,所以其运行速度相对较慢。 

   页映射

       页映射是虚拟存储机制的一部分。

       首先将内存和进程虚拟地址空间、磁盘中的数据和指令按“页”为单位划分。页大小有4096字节、8192字节、2MB、4MB等。比如:32KB按4096字节为一页,可划分为8页。

       再将常用的数据和代码页装载到内存中,不常用的代码和数据保存到磁盘。

       当我们程序执行需要某一页时,再将对应页从磁盘中装入到内存。

       如上图所示,我们将VP0、VP1、VP6、VP7装入内存,而此时程序要访问VP2时,此时硬件会捕获到这个信息,就是所谓的页错误,然后操作系统接管进程,负责将VP2从磁盘中读出来并装入内存,然后在内存中的这个页与VP2建立映射关系

     在处理页错误时,我们可以通过多种算法处理。如:FIFO(先进先出算法)、LUR(最少使用算法)...。

        在上述的页映射的动态装入的方式可看出,虚拟地址空间中的页可能被映射到内存中任意页。如VP2可以被装入到PP0~PP3的任意一个页中,显然,如果程序使用物理地址直接进行操作时,需要对每次装入的页进行重定位,这一功能通过硬件MMU来实现。

 

Linux内核装载ELF过程

        ELF:Linux下可执行文件的格式。

ELF格式中的可执行文件:

  1.  可重定位文件。文件保存着代码和适当的数据,用来与其他目标文件创建一个可执行文件或者共享文件;
  2.  可执行文件。保存这用来执行的程序,该文件只出了exec(BA_OS)如何来创建程序进程映像;
  3.  共享目标文件。保存着代码和合适的数据,用来被下面的两个链接器链接(连接编辑器、动态链接器)。

        ELF文件装载过程:

以上内容还可以参考: https://www.cnblogs.com/L20135304/p/5375698.html

### 使用 GCC 生成可执行文件及相关选项 #### 可执行文件生成概述 通过 `gcc` 将源代码编译成最终的可执行文件是一个多阶段的过程,通常包括预处理、编译、汇编和链接四个主要步骤。默认情况下,当用户提供 `.c` 或其他支持的语言源文件时,`gcc` 自动完成这些步骤并生成一个名为 `a.out` 的可执行文件[^1]。 如果希望自定义输出文件名称,则可以使用 `-o` 参数来指定目标文件名。例如: ```bash gcc source.c -o my_program ``` 这条命令会将 `source.c` 文件编译为名为 `my_program` 的可执行文件[^2]。 #### 关键编译选项解析 - **`-I<directory>`**: 指定额外的头文件搜索路径。这对于项目中使用的第三方库或者非标准安装位置非常重要[^3]。 示例: ```bash gcc -I/usr/local/include/mylib source.c -o program_with_headers ``` - **`-L<directory>`**: 设置库文件的查找目录。这允许链接器找到所需的静态或动态库文件。 示例: ```bash gcc -L/usr/local/lib source.c -o program_linked_to_library ``` - **`-l<library_name>`**: 请求链接指定的库(去掉前缀 `lib` 和后缀 `.so` 或 `.a`)。例如,要链接名为 `libexample.so` 的共享库,只需加上 `-lexample` 即可[^2]。 综合示例: ```bash gcc -I/usr/local/include -L/usr/local/lib -lexample source.c -o complete_executable ``` - **`-g`**: 插入调试信息以便于后期利用 GDB 等工具进行错误定位和修复工作[^3]。 - **优化等级 (`-O`)** - `-O0`: 默认值,表示不启用任何优化措施; - `-O1`, `-O2`, `-O3`: 分别对应不同程度的速度提升策略,其中更高的级别可能会带来更大的二进制大小增加以及更复杂的构建时间消耗; - `-Os`: 主要考虑减少内存占用而非单纯提高速度[^5]。 - **生成警告信息** - `-w`: 抑制全部警告提示; - `-Wall`: 开启大部分常见类型的提醒事项; - `-Wextra`: 补充更多可能存在问题的地方给开发者注意^. - **创建共享对象 (-shared)**: 若打算制作可供他人调用的 DLL(Windows)/SO(Linux), 此标记必不可少,并且往往还需要配合 `-fPIC` 来确保产生的机器码能够适应任意装载地址[^3]. 综合实例展示整个流程: 假设存在两个文件——main.c 和 helper.c ,并且需要连接 math 库实现某些计算功能. ```bash gcc -std=c99 -Wall -Wextra -pedantic-errors \ -I./include -L./lib -lm \ main.c helper.c -o final_app ``` 这里包含了现代 C 标准的选择(-std=c99),全面开启告警机制(Wall Wextra pedantic-errors),指定了本地包含与库所在的具体地方(I L flags),最后还加入了数学运算能力(lm). --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值