1 进程虚拟地址空间
(1) 32 位 CPU 虚拟地址空间
sizeof(ptr) = 4 <=> 32 位 CPU
0 - 2^32 - 1 = 0x0 - 0xFFFF FFFF
4G
(2) `4G 虚拟空间 是否可` 被进程 `任意使用` ? 不行&原因
进程
受 OS 监管, 只能使用 OS 分配给自己的 虚拟空间
若 `访问 未经 OS 允许的空间` 被 OS 视为非法操作 & 强制结束
Linux : "Segmentation fault"
Windows: "进程因 非法操作 需要关闭"
4G 虚拟空间 分配?
Linux
用户进程 3G: 0x0 ~ (0xC0000 0000 - 1)
OS 1G: 0xC0000 0000 ~ 0xFFFF FFFF
(3) `32 位 CPU, 进程使用的空间 是否能超过 4G` ?
空间 指
1) 虚拟地址空间
答: 否, 因为 `CPU 位数 决定` 了 虚拟空间大小
2) 计算机的 内存空间
答: 是
32 位字长 (CPU 位数) + 36位 地址线
OS `窗口映射` + 应用程序 调 `mmap() 系统调用`
2 装载 到内存
(1) 静态装载: 全部装载
(2) 动态装载
: 程序 局部性原理
进程对各模块, 用到时才装载;
不用时, 放磁盘
2 种方法
(1) 覆盖装载: 虚拟存储 前
嵌入式内存受限
程序员
1) `模块划分`
分析 `模块间依赖关系` -> `树状结构`
1> 当 某模块被调用时, 从该模块 到 树根/main模块 的 `调用路径` 上 `所有模块` 必须都在内存中
=>
2> 禁止跨树间调用
<=> `调用` 只能是 从 main 到 目标模块的 `单向向下路径`, 禁止 其他方向
2) `覆盖管理器`
管理 这些模块 `何时应该` 驻留内存/被替换
(2) 页映射: 虚拟存储
将 `内存和磁盘` 中所有 `数据和指令`
按 `页(Page) 划分` -> 被 (从 .elf ) `装载` 进 内存 -> 被 `替换`
页
4K Byte = 4096 Byte
3 OS 角度看 装载
OS
(1) 建立进程: 3 step
1) 建 虚拟地址空间
只分配 页目录
2) 读 ElfHeader + 建立 `.elf 文件 ( 段 ) 与 进程虚拟空间(页中 VMA, 虚拟内存区) 的 映射关系` -> 作为数据结构 保存在 OS 中
3) PC 设为 .elf 文件中 入口地址)
进程角度看
1 条 jump 指令
(2) Page Fault 处理
4) CPU 执行 PC 处指令
-> 页空白: VP(Virtual Page) 还没映射到 PP(Physical Page)
-> 页错误(Page Fault)
-> `控制权交给 OS`
-> OS
1> find `空页(VP)` 的 VMA
2> 求 VMA 相应在 `.elf 文件中的 偏移(offset) = offsetToVP`
3> 分配 PP
4> VP 映射到 PP: MMU
5> 页映射
`.elf 文件 中 offsetToVP 开始的 content` 动态装载 进 `PP`
-> 控制权 还给 CPU/进程
-> CPU 从 `刚才的 空页处(现已非空) `重新开始执行`
4 进程 虚存空间分布
(1) elf 文件 链接视图 和 执行视图
.elf - 进程虚拟空间的 映射
`1 个 Section` 映射为 `1 个 VMA(占 n 个 Page)`
|
| 问题: `内存空间浪费、Page 内部 碎片`
|
| 解决: `属性类似` 的 `多个 Section 视为 1 个 Segment` 整体映射为 `1个 VMA`
|
| 1) 可读可执行
| .init .text .rodata - VMA0
| 2) 可读可写
| .data .bss - VMA1
| 3) 装载时 没映射, 程序执行时没有用
| .symtab .strtab
|/
`1 个 Segment` 映射为 `1 个 VMA`: VMA 与 Segment 并不完全对应 -> Linux 的 "Hack" 装载
(2) 堆 和 栈
1) `VMA 2 个 作用`
OS 用于
1> 映射 可执行文件 中各 Segment
2> 管理 进程虚拟空间
`堆(Heap) / 栈(Stack, 全称 堆栈)` 在 进程虚拟空间中 `各对应1个 VMA`
查 2) `进程 虚拟空间分布`
$./SectionMapping.elf &
[1] 21963
cat /proc/21963/maps
// VMA 地址范围 / VMA 权限(r/w/x p =私有-Copy On Write, s = 共享)
// `VMA 对应的 Segment` 在 `映像文件(.elf) 中的 偏移`
// / 映像文件 所在 `主|辅设备号` / 映像文件 `节点号` / 映像文件 `路径`
—————————————————————————————————————————————————————
有映像文件 | 代码 VMA | 只读 可执行
| 数据 VMA | 可读写 可执行
—————————————————————————————————————————————————————
AVMA | heap VMA(可向上扩展)| 可读写 可执行
| | stack VMA(可向下扩展)| 可读写 不可执行
无映像文件 | vdso/与内核通信 | 只读 可执行
—————————————————————————————————————————————————————
(3) 堆 最大申请数量
1) 由 OS 地址空间分配策略决定
2) 可通过 malloc 程序测, 但 程序的每次运行结果可能不同
外层循环: 3次 -> 申请的 `最小内存空间块 blockSize[i]` 分别取 1MByte / 1KByte / 1 Byte
内存循环: 无限次 + 直到 malloc 申请内存失败( => 返回 ptr == NULL ) 时, break -> 进入 外层循环
以 `最小内存空间块` 为 Uint, `逐次 增加` 要申请的 内存空间
for: i++
for: count++
blockSize[i] * count
blockSize[] = {1024 * 1024, 1024, 1}
=> Linux 2.9 G
5 Linux 内核装载 elf 过程
Linux bash 下 输入命令执行 .elf
`bash 进程` 调 `fork() 建新进程`
原 bash 进程 `return` 并 `等待` 新进程结束 -> 然后 `继续等待 用户输入命令`
新进程 调 `execve() 系统调用` 执行 .elf 文件
execve()
| |\
用户态 | |
| |
—— —— — —— —|— —|—— —— ——
| | sys_execve() 从 内核态 返回 用户态 时,
内核态 | | `eip 寄存器 / PC` 直接跳转到 `.elf 中 入口地址` => 新进程 开始执行
| |
|/ |
sys_execve()
| /\
|/ /
do_exec()
`装载`
覆盖装载.jpg
覆盖装载: 树状 调用依赖关系.jpg
页映射与页装载.jpg
可执行文件 与 进程虚拟空间 的 映射关系(粗略图).jpg
页错误.jpg
elf Segment.jpg
可执行文件 与 进程虚拟空间 的 映射关系(细致图).jpg
可执行文件 与 进程虚拟空间 的 映射关系(实际图: "Hack").jpg
堆 空间申请的最大值.jpg
Linux 进程 初始 堆栈.jpg
网友评论