程序是如何一步步跑进内存的?白帽白捡一个n1

2/6/2026

从“点下运行”到 CPU 执行,中间到底发生了什么?

我们每天都在运行程序,却很少有人真正想过一个问题:

当你双击一个程序,或者在终端敲下回车的那一刻,计算机到底做了什么?

这不是一个“操作系统课本问题”,而是理解计算机系统的关键入口。

一、程序在运行之前,只是一个“普通文件”

先明确一个非常重要的概念:

程序 ≠ 进程

1️⃣ 程序是什么?

在运行之前,程序只是磁盘上的一个文件,我们也叫做可执行文件:

• .exe

• ELF(Linux)

• Mach-O(macOS)

一段按照特定格式组织的二进制数据

二、运行程序的第一步:操作系统介入

当我们执行一个程序:

./hello

真正“开始工作”的其实不是程序,而是——

2️⃣ 操作系统做的第一件事

操作系统会先问一个问题:

这个文件能不能运行?

• 文件类型(是否是可执行格式)

• 权限位(是否有执行权限)

• 架构是否匹配(x86 / ARM)

所以程序执行的第一步就是操作系统先去检查它.

三、创建进程:程序通过什么去运行?

如果检查通过,操作系统会:

创建一个新的进程(Process)

这一步非常关键。

3️⃣ 什么是进程?

进程不是代码,而是一整套运行环境,包括:

• 独立的虚拟地址空间

• 寄存器状态

• 打开的文件

• 权限信息

• 调度信息

进程是操作系统分配资源的最小单位

四、加载程序:代码如何进入内存?

现在,操作系统要做一件核心工作:

把程序“加载”进内存

4️⃣ 加载 ≠ 全部读进内存

这是一个常见误区。

现代操作系统使用的是:

按需加载(Lazy Loading)

• 程序文件在磁盘

• 只有需要的部分才会被映射到内存

5️⃣ 内存中的典型布局

一个进程的内存空间通常长这样:

┌────────────┐

│ 栈 Stack │ ← 函数调用、局部变量

├────────────┤

│ 堆 Heap │ ← 动态内存

├────────────┤

│ 数据段 │ ← 全局变量

├────────────┤

│ 代码段 │ ← 程序指令

└────────────┘

这些区域并不是“随便放的”,

而是由操作系统和程序格式共同决定。

五、虚拟内存

这里是整个过程最重要的一点。

6️⃣ 为什么要有虚拟内存?

这时候每个进程都会觉得:

“我独占了整个内存”

• 内存是共享的

• 地址是虚拟的

• 映射由操作系统维护

虚拟内存的作用:

• 进程隔离

• 内存保护

• 简化程序设计

• 支持按需加载

六、动态链接:程序并不孤单

我们写的程序,其实很少是“单独运行”的。

7️⃣ 动态库是什么时候加载的?

• libc

• libm

在程序启动时:

• 加载器(loader)解析依赖

• 映射共享库到内存

• 修正符号地址

这也是为什么多个程序可以共享同一份库代码。

七、设置入口点:CPU 从哪里开始执行?

程序被加载进内存后,

还差最后一步。

8️⃣ 程序的“第一条指令”

每个可执行文件都有一个:

入口地址(Entry Point)

操作系统会:

• 设置 CPU 寄存器

• 把指令指针指向入口点

• 准备好栈和参数

CPU 开始执行第一条指令

从这一刻开始,程序真正“跑起来了”。

八、从 main() 之前开始的世界

很多人以为程序是从 main() 开始的。

main() 之前,已经发生了很多事情

• 运行时环境初始化

• 动态库初始化

• 全局对象构造

main() 只是你能看到的入口之一。

九、为什么理解这个过程如此重要?

因为很多“看似高级”的问题,本质都在这里:

• 程序为什么会崩溃?

• 内存为什么会越界?

• 程序为什么启动慢?

• 为什么同一程序能运行多个实例?

理解程序如何进入内存,本质是在理解操作系统如何管理世界

理解程序从磁盘到内存,从静态到运行态的这个过程,会让你对计算机底层有更深的了解,接下来我也会不断更新这个系列的文章,我是N1,欢迎阅读.

Scroll for more