物理页面周转(分配/释放、换出/换入)
本帖最后由 _nosay 于 2016-09-20 10:52 编辑本篇文章是对《Linux内核源代码情景分析》2.6~2.9节(共65页)非常粗略的总结,更深入了解需要看书和代码
某个虚拟页面,为其分配一个物理页面,并建立映射关系后(建立映射关系的过程中,可能还需要分配页表),硬件在访问这个虚拟页面中的虚拟地址时,才不会出错。所以说物理页面是非常宝贵的资源,每个进程拥有自己的4G虚拟空间,但物理内存只有一份(≤4G),由所有进程共享,因此设计一种合理的物理页面周转策略,就尤为重要!比如提供释放机制,保证进程在确定不再使用某块内存时,有渠道可以将它还给系统;比如长时间没有使用,但又没释放的物理页面,在内存紧缺时,可以先将它的内容拷贝到磁盘(相应虚拟地址的映射方向也要改为指向这块磁盘,当硬件将来访问这块虚拟地址时,重新找一块物理内存与其建立映射,并从磁盘恢复原来的内容),将其挪出来急用;等等。
[*]内核空间映射
通过之前的文章,很新的新手朋友,可能仍然对内核空间心存焦虑,不能完全明白内核空间到底是什么?
首先要明确,用户空间、内核空间只是对虚拟空间的划分(用户:0~3G,内核:3~4G),用户进程调用malloc()等内存分配函数,是不可能返回3~4G范围的虚拟地址的(否则就是内核有bug)。
其次要明确,用户态与内核态切换,跟进程切换是两回事(即使进程切换必须进入内核完成,《Linux内核源代码情景分析》第四章)。之前提到过,“切换”的本质,就是硬件状态的改变,而这两种切换,改变的硬件不一样,比如用户态与内核态切换,改变了段寄存器的RPL值,进程切换时,改变了CR3寄存器值。
那么,由于用户进程通过系统调用等方式进入内核空间,CR3寄存器值不变(只会在进程切换时改变),就是说3~4G范围虚拟地址依据的映射关系不变,都是该进程的映射表。那么很显然,每个进程目录表(也正好一页)尾部1/4的表项,不会像用户空间空间内存释放那样--撤消映射关系时,如果撤消的是页表中最后一个表项,将该页表也释放。任何进程尾部1/4的目录表项,都指向256个相同的页表(可以把这些目录表项理解为“指针”),这256个页表在内核启动时分配,并且再也不会释放。这样,各个进程用相同的内核空间虚拟地址,访问到的最终物理页面也会一样,并且某个进程新建或撤消了256个页表中的某些表项,其它进程进入内核态时也能“发现”(“公共空间”)。
同时,很自然能理解为什么内核空间的映射规则简单了,因为任何进程的同一内核虚拟地址,本来就应该映射到同一物理页面,而简单的映射规则就已经可以保证这一点。
顺便再想一下,为什么不像固定的256个PT一样,也搞个固定的PGD,硬件设计上保证用户态切换到内核态时,CR3切换到该PGD位置?
这样,进程进入内核后,就没办法再访问自己的用户空间。
[*]分配/释放
① 分配/释放虚拟页面(虚拟地址也要当作资源来管理,否则malloc(2G),free(2G),malloc(2G),free(2G)..即使物理页面分配到了,也会认为“代号”不够而分配失败)
② 分配/释放物理页面
③ 建立/撤消虚拟页面与物理页面映射关系
[*]换出/换入
当物理页面紧缺时,内核会根据一些策略挑选一些物理页面(比如分配了,但很长时间没有访问的),将它们的内容复制到交换文件的某个逻辑页(驱动以“块”为单位操作磁盘,1个文件逻辑页一般又映射到4个磁盘块,那么也就是某4个block上),并修改相应页表项的值,表示虚拟页面的内容已经不在物理页面,而是磁盘上。
① 虚拟页面<->物理页面,pte结构(pte_t):
高20位分别是PGD下标、PT下标;剩余低12位表示该物理页面的属性,其中最低的P位,表示物理页面是否存在(换出了就相当于“不存在”了),所以只要物理页面没被换出,P位肯定为1。
② 虚拟页面<->交换文件逻辑页面,pte结构(swp_entry_t):
type表示映射到第几个交换设备(系统中最多可以有128个交换设备,每个设备由一个swap_info_struct结构表示);offset表示对应的页面内容,映射到该设备的第几个逻辑页面(0下标逻辑页面永远不用于映射,所以只要有映射,该位段的值不可能为0)。
总之,P=1,pte对应于①说明的结构,虚拟页面肯定映射到一个物理页面;P=0,pte对应于②说明的结构,当offset非0时,虚拟页面肯定映射到一个交换文件的逻辑页面,当offset也为0时,相应的虚拟页面还没有映射(未分配/已释放),访问时就可能出现段错误或得到扩展。
另外,硬件只认①说明的结构,P=0时,硬件就会认为有错误发生了,从页要经历一次中断过程,最终调用do_page_fault()函数。
换入是换出的逆过程:do_page_fault()函数,会判断错误的原因,如果是由于物理页面被换出了(pte != 0),就会分配一个物理页面,并从交换文件拷贝内容,并将映射关系再改为虚拟页面到物理页面的映射。
[*]换出/换入策略
由于换出过程需要写磁盘操作(比较慢),如果在内存真正出现紧缺时才换出,系统就会显得很“卡顿”,所以Linux内核的策略是“未雨绸缪”,在空闲的时候为换出操作做一些准备,这样,在真正执行换出操作时,就可以少做一些事情。
具体怎么准备呢?
方案①:将物理页面内容复制到磁盘,映射方向也改向磁盘
这种方案确实为分配操作带来了实惠,因为它总是在腾出可用的物理页面,但对于之前使用这个物理页面的进程来说,这样做是在“添乱”,当进程再次想访问这些物理页面时,发现已经不在了,要重新分配物理页面,并从磁盘把内容读回来。
方案②:将物理页面内容复制到磁盘,但不是马上就将映射方向也改变,而是在对应page结构中,用一些信息表示,这个页面的内容已经复制到磁盘,当需要换出这个页面时,只需要修改映射就OK了,这样一来,真正的换出就很快了,而浪费时间的操作是系统空闲时完成的(反正闲着也是闲着,顶多浪费点电{:qq33:})
有了大致思路,再去理解换出/换入过程的细节,就只是时间问题了。同时也能感受到,很多结构体、变量关系也不是一下子就想出来的,最终的设计是经过长期的实践得到的,比如page结构、不活跃脏链表、管理区中的不活跃干净链表等。
[*]用户空间内存页面性质
① 程序指令、全局/静态变量、起始堆栈,内核在创建/销毁该进程时分配/释放;
② 程序中调用malloc()/free()、mmap()/unmmap()等,在进程执行过程中分配/释放。
页:
[1]