美文网首页
6章 可执行文件的 装载与进程

6章 可执行文件的 装载与进程

作者: my_passion | 来源:发表于2022-07-10 15:53 被阅读0次

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

相关文章

网友评论

      本文标题:6章 可执行文件的 装载与进程

      本文链接:https://www.haomeiwen.com/subject/jmpwbrtx.html