Linux核心 之 (第三章)
第 3 章 内存管理
内存管理子系统是操作系统中最重要的组成部份之一。从早期计算机开始,系统的
实际内存总是不能满足需求,为解决这一矛盾,人们想了许多办法,其中虚存是最
成功的一个。虚存让各进程共享系统内存空间, 这样系统就似乎有了更多的内存。
虚存不仅使计算机的内存看起来更多,内存管理子系统还提供以下功能:
扩大地址空间
操作系统扩大了系统的内存空间。虚存能比系统的实际内存大许多倍。
内存保护
系统中每个进程都有它自己的虚拟地址空间。这些虚拟地址空间之间彼此分开,以
保证应用程序运行时互不影响。另外,虚存机制可以对内存部份区域提供写保护,以
防止代码和数据被其它恶意的应用程序所篡改。
内存映射
内存映射被用于将映像和数据文件映射到一个进程的虚拟地址空间中, 也就是将
文件内容连接到虚地址中。
公平分配内存
内存管理子系统公平地分配内存给正在运行的各进程。
虚存共享
尽管虚存允许各进程有各自的 ( 虚拟 ) 地址空间,但有时进程间需要共享内存。
例如,若干进程同时运行 Bash 命令。并非在每个进程的虚地址空间中,都有一个
Bash的拷贝。在内存中仅有一个运行的Bash拷贝供各进程共享。又如,若干进程可
以共享动态函数库。
共享内存也能作为一种进程间的通信机制(IPC)。两个或两个以上进程可以通过共享
内存来交换数据。Linux 支持 Unix 系统 V 的共享内存 IPC标准。
3.1 一个抽象的虚存模型
图 3.1 :虚地址与物理地址之间的映射
在分析 Linux 实现虚存的方法前,让我们先来看一个没有过多细节的抽象模型。
当处理器执行一段程序时,它先从内存中读出一条指令并对它进行解码。解码时可
能需要在内存中的某一地址存取数据。然后处理器执行这条指令并移向下一条。可
见处理器总是不断地在内存中存取数据或指令。
在虚存系统中,所有地址都是虚地址而非物理地址。处理器根据操作系统中的一组
表格而把这些虚地址翻译成相应的物理地址。
为使这翻译的过程更容易,虚存和物理内存被划分成许多适当大小的块,叫做“页”
(page)。为便于系统管理,这些页都是一样大小的。在 Alpha AXP 上的 Linux 系
统中,每页有 8 Kbyte,但在 Intel x86 系统中,每页有 4 Kbyte。每一页又被分
配了一个各不相同的数字,叫页号 ( PFN ) 。
在本模型中,一个虚地址由两部份组成;偏移量和虚页号。如果页的大小是 4 Kbytes,
那么虚地址的0至11位是偏移量,第12位以上是虚页号。每次处理器遇到虚地址时,
它先取出偏移量和虚页号。然后,处理器把虚页号翻译成物理页号,再由偏移量得
到正确的物理地址,最后存取数据。处理器需要使用页表来完成这整个过程。.
图 3.1 显示了两个进程的虚存地址空间。进程 X 和进程 Y 分别有各自的页表。页
表记录各进程虚页和物理页之间的映射。如图:X 虚存的第0页对应物理地址的第4页。
理论上,页表中每条记录包含以下信息:
-有效性标志。 用以标识页表记录有效与否。
-页号。用以记录对应的物理内存页号。
- 存取控制信息。描述这页该怎样被使用。可否可写?可否包含可执行代码?
页表中使用虚页号作为偏移量。虚页 5 将是表中的第 6 条记录( 0 是第一条记录
)。
把一个虚地址翻译成物理地址时, 处理器必须先得出虚页号和偏移量。页的大小总
是 2 的幂,这便于进行mask和移位操作。图 3.1中, 假定页的大小是 0x2000 字
节 ( 它是十进制的 8192 ),在进程 Y的地址空间中有一虚地址 0x2194。那么处理
器将把这个地址翻译成偏移量为 0x194,虚页号为1。
处理器使用虚页号作为检索进程页表记录的索引。如果对应那偏移量的页表记录是
有效的,处理器就从中拿出物理页号。如果记录是无效的,表明进程想存取一个不
在物理内存中的地址。在这种情况下,处理器不能翻译这虚地址,必须把控制权传
给操作系统,让它处理。
当进程试图存取一个无法翻译的虚地址时,处理器将通知操作系统, 这被称为一个
页错。各种处理器处理页错的方法是不同的,但都会通知操作系统产生页错的虚地
址和原因。
假设找到的是一有效的页表记录,处理器就取出物理页号并且乘以页的大小,得到
内存中页的基地址。最后,处理器加上偏移量得到它需要数据的地址。
例如, 进程 Y 的虚存第1页被映射到内存第 4页,它从 0x8000(4 x 0x2000 )开始。
加上偏移量 0x194 字节就得到最后的物理地址是 0x8194。
由虚地址映射到物理地址时, 虚存各页映射到系统内存中的顺序是任意的。例如,
在图 3.1 中, 进程 X 的虚存第 0 页被映射到内存第1页,而虚存第 7页被映射到
内存第 0页。这说明了虚存的一个有趣现象,虚存各页在物理内存中不必有任何顺
序。
3.1.1 按需装载页(Demanding Paging)
虚存比实际内存大很多,所以操作系统一定要小心有效地使用内存。节省内存的一
个方法是只装载被当前执行程序使用的虚页。例如,有一个用来查询数据库的程序。
此时,并非所有数据库中的数据都需被装载进内存,只需要那些正在被访问的数据。
如果正运行一条数据库搜索命令,那么就不必载入添加新记录的代码。当代码或数
据被访问时才装载进内存,这叫作按需装载页(demand paging)。
当进程试图存取一个不在内存中的虚地址时,处理器不可能在页表中找到这一虚页
的记录。例如,在图 3.1中, 进程 X 的虚存第 2页没有对应的页表记录,如果尝
试对这页进行读操作,那么处理器不能把虚地址翻译成物理地址。处理器就会通知
操作系统页错发生了。
如果页错(faulting) 对应的虚地址是无效的,这意味着进程试图存取它不应该访问
的虚地址。这也许是因为应用程序出了某些错误, 例如试图在内存中任意进行写操
作。在这种情况下,操作系统将终止这个错误进程,以保护其它进程。
如果页错(faulting) 对应的虚地址是有效的,只是它所在页目前不在内存中,操作
系统必须将对应的页从磁盘载入内存。相对来说,磁盘存取会花很多时间,所以进
程必须等待相当一会儿直到页被读入。这时候,如果有其它进程能运行,操作系统
将选择其中之一。被取的页将被读入内存一空页中,并在进程页表中加入一条记录。
然后,进程从产生页错的机器指令重新启动。这次处理器能将虚地址翻译成物理地
址了,因此进程能继续运行下去。
Linux 使用按需装载页来读入可执行进程的映像。一个命令被执行时,包含它的文
件被打开,它的内容被印射入进程的虚存。这操作需修改描述这进程内存映像的数
据结构 (memory mapping)。然而,只有映像的第一部份被实际载入物理内存,余下
部份被留在磁盘上。当映像执行时,它将不断产生页错, Linux 使用进程的内存映
像表来决定哪块映像该被载入内存。
3.1.2 页交换 (Swapping)
当进程要装载一虚页进物理内存时,如果得不到空页, 操作系统必须从内存中丢弃
别的页,为这页提供空间。
如果从内存中被丢弃的那页是从映像或数据文件中来的,并且映像和数据文件没被
修改过,那这页不需再被保存,可以直接丢掉。如果进程再需要那页,它可以重新
被从映像或数据文件中读入内存。
但如果该页已被修改了,操作系统必须保存这页的内容以便它以后能再被访问。这
类页叫作脏 (dirty) 页,当它们被从内存中移出时,它们被作为特殊的交换文件
(swap file) 保存。相对于处理器和内存的速度,交换文件的存取时间是很长的,
所以操作系统必须权衡是否需要把页写到磁盘上,还是保留在内存中以备后用。
如果交换算法的效率不高,那么thrashing现象就会发生。在这种情况下,页常常一
会儿被写到磁盘上,一会儿又被读回来,操作系统忙于文件存取而不能执行真正的
工作。例如,图3.1 中,如果内存第 1页不断被访问,那它就不应该被交换到硬盘
上。进程当前正在使用的页的集合被叫作工作集 (working set)。有效的交换算法
将保证所有进程的工作集都在内存中。
Linux 使用最近最少使用算法(Least Recently Used) 来公平选择从内存中被丢弃
的页。这个算法中,当页被存取时,它的年龄 (aging) 就变化了。页越多被存取,
便越年轻;越少被存取就越旧。旧页通常是被丢弃的好候选。
3.1.3 共享虚存
虚存使得若干进程更容易共享内存。进程所有的内存访问都要通过页表,并且各进
程有各自独立的页表。当多进程共享内存中一页时,物理页号就会同时出现在每个
进程的页表中。
图3.1 中显示两进程共享物理第4页。对进程 X 而言,那是虚存的第 4页,对进程
Y而言,
那是虚存第 6页。这说明一个有趣的现象:被共享的物理页对应的虚存页号可以各
不相
同。
3.1.4 物理和虚拟地址模式
把操作系统运行在虚存中是不明智之举,如果操作系统还要为自己保存页表,那将
是一场恶梦。因此,很多种处理器同时支持虚拟地址模式和物理地址模式。物理地
址模式不需要页表,处理器不必做任何地址翻译。 Linux 内核被直接连在物理地址
空间中运行。
Alpha AXP 处理器没有物理地址模式。相反, 它把内存划分成若干区域并且指定其
中两
块为物理地址区。这段核地址空间叫作KSEG,包括所有0xfffffc0000000000以上的
地址。
在 KSEG执行的 (按定义,核代码 ) 或在那里存取数据的代码肯定是在核模式下执
行。在 Alpha 上的 Linux 核被连接从0xfffffc0000310000开始执行。
3.1.5 存取控制
页表记录中也包含了存取控制信息。处理器使用页表记录来把虚地址翻译成物理地
址的同时,它也很容易地使用其中的存取控制信息来检查进程是否在正确地访问内
存。
在很多种情况下,你想要为内存的一段区域设置存取限制。一段内存, 例如包含可
执行的代码, 应为只读内存;操作系统应该不允许进程在它的可执行的代码上写数
据。相反的,包含数据的页能被写,但是当指令试图执行那段内存时,应该失败。
大多数处理器的执行代码有两种模式:核 态和用户态。你将不想由一个用户执行核
代码,或者让核数据结构被不是核态执行的代码所访问。
图 3.2 : Alpha AXP 的页表记录 (Page Table Entry)
存取控制信息被保存在 PTE中,并且不同的处理器,PTE的格式是不同的;图3.2 显
示的是 Alpha AXP 的PTE。各位包含以下信息:
V 有效位。如果设置,表示这 PTE 是有效的。
FOE (Fault on Execute) 无论何时试图在这页执行指令时,处理器将报告页错,
并且把控制权传给操作系统。
FOW (Fault on Write) 当在这页上进行写操作时报页错。
FOR (Fault on Read) 当在这页上进行读操作时报页错。
ASM(Address Space Match) 地址空间匹配。当操作系统仅仅希望清除翻译缓冲区中
若干记录时,这一位被使用。
KRE 在核模式下运行的代码能读这页。
URE 在用户模式下运行的代码能读这页。
GH 粒度性,指在映射一整块虚存时,是用一个翻译缓冲记录还是多个。
KWE 在核模式下运行的代码能写这页。
UWE 在用户模式下运行的代码能写这页。
页号 在有效的PFE中, 这域包含对应的物理页号 (page frame number )。对无效
的PTEs ,如果这域不是零,它包含了页在交换文件中的信息。
以下两位是 Linux 定义并使用的:
_PAGE_DIRTY 如果设置,页需要被写到交换文件中。
_PAGE_ACCESSED 由 Linux 标记这页是否曾被访问。
3.2 缓存
如果你按照上面理论模型,可以实现一个工作的系统,但不会特别高效。操作系统
和处理器的设计者都在努力提高系统性能。除提高处理器和内存的速度外,最好的
途径是把有用的信息和数据保存在缓存中。 Linux 就使用了很多与内存管理有关的
缓存:
缓冲区
缓冲区包含块设备驱动程序 (block device driver) 使用的数据缓冲区。
这些缓冲区有固定的大小 ( 例如 512 个字节 ) ,记录从一台块设备读或写的信息。
一台块设备只能存取整块数据。所有的硬盘都是块设备。
缓冲区通过设备标识符和需要的块号的索引来迅速发现所需数据。块设备只能通过
缓冲区进行存取操作。如果数据在缓冲区中,那么它就不需要再从块设备中被读(例
如硬盘),这样存取得更快。
页缓存
它被用来加快磁盘上映像和数据的存取。
它被用来一次缓存文件的一页,存取操作通过文件名和偏移量来实现。当页从磁盘
被读进内存时,他们被缓存在页缓存中。
交换缓存
只有修改了的页,即脏(dirty ) 页,被保存在交换文件中。
只要一页在被写进交换文件以后,没有再被修改,下次这页被换出内存时,可以直
接被扔掉。对一个进行许多页面交换的系统,这将节省许多不必要的并且昂贵的磁
盘操作。
硬件缓存
处理器中有一经常用到的硬件缓存:页表记录的缓存。通常情况下,处理器并不总
是直接读页表,而是用页表缓存保留用到的记录。这些被叫做 Translation Look-aside
Buffer,保存了系统中多个进程页表的拷贝。
当翻译地址时,处理器先试图找到一匹配的TLB 记录。如果它发现了一个,它能直
接把虚地址翻译成物理地址,并且对数据进行存取操作。如果处理器不能发现一匹
配的 TLB 记录,那就必须借助操作系统。它发信号给操作系统,报告有一个 TLB
疏漏。特定的机制将把异常信号送给操作系统的代码。操作系统为印射的地址产生
一个新的 TLB 记录。当异常被解决后,处理器将尝试再翻译那个虚地址。因为现在
那个地址在 TLB 中有一个有效的记录,这次的地址翻译一定成功。
使用缓冲区,硬件缓存等的缺点是Linux 必须花费更多的时间和空间来维护这些缓
存, 如果缓存发生错误,系统将崩溃。
3.3 Linux 页表
图 3.3 : 3级页表
Linux页表有3层。每一层负责保存下一层页表所在的页号。图3.3 显示一个虚地址
被分成了很多域;每个域记录在某一层页表中的偏移量。把一个虚地址翻译成物理
地址时,处理器拿出每个域的内容把它变成页表中的偏移量,进而读出下层页表的
所在页号。这样重复 3 次直到找到包含虚地址的物理页号。虚地址的最后一个域,
叫做字节偏移量, 被用来在物理页内找到所需数据。
每个运行 Linux 的平台必须提供翻译宏(Translation macros) 以便内核可以检索
页表,完成某种操作。这样,内核不需要知道各平台上页表记录的具体格式和它们
是怎么被安排的。
这就是为什么 Linux 的 Alpha 处理器和Intel x 86 处理器使用一样的页表操作代
码, 而Alpha有3层页表,Intel x86处理器只有2层页表。
3.4 页的分配和回收
在系统中,对页有许多操作。例如, 当一段映像被装载进内存时,操作系统需要分
配页。当映像执行完成并且被卸掉时,这些页将被释放。页的另外的用途是保存内
核特定的数据结构,例如页表。页的分配和回收机制是维持虚存分系统效率的关键。
系统中所有物理内存页由 mem_map 数据结构描述,men_map由一列 mem_map_t 组成。
在初始化时,每个 mem_map_t 描述系统中的一页。它重要的域如下(有关内存管理
) :
计数器 描述使用这页的用户数。如果计数器比一大,则这页被多进程共享。
年龄 描述页的年龄,被用来决定页是否是被丢弃或交换的好候选。
map_nr 描述这个 mem_map_t 对应的页的物理页号。
页分配代码使用矢量 free_area 来寻找并释放页。这机制支持整个缓冲区管理,对
于代码来说,页的大小和处理器对页的操作机制是与其无关的。
free_area 每个单元都包含一种页块的信息。在数组的第一单元描述单个的页, 下
一单元描述 2 页块,再下一单元描述 4 页的块,并以2的幂上升。表中每个单元作
为一个队头,有指针指向mem_map 数组中的页。空的页块在这里排队。map是指向bitmap的
一个指针,bitmap 记录了这种大小页块的分配情况。位图中,如果第 n 块页是空
的,那么位 N 被置。
图 free_area_figure 显示的是 free_area 的结构,第0单元记录有一个空页,从
第0页开始。第2单元记录有两个4页的空块,第一块从第4页开始,第二块从第56页
开始。
3.4.1 页的分配
Linux 使用伙伴(Buddy) 算法来有效地分配和回收页块。页分配代码被用来分配一
页或多页的块。页的大小总是 2 的幂,即能分配1页, 2 页, 4 页等等。只要系统
中有足够满足请求的空页 ( nr_free_pages >min_free_pages ),分配代码就能在
free_area 里找到所需大小的页块。free_area每个单元有一张分配图 (bitmap)。
例如, 数组的单元 2 有描述长度为4的页块的分配图。
算法寻找所需大小的页块时,它先搜索 free_area 数据结构中那种页块的队列。如
果所需大小的页块没有空,就在下一对列中寻找(页块的大小是所需的两倍)。继续
这一过程直到free_area 中所有单元都被找过了或发现了一空页块。如果找到的空
页块比所需的大,它必须被分割成正确的大小。
图 3.4 : free_area 数据结构
例如, 在图3.4 中,如果需要一 2 页块,那么第一个空的 4 页块 (从第4页起 )
将被分成两半。从第4页开始的 2 页块被返回给请求者;从第6页开始的 2 页块被
排在free_area的空的两页块的队中。
3.4.2 页的回收
页分配时容易将大块连续的内存分成很多小块。页的回收代码须尽可能将小块的空
内存重新组合成大块的。事实上,页块的大小对内存的重新组合很重要。
当一页块被释放时,系统会检查它旁边的和一样的大小的页块,看它们是否是空的。
如果是,它们将被拼成一个大的整块。每次当两块内存被拼成了更大的空块时,页
回收代码尝试将它们与其它空块继续组合,以得到更大的空间。这样得到的空页块
可以满足任何对内存的需求。
例如,在图 3.1中,如果第 1 页被释放,那它将与第0页结合,并被放到 free_area
的两页空块的队中。
3.5 内存印射
当一映像被执行时,它的内容必须被读入进程的虚存。它调用的库函数也必须被读
入虚存。这个可执行文件并非被实际读入内存, 相反它只是被连接入进程的虚存。
然后,当程序的一部份被应用程序调用时,系统才将这部份映像读入内存。将映像
连接到进程的虚地址空间叫做内存印射(memory mapping)。
图 3.5 :虚存
每个进程的虚存空间由一个 mm_struct 数据结构表示。这包含当前正在执行的映像
的信息 (例如 Bash ),还有很多指向 vm_area_struct 的指针。每个 vm_area_struct
数据结构
描述一段虚存区域的开始和结束,及进程对那段虚存的存取权限和允许的操作。这
些操作是Linux 对这段虚存必须使用的一套例程。例如, 当进程试图存取虚存中某
页,但发现这页并不在内存中时,应执行的正确操作是 nopage 操作(通过页错)。
Linux使用nopage 操作可以按需将一页可执行映像载入内存。
当一段可执行映像被印射入进程的虚存时,会产生一组 vm_area_struct 数据结构。
每个 vm_area_struct 数据结构代表可执行映像的一部份;可执行代码, 初始化数据
(变量),未初始化数据等等。 Linux 支持很多标准的虚存操作,当 vm_area_struct
数据结构产生时,系统会把正确的虚存操作集与他们相联。
3.6 按需换页 (Demanding Paging)
当一部份可执行映像被印射入进程虚存后,它就可以开始执行了。可是这时只有映
像的开始部份被实际读入内存,它将不断访问不在内存中的部份。当进程存取一个
没有有效页表记录的虚地址时,那处理器将报页错给 Linux 系统。
页错描述页错发生的虚地址和引起的存取操作。
Linux 必须先找到代表页错发生区域的 vm_area_struct。由于搜索 vm_area_struct
数据结构对高效处理页错非常关键,所以所有 vm_area_struct 被连接成AVL树结构
(Adelson-Velskii and Landis)。 如果没有 vm_area_struct 代表这页错发生的
虚地址, 表示这进程企图访问一个非法的虚地址。Linux 将发送 SIGSEGV 信号给
进程,如果进程没有对应这个信号的处理程序,它将被终止。
Linux 再检查存取操作是否是被允许的。如果进程在用一个非法的方法存取内存,
例如,写一个只读区域,它也将引起一个内存错误信号。
如果 Linux 确定页错是合法的, 它就会处理它。
Linux 必须首先区别映像是在交换文件中还是在磁盘上。它是通过页表记录来区别
的。
如果那页的页表记录是无效的,但非空,说明产生页错的那页当前在交换文件中。
例如, Alpha AXP 页表记录中,这样的记录有效位未置,但是PFN 域不为零。在这
种情况下, PFN 域容纳的信息表示这页被保持在哪个交换文件中的哪里。本章后半
部将讲述怎样处理在交换文件中的页。
并非所有的 vm_area_struct 数据结构都有一组虚存操作,即使有,也不一定有nopage
操作。缺损情况下,Linux 将分配一页新内存,并为这页增加一项页表记录。但如
果这段虚存有 nopage 操作,Linux 将使用它。
通常 Linux 的 nopage 操作被用于把可执行映像通过页缓存读入内存。
当页被读入内存后,进程的页表将被更新。特别是如果处理器使用TLA 缓冲区的话,
它可能需要通过硬件操作来完成更新。页错被处理后,进程在产生页错的指令处重
新开始执行。
3.7 Linux 页缓存
图 3.6 : Linux 页缓存
Linux 页缓存的作用是加快从磁盘上存取文件的速度。每次系统读取文件的一页并
将它放在页缓存中。图 3.6 显示页缓存包括 page_hash_table,它是一组指向 mem_map_t
的指针。
Linux 的每个文件由一 VFS inode 数据结构表示 (请参看文件系统章 ),并且每个
VFS inode 是唯一的并且描述一个且仅一个文件。页表中的索引包括了文件的 VFS号
及其在文件中的偏移量。
当从印像文件中读一页时,例如,按需装载一页回内存时,读操作将通过页缓存。
如果页在缓存中,一个指向它的 mem_map_t 指针将被返回给处理页错的代码。否则,
这页必须被从文件系统中读入内存。 Linux 需分配一页内存并从磁盘上读文件。
如果可能, Linux 将开始读文件的下一页。向前多读一页意味着如果进程是连续地
访问文件,那么下一页将等在内存中。
页缓存中的内容将随着文件的存取而越来越多。当他们不再被任何进程使用时,这
些页将被从缓存中移出。当 Linux 的空闲内存变得很少时,Linux 将减少页缓存的
大小。
3.8 页的交换和释放
当空内存变得很少时, Linux 内存管理系统必须释放一些页。这任务由内核交换程
序来完成( kswapd )。
内核交换程序是一种特殊的进程,是一个核线程。核线程是没有虚存的进程,他们
在物理地址空间以核模式运行。内核交换程序不仅把页交换到系统的交换文件中,
它的角色是保证系统有足够的内存而使内存管理系统可以高效工作。
内核交换程序被内核 init 进程在初始时启动,并等待内核交换定时器周期性地到
期时开始运行。
每次定时器到期,内核交换程序检查系统中的空页数是否变得太低。交换程序使用
两个变量,free_pages_high 和 free_pages_low 来决定是否它应该释放一些页。
只要系统的空页数大于 free_pages_high, 内核交换程序不做任何事情;它继续休
息直到定时器再次到期。在做这项检查时,交换程序计算了正在往交换文件中写的
页数。每次有一页等待写入交换文件时,计数器加1,当操作结束后,计数器减1。
free_pages_low 和free_pages_high 在系统开始时被设置,并且与系统内存的页数
有关。如果系统的空页数小于 free_pages_high 或甚至小于 free_pages_low , 核
交换驻留程序将尝试 3 种方法以减少系统使用的页数:
减少缓冲区和页缓存的大小
换出系统 V 的共享页
换出并释放一些页
如果系统的空页数小于 free_pages_low , 核交换程序在它下次运行以前,将尝试
释放 6 页,否则它将尝试释放 3 页。上面的方法将依次被使用直到有足够的页被
释放。核交换程序将记住上一次它是用什么方法释放内存的,下一次将首先使用这
个成功的方法。
在系统有足够的空页后,交换程序将休息直到它的定时器到期。如果上次空页数小
于free_pages_low, 它只休息一半时间。直到空页数多于 free_pages_low,核交
换程序才恢复休息的时间。
3.8.1 减少页缓存和缓冲区的大小
在页缓存和缓冲区中保存的页是被释放的最佳候选。页缓存保存着内存映像文件,
很可能包括了许多没用的页。同样,缓冲区中,它保存读写物理设备的数据, 也很
可能包含许多不需要的数据。当系统的内存页快用完时,从这些缓存丢弃页是相对
容易的 (不同于从内存交换页),因为它们不需要写物理设备。丢弃这些页除了使访
问设备和内存的速度减慢一些以外,没有其它的副作用。并且如果对各进程公平对
待的话,对各进程的影响是相同的。
每次内核交换程序尝试缩小这些缓存时,它先检查在 mem_map 中的页块,看是否有
页可以被从内存中释放。如果内核交换程序经常作交换操作,也就是系统空页数已
经非常少了,它会先检查大一些的块。页块会被轮流检查;每次减少缓存时检查一
组不同的页块。这被称作时钟算法,像钟的分针一样轮流检查 mem_map 中的页。
检查一页是看它是否在页缓存或缓冲区中。应该注意共享页在这时候不能被释放,
并且一页不能同时在两个缓存中。如果页不在任何一个缓存中,那么就检查 mem_map
中的下一页。
页被缓存在缓冲区中 ( 或页内的缓冲区被缓存 )是为更有效地分配和回收缓存。缩
小内存代码将尝试释放被检查页中的缓冲区。
如果所有的缓冲区都被释放了,那么对应它们的内存也就被释放了。如果被检查的
页在 Linux 页缓存中,它将被从页缓存中移出并释放。
当足够的页被释放后,内核交换程序将等到下一个周期再运行。因为释放的页都是
进程的虚存部份 ( 他们是被缓存的页 ), 所以没有页表记录需要更新。如果没有释
放足够的页,那么交换程序将试着释放一些共享页。
3.8.2 交换出系统 V 的 共享页
系统 V 共享内存提供了进程间的通信机制。进程间如何共享内存,请参看IPC章。
系统 V 的共享区域被描述成一个 shmid_ds 数据结构。这包含一根指向一组 vm_area_struct
数据结构的指针,每个 vm_area_struct 对应共享这区域的一个进程。vm_area_struct
数据结构描述了每个进程在各自虚存的哪里共享系统 V 的这个区域。每个 vm_area_struct
由 vm_next_shared 和vm_prev_shared 指针相连。每个 shmid_ds 数据结构还包括
一组页表记录,描述这些共享页是对应内存中的哪些页。
内核交换程序也使用时钟算法来换出系统 V 的共享页。每次它运行时,它记得上次
换出的是哪个共享页。它将其记录在两个索引中,第一个是 shmid_ds 数据结构的
索引, 第二个是系统的这段共享内存的页表记录的索引。这保证它公平地对待系统
V 的所有共享页。
由于共享页的物理页号在每一个共享进程中都有记录,内核交换程序必须修改这些
页表,显示页已不在内存中了,而被保存在交换文件中。对于每个换出的共享页,
内核交换程序是顺着 vm_area_struct 的指针找到这共享页在各个进程中的页表记
录。如果这共享的系统 V 的页对应的页表记录是有效的,交换程序将把它改成无效,
标为在交换文件中,再将对应这页的计数器减1。被换出的系统 V 的共享页仍包括
两个索引,第一个是 shmid_ds 数据结构的索引, 第二个是系统中共享这段内存的
进程的页表记录的索引。
如果各进程的页表修改过后,页的计数器变成0,那么这页就可以被写入交换文件了,
shmid_ds 中各页表记录的值将变为交换文件中的地址,在交换文件中的页的记录
包括其对应交换文件的索引和偏移量。当这页要被重新读回内存时,这些信息将被
使用。
3.8.3 换出及释放(进程的)页
交换程序检查系统中每一个进程,看它们是不是好的候选。好的候选是那些能被换
出的进程或那些能从内存中换出并释放若干页的进程。只有当这些页不能从其它地
方得到时,它们才会被写进交换文件。
许多映像的内容是可以从映像文件中读出的。例如, 一段映像的可执行指令决不会
被修改,所以不用被写进交换文件。这些页能被直接释放;当他们再被进程调用时,
他们将被从可执行映像中重新读入内存。
一旦确定了换出的进程,交换程序将检查它所有的页表记录,找出不是共享或被锁
的区域。
Linux 并不换出它所选择进程的所有可交换页;相反它仅移出其中的一小部份。
如果页在内存中被锁住了,它们就不能被换出或释放。
Linux 交换算法使用页的年龄 (aging)。每页有一个记数器 (保持在 mem_map_t 数
据结构中),告诉交换程序是否应将它移出。当它们闲置时,页会变老;当被访问时,
页变年轻。交换程序仅仅移出旧页。缺省状态下,当一页被分配时,起始年龄是3,
每次它被访问,它的年龄从 3 增加直到最大值 20。每次内核交换程序运行时,它
把所有页的年龄数减1。这些缺省操作都能被改变,它们被存储在 swap_control 数
据结构中。
如果页是旧的 ( 年龄 = 0 ),交换程序就进一步处理它(将它移出内存)。脏页也可
以被移出。Linux 用PTE中的特定位来标示 (见 3.2图)。然而, 并非所有的脏页必
须被写进交换文件。进程的每个虚存区域都可以有它们自己的交换操作 (由 vm_area_struct
中的 vm_ops 指出),这个特定的操作将被调用。否则,交换程序将分配一页交换文
件,并将那页写到磁盘上。
页对应的页表记录将被改为无效,但包含了它在交换文件中的信息,它将指出是哪
个交换文件,并且偏移量是多少。无论采取什么交换方法,原来的物理页将被放回
free_area。.乾净的 (not dirty) 页可以直接被释放并放回 free_area以备后用。
如果有足够的页被换出或释放, 交换程序就又开始休息。下一次它运行时,它将检
查系统中的下一个进程。这样,交换程序对每个进程都移出几页,直到系统内存恢
复正常,这比移出一整个进程来的公平。
3.9 交换缓存
当将页移入交换文件中时,并非所有情况,Linux 都需进行写操作。有时一页既在
交换文件中,又在内存中。这种情况是由于这页本来被移到了交换文件中,后又因
为被调用,重又被读入内存。只要在内存中的页没被修改过, 在交换文件中的拷贝
仍然是有效。
Linux 使用交换缓存来记录这些页。交换缓存是一张页表记录的表,每条记录对应
一页。每条页表记录描述被换出的页在哪个交换文件中及其在文件中的位置。如果
一交换缓存记录非零,表示在交换文件中的那页没被修改过,如果页被修改了(被写
),它的记录将被从交换缓存中移出。
当 Linux 需要移出一页内存到交换文件中时,它先查询交换缓存, 如果这页有一个
有效的记录,它就不需要把页写到交换文件中了。因为自从它上次被从交换文件中
读出后,在内存中没被修改过。
交换缓存中的记录描述已被移到交换文件中的页。它们被标为无效,但是告许Linux
页在哪个交换文件以及在交换文件的哪一页。
3.10 移入页
保存在交换文件中的脏页可能会被再次调用。例如,一个应用程序要将某些内容写到一
已移出的页中。当这页被换到交换文件中时,描述这页的页表记录已被标记为“无效”。
这样,存取不在内存中的虚地址将引起页错。页错是由处理器发信号给操作系统,告诉
操作系统它不能把某个虚地址翻译成物理地址,并告之引起页错的虚地址及原因 (不同的
处理器是用不同的格式传递这些信息的),同时,处理器把控制权交给操作系统。
操作系统用特定(与处理器有关)的代码来找到引起页错的虚地址对应的 vm_area_struct 数
据结构。在这个过程中,系统检索进程所有的 vm_area_struct。这段代码对时间的要求很
高,所以vm_area_struct 应被合理组织起来,以缩短所需的时间。
系统执行了以上操作,证实了引起页错的虚地址是有效的后,处理页错的其它代码是与
处理器无关的。
下一步,(系统)处理代码寻找虚页对应的页表记录。如果它发清b页表记录指示这页在交
换文件中, Linux 就把这页读回内存。页表记录的格式因处理器的不同而各不相同,但“有
效位”都应该是无效,并都保存着有关这页在交换文件中的信息。Linux 需要利用这些信
息来把页重新载入内存。
此时,Linux 知道了引起页错的虚地址及其对应的页表记录,记录中保存着有关交换文件
的信息。而将页从交换文件中读回内存的函数通常由vm_area_struct 中的指针指向。这种
函数叫移入(swapin) 函数。如果能从vm_area_struct 中找到这函数,Linux 就会调用它。例
如,因为系统 V 的页的格式与一般的页不同,所以系统 V 中移出的页需要特殊处理,这
时就需要调用它们的移入函数。然而,某页可能没有对应的移入函数,在这种情况下,
Linux 将认为它是一普通的页,而不需要做任何特别处理。
系统将分配内存中的一空页并从交换文件中把这页读回来,交换文件中的地址信息是从
无效的页表记录中取回的。
如果引起页错的不是写操作,那么这页将被留在交换缓存中,它的页表记录不会被标为
“可写”。如果后来这页被写了,那么会产生另一个页错,这时,页被标成“dirty”,并
被从交换缓冲中删去。如果这页没被修改过,而它又需要被换出,Linux将不会再把这页
写到交换文件中,因为它已经在那儿了。
如果引起页错的是写操作,页将被从交换缓存中删除,它的页表记录将被标成“dirty”和“可写( writable)”。
发布人:netbull 来自:linuxeden