程序的启动
程序执行的整个步骤:
一个hello.c
的源程序
mov eax, 1
这行代码是 ASCII 文本(你用编辑器打开 .s 文件是能看到的),但它 不是直接转换为二进制的。
汇编器会:
解析这行指令 → 知道是 mov、寄存器 eax、常量 1
查 CPU 指令集文档 → 找到 mov eax, imm32 的编码格式
转换为机器码:
b8 01 00 00 00
这是 5 个字节的机器码(二进制),CPU 就能执行。
- 编译:源代码被编译为 二进制码,形成_可执行文件存储在磁盘_中
ASCII 只是“字符 ➜ 数字”的映射表
比如 'A' 映射成数字 65
内存里存的是 65 的二进制表示:01000001
也就是说:
你写的是字符 'A',但存进去的是数字 65,用 8 个比特(二进制)表示。
- 加载: 运行程序时 操作系统会将二进制码存储到内存中,但_并不是一次性存储_,而是按需加载
这就涉及了 当CPU开始执行指令的时候 指令会涉及到地址 就会有-> 虚拟地址 到 物理内存的地址映射 因为不是一次性取出所有的二进制码 所以在寻找映射关系的时候 就会造成_缺页错误_
为什么不是一次性加载?
程序中很多代码、数据可能根本不会被执行到(比如某些分支逻辑、异常处理函数)。
按需加载可以_降低内存占用、减少磁盘 IO。_
首先查看进程的内存分布:
高地址 ┌────────────┐ │ 栈区(stack) │ ⬇向低地址增长(函数调用栈、局部变量等) ├────────────┤ │ 堆区(heap) │ ⬆向高地址增长(malloc、new 动态分配内存) ├────────────┤ │ BSS 段(未初始化全局变量)│ 初始值全 0 ├────────────┤ │ 数据段(已初始化全局变量) │ ├────────────┤ │ 代码段 / 文本段(text) │ 存放程序指令(只读) ├────────────┤ │ 内核保留空间(不可访问) │ 低地址
按需加载机制:
只有当程序第一次访问某个虚拟地址(比如执行代码、读写数据、malloc 分配新内存等),操作系统才会:
把对应的磁盘页加载到物理内存;
或给该虚拟页分配物理页帧。
示例:
假设你有一个程序,包含:main函数一个未调用的函数foo一些全局变量执行过程可能是这样的:
程序启动时,操作系统加载main函数和全局变量所在的页到物理内存,foo的代码页留在磁盘上。
当程序运行到调用foo时,CPU尝试访问foo的虚拟地址。如果foo的页不在物理内存中,就会触发缺页中断。
操作系统从磁盘加载foo的代码页到内存,更新映射,然后继续执行。在这个例子中,虽然整个程序的二进制文件在磁盘上,但物理内存只加载了部分内容,所以访问foo时会出现“找不到”的情况。
一条语句:
我们所编写的每一条语句,都对应着多条机器指令,机器指令 在现代中是可以并行执行的 那也就是涉及了CPU的流水线设计