可执行文件的装载

news2024/10/5 17:21:52

装载方式

回顾一下操作系统的知识,程序执行的时候需要的指令和数级都必须在内存中时,程序才能正常运行,最简单的方式就是将指令和数级全部加载到内存中,这样肯定可以顺利执行,但这样的方式对内存大小来说是一个考验。因此,装载方式必须尽可能的有效利用内存。

目前主流的装载方式是分页加载,它跟随着虚拟内存的发明而诞生。分页加载的方式不是一口气将所有数据和指令都加载到内存中,而是通过将磁盘中的指令和数据按照固定大小划分成多个页(一般情况下页的默认大小是4096字节),在执行过程中将需要被用到的指令和对应的数据所在的页加载到内存中即可。

一个页被加载到内存之后,会被内存管理单元所管理,当内存不足的时候,内存管理单元会选择一个页先移除出内存,然后装载新的页面。至于选择哪个页面则有多种算法控制,比如FIFO、LRU、LFU等。

进程的建立

通常情况下,进程的建立包含以下三个部分

  1. 创立独立的虚拟空间

  1. 读取可执行文件的信息,建立虚拟空间与可执行文件的映射关系

  1. 设置CPU的指令寄存器为可执行文件的入口地址

创建虚拟空间

虚拟空间实际上是由一组页映射构成,所以创建空间就是创建页映射所需要的数据结构。在i386的Linux下,创建虚拟空间只需要分配一个页目录(Page Directory)即可,其中的虚拟页到对应的物理空间的关系等到需要被访问到的时候才会被设置。

读取可执行文件信息

Segment

在创建完虚拟空间后,空间内的页还没加载。当在执行过程中,碰到这些缺少的页,那么操作系统应该从磁盘中读取缺少的页,然后分配一块对应大小的物理内存给它。在这整个过程中,最重要的一点就是操作系统应该知道怎么从磁盘找那个找到缺少的页。

操作系统在读取ELF信息进行映射的时候是以页作为单位的,如果在装载的时候,按section进行映射的话,每个section需要占用的内存都是页的整数倍,一些没占满一页的section会导致对应页出现内存碎片。而一般情况下,可执行文件中会具有十几个section,内存碎片所占用的空间就会非常的多。

为了解决内存碎片的问题,ELF在可执行文件中引入了一个Segment的概念,Segment包含多个属性类似的section。Segment的数据结构如下:

typedef struct
{
  Elf32_Word    p_type;         /* Segment type */
  Elf32_Off p_offset;       /* Segment file offset */
  Elf32_Addr    p_vaddr;        /* Segment virtual address */
  Elf32_Addr    p_paddr;        /* Segment physical address */
  Elf32_Word    p_filesz;       /* Segment size in file */
  Elf32_Word    p_memsz;        /* Segment size in memory */
  Elf32_Word    p_flags;        /* Segment flags */
  Elf32_Word    p_align;        /* Segment alignment */
} Elf32_Phdr;

typedef struct
{
  Elf64_Word    p_type;         /* Segment type */
  Elf64_Word    p_flags;        /* Segment flags */
  Elf64_Off p_offset;       /* Segment file offset */
  Elf64_Addr    p_vaddr;        /* Segment virtual address */
  Elf64_Addr    p_paddr;        /* Segment physical address */
  Elf64_Xword   p_filesz;       /* Segment size in file */
  Elf64_Xword   p_memsz;        /* Segment size in memory */
  Elf64_Xword   p_align;        /* Segment alignment */
} Elf64_Phdr;
  • p_type: Segment的类型,目前只需要关注PT_LOAD(1),其他类型如PT_DYNAMIC(2)、PT_INTERP(3)的类型会在动态链接中碰到

  • p_offset: Segment在文件中的偏移

  • p_vaddr: Segment的第一个字节在虚拟空间中的问题。

  • p_paddr: Segment的物理装载地址,实际上就是之前有碰到的LMA,一般情况下p_paddr 和 p_vaddr的值时相同的。

  • p_filesz: Segment在文件中的长度

  • p_memse: Segment在虚拟空间的长度

  • p_flags:Segment的权限属性

  • p_align: Segment的对齐属性。实际上存储的是2的幂,也就是说当p_align 等于10的时候,对齐字节为1024.

以下面的代码为例子

// a.cpp
#include<unistd.h>
  
int main(){

    while(1){
        sleep(1);
    }
    return 0;
}

使用gcc -static a.cpp -o main编译成可执行文件,通过readelf 可以看到这个程序具有二十多个section。

>>>> readelf main -S
There are 29 section headers, starting at offset 0xb8070:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.ABI-tag     NOTE             0000000000400200  00000200
       0000000000000020  0000000000000000   A       0     0     4
  [ 2] .note.gnu.build-i NOTE             0000000000400220  00000220
       0000000000000024  0000000000000000   A       0     0     4
readelf: Warning: [ 3]: Link field (0) should index a symtab section.
  [ 3] .rela.plt         RELA             0000000000400248  00000248
       0000000000000228  0000000000000018  AI       0    18     8
  [ 4] .init             PROGBITS         0000000000401000  00001000
       0000000000000017  0000000000000000  AX       0     0     4
  [ 5] .plt              PROGBITS         0000000000401018  00001018
       00000000000000b8  0000000000000000  AX       0     0     8
  [ 6] .text             PROGBITS         00000000004010d0  000010d0
       000000000007a510  0000000000000000  AX       0     0     16
  ..........
  [26] .symtab           SYMTAB           0000000000000000  000a62f0
       000000000000aec0  0000000000000018          27   742     8
  [27] .strtab           STRTAB           0000000000000000  000b11b0
       0000000000006d93  0000000000000000           0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  000b7f43
       0000000000000128  0000000000000000           0     0     1

而通过readelf -l 查看Segment Header的信息可以看到Segment信息并不多

>>>>> readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x401a30
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000470 0x0000000000000470  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x000000000007b071 0x000000000007b071  R E    0x1000
  LOAD           0x000000000007d000 0x000000000047d000 0x000000000047d000
                 0x00000000000237ac 0x00000000000237ac  R      0x1000
  LOAD           0x00000000000a10e0 0x00000000004a20e0 0x00000000004a20e0
                 0x00000000000051f0 0x0000000000006940  RW     0x1000
  NOTE           0x0000000000000200 0x0000000000400200 0x0000000000400200
                 0x0000000000000044 0x0000000000000044  R      0x4
  TLS            0x00000000000a10e0 0x00000000004a20e0 0x00000000004a20e0
                 0x0000000000000020 0x0000000000000060  R      0x8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x00000000000a10e0 0x00000000004a20e0 0x00000000004a20e0
                 0x0000000000002f20 0x0000000000002f20  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     .note.ABI-tag .note.gnu.build-id .rela.plt 
   01     .init .plt .text __libc_freeres_fn .fini 
   02     .rodata .eh_frame .gcc_except_table 
   03     .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data __libc_subfreeres __libc_IO_vtables __libc_atexit .bss __libc_freeres_ptrs 
   04     .note.ABI-tag .note.gnu.build-id 
   05     .tdata .tbss 
   06     
   07     .tdata .init_array .fini_array .data.rel.ro .got 

目前只关心LOAD的Segment,其他类型的段都是在装载过程中起辅助作用。通过上面的Segment信息,可以看到.init、.text等具有可读、可执行属性的Section被分到了同一个Segment中;.tdata、 .tbss等可读可写的Section被分到了同一个Segment中。

Segment和Section实际上是从不同角度来划分一个ELF文件,从Section来看ELF文件是链接视图(Linking View);从Segment角度来看就是执行试图(Execution View)

linux将虚拟空间的一个Segment称之为VMA(Virtual Memory Adress).

在操作系统里面,VMA除了被用来映射各个Segment之外,它还需要被使用管理进程的地址空间,比如大家熟知的堆(Heap)、栈(Stack)就是以VMA的形式存在的。通过查看/proc来看进程空间的分布。


>>> ./main &
[1] 12697

>>> cat /proc/12697/maps 
00400000-00401000 r--p 00000000 fe:21 805333484                           ./main
00401000-0047d000 r-xp 00001000 fe:21 805333484                           ./main
0047d000-004a1000 r--p 0007d000 fe:21 805333484                           ./main
004a2000-004a8000 rw-p 000a1000 fe:21 805333484                           ./main
004a8000-004a9000 rw-p 00000000 00:00 0 
00d3a000-00d5d000 rw-p 00000000 00:00 0                                  [heap]
7ffd4bf62000-7ffd4bf83000 rw-p 00000000 00:00 0                          [stack]
7ffd4bfc1000-7ffd4bfc4000 r--p 00000000 00:00 0                          [vvar]
7ffd4bfc4000-7ffd4bfc6000 r-xp 00000000 00:00 0                          [vdso]
  • 第一列是VMA的地址范围

  • 第二列是VMA的权限,r是可读,w是可写,x是可执行,p是私有。

  • 第三列是VMA对应的Segment在文件中的偏移

  • 第四列是VMA对应的主设备号和次设备号

  • 第五列是映像文件的节点号

  • 第六列是文件路径

像上面没有设备号的VMA被成为匿名虚拟内存空间,像stack、heap都属于这种VMA。

段地址对齐

引用书上的例子,考虑有以下几个Segment需要加载

长度

偏移

SEG0

127

34

SEG1

9899

164

SEG2

1988

ELF可执行文件的起始虚拟地址是0X08048000,对于每个Segment不是页的整数倍,假设按向上取整进行分配的话,则分配结果如下

起始虚拟地址

大小

有效字节

假设加载的物理地址

在文件中的偏移

SEG0

0X8048000

0X1000

127

0X00000 - 0X00FFF

34

SEG1

0X8049000

0X3000

9899

0X01000 - 0X03FFF

164

SEG2

0X804C000

0X1000

1988

0X04000 - 0X04FFF

3个Segment总长度12014字节,按这种对齐方式 会分配5个物理页面,共20480字节,空间使用效率只有58.6%。

为了解决这个内存碎片的问题,诞生了一种取巧的方式,即让部分物理页面引射两次。SEG1 和 SEG0可以一个物理页面,然后系统将这个物理地址映射成两个虚拟地址。

起始虚拟地址

大小

有效字节

假设加载的物理地址

在文件中的偏移

SEG0

0X8048022 (0X804800 + 34(0X22))

0X1000

127

0X00022 - 0X000A0

34(0x22)

SEG1

0X80490A4 (0x8048022 + 127(0x7F) + 3(字节对齐) + 0X1000(逻辑页面))

0X3000

9899

0X000A4 - 0X0274E

164(0xa4)

SEG2

0X804C74F (0X80490A4 + 9899(26AB) + 0X1000(逻辑页面))

0X1000

1988

0X0274F - 0X02F12

看到各个地址的运算,大概就能看得出来,物理地址从5个页面的分配被压缩到了3个页面的分配,实际物理的内存利用率是得到了提升,注意逻辑页面还是分配了5个,但逻辑页面是可以被替换出内存的,所以还是优先考虑物理内存的使用。

这边还有一个规律,那就是任何一个可装载的Segment, 它的p_vaddr % align == p_offset % align。

https://stackoverflow.com/questions/72414574/why-elfs-vaddr-is-not-page-aligned这篇帖子提供了原因。

设置程序执行入口地址

这一步的逻辑比较简单,操作系统设置CPU寄存器,将控制权转交给进程。虽然看上去是一句话的事情,但实际上操作系统所做的事情会比较复杂,涉及内核态与用户态的切换等。这个程序执行的入口地址存储在ELF文件头中,就是Entry point address。

总结

这边讨论了进程创建的一个大概的流程,主要是通过实验介绍了进程在创建的时候,是如何使用读取ELF文件的Segment信息的。还通过对《程序员自我修养》一书中的例子进行分析,详细分解了进程在加载Segment的时候是如何对物理页面的使用进行优化的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python中的socket网络编程

目录 一.服务端开发 1.什么是Socket网络编程 2.基于Socket完成服务端程序开发 步骤 演示 二.客户端开发 步骤 演示 一.服务端开发 1.什么是Socket网络编程 socket(简称套接字)是进程之间通信一个工具&#xff0c;好比现实生活中的插座,所有的家用电器要想工作都是基于…

Git系列:入门必备指令详解

Git系列&#xff1a;入门必备指令详解总览高频指令参考资料总览 常用指令使用流程如下图&#xff1a; workspace&#xff1a;工作区staging area&#xff1a;暂存区/缓存区local repository&#xff1a;版本库或本地仓库remote repository&#xff1a;远程仓库 ——引用自&…

线程学习笔记

线程 出现原因 MP3多个模块放在一个进程中&#xff0c;CPU处理能力&#xff0c;播放可能不连续&#xff1b;放在多个进程中&#xff0c;资源传递、进程维护等开销很大 进程相比于进程&#xff0c;地址空间直接共享 定义 线程是进程的执行流程&#xff0c;除了线程&#xf…

WPF之调用Iconfont

一、调用iconfont 1、首先再Iconfont中新建一个项目&#xff0c;注意修改font family 2、将在线的icon添加到项目中 3、下载安装包到本地 4、更改本地的fontfamily格式如下‘./路径文件夹名/#***’ ***是指再iconfont中的fontfamily 注意如果显示为口 可能是路径不对 如果…

数据结构——栈和队列

目录 一、栈&#xff08;Stack&#xff09; 1、定义 2、顺序结构模拟实现栈和常用方法 &#xff08;1&#xff09;.栈的顺序存储 &#xff08;2&#xff09;.基本方法 3、栈的链式结构与顺序结构对比 &#xff08;1&#xff09;.对比 4、区分概念 &#xff08;1&#x…

JavaScript 库之 vanilla-tilt(一个平滑的 3D 倾斜库)

JavaScript 库之 vanilla-tilt&#xff08;一个平滑的 3D 倾斜库&#xff09;参考获取vanilla-tilt特点使用示例使用1. data-tilt2. VanillaTilt.init()优先级示例配置选项其他参考 项目描述GitHub前往Vanilla-tilt.js前往 获取 Vanilla-tilt.jsGitHubnpm npm install vanil…

【开发环境】JRE 裁剪 ② ( 裁剪 bin 目录下的 dll 动态库文件 | 压缩 rt.jar 文件 )

文章目录一、裁剪 lib 目录下的 jar 文件二、压缩 rt.jar 文件参考博客 : 精简jre1.8精简jre步骤裁剪JRE(嵌入式设备的java环境移植) 资源下载地址 ( 本篇博客的资源快照 , 不是最终版的裁剪效果 ) : https://download.csdn.net/download/han1202012/87389091 JRE 裁剪分为三…

Pytorch深度学习【十五】

微调 网络架构 一个神经网络一般可以分成两块 特征抽取将原始像素变成容易线性分割的特征线性分类器来做分类 微调 思路—将相同功能的网络及其参数直接进行迁移使用&#xff0c;而并不是通过重新学习&#xff0c;只改变部分层次即可 训练 是一个目标数据集上的正常训练任务…

老杨说运维 | 2023,浅谈智能运维趋势(二)

&#xff08;文末附视频&#xff0c;一键观看精彩内容&#xff09; 前言&#xff1a; 上文提到了智能运维现状中的变化趋势以及 上文提到了智能运维现状中的变化趋势以及过往误区&#xff0c;老杨认为智能运维的体系化建设还需从抓牢数据治理为起点&#xff0c;以终为始做好规…

miniconda虚拟环境安装使用jupyter notebook及相关

一、安装jupyter 1创建miniconda虚拟环境。&#xff08;前面文章讲过了&#xff09; 2在创建的虚拟环境下&#xff0c;conda install ipython jupyter 3在该环境下执行jupyter notebook。 注意&#xff1a;此时打开的jupyter notebook内核Python 3(ipykernel)对应该虚拟环境&am…

数据安全治理 1

数据安全治理应以数据为中心&#xff0c;多元化主体共同参与&#xff0c;兼顾发展与安全。 数据安全总体视图&#xff0c;包含了体系&#xff0c;维度&#xff0c;目标&#xff0c;实践。 制度建设 技术体系&#xff0c;如加密、脱敏等手段&#xff0c;进行数据全生命周期的管…

盘点:2022年豆瓣评分8.0以上的计算机书籍有哪些?

2022年已经结束 &#xff0c;小编来盘点一下过去一年里出版的计算机图书里&#xff0c;有哪些计算机书籍是豆瓣评分8.0以上图书。 1、人工智能&#xff1a;现代方法&#xff08;第4版&#xff09;&#xff08;上下册&#xff09; ​ 系统性总结人工智能的方方面面&#xff0c;…

寒假每日一题2023——4261. 孤独的照片

写在前面 题目来源&#xff1a;AcWing 寒假每日一题2023活动 链接&#xff1a;https://www.acwing.com/problem/content/description/4264/ 题目 Farmer John 最近购入了 N 头新的奶牛&#xff0c;每头奶牛的品种是更赛牛&#xff08;Guernsey&#xff09;或荷斯坦牛&#x…

论文解读12——NGBoost: Natural Gradient Boosting for Probabilistic Prediction

目录1、文章贡献2、评分规则3、自然梯度4、自然梯度提升算法NGBoost1、文章贡献 由吴恩达团队提出的NGBoost是一种通过梯度提升进行概率预测的算法&#xff0c;与经典的回归模型返回一个点估计结果不同&#xff0c;概率回归模型返回全概率分布&#xff0c;将条件分布的参数视为…

Windows系统安装jenkins服务 war包形式

1.首先下载 jenkins war包 注意和jdk 的版本匹配 https://www.jenkins.io/zh/download/ 2.配置jenkins环境变量 如果不配置环境变量, jenkins的主目录默认生成在c盘下 环境变量怎么配? 请自行百度 3.运行jar包 注意修改war包路径 java -jar /data/jenkins/jenkins.war…

DNS原理与搭建(一)

文章目录一、DNS的概念与原理二、DNS私服搭建一、DNS的概念与原理 概念 DNS指的是域名服务器&#xff0c;就是将域名转换成IP[或者将IP转换成域名];当我们在浏览器中输入域名并按下回车&#xff0c;会对我们输入的域名进行解析&#xff0c;并返回一个IP地址&#xff0c;在通过…

Deepin系统深度学习环境配置指南

Deepin深度操作系统在众多Linux发行版本中深受欢迎&#xff0c;在实用性和专业方面到达了很好的均衡&#xff0c;该系统软件适配丰富支持很多第三方软件&#xff0c;增加了系统任务的灵活性。在装好该系统后&#xff0c;如何在该系统上进行深度学习代码训练调试&#xff0c;这就…

网络编程.

文章目录一、概述通信要素一&#xff1a;IP和端口号IP端口号通信要素二&#xff1a;网络协议TCP网络编程UDP网络编程URL网络编程每日一考一、概述 1、网络编程中两个主要问题&#xff1a; 如何定位网上的一台或多台主机&#xff0c;定位主机上的特定应用 找到主机后&#xff0…

北京化工大学1/17寒假集训题解(>1800)

目录 A - 文艺平衡树 B - 可持久化文艺平衡树 C - 可持久化平衡树 主要思路&#xff1a;FHQ Treap 可持久化 D - 维护数列 初始化 Insert操作 Delete操作 Reverse操作 Make-Same操作 Get-Sum操作 Max-Sum操作 懒标记的处理 E - 文本编辑器 A - 文艺平衡树 这里的…

贪心策略(二)兑换零钱(最后还得是动规)

兑换零钱(一)_牛客题霸_牛客网 兑换零钱(二)_牛客题霸_牛客网 兑换零钱(一)_牛客题霸_牛客网 描述 给定数组arr&#xff0c;arr中所有的值都为正整数且不重复。每个值代表一种面值的货币&#xff0c;每种面值的货币可以使用任意张&#xff0c;再给定一个aim&#xff0c;代表要…