编译后的程序是如何在操作系统(linux)中运行的,虚拟地址空间到实际物理内存的访问


Linux中,每个进程通过一个task_struct结构体描述,每个进程地址虚拟空间通过一个mm_struct描述,c语言中每个段空间通过vm_area_struct描述,关系如下,



编译后的程序是如何在操作系统(linux)中运行的,虚拟地址空间到实际物理内存的访问


当执行一个程序时,linux创建一个进程,通过sys_exec()将该程序的内容(程序编译后产生的是虚拟地址空间映射到进程的虚拟地址空间中而不是物理内存中,生成一组vm_area_struct数据结构用来表示可执行程序的信息。具体步骤如下:

(1)创建一组vm_area_struct

(2)将程序编译的起始地址保存到vm_startvm_end

(3)将磁盘file句柄保存在vm_file中;

(4)将对应段在磁盘file中的偏移值保存在vm_pgoff中;

(5)将操作该磁盘file的磁盘操作函数保存在vm_ops中;


编译后的程序是如何在操作系统(linux)中运行的,虚拟地址空间到实际物理内存的访问


(6)假设程序有一条指令需要读取地址vm_start~vm_end之间的某内容,例如:

mov[0x08000011],%eax; 那么将会执行如下序列:


(1)cpu依据CR3(current->pgd)找到0x08000011地址对应的pgd[i],由于该pgd[i]内容保持初始化状态即为0,导致cpu page fault

(2)调用do_page_fault()系统调用,为pgd[i]在内存中分配一个页表,并该让页表指向它,如下图所示:


编译后的程序是如何在操作系统(linux)中运行的,虚拟地址空间到实际物理内存的访问

(3)pte[j]分配一个真正的物理内存页面,依据vm_area_struct中的vm_filevm_pgoffvm_ops,调用filemap_nopage将磁盘filevm_pgoff偏移处的内容读入到该物理页面中,如下图:


编译后的程序是如何在操作系统(linux)中运行的,虚拟地址空间到实际物理内存的访问


1 分配物理内存页

2 从磁盘文件中将内容读取到物理内存页面中


总结,执行程序过程中,如果当前指令或数据在虚拟地址空间中,实际没有在物理内存中,将发生缺页错误,这时操作系统再从物理内存分配一个空闲的物理页,并将虚拟地址页对应的数据从磁盘加载到该物理页中,并建立页表项和页帧的映射关系。假设程序占用内存非常大,随着进程的执行,缺页错误不断产生,操作系统会响应每个缺页错误并未进程分配物理内存页框。但是物理内存是有限的,全部物理内存都分配给进程后,如果进程继续抛出缺页错误,此时操作系统就会采用页置换算法,在保证进程正常运行的前提下,将先前的分配给进程的物理页收回,重新分配给进程。常用的页面置换算法有先进先出FIFO),最近未使用NRU),最近最少使用LRU)等。