一.可执行文件和.o文件和库的格式
两个都是elf格式的文件,格式如下:
section:节,代码编译完的原代码段,数据段等都是一个一个section
(注意:在 ELF(Executable and Linkable Format)文件中,数据段(Data Segment) 通常由多个 Section 组成,主要用于存储程序运行时的已初始化数据和全局/静态变量)
1.ELF Header:elf的头部信息,起始执行指令地址,段大小,数量等附加信息,一般用readelf -h 程序名读取
2.Program Header Table:整个程序的内存布局和访问权限
LOAD表示要将文件加载入内存的部分。下面两个LOAD分别表示代码区和数据区
注意:elf文件由节组成,故抽象为了一维数组,是以偏移量+节大小来定位数据的。
(readelf -l 文件名)
3.Section Header Table:文件中节的信息,如节相对文件起始位置的偏移量(字节),大小(字节),类型等
readelf -S 文件名
补充:链接本质是什么呢?
答:将.o文件的elf内每个部分进行合并成一个大的elf格式文件。(能找到函数等定义原因)
二.重谈进程地址空间
前提:
进程的虚拟地址空间是谁规定的?
答:编译时确定的,通过加载可执行程序的节来。验证:反汇编(反汇编出的是节的部分)
可见,编译完程序就有逻辑地址(=起始地址+偏移量),平坦模式(从00000开始,非平坦模式起始地址不是0)下就是进程虚拟地址空间内的虚拟地址
问2:CPU执行程序的时候用的是什么地址?
答:虚拟地址:因为pc寄存器拿到的是head table里的entry就是虚拟地址的。
问3:拿怎么加载实际地址拿到指令的?
答:CR3寄存器其实保存了页表的物理地址,MMU(内存管理单元)硬件可以直接拿pc寄存器里的虚拟地址去页表找物理地址,找到给cpu IR寄存器加载指令
问4:再谈为什么要有虚拟地址?
答:让编译器不用再考虑物理地址,让编译器和操作系统解耦。
问5:为什么有的程序8G,内存只有4G,是怎么让他运行的?
答:分批加载(懒加载)。
进程的虚拟地址空间其实是一个链表,也就是mmstruct内存了一个vm_area_struct指针,每个链表节点都是一个连续区域(一个大型的段,如代码段会被拆分成多个vm_area_struct)。程序加载时仅标记为“需要时加载”,实际页表条目为空,只记录那块的elf内偏移量。程序加载只要先给cpu entry入口地址,首次访问触发缺页异常,内核再按需加载数据并插入 vm_area_struct(如果尚未存在)/
问:为什么mmstruct要设计成内容为链表?
1.分批加载
2. 程序加载时的“确定”是初始布局,但运行时会动态变化
操作系统将elf部分载入内存时,就确定了mmstruct大小信息等,再建立页表映射。