Linux的细节我不是太懂,反而Windows的了解的多一些。
首先,换页和进程上下文切换不是两个相关的概念。
换页时进程可能没切换;
进程切换可能不伴随换页;
虚存和分页也不是必须同时存在的,RTOS里就有支持分页但不支持虚存的设计。
以下讨论仅限Windows x86平台32位场景。
操作系统管理内存的最小单元是页,跟段没有太大关系。在平坦模型(flat)下,各个段寄存器指向的内容是完全一致的,都是4G,基地址是0,连续的的线性地址。
页的数量跟物理内存以及虚存的配置有关系,跟进程的大小没关系。
对于Windows来说,每个进程都有一个私有的页表。同时,操作系统还维护几个双向链表,这几个链表分别是:清零的页,空闲的页,可以换出的修改的页,不可换出被修改的页,就绪的页等等。
内核中通过MiInsertPageInList向上述链表中添加数据,一旦需要被换出的页的数量达到一定数值(MmModifiedPageMaximum),该函数会释放MmModifiedPageWriterEvent事件,内核中有线程MiMappedPageWriter会收到事件并处理换出的工作。
系统准备关闭时或者试图申请内存但不够时也会触发上述事件。
在早期的Windows版本中,MmModifiedPageMaximum其实不大,最小100,最大800,并且对于Windows2K来说,大于64MB就算大内存了。
对于Windows来说,管理虚存的模型很简单,维护链表,满了就刷。
如果一个页不在内存里,但在页面文件里,会触发访问异常,大致的调用栈是MmX86Fault->MmAccessFault->MiDispatchFault,在MiDispatchFault里发送IO请求,读取页面内容,放到就绪的页里。
加载的进程的过程:
首先内核会创建一个进程的地址空间(使用MmCreateProcessAddressSpace),初始化地址空间的内存结构(如Page Directory等等),然后把地址空间对应的CR3记录到。
然后,把PE格式的文件map成一个虚存,所有加载、运行的过程都可以走缺页中断了。程序入口是0x408000,指令走到0x409000,发现该位置在内存中不存在,走缺页中断,分配一个物理页,把磁盘上对应的区域加载到物理页上并映射到0x409000上。
对于堆栈和其它数据区,通过VirtualAlloc申请,申请之后内核会把申请到的页挂到对应的链表上。
进程切换时:
进程切换时不一定伴随换页,如果内存足够大,可能数据一直在物理内存里待着,进程切换时,只是改变了CR3的值,之后页表项也都跟着换了。至少从Windows的设计思路上看,进程切换的动作和换页没有关系。
MmOutSwapProcess/MmInSwapProcess负责进程的换出换入时页表项的准备(检查进程页表项是否就绪)。
SwapContext负责具体的更新CR3的动作,进程的数据结构KPROCESS里DirectoryTableBase成员用于记录当前进程的CR3的值。
Pagefile的结构:
具体结构不太清楚,Windows内核通过MiResolveMappedFileFault来获得发生pagefault需要具体操作的文件位置。
Windows使用section的概念管理内存,这个东西理解起来比较复杂,不是很懂,Pagefile的管理与section可能相关。
这个资料里提供了一些相关信息:
Windows内存与进程管理器底层分析