Linux内存管理--系列文章陆——可执行文件的装载

应届生面试时,经常会有人问程序和进程有什么区别。简单来讲,程序是一个静态物品,就是存放在磁盘上的一些预先编译好的指令和数据的文件。而进程是一种运行的实例,它是程序在操作系统中的一次运行活动,具有生命周期。进程由程序代码、数据、当前执行状态和系统为其分配的资源组成。进程是程序的动态表现形式,它代表了程序的运行状态和执行过程。我们前面的三篇文章,其主要是对程序的处理,程序要运行起来、产生进程就要装载进内存。本章将讲述程序装载的过程。

一、装载的方式

程序的装载方式是指将程序代码和数据从外部存储器加载到内存中的过程,以便程序能够被CPU执行。根据加载方式的不同,程序的装载方式可以分为静态装载动态装载两种。

1.1 静态装载

静态装载是指在程序运行之前将整个程序代码和数据都加载到内存中。这种方式的优点是加载速度快,程序运行时无需再进行额外的加载操作。

优点
加载速度快: 因为所有代码和数据都已经在内存中,所以程序可以立即开始执行,而无需等待额外的加载操作。
执行效率高: 由于代码和数据都已经在内存中,所以CPU可以直接访问它们,而无需进行额外的磁盘 I/O 操作。
缺点
占用内存空间大: 因为整个程序都被加载到内存中,所以即使程序没有完全运行,也需要占用大量的内存空间。这对于大型程序来说尤其明显。
程序灵活性差: 由于代码和数据都已经被固化在内存中,所以很难对程序进行动态修改或更新

典型方法包括
全地址装载: 将程序代码和数据加载到预先分配的内存地址中。
相对地址装载: 将程序代码和数据加载到内存中,并根据程序加载的起始地址调整指令和数据的地址。
重定位装载: 将程序代码和数据加载到内存中,并在程序运行时根据实际内存地址进行重定位。

装载方式优点缺点典型应用场景
全地址装载简单易实现、执行效率高程序灵活性差、内存利用率低小型程序
相对地址装载程序灵活性高、内存利用率高执行效率略低、实现难度稍高中小型程序、可移植性要求高的程序
重定位装载程序灵活性高、内存利用率高、执行效率高实现难度较高、需要额外的硬件支持大型程序、共享库、动态链接程序

1.1.1 相对地址装载

相对地址装载(Relative Address Loading)是一种静态装载方式,它将程序代码和数据加载到内存中,并根据程序加载的起始地址调整指令和数据的地址。这意味着程序中的指令和数据地址并不是固定的,而是相对于程序加载的起始地址而言的。
优点
程序灵活性高: 由于程序的地址是相对于加载地址而言的,因此可以很容易地对程序进行动态修改或更新。
内存利用率高: 由于程序的地址是相对于加载地址而言的,因此可以有效地避免内存碎片化问题,提高内存利用率。
缺点
执行效率略低: 由于指令和数据的地址需要进行地址转换,因此执行效率略低于全地址装载。
实现难度稍高: 相对于全地址装载,相对地址装载的实现难度稍高。

1.1.2 重定位装载

重定位装载(Relocation Loading)是一种将程序代码和数据加载到内存中,并在程序运行时根据实际内存地址进行重定位的静态装载方式。这意味着程序中的指令和数据的地址并不是固定的,而是根据内存的分配情况进行调整。
实现细节
程序地址分配: 在程序加载之前,操作系统会为程序分配一段虚拟内存空间。虚拟内存空间是逻辑地址空间的一部分,它可以被程序使用的地址空间。
程序加载: 将程序代码和数据从外部存储器加载到预先分配的虚拟内存地址中。
地址转换: 在程序运行过程中,MMU会将程序中的虚拟地址转换为实际的物理地址。物理地址是内存中的实际地址,它可以被CPU直接访问。

优点
灵活性:程序可以加载到内存的任意位置,不受固定地址限制。
内存利用效率:操作系统可以根据当前内存使用情况选择最佳加载地址,避免内存碎片。
模块化:支持动态链接库和模块化程序设计,使得代码可以更好地复用和共享。
执行效率高: 与相对地址装载相比,重定位装载的执行效率更高,因为它只需要在程序加载时进行一次地址转换,而程序运行过程中无需进行额外的地址转换。
缺点
加载开销:需要在加载时进行地址调整,增加了程序启动时间。
复杂性:需要维护重定位表和符号表,增加了系统的复杂性。
需要额外的硬件支持: 重定位装载需要硬件的支持,例如内存管理单元(MMU),才能实现地址转换功能。

1.2 动态装载

动态装载是指在程序运行时只加载程序运行所需的部分代码和数据,并将未使用的部分代码和数据留在外部存储器中。这种方式的优点是节省内存空间,提高内存利用率。缺点是加载速度较慢,程序运行时可能会因为需要加载额外的代码和数据而导致性能下降。
典型方法包括
覆盖装载: 将程序代码和数据分成多个模块,根据程序运行情况动态加载需要的模块。
段式装载: 将程序代码和数据分成多个段,根据程序运行情况动态加载需要的段。
页式装载: 将程序代码和数据分成多个页,根据程序运行情况动态加载需要的页。

装载方式优点缺点典型应用场景
覆盖装载提高内存利用率、降低程序复杂度降低程序执行效率、增加程序复杂度小型计算机、嵌入式系统
段式装载提高内存利用率、增强程序安全性、方便程序共享增加程序复杂度、降低程序执行效率大型程序、共享库、操作系统
页式装载方便实现虚拟内存、内存利用率高、简化地址转换增加程序复杂度、需要额外的硬件支持大型程序、操作系统、虚拟机

1.2.1 段式装载

段式装载(Segmented Loading)是一种内存管理技术,将程序的地址空间划分为若干个段(Segment),每个段表示一组具有相同属性的逻辑单元,如代码段、数据段和堆栈段。段式装载不仅提高了内存利用效率,还支持灵活的内存保护和共享。
基本概念
段(Segment):程序的地址空间被划分为若干个段,每个段对应一个逻辑单元,如代码段、数据段、堆栈段等。段的大小不固定,可以根据程序的需要动态调整。
段表(Segment Table):操作系统维护一个段表,记录每个段的基地址和段的长度。每个进程有自己的段表,用于管理其地址空间。
段选择器(Segment Selector):用于标识程序访问的段。通过段选择器在段表中查找对应段的基地址和长度。
逻辑地址(Logical Address):程序使用逻辑地址进行内存访问,逻辑地址由段选择器和段内偏移量组成。段选择器用于查找段表,段内偏移量用于计算段内的具体地址。

段式装载的过程
初始化:当程序开始执行时,操作系统为其分配段表,并初始化段表中的各个段。
地址转换:程序使用逻辑地址访问内存。每次访问时,CPU使用段选择器查找段表,获取对应段的基地址和段的长度。检查段内偏移量是否在段的长度范围内。如果在范围内,计算实际的物理地址,否则产生段错误(Segment Fault)。物理地址由段基地址加上段内偏移量计算得到。
段的加载和管理:段可以按需加载和卸载,未使用的段可以不加载到内存中,以节省内存。段的大小可以动态调整,支持程序的内存需求变化。

1.2.2 页式装载

页式装载(Paging)是一种内存管理技术,将程序的内存地址空间划分为固定大小的块(称为页),并将这些页动态加载到物理内存中。这种方式使得内存管理更加高效,并且支持虚拟内存技术,从而允许程序在不连续的物理内存中执行。
基本概念
页(Page):程序的虚拟地址空间被划分为固定大小的块,通常是4KB或更大。这些块称为页。
页框(Page Frame):物理内存也被划分为与页大小相同的块,称为页框。
页表(Page Table):页表是操作系统维护的一种数据结构,用于记录虚拟页和物理页框之间的映射关系。
页面错误(Page Fault):当程序访问的页不在内存中时,会发生页面错误。操作系统捕获这个错误,并将所需的页从磁盘加载到物理内存中。

页式装载的过程
初始化:当程序开始执行时,操作系统为其分配页表,并初始化部分必要的页到物理内存中。
地址转换:程序使用虚拟地址进行访问。每次访问时,CPU使用页表将虚拟地址转换为物理地址。
虚拟地址通常分为两部分:页号(Page Number)和页内偏移量(Offset)。
页号用于查找页表,获取对应的物理页框号,再加上页内偏移量形成物理地址。
页面加载:当访问的页不在物理内存中时,发生页面错误。操作系统处理页面错误,将所需的页从磁盘加载到物理内存中的一个页框。如果内存已满,操作系统选择一个页框进行替换,将其内容写回磁盘(如果已修改),然后加载新页。
页表更新:页表中的对应条目被更新,记录新加载的页的物理页框号。

1.3 补充

1.3.1 MMU

内存管理单元(Memory Management Unit,MMU)是计算机系统中的一个硬件组件,负责处理虚拟内存到物理内存的地址转换,以及提供内存保护和权限控制。MMU在现代计算机体系结构中起着至关重要的作用,使得操作系统能够高效管理内存,并实现多任务处理和内存保护。
功能
地址转换:将程序生成的虚拟地址转换为物理地址。使用页表(Page Table)或段表(Segment Table)来进行转换。
内存保护:提供内存访问权限控制,防止进程非法访问其他进程的内存。设置读、写、执行等访问权限,保护操作系统和关键数据的安全。
分页管理:支持分页(Paging)机制,将内存划分为固定大小的页和页框。实现虚拟内存管理,使得程序可以使用比物理内存更大的地址空间。
段式管理:支持段式(Segmentation)机制,将内存划分为不同大小的段。每个段可以有不同的访问权限和属性,提供灵活的内存管理。
页面替换:管理页面替换算法,当物理内存不足时,将不常用的页面换出到磁盘。保证系统的稳定性和高效性。
工作原理
地址生成:CPU生成一个虚拟地址,包含页号和页内偏移量(对于分页机制)或段选择器和段内偏移量(对于段式机制)。
查找页表/段表:MMU根据页号查找页表,获取对应的物理页框号;或根据段选择器查找段表,获取对应的段基地址和段长度。
地址转换:对于分页机制,MMU将页框号与页内偏移量组合,生成物理地址。对于段式机制,MMU将段基地址与段内偏移量相加,生成物理地址。
权限检查:MMU检查访问权限,确保当前操作(读、写、执行)在允许的范围内。如果权限不符,MMU产生内存保护异常(如页面错误或段错误),通知操作系统处理。
访问物理内存:如果地址转换和权限检查成功,MMU将物理地址发送到内存控制器,完成内存访问操作。

1.3.2 页面错误

原因
1.程序请求的页面尚未加载到内存中。 这种情况常见于程序启动时或程序需要访问新的数据或代码时。
2.程序请求的页面被换出到磁盘上。 为了实现虚拟内存,操作系统会将未使用的页面换出到磁盘上,以释放内存空间。当程序需要访问被换出的页面时,就会发生页面错误。
3.程序访问的页面无效或受保护。 这种情况常见于程序试图访问非法内存地址或受保护的内存区域。
处理方式
1.将请求的页面加载到内存中。 这是最常见的情况。操作系统会从磁盘上将请求的页面加载到内存中,并更新页表,使程序能够访问该页面。
2.终止程序。 如果页面错误无法被修复,操作系统可能会终止程序。
3.显示错误消息。 操作系统可能会显示一条错误消息,指示用户发生了页面错误。

页面错误会对程序的运行性能产生负面影响,因为它会导致程序暂停运行,直到页面错误被处理完毕。为了减少页面错误的发生,操作系统通常会采用以下策略
1.使用最近最少使用(LRU)算法来管理内存, LRU算法会优先将最近使用过的页面保留在内存中,而将最久未使用过的页面换出到磁盘上。
2.增加内存容量, 更大的内存容量可以减少页面错误的发生,因为更多的页面可以保留在内存中。
3.使用预取技术, 预取技术是指在程序需要使用某个页面之前将其加载到内存中。这样可以减少程序运行时发生页面错误的概率。

1.3.3 覆盖装载

覆盖装载(Overlay Loading)在早期计算机系统中广泛应用,特别是在内存资源非常有限的环境下。虽然现代计算机系统内存容量大幅增加,覆盖装载的应用场景减少,但在某些嵌入式系统和特定环境中,覆盖装载技术仍然具有一定的应用价值。
过程
程序划分
在编写和编译程序时,将程序划分为根模块和若干覆盖区。
根据程序的逻辑和功能,将互斥的部分划分到不同的覆盖区。
覆盖结构图设计
设计覆盖结构图,描述覆盖区之间的调用关系和覆盖层次。
确定覆盖区的加载顺序和依赖关系。
程序链接
链接器根据覆盖结构图和覆盖区的划分生成可执行文件。
可执行文件包含根模块和所有覆盖区的信息。
加载和执行
程序开始执行时,操作系统将根模块加载到内存。
根模块根据需要动态加载和卸载覆盖区,确保程序在有限的内存中运行。
当需要切换覆盖区时,根模块将当前覆盖区的内容保存,并加载新的覆盖区。
注意点
覆盖区中使用的模块必须全部在内存中:从根模块到覆盖区的最后一个模块,都必须全部在内存中。为了保证最后一个模块在执行完毕后,可以正确的返回上级模块和根模块。
禁止跨覆盖区调用模块:覆盖装载无法保证跨覆盖区的模块能够同时存在于内存。但如有一个模块被多个覆盖区依赖,可以将该模块并入根模块中。

1.3.4 装载和动态链接的区别

装载是将程序代码从磁盘加载到内存中的过程,而动态链接是在程序运行时将共享库加载到内存并链接到程序的过程。无论是动态链接还是静态链接,它们都是需要使用到装载的。不一样的是静态链接先链接再装载,动态链接先装载再链接。

方式优点缺点典型应用场景
静态装载简单易实现、执行效率高程序灵活性差、内存利用率低小型程序
动态链接程序灵活性高、内存利用率高增加程序复杂度、降低程序执行效率中小型程序、可移植性要求高的程序
动态装载提高内存利用率、提高程序灵活性、降低程序耦合度增加程序复杂度、降低程序执行效率大型程序、共享库、插件

二、Linux装载ELF的过程

装载过程主要操作是在LInux的进程管理模块中进行,本文主要讲一下大致流程,详细的以后会在进程管理中讲解。大家可以查看do_execve函数在内核中的执行过程。
步骤
1. 程序启动
当用户执行一个可执行文件时,操作系统首先会创建一个新的进程。该进程将拥有自己的虚拟地址空间和内存空间。
然后,操作系统会将可执行文件映射到进程的虚拟地址空间中。这意味着可执行文件的内容会被复制到进程的内存空间中,但不会实际占用物理内存。
2. 加载程序头
ELF文件的程序头表位于文件的开头。它包含有关程序的信息,例如程序的入口地址、代码段和数据段的大小和位置等。
操作系统会读取程序头表,以获取这些信息。这些信息将用于加载程序的段和设置程序上下文。
3. 加载段
程序的代码段和数据段通常是程序中最大的部分。它们包含程序的代码和数据。
操作系统会将代码段和数据段加载到内存中。代码段通常被标记为可执行,这意味着CPU可以从该段中读取和执行指令。数据段通常被标记为可读写,这意味着程序可以从该段中读取和写入数据。
4. 设置程序上下文
程序的上下文是指程序的寄存器值。寄存器是CPU内部的存储器,用于存储程序的状态。
操作系统会设置程序的寄存器,例如程序计数器 (PC) 和堆栈指针 (SP)。PC 指向程序的入口地址,SP 指向程序堆栈的顶部。
5. 开始执行
当程序的上下文设置好之后,操作系统会将CPU控制权交给程序。程序会从其入口地址开始执行。
入口地址通常位于程序的代码段中。它是一个函数的地址,该函数包含程序的主逻辑。

本篇大致讲述了程序装载的情况,也提到了页等概念。下一篇文章将会对页的情况进行阐述,这将涉及到物理内存,所以下一篇文章主要讲硬件架构。

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值