程序是如何一步步跑进内存的?
2026-02-06 13:25:26 · chineseheadlinenews.com · 来源: 白帽白捡一个n1
从“点下运行”到 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,欢迎阅读.