可执行文件的装载与进程*
程序执行时所需要的指令和数据必须在内存中才能够正常运行,最简单的办法就是将程序运行所需要的指令和数据全都装入内存中。但是内存不够硬伤,所以采用动态装入。
动态装载
装载的两种方式:曾经使用的覆盖装入,现在使用的页映射。
覆盖装入:将模块按照它们之间的依赖关系组织成树状结构。本质上还是想节约内存,将没有依赖的模块可以覆盖在同一块内存。
页映射:每一个进程立起来的时候分配的空间其实只是先建立页表映射,在虚拟地址中根据分区算法等“拿到自己的段”,建立页表,建立对应的页项,此时仍然没有拿到物理内存。等到执行到相应的代码时,发现该页没有影射到物理页,发生页错误,再从文件中读入相应内容到内存,这就真的拿到了内存。
每个进程都有自己的页表段表,进程的段表和页表肯定放在进程的“身份证”--PCB中,每个进程肯定也会拿到自己的文件fnode 之类。
进程的段表:每个进程的段表就是LDT表,LDT段表的基址可以放在特定的寄存器中。
进程的页表:每个进程都有自己的页表,页表的基址依然放在特定的寄存器中。
程序分段:段号+偏移:CS+IP给出的是虚拟地址,在保护模式下,CS中放的就是段选择子而非物理地址。
分页的存在,是为了解决内存分区的效率问题。操作系统初始化的时候,会将内存打散成一页一页的。
操作系统的段表就是GDT表,每个进程的段表就是LDT表。
由于可执行文件在装载时实际上是被映射的虚拟空间,所以可执行文件很多时候又被称为映像文件(Image)。
三种重定位方式:
编译时重定位:将内存地址提前写死。
载入时重定位:更灵活,但是换入换出后,失效
运行时重定位:base 放在pcb 中,换入换出,pcb 更新base。
ELF文件的链接视图和执行视图
ELF文件在映射时,是以页为单位的,每个段在映射时都是页的整数倍,当段的数量增多时,这会导致很大的空间浪费。
于是采用将属性相似又连在一起的section 合并为 segment。装载时以segment为映射的方法减小空间浪费。这是将很多的section合并为较少的segment。在映射的时候是用segment视图,因为有很多section不需要被映射到内存。
ELF可执行文件中有一个程序头表,来保存segment 的信息。因为ELF目标文件不需要被装载,所以目标文件没有程序头表。
一个进程基本上可以分为以如下几种VMA区域:
- 代码VMA,权限只读、可执行;有映像文件。
- 数据VMA,权限可读写、可执行;有映像文件。
- 堆VMA,权限可读写、可执行:无映像文件,匿名,可向上扩展。
- 栈VMA,权限可读写、不可执行;无映像文件,匿名,可向下扩展。
装载的基本过程: