【Linux】Linux Kernel--Memory Management(二)

3.6 Demand Paging

 

只要执行映像映射到进程的虚拟内存中,它就可以开始运行。因为只有映像的最开始的部

分是放在物理内存中,很快就会访问到还没有放在物理内存的虚拟空间区。当进程访问没有有效页表条目的虚拟地址的时候,处理器向Linux报告page faultPage fault描述了发生page fault的虚拟地址和内存访问类型。

 

Linux必须找到page fault 发生的空间区所对应的vm_area_struct数据结构(用Adelson-Velskii and Landis AVL树型结构连接在一起)。如果找不到这个虚拟地址对应的vm_area_struct结构,说明进程访问了非法的虚拟地址。Linux将向该进程发信号,发送一个SIGSEGV信号,如果进程没有处理这个信号,它就会退出。

参见 handle_mm_fault() in mm/memory.c

 

Linux然后检查page faul的类型和该虚拟内存区所允许的访问类型。如果进程用非法的方式访问内存,比如写一个它只可以读的区域,也会发出内存错的信号。

 

现在Linux确定page fault是合法的,它必须进行处理。Linux必须区分在交换文件和磁盘映像中的页,它用发生page fault的虚拟地址的页表条目来确定。

参见do_no_page() in mm/memory.c

 

如果该页的页表条目是无效的但非空,此页是在交换文件中。对于Alpha AXP页表条目来讲,有效位置位但是PFN域非空。这种情况下PFN域存放了此页在交换文件(以及那一个交换文件)中的位置。页在交换文件中如何处理在本章后面讨论。

 

并非所有的vm_area_struct数据结构都有一整套虚拟内存操作,而且那些有特殊的内存操作的也可能没有nopang操作。因为缺省情况下,对于nopage操作,Linux会分配一个新的物理页并创建有效的页表条目。如果这一段虚拟内存有特殊的nopage操作,Linux会调用这个特殊的代码。

 

通常的Linux nopage操作用于对执行映像的内存映射,并使用page cache将请求的映像页加载到物理内存中。虽然在请求的页调入的物理内存中以后,进程的页表得到更新,但是也许需要必要的硬件动作来更新这些条目,特别是如果处理器使用了TLB。既然page fault得到了处理,就可以扔在一边,进程在引起虚拟内存访问错误的指令那里重新运行。

参见mm/filemap.c filemap_nopage()




 

3.7 The Linux Page Cache

 

Linuxpage cache的作用是加速对于磁盘文件的访问。内存映射文件每一次读入一页,这些页被存放在page cache中。图3.6显示了page cache,包括一个指向mem_map_t数据结构的指针向量:page_hash_tableLinux中的每一个文件都用一个VFS inode的数据结构标示(在第9章描述),每一个VFS I节点都是唯一的并可以完全确定唯一的一个文件。页表的索引取自VFS I节点号和文件中的偏移。

参见linux/pagemap.h

 

当一页的数据从内存映射文件中读出,例如当demand paging时需要放到内存中的时候,此页通过page cache中读出。如果此页在缓存中,就返回一个指向mem_map_t数据结构的指针给page fault 的处理代码。否则,此页必须从存放此文件的文件系统中加载到内存中。Linux分配物理内存并从磁盘文件中读出该页。如果可能,Linux会启动对文件下一页的读。这种单页的超前读意味着如果进程从文件中顺序读数据的话,下一页数据将会在内存中等待。

当程序映像读取和执行的时候page cache 不断增长。如果页不在需要,将从缓存中删除。比如不再被任何进程使用的映像。当Linux使用内存的时候,物理页可能不断减少,这时Linux可以减小page cache

 

3.8 Swapping out and Discarding Pages(交换出去和废弃页)

 

当物理内存缺乏的时候,Linux内存管理子系统必须试图释放物理页。这个任务落在核心交换进程上(kswapd。核心交换守护进程是一种特殊类型的进程,一个核心线程。核心线程是没有虚拟内存的进程,以核心态运行在物理地址空间。核心交换守护进程名字有一点不恰当,因为它不仅仅是将页交换到系统交换文件上。它的任务是保证系统有足够的空闲页,使内存管理系统有效地运行。

 

核心交换守护进程(kswapd)在启动时由核心的init 进程启动,并等待核心的交换计时器到期。每一次计时器到期,交换进程检查系统中的空闲页数是否太少。它使用两个变量:free_pages_highfree_pages_low来决定是否释放一些页。只要系统中的空闲页数保持在free_pages_high之上,交换进程什么都不做。它重新睡眠直到它的计时器下一次到期。为了做这种检查,交换进程要考虑正在向交换文件中写的页数,用nr_async_pages来计数:每一次一页排到队列中等待写到交换文件中的时候增加,写完的时候减少。Free_page_lowfree_page_high是系统启动时间设置的,和系统中的物理页数相关。如果系统中的空闲页数小于free_pages_high或者比free_page_low还低,核心交换进程会尝试三种方法来减少系统使用的物理页数:

参见mm/vmscan.c 中的kswapd()

 

减少buffer cache page cache的大小

将系统V的共享内存页交换出去

交换和废弃页

 

如果系统中的空闲页数低于free_pages_low,核心交换进程将试图在下一次运行前释放6页。否则试图释放3页。以上的每一种方法都要被尝试直到释放了足够的页。核心交换进程记录了它上一次使用的释放物理页的方法。每一次运行时它都会首先尝试上一次成功的方法来释放页。

 

释放了足够的页之后,交换进程又一次睡眠,直到它的计时器又一次过期。如果核心交换进程释放页的原因是系统空闲页的数量少于free_pages_low,它只睡眠平时的一半时间。只要空闲页数大于free_pages_low,交换进程就恢复原来的时间间隔进行检查。

 

3.8.1 Reducing the size of the Page and Buffer Caches

 

page buffer cache中的页是释放到free_area向量中的好选择。Page Cache,包含了内存映射文件的页,可能有不必要的数据,占去了系统的内存。同样,Buffer Cache ,包括了从物理设备读或向物理设备写的数据,也可能包含了无用的缓冲。当系统中的物理页将要耗尽的时候,废弃这些缓存区中的页相对比较容易,因为它不需要向物理设备写(不象将页从内存中交换出去)。废弃这些页不会产生多少有害的副作用,只不过使访问物理设备和内存映射文件时慢一点。虽然如此,如果公平地废弃这些缓存区中的页,所有的进程受到的影响就是平等的。

 

每一次当核心交换进程要缩小这些缓存区时,它要检查mem_map页矢量中的页块,看是否可以从物理内存中废弃。如果系统空闲页太低(比较危险时)而核心交换进程交换比较厉害,这个检查的页块大小就会更大一些。页块的大小进行循环检查:每一次试图减少内存映射时都用一个不同的页块大小。这叫做clock算法,就象钟的时针。整个mem_map页向量都被检查,每次一些页。

参见mm/filemap.c shrink_map()

 

检查的每一页都要判断缓存在page cache 或者buffer cache中。注意共享页的废弃这时不考虑,一页不会同时在两个缓存中。如果该页不在这两个缓冲区中,则mem_map页向量表的下一页被检查。

缓存在buffer cache ch中的页(或者说页中的缓冲区被缓存)使缓冲区的分配和释放更有效。缩小内存映射的代码试图释放包含检查过的页的缓冲区。如果缓冲区释放了,则包含缓冲区的页也被释放了。如果检查的页是在Linuxpage cache 中,它将从page cache 中删除并释放。

参见 fs/buffer.c free_buffer()

 

如果这次尝试释放了足够的页,核心交换进程就会继续等待直到下一次被周期性地唤醒。因为释放的页不属于任何进程的虚拟内存(只是缓存的页),因此不需要更新进程的页表。如果废弃的缓存页仍然不够,交换进程会试图交换出一些共享页。

 

3.8.2 Swapping Out System V Shared Memory Pages(交换出系统V的共享内存页)

 

系统V的共享内存是一种进程间通讯的机制,通过两个或多个进程共享虚拟内存交换信息。进程间如何共享内存在第5章详细讨论。现在只要讲讲每一块系统V共享内存都用一个shmid_ds的数据结构描述就足够了。它包括一个指向vm_area_struct链表数据结构的指针,用于共享此内存的每一个进程。Vm_area_struct数据结构描述了此共享内存在每一个进程中的位置。这个系统V的内存中的每一个vm_area_struct结构都用vm_next_sharedvm_prev_shared指针连接在一起。每一个shmid_ds数据结构都有一个页表条目的链表,每一个条目都描述一个共享的虚拟页和物理页的对应关系。

 

核心交换进程将系统V的共享内存页交换出去时也用clock算法。它每一次运行都记录了上一次交换出去了那一块共享内存的那一页。它用两个索引来记录:第一个是shmid_ds数据结构数组中的索引,第二个是这块共享内存区的页表链中的索引。这样可以共享内存区的牺牲比较公平。

参见ipc/shm.c shm_swap()

 

因为一个指定的系统V共享内存的虚拟页对应的物理页号包含在每一个共享这块虚拟内存的进程的页表中,所以核心交换进程必须修改所有的进程的页表来体现此页已经不在内存而在交换文件中。对于每一个交换出去的共享页,交换进程必须找到在每一个共享进程的页表中对应的此页的条目(通过查找每一个vm_area_struct指针)如果在一个进程页表中此共享内存页的条目有效,交换进程要把它变为无效,并且标记是交换页,同时将此共享页的在用数减1。交换出去的系统V共享页表的格式包括一个在shmid_ds数据结构组中的索引和在此共享内存区中页表条目的索引。

 

如果所有共享的内存都修改过,页的在用数变为0,这个共享页就可以写到交换文件中。这个系统V共享内存区的shmid_ds数据结构指向的页表中此页的条目将会换成交换出的页表条目。交换出的页表条目无效但是包含一个指向打开的交换文件的索引和此页在此文件内的偏移量。这个信息用于将此页再取回物理内存中。

 

3.8.3 Swapping Out and Discarding Pages

 

交换进程轮流检查系统中的每一个进程是否可以用于交换。好的候选是可以交换的进程(有一些不行)并且有可以从内存中交换出去或废弃的一个或多个页。只有其他方法都不行的时候才会把页从物理内存交换到系统交换文件中。

参见 mm/vmscan.c swap_out()

 

来自于映像文件的执行映像的大部分内容可以从文件中重新读出来。例如:一个映像的执行指令不会被自身改变,所以不需要写到交换文件中。这些页只是被简单地废弃。如果再次被进程引用,可以从执行映像再次加载到内存中。

 

一旦要交换的进程确定下来,交换进程就查看它的所有虚拟内存区域,寻找没有共享或锁定的区域。Linux不会把选定进程的所有可以交换出去的页都交换出去,而只是去掉少量的页。如果页在内存中锁定,则不能被交换或废弃。

参见mm/vmscan.c swap_out_vme() 跟踪进程mm_struct中排列的vm_area_struct结构中的vm_next vm_nex指针。

 

Linux的交换算法使用了页的年龄。每一个页都有一个计数器(放在mem_map_t数据结构中),告诉核心交换进程此页是否值得交换出去。页不用时变老,访问时更新。交换进程只交换老的页。缺省地,页第一次分配时年龄赋值为3。每一次访问,它的年龄就增加3,直到20。每一次系统交换进程运行时它将页的年龄减1使页变老。这个缺省的行为可以更改,所以这些信息(和其他相关信息)都存放在swap_control数据结构中。

如果页太老(年龄age = 0),交换进程会进一步处理。脏页可以交换出去,Linux在描述此页的PTE中用一个和体系结构相关的位来描述这种页(见图3.2)。但是,并非所有的脏页都需要写到交换文件。每一个进程的虚拟内存区域都可以拥有自己的交换操作(由vm_area_struct中的vm_ops指针指示),如果这样,交换进程会用它的这种方式。否则,交换进程会从交换文件中分配一页,并把此页写到该文件中。

 

此页的页表条目会用一个无效的条目替换,但是包括了此页在交换文件的信息:此页所在文件内的偏移和所用的交换文件。不管什么方式交换,原来的物理页被放回到free_area重释放。干净(或不脏)的页可以被废弃,放回到free_area中重用。

 

如果交换或废弃了足够的可交换进程的页,交换进程重新睡眠。下一次唤醒时它会考虑系统中的下一个进程。这样,交换进程轻咬去每一个进程的物理页,直到系统重新达到平衡。这种做法比交换出整个进程更公平。

 

3.9 The Swap Cache(交换缓存)

 

当把页交换到交换文件时,Linux会避免写不必要写的页。有时可能一个页同时存在于交换文件和物理内存中。这发生于一页被交换出内存然后在进程要访问时又被调入内存的情况下。只要内存中的页没有被写过,交换文件中的拷贝就继续有效。

 

Linuxswap cache来记录这些页。交换缓存是一个页表条目或者系统物理页的链表。一个交换页有一个页表条目,描述使用的交换文件和它在交换文件中的位置。如果交换缓存条目非0,表示在交换文件中的一页没有被改动。如果此页后来被改动了(被写),它的条目就从交换缓存中删除)

Linux需要交换一个物理页到交换文件的时候,它查看交换缓存,如果有此页的有效条目,它不需要把此页写到交换文件。因为内存中的此页从上次读到交换文件之后没有被修改过。

 

交换缓存中的条目是曾经交换出去的页表条目。它们被标记为无效,但是包含了允许Linux找到正确交换文件和交换文件中正确页的信息。

 

3.10 Swapping Page In(交换进)

 

保存在交换文件中的脏页可能又需要访问。例如:当应用程序要向虚拟内存中写数据,而此页对应的物理页交换到了交换文件时。访问不在物理内存的虚拟内存页会引发page faultPage fault是处理器通知操作系统它不能将虚拟内存转换到物理内存的信号。因为交换出去后虚拟内存中描述此页的页表条目被标记为无效。处理器无法处理虚拟地址到物理地址的转换,将控制转回到操作系统,告诉它发生错误的虚拟地址和错误的原因。这个信息的格式和处理器如何把控制转回到操作系统是和处理器类型相关的。处理器相关的page faule处理代码必须定位描述包括出错虚拟地址的虚拟内存区的vm_area_struct的数据结构。它通过查找该进程的vm_area_struct数据结构,直到找到包含了出错的虚拟地址的那一个。这是对时间要求非常严格的代码,所以一个进程的vm_area_struct数据结构按照特定的方式排列,使这种查找花费时间尽量少。

参见 arch/i386/mm/fault.c do_page_fault()

 

执行了合适的和处理器相关的动作并找到了包括错误(发生)的虚拟地址的有效的虚拟内存,page fault的处理过程又成为通用的,并可用于Linux能运行的所有处理器。通用的page fault处理代码查找错误虚拟地址的页表条目。如果它找到的页表条目是交换出去的页,Linux必须把此页交换回物理内存。交换出去的页的页表条目的格式和处理器相关,但是所有的处理器都将这些页标为无效并在页表条目中放进了在交换文件中定位页的必要信息。Linux使用这种信息把此页调回到物理内存中。

参见mm/memory.c do_no_page()

 

这时,Linux知道了错误(发生)的虚拟地址和关于此页交换到哪里去的页表条目。Vm_area_struct数据结构可能包括一个例程的指针,用于把这块虚拟内存中的页交换回到物理内存中。这是swapin操作。如果这块内存中有swapin操作,Linux会使用它。其实,交换出去的系统V的共享内存之所以需要特殊的处理因为交换的系统V的共享内存页的格式和普通交换页的不同。如果没有swapin操作,Linux假定这是一个普通页,不需要特殊的处理。它分配一块空闲的物理页并将交换出去的页从交换文件中读进来。关于从交换文件哪里(和哪一个交换文件)的信息取自无效的页表条目。

参见mm/page_alloc.c swap_in()

 

如果引起page fault的访问不是写访问,页就留在交换缓存中,它的页表条目标记为不可写。如果后来此页又被写,会产生另一个page fault,这时,此页被标志为脏页,而它的条目也从交换缓存中删除。如果此页没有被修改而又需要交换出来,Linux就可以避免将此页写到交换文件,因为此页已经在交换文件中了。

 

如果将此页从交换文件调回的访问是写访问,这个页就从交换缓存中删除,此页的页表条目页标记为脏页和可写。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值