6.可执行文件的装载与进程——《程序员的自我修养》读书笔记

本文为《程序员的自我修养——链接、装载与库》的读书笔记,内容和图片为书本内容的整理。

6.1 进程虚拟地址空间

程序在运行起来后将拥有自己独立的虚拟地址空间(Virtual Address Space),这个空间的大小由计算机的硬件决定。对于32位OS,使用32位指针寻址,寻址空间为0 ~ 2^32-1,即0x00000000 ~ 0xFFFFFFFF,共4GB。对于64位OS,使用64位指针寻址,寻址空间位0x00……0(16个0) ~ 0xFF……F(16个F)。共17 179 869 184GB。

问题:在32位OS中程序能否使用超过4GB的内存空间?

使用PAE(Physical Address Extension)的方法,将地址线拓展到36位后,Intel修改了页映射的方式,通过窗口映射的方法,应用程序可以根据需要申请和映射内存,例如可以从高于4GB的物理空间申请多个大小为256MB的物理空间,在Windows下这种访问内存的方式叫AWE(Address Windowing Extensions)。

6.2 装载的方式

6.2.1 覆盖装入(Overlay)

覆盖装入的方法将内存管理的任务给了程序员,需要由程序员编写一段辅助代码—(覆盖管理器)来决定程序中的模块何时在内存驻留、何时被替换掉。在复杂的情况下,程序员需要将模块按调用依赖关系组织成树状结构,以正确地释放内存空间。

6.2.2 页映射

页映射是虚拟存储机制的一部分,它将内存和磁盘中的数据和指令按照“页(Page)“为单位划分为若干页,由OS来控制如何将页加载进内存,页的选择算法有多种,例如FIFO(先进先出算法)、LUR(最少使用算法),详见操作系统原理。

6.3 从操作系统角度看可执行文件的装载

从OS的角度看,创建一个进程有三个步骤:

  1. 创建一个独立的虚拟地址空间。
  2. 读取可执行文件头,建立虚拟地址空间与可执行文件的映射关系。
  3. 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行。

在第一步创建一个独立的虚拟地址空间中,OS实际上是创建映射函数所需要的相应的数据结构。在i386下只需分配一个页目录即可,不需要设置页映射关系。

在第二步建立虚拟地址空间与可执行文件的映射关系中,当程序执行最初开始执行时,程序的页还没有装载进内存中,此时会触发页错误(Page Fault),OS使用缺页中断将需要的页装载进内存。在缺页中断的情况下,OS会查询页目录,找到空页面的VMA,计算得到相应页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,将进程中该虚拟页与物理页建立映射关系,再将控制权还给进程继续执行。Linux中将进程虚拟空间中的一个段叫做虚拟内存区域(VMA),Windows中称为虚拟段(Virtual section)。

在第三步将CPU的指令寄存器设置成可执行文件的入口地址启动运行中,涉及内核堆栈和用户堆栈的切换,CPU特权级的切换等操作。

6.4 进程虚拟内存空间分布

对于可执行文件的装载,OS并不关注可执行文件各个段的实际内容,主要关注与装载相同的问题,例如段的权限。段的权限主要包括三种:

  • 可读可执行,例如代码段。
  • 可读可写,例如数据段和BSS段。
  • 只读,例如只读数据段。

对于ELF文件,可以从链接和装载的角度将其分为两种视图(View):从链接视图看,ELF被分为多个section;从装载视图看,ELF被分为多个segment。OS在装载时会将权限相同的段合并到一个段进行映射,映射后在进程空间空只有一个相对应的VMA,也就是一个segment包含多个section。

对于可读可执行的段统一映射到VMA0,可读可写段统一映射到VMA1,还有一些包含调试信息的section没有被映射。

6.4.2 堆和栈

OS通过VMA堆进程的地址空间进行管理,堆、栈在进程虚拟空间中是以VMA的形式存在的。

进程虚拟空间属性解释:

6.可执行文件的装载与进程——《程序员的自我修养》读书笔记_第1张图片

第三列:VMA对应的segment在映像文件中的偏移。
第四列:映像文件所在的主设备号、次设备号。
第五列:映像文件的节点号。

堆、栈、vdso的主次设备号均为0,表示他们没有映射到文件中,这种VMA叫做匿名虚拟内存区域。vdso是一个特殊的VMA,他的地址位于内核空间,是一个内核模块,进程可以通过该VMA与内核进行通信。

6.4.4 段地址对齐

对于x86处理器,一页为4096字节(4K),对于物理内存和进程虚拟空间中之间建立映射关系时,内存长度必须是4096的整数倍,且这段空间在物理内存和进程虚拟空间中的起始地址也要是4096的整数倍。

一种最简单的方式是将每个段分开映射,长度不足一页的占一页,这种方式会产生页碎片浪费空间。

还有一种映射方式是让接壤的两个段共享一个物理页面,然后将该物理页面映射两次。映射两份到虚拟地址空间。在这种映射方式下,一个物理页面可能包括两个段的数据。因为段地址对齐的关系,此时各个段的虚拟地址就不是页面长度的整数倍了。

6.可执行文件的装载与进程——《程序员的自我修养》读书笔记_第2张图片

可以得到一个规律:在ELF中,对于任何一个可装载的segment,它的p_vaddr除以对其属性的余数等于p_offset除以对齐属性的余数。

6.5 Linux内核装载ELF过程简介

在用户态上,bash进程会调用fork()系统调用创建一个新的进程,这个新的进程会调用execve()系统调用执行指定的ELF文件,然后bash进程等待新的线程的结束。

ELF装载的主要步骤:

  1. 检查ELF文件的有效性,例如magic number。
  2. 如果需要动态链接,寻找动态链接中的.interp段,设置动态链接器路径。
  3. 根据ELF文件头进行ELF的映射。
  4. 初始化ELF进程环境。
  5. 将系统调用的返回地址改为ELF的执行入口点:若为静态链接,程序入口为ELF文件头中e_entry所指的地址;若为动态链接,程序入口点是动态链接器。

你可能感兴趣的:(操作系统原理)