本文装载自 嵌入式Linux中文站
页面换入换出 在找到了引用待回收页面的页表项后,对于文件映射,可以直接把引用该页面的页表项清空。等用户再访问这个地址的时候触发缺页异常,异常处理代码再重新分配一个页面,并去磁盘里面把对应的数据读出来就行了(说不定,页面在对应的磁盘高速缓存里面已经有了,因为其他进程先访问过)。这就跟页面映射以后,第一次被访问的情形一样; 对于匿名映射,先将页面写回到交换文件,然后还得在页表项中记录该页面在交换文件中的index。 页表项中有一个present位,如果该位被清除,则mmu认为页表项无效。在页表项无效的情况下,其他位不被mmu关心,可以用来存储其他信息。这里就用它们来存储页面在交换文件中的index了(实际上是交换文件号 交换文件内的索引号)。
将匿名映射的页面交换到交换文件的过程(换出过程)与将磁盘高速缓存中的脏页写回文件的过程很相似。 交换文件也有其对应的address_space结构,匿名映射的页面在换出时先被放到这个address_space对应磁盘高速缓存中,然后跟脏页写回一样,被写回到交换文件中。写回完成后,这个页面才被释放(记住,我们的目的是要释放这个页面)。 那么为什么不直接把页面写回到交换文件,而要经过磁盘高速缓存呢?因为,这个页面可能被映射了多次,不可能一次性把所有用户进程的页表中对应的页表项都修改好(修改成页面在交换文件中的索引),所以在页面被释放的过程中,页面被暂时放在磁盘高速缓存上。 而并不是所有页表项的修改过程都是能成功的(比如在修改之前页面又被访问了,于是现在又不需要回收这个页面了),所以页面放到磁盘高速缓存的时间也可能会很长。
同样,将匿名映射的页面从交换文件读出的过程(换入过程)也与将文件数据读出的过程很相似。 先去对应的磁盘高速缓存上看看页面在不在,不在的话再去交换文件里面读。文件里的数据也是被读到磁盘高速缓存中的,然后用户进程的页表中对应的页表项将被改写,直接指向这个页面。 这个页面可能不会马上从磁盘高速缓存中拿下来,因为如果还有其他用户进程也映射到这个页面(它们的对应页表项已经被修改成了交换文件的索引),他们也可以引用到这里。直到没有其他的页表项再引用这个交换文件索引时,页面才可以从磁盘高速缓存中被取下来。
最后的必杀 前面说到,PFRA可能扫描了所有的LRU还没办法回收需要的页面。同样,在slab、dentrycache、inodecache、等地方,可能也无法回收到页面。 这时,如果某段内核代码一定要获得页面呢(没有页面,系统可能就要崩溃了)?PFRA只好使出最后的必杀技OOM(outofmemory)。所谓的OOM就是寻找一个最不重要的进程,然后将其杀死。通过释放这个进程所占有的内存页面,以缓解系统压力。
5.内存管理架构
针对上图,说几句,
[地址映射](图:左中) linux内核使用页式内存管理,应用程序给出的内存地址是虚拟地址,它需要经过若干级页表一级一级的变换,才变成真正的物理地址。 想一下,地址映射还是一件很恐怖的事情。当访问一个由虚拟地址表示的内存空间时,需要先经过若干次的内存访问,得到每一级页表中用于转换的页表项(页表是存放在内存里面的),才能完成映射。也就是说,要实现一次内存访问,实际上内存被访问了N 1次(N=页表级数),并且还需要做N次加法运算。 所以,地址映射必须要有硬件支持,mmu(内存管理单元)就是这个硬件。并且需要有cache来保存页表,这个cache就是TLB(Translation lookaside buffer)。 尽管如此,地址映射还是有着不小的开销。假设cache的访存速度是内存的10倍,命中率是40%,页表有**,那么平均一次虚拟地址访问大概就消耗了两次物理内存访问的时间。 于是,一些嵌入式硬件上可能会放弃使用mmu,这样的硬件能够运行VxWorks(一个很高效的嵌入式实时操作系统)、linux(linux也有禁用mmu的编译选项)、等系统。 但是使用mmu的优势也是很大的,最主要的是出于安全性考虑。各个进程都是相互独立的虚拟地址空间,互不干扰。而放弃地址映射之后,所有程序将运行在同一个地址空间。于是,在没有mmu的机器上,一个进程越界访存,可能引起其他进程莫名其妙的错误,甚至导致内核崩溃。 在地址映射这个问题上,内核只提供页表,实际的转换是由硬件去完成的。那么内核如何生成这些页表呢?这就有两方面的内容,虚拟地址空间的管理和物理内存的管理。(实际上只有用户态的地址映射才需要管理,内核态的地址映射是写死的。)
[虚拟地址管理](图:左下) 每个进程对应一个task结构,它指向一个mm结构,这就是该进程的内存管理器。(对于线程来说,每个线程也都有一个task结构,但是它们都指向同一个mm,所以地址空间是共享的。) mm- |