020-29133788
    资 讯
    您的位置:首页 >> 资 讯 >> LINUX应用 >> LINUX软件安装 >> 正文
    Linux Kemel - 内存管理 (一)

    点击:   发布日期:2013-01-05

    本文来自 www.020fix.com

    内存管理
    内存管理子系统是操作系统中最重要的组成部份之一。从早期计算机开始,系统的实际内存总是不能满足需求,为解决这一矛盾,人们想了许多办法,其中虚存是最成功的一个。虚存让各进程共享系统内存空间, 这样系统就似乎有了更多的内存。虚存不仅使计算机的内存看起来更多,内存管理子系统还提供以下功能:扩大地址空间操作系统扩大了系统的内存空间。虚存能比系统的实际内存大许多倍。内存保护 系统中每个进程都有它自己的虚拟地址空间。这些虚拟地址空间之间彼此分开,以保証应用程序运行时互不影响。另外,虚存机制可以对内存部份区域提供写保护,以防止代码和数据被其它恶意的应用程序所篡改。内存映射内存映射被用于将映像和数据文件映射到一个进程的虚拟地址空间中,也就是将文件内容连接到虚地址中。公平分配内存内存管理子系统公平地分配内存给正在运行的各进程。虚存共享尽管虚存允许各进程有各自的 ( 虚拟 ) 地址空间,但有时进程间需要共享内存。例如,若干进程同时运行 Bash 命令。并非在每个进程的虚地址空间中,都有一个Bash的拷贝。在内存中仅有一个运行的Bash拷贝供各进程共享。又如,若干进程可以共享动态函数库。共享内存也能作为一种进程间的通信机制(IPC)。两个或两个以上进程可以通过共享内存来交换数据。

    在分析 Linux 实现虚存的方法前,让我们先来看一个没有过多细节的抽象模型。当处理器执行一段程序时,它先从内存中读出一条指令并对它进行解码。解码时可能需要在内存中的某一地址存取数据。然后处理器执行这条指令并移向下一条。可见处理器总是不断地在内存中存取数据或指令。在虚存系统中,所有地址都是虚地址而非物理地址。处理器根据操作系统中的一组表格而把这些虚地址翻译成相应的物理地址。为使这翻译的过程更容易,虚存和物理内存被划分成许多适当大小的块,叫做“页”(page)。为便于系统管理,这些页都是一样大小的。在 Alpha AXP 上的 Linux 系统中,每页有 8 Kbyte,但在 Intel x86 系统中,每页有 4 Kbyte。每一页又被分配了一个各不相同的数字,叫页号 ( PFN ) 。在本模型中,一个虚地址由两部份组成﹔偏移量和虚页号。如果页的大小是 4Kbytes,那么虚地址的0至11位是偏移量,第12位以上是虚页号。每次处理器遇到虚地址时,它先取出偏移量和虚页号。然后,处理器把虚页号翻译成物理页号,再由偏移量得到正确的物理地址,最后存取数据。处理器需要使用页表来完成这整个过程。

    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 存取控制
    页表记录中也包含了存取控制信息。处理器使用页表记录来把虚地址翻译成物理地址的同时,它也很容易地使用其中的存取控制信息来检查进程是否在正确地访问内存。在很多种情况下,你想要为内存的一段区域设置存取限制。一段内存, 例如包含可执行的代码, 应为只读内存﹔操作系统应该不允许进程在它的可执行的代码上写数据。相反的,包含数据的页能被写,但是当指令试图执行那段内存时,应该失败。大多数处理器的执行代码有两种模式:核 态和用户态。你将不想由一个用户执行核代码,或者让核数据结构被不是核态执行的代码所访问。存取控制信息被保存在 PTE中,并且不同的处理器,PTE的格式是不同的﹔当在这页上进行写操作时报页错。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 页表
    Linux页表有3层。每一层负责保存下一层页表所在的页号。每个域记录在某一层页表中的偏移量。把一个虚地址翻译成物理地址时,处理器拿出每个域的内容把它变成页表中的偏移量,进而读出下层页表的所在页号。这样重复 3 次直到找到包含虚地址的物理页号。虚地址的最后一个域,叫做字节偏移量, 被用来在物理页内找到所需数据。每个运行 Linux 的平台必须提供翻译宏(Translation macros) 以便内核可以检索页表,完成某种操作。这样,内核不需要知道各平台上页表记录的具体格式和它们是怎么被安排的。这就是为什么 Linux 的 Alpha 处理器和Intel x 86 处理器使用一样的页表操作代码, 而Alpha有3层页表,Intel x86处理器只有2层页表。