从一个程序文件是如何被加载运行开始讲起

分类: 365速度发国际大厅 时间: 2025-07-21 12:49:02 作者: admin 阅读: 2249

目录

一、一个程序文件是如何被加载运行的

📝 第一步:加载可执行文件

🛠 第二步:为进程创建独立的地址空间

🎯 第三步:执行程序

🛑 程序结束后

🌟 结论

二、分段为什么通常将内存分为 代码段、数据段和栈段

📌 为什么要分成这三类?

📌 为什么不直接用一个大块内存存所有东西?

📌 总结

三、从图书管理来理解分页(Paging)和分段(Segmentation)

🌟 场景设定

📖 分段(Segmentation) = 按类别存书

📦 分页(Paging)= 按格子存书

🌍 现实中的应用

分段(Segmentation) 适合模块化管理

分页(Paging) 适合现代内存管理

🎯 总结

四、总结分段和分页

1. 分段(Segmentation)

2. 分页(Paging)

3. 分段 vs. 分页

4. 分段 + 分页(现代 x86 处理器)

总结

一、一个程序文件是如何被加载运行的

当你运行一个可执行文件(比如 ELF 格式的 Linux 可执行文件),计算机需要完成以下步骤:

📝 第一步:加载可执行文件

🔹 操作系统(OS)使用程序加载器(Loader)完成这个过程:

找到程序文件(Executable File)

你在终端输入 ./a.out 或双击应用程序,OS 先去硬盘(磁盘)中找到这个可执行文件。 解析可执行文件的格式

可执行文件内部有一个“程序头表”(Program Header Table),记录了:

代码(text segment)存在哪数据(data segment)存在哪入口点(entry point),也就是 main() 函数的位置。 创建进程,并分配地址空间(由 OS 的**内存管理单元(MMU)**负责)

代码段(CS):把程序代码加载到只读的内存区域。数据段(DS):存放全局变量、静态变量。栈段(SS):给函数调用的栈帧(Stack Frame)预留空间。堆区(Heap)(malloc/new 用的内存):初始时几乎为空,运行时才动态增长。映射段(Memory Mapping Segment):加载共享库(如 libc.so),也用于 mmap() 操作。

🛠 第二步:为进程创建独立的地址空间

每个进程都会有自己独立的虚拟地址空间(Virtual Address Space),虽然多个进程可能在物理内存(RAM)中共享同样的代码段(比如多个进程都运行 bash),但它们在虚拟地址空间里互不干扰。

🖥 典型的虚拟地址空间布局(Linux x86-64):

0x0000000000000000 → ❌ 空地址(NULL)

0x0000000040000000 → 📜 代码段(Text Segment)

0x0000000060000000 → 📝 数据段(Data Segment)

0x000000007FFFFFFF → 🔼 栈段(Stack Segment,向下增长)

0x00007FXXXXXXX000 → 📦 映射段(共享库、mmap)

0x00007FFFFFFFFFFF → 💭 未分配区域

代码段(Text Segment)

多个进程可以共享,只读,不可修改。 数据段(Data Segment)

进程独立,存放全局变量、静态变量。 栈段(Stack Segment)

进程独立,每次调用函数时都会向下增长(LIFO)。 堆区(Heap Segment)

由 malloc() 和 new 动态分配,可以不断扩展。

💡 共享代码,但数据和栈独立: 比如两个进程都运行 vim,它们的代码段可以共用,但它们的数据段和栈段是各自独立的,互不影响。

🎯 第三步:执行程序

CPU 设置程序计数器(PC)= 入口点地址(Entry Point)

入口点通常是 main(),但在执行 main() 之前,操作系统会调用运行时环境(Runtime)做一些初始化工作(比如初始化 libc)。 开始执行指令

CPU 读取 代码段(text segment)中的指令并执行。 使用数据段和栈段

全局变量:从数据段(DS)中读取和写入。局部变量、函数调用:在栈段(SS)中存储和管理。 动态内存分配

运行过程中,如果 malloc() / new 申请新内存,操作系统会调整**堆区(Heap)**的大小。

🛑 程序结束后

释放资源:进程退出时,OS 负责回收进程的地址空间,释放代码段、数据段、栈段、堆区等内存。共享库可以继续被其他进程使用,不需要重复加载。

🌟 结论

每个进程有独立的代码段、数据段和栈段,确保进程互不干扰。代码段可能被多个进程共享(比如多个 vim 进程共用 vim 的代码)。数据段和栈段是独立的,因为每个进程的数据和栈都不一样。程序执行时,CPU 依次访问代码段、数据段和栈段,并通过分页/分段进行地址转换。程序退出后,OS 负责回收进程的所有内存,确保不浪费资源。

💡 总结一句话: 当你运行一个程序,OS 会给它分配独立的虚拟地址空间,包括代码段、数据段、栈段和堆区,其中代码段可能被多个进程共享,而数据段和栈段是独立的。CPU 通过分页/分段机制管理这些内存,确保高效、安全地执行程序。 🚀

二、分段为什么通常将内存分为 代码段、数据段和栈段

分段(Segmentation)通常将内存分为 代码段(Code Segment)、数据段(Data Segment)和栈段(Stack Segment),是基于程序运行的实际需求和访问模式来划分的。这样做的目的是提高安全性、可管理性和执行效率。

📌 为什么要分成这三类?

代码段(Code Segment, CS)

存放:程序的可执行代码(指令)。特点:

代码通常是只读的,防止程序错误地修改自己的指令(提升安全性)。共享性:多个进程可以共享相同的代码段(例如多个程序运行同一个库)。执行时大小固定,不需要频繁扩展。 现实类比: 代码段就像是教科书📖,你可以阅读(执行)它,但不能随意修改内容。 数据段(Data Segment, DS)

存放:全局变量、静态变量、已初始化和未初始化的数据。特点:

可读写:数据是可以修改的(不像代码段)。一般不会频繁变动大小(不像栈段)。 现实类比: 数据段就像是笔记本📔,你可以写入或修改内容。 栈段(Stack Segment, SS)

存放:函数调用时的局部变量、返回地址、参数等。特点:

自动增长和缩小(栈是动态的)。遵循 “后进先出(LIFO)” 规则。由 CPU 自动管理,访问速度极快。 现实类比: 栈段就像是便签纸📑,每次你调用一个函数,就像往上贴一张新便签(入栈);当函数执行完,就撕掉最上面的便签(出栈)。

📌 为什么不直接用一个大块内存存所有东西?

如果不分段,而是把所有数据、代码和栈混在一起,会带来以下问题:

安全性低

假如代码和数据混在一起,错误的操作可能会覆盖代码,导致程序崩溃甚至被恶意利用(比如 buffer overflow 攻击)。现代 CPU 一般禁止代码段被写入(即“不可自修改”)。 管理困难

代码通常是只读的,而数据需要读写,如果混在一起,系统不好管理权限。栈需要动态调整大小,而代码是固定的,放一起会很乱。 性能优化

现代 CPU 通过**指令缓存(Instruction Cache, I-Cache)加速代码执行,而数据则通过数据缓存(D-Cache)**加速访问。如果代码和数据混合存储,缓存效率会下降。

📌 总结

类别用途特点现实类比代码段(CS)存放程序指令只读,可共享教科书📖(可读但不能改)数据段(DS)存放全局变量可读写笔记本📔(可以修改)栈段(SS)存放函数调用信息动态增长/缩小便签📑(调用新函数就贴新便签)

🔹 现实中,大多数现代 CPU(如 x86-64)虽然仍然有分段机制,但主要使用**分页(Paging)**来管理内存。分段的概念更多用于早期 16-bit/32-bit 体系,或者在某些特定系统(如嵌入式)仍有应用。

三、从图书管理来理解分页(Paging)和分段(Segmentation)

🌟 场景设定

你是一名图书管理员,需要管理一个超大书架(内存)。 书架上存放的是不同种类的书籍(程序数据),读者(CPU)会不断来借书(访问内存)。 问题来了:如何高效地管理书籍的位置?

📖 分段(Segmentation) = 按类别存书

方法:

你按照书籍的类型,把书架分成不同的区域(段),比如:

第一层 → 放小说 📚(代码段)第二层 → 放科学书 📖(数据段)第三层 → 放字典 📕(栈段)

特点:

每个类别的书籍大小不固定,可能有一层全是小说,也可能有半层都是科学书。当新的书籍进来时,你需要调整整个书架的布局,防止某个类别的书超出范围(内存碎片问题)。如果读者要借一本小说,你直接告诉他去第一层。

优点: ✅ 逻辑清晰,每个段存放特定的内容。 ❌ 如果小说区放满了,但科学书区还有空间,小说区不能用科学书区的空间(内存碎片)。

📦 分页(Paging)= 按格子存书

方法:

你把书架均匀地分成固定大小的格子(页),每个格子可以存放一本书,不管是小说还是科学书。你有一本索引表(页表),记录每本书在哪个格子里。

特点:

每本书不再按类别存放,而是按固定大小的格子存储。读者来借书时,你查索引表,告诉他书在第 10 格、32 格、58 格。如果书架满了,但某些书没被读者借阅很久了,你可以把它们存到仓库(换页到磁盘),等读者要借时再拿回来(虚拟内存)。

优点: ✅ 书可以灵活放置,不会浪费空间。 ✅ 需要更多空间时,可以随时扩展到别的格子(避免外部碎片)。 ❌ 读者要借书时,你得先查索引表,可能会慢一些(多了一次地址转换)。

🌍 现实中的应用

分段(Segmentation) 适合模块化管理

就像图书馆按照小说区、科技区、历史区分类书籍,分段适合把代码、数据、栈分开管理。但如果某个类别的书太多,可能会占满空间,而其他类别还有很多剩余空间无法利用(碎片问题)。

分页(Paging) 适合现代内存管理

就像书架固定成相等的小格子,分页让内存可以灵活分配,不管是小说还是字典,都能均匀放入。但每次借书时,需要查索引表才能知道具体在哪个格子(页表查找),可能会增加一点开销。

🎯 总结

方式比喻优点缺点分段(Segmentation)书架按类别划分(小说区、科技区)逻辑清晰,按用途分区可能浪费空间,碎片问题分页(Paging)书架按均等格子划分高效利用空间,不浪费需要额外的索引表(页表)

🔹 现实中,现代 CPU 同时使用分页+分段,但大多数操作系统主要依赖分页来管理内存,避免碎片问题。

四、总结分段和分页

CPU 的分页(Paging)和分段(Segmentation)是两种不同的内存管理机制,用于解决内存分配、保护和虚拟内存访问问题。

1. 分段(Segmentation)

概念: 分段是将内存划分为逻辑段(Segment),每个段具有不同的基地址和大小,并且段可以用于不同目的(如代码段、数据段、栈段)。

特点:

段的长度可变,不像分页那样固定大小。每个段都有一个段基址(Base Address)和段界限(Limit),超出界限访问会触发异常。保护性强,适合程序模块化,如代码段只允许执行,数据段只允许读写。

x86 32 位模式下的段寄存器:

CS(Code Segment) → 存放代码DS(Data Segment) → 存放数据SS(Stack Segment) → 存放栈ES、FS、GS → 额外的段寄存器

示例: 假设有一个数据段:

段基址 = 0x100000段界限 = 0x20000(128 KB)

如果程序访问 0x100000 + 0x15000(在界限内),则访问成功;但访问 0x100000 + 0x25000(超出界限)会导致异常。

2. 分页(Paging)

概念: 分页是将虚拟地址划分为固定大小的页(Page),并通过页表(Page Table)映射到物理内存帧(Frame)。

特点:

每个页面大小固定(如 4KB)。解决外部碎片问题,内存分配更灵活。虚拟地址到物理地址的映射由 MMU(内存管理单元)完成。需要页表来存储映射关系。

分页的工作原理:

CPU 访问虚拟地址(Virtual Address),例如 0x12345000。MMU 使用页目录(Page Directory)和页表(Page Table)找到对应的物理页帧(Frame)。物理地址可能是 0x3ABCD000,CPU 访问该物理地址。

示例(4KB 页): 假设进程访问虚拟地址 0x00102030:

虚拟页号(VPN) = 0x00102(索引页表)页内偏移(Offset) = 0x030(物理页内的偏移)页表映射 0x00102 → 物理页 0x3AB物理地址 = 0x3AB0030

3. 分段 vs. 分页

特性分段(Segmentation)分页(Paging)内存单位逻辑段(可变大小)固定大小的页(如 4KB)地址结构段基址 + 偏移量虚拟页号 + 页内偏移内存管理按逻辑划分(代码/数据/栈)按页划分(连续地址)优势逻辑清晰、模块化好避免碎片、支持虚拟内存劣势内存碎片、分段表开销大需要页表、TLB 查找消耗

4. 分段 + 分页(现代 x86 处理器)

现代 CPU(如 x86)同时使用分页和分段:

分段管理逻辑地址(大多数操作系统只使用“平坦模式”忽略分段)。分页管理物理内存(核心是页表映射)。

总结

分段:基于逻辑结构,适合模块化编程,但会产生碎片。分页:基于固定大小的块,适合现代虚拟内存管理,减少碎片但需要页表。

相关文章

be365备用网址

正在阅读:抖音小黄车在哪查看 抖音小黄车位置介绍抖音小黄车在哪查看 抖音小黄车位置介绍

365bet正网注册

在 iPhone、iPad 或 iPod touch 上关闭“经典语音控制”

365bet正网注册

2006年世界杯克罗地亚国家队大名单