- 在线时间
- 8 小时
- 经验
- 3788
- 性别
- 女
- 最后登录
- 2012-11-13
- 精华
- 1
- 日志
- 1
- 相册
- 0
- UID
- 28
  
- 帖子
- 1491
- 积分
- 3808
- 注册时间
- 2007-4-28
|
Linux Kernel核心中文手册 来自:蓝森林自由软件
Chapter 3
Memory Management (内存管理)
内存管理子系统是操作系统的重要部分。从计算机发展早期开始,就存在对于大于系统中物理能力的内存需要。为了克服这种限制,开发了许多种策略,其中最成功的就是虚拟内存。虚拟内存通过在竞争进程之间共享内存的方式使系统显得拥有比实际更多的内存。
虚拟内存不仅仅让你的计算机内存显得更多,内存管理子系统还提供:
Large Address Spaces (巨大的地址空间)操作系统使系统显得拥有比实际更大量的内存。虚拟内存可以比系统中的物理内存大许多倍。
< Protection (保护)系统中的每一个进程都有自己的虚拟地址空间。这些虚拟的地址空间是相互完全分离的,所以运行一个应用程序的进程不会影响另外的进程。另外,硬件的虚拟内存机制允许对内存区写保护。这可以防止代码和数据被恶意的程序覆盖。
< Memory Mapping (内存映射)内存映射用来将映像和数据映射到进程的地址空间。用内存映射,文件的内容被直接连结到进程的虚拟地址空间。
< Fair Physics Memory Allocation (公平分配物理内存)内存管理子系统允许系统中每一个运行中的进程公平地共享系统的物理内存
Shared Virtual Memory (共享虚拟内存)虽然虚拟内存允许进程拥有分离(虚拟)的地址空间,有时你也需要进程之间共享内存。例如,系统中可能有多个进程运行命令解释程序 < bash 。虽然可以在每一个进程的虚拟地址空间都拥有一份 bash 的拷贝,更好的是在物理内存中只拥有一份拷贝,所有运行 bash 的进程共享代码。动态连接库是多个进程共享执行代码的另一个常见例子。共享内存也可以用于进程间通讯 < (IPC) 机制,两个或多个进程可以通过共同拥有的内存交换信息。 Linux 系统支持系统 V 的共享内存 IPC 机制。
3.1 An Abstract Model of Virtual Memory (虚拟内存的抽象模型)
在考虑 Linux 支持虚拟内存的方法之前,最好先考虑一个抽象的模型,以免被太多的细节搞乱。
在进程执行程序的时候,它从内存中读取指令并进行解码。解码指令也许需要读取或者存储内存特定位置的内容,然后进程执行指令并转移到程序中的下一条指令。进程不管是读取指令还是存取数据都要访问内存。
在一个虚拟内存系统中,所有的地址都是虚拟地址而非物理地址。处理器通过操作系统保存的一组信息将虚拟地址转换为物理地址。
为了让这种转换更简单,将虚拟内存和物理内存分为适当大小的块,叫做页( < page )。页的大小一样。(当然可以不一样,但是这样一来系统管理起来比较困难)。 Linux 在 Alpha AXP 系统上使用 8K 字节的页,而在 Intel x86 系统上使用 4K 字节的页。每一页都赋予一个唯一编号: page frame number(PFN 页编号 ) 。在这种分页模型下,虚拟地址由两部分组成:虚拟页号和页内偏移量。假如页大小是 < 4K ,则虚拟地址的位 < 11 到 < 0 包括页内偏移量,位 < 12 和以上的位是页编号。每一次处理器遇到虚拟地址,它必须提取出偏移和虚拟页编号。处理器必须将虚拟页编号转换到物理的页,并访问物理页的正确偏移处。为此,处理器使用了页表( < page tables )。
图 3.1 显示了两个进程的虚拟地址空间,进程 X 和进程 Y ,每一个进程拥有自己的页表。这些页表将每一个进程的虚拟页映射到内存的物理页上。图中显示进程 < X 的虚拟页号 < 0 映射到物理页号 < 1 ,而进程 < Y 的虚拟页编号 < 1 映射到物理页号 < 4 。理论上页表每一个条目包括以下信息:
有效标志 表示页表本条目是否有效
本页表条目描述的物理页编号
访问控制信息 描述本页如何使用:是否可以写?是否包括执行代码?
页表通过虚拟页标号作为偏移来访问。虚拟页编号 5 是表中的第 6 个元素( 0 是第一个元素)
要将虚拟地址转换到物理地址,处理器首先找出虚拟地址的页编号和页内偏移量。使用 < 2 的幂次的页尺寸,可以用掩码或移位简单地处理。再一次看图 3.1 ,假设页大小是 0x2000 (十进制 8192 ),进程 Y 的虚拟地址空间的地址是 0x2194 ,处理器将会把地址转换为虚拟页编号 1 内的偏移量 0x194 。
处理器使用虚拟页编号作为索引在进程的页表中找到它的页表的条目。如果该条目有效,处理器从该条目取出物理的页编号。如果本条目无效,就是进程访问了它的虚拟内存中不存在的区域。在这种情况下,处理器无法解释地址,必须将控制权传递给操作系统来处理。
处理器具体如何通知操作系统进程在访问无法转换的无效的虚拟地址,这个方式是和处理器相关的。处理器将这种信息( < page fault )进行传递,操作系统得到通知,虚拟地址出错,以及出错的原因。
假设这是一个有效的页表条目,处理器取出物理页号并乘以页大小,得到了物理内存中本页的基础地址。最后,处理器加上它需要的指令或数据的偏移量。
再用上述例子,进程 Y 的虚拟页编号 1 映射到了物理页编号 4 (起始于 0x8000 , 4x 0x2000 ),加上偏移 0x194 ,得到了最终的物理地址 0x8194 。
通过这种方式将虚拟地址映射到物理地址,虚拟内存可以用任意顺序映射到系统的物理内存中。例如,图 < 3.1 中,虚拟内存 < X 的虚拟页编号映射到了物理页编号 < 1 而虚拟页编号 < 7 虽然在虚拟内存中比虚拟页 < 0 要高,却映射到了物理页编号 < 0 。这也演示了虚拟内存的一个有趣的副产品:虚拟内存页不必按指定顺序映射到物理内存中。
3.1.1 Demand Paging
因为物理内存比虚拟内存少得多,操作系统必须避免无效率地使用物理内存。节省物理内存的一种方法是只加载执行程序正在使用的虚拟页。例如:一个数据库程序可能正在数据库上运行一个查询。在这种情况下,并非所有的数据必须放到内存中,而只需要正被检查的数据记录。如果这是个查找型的查询,那么加载程序中增加记录的代码就没什么意义。这种进行访问时才加载虚拟页的技术叫做 < demand paging 。
当一个进程试图访问当前不在内存中的虚拟地址的时候处理器无法找到引用的虚拟页对应的页表条目。例如:图 < 3.1 中进程 < X 的页表中没有虚拟页 2 的条目,所以如果进程 < X 试图从虚拟页 < 2 中的地址读取时,处理器无法将地址转换为物理地址。这时处理器通知操作系统发生 page fault 。
如果出错的虚拟地址无效意味着进程试图访问它不应该访问的虚拟地址。也许是程序出错,例如向内存中任意地址写。这种情况下,操作系统会中断它,从而保护系统中其他的进程。
如果出错的虚拟地址有效但是它所在的页当前不在内存中,操作系统必须从磁盘映像中将相应的页加载到内存中。相对来讲磁盘存取需要较长时间,所以进程必须等待直到该页被取到内存中。如果当前有其他系统可以运行,操作系统将选择其中一个运行。取到的页被写到一个空闲的页面,并将一个有效的虚拟页条目加到进程的页表中。然后这个进程重新运行发生内存错误的地方的机器指令。这一次虚拟内存存取进行时,处理器能够将虚拟地址转换到物理地址,所以进程得以继续运行。
Linux 使用 < demand paging 技术将可执行映像加载到进程的虚拟内存中。当一个命令执行时,包含它的文件被打开,它的内容被映射到进程的虚拟内存中。这个过程是通过修改描述进程内存映射的数据结构来实现,也叫做内存映射( < memory mapping )。但是,实际上只有映像的第一部分真正放在了物理内存中。映像的其余部分仍旧在磁盘上。当映像执行时,它产生 < page fault , < Linux 使用进程的内存映像表来确定映像的那一部分需要加载到内存中执行。
3.1.2 Swapping (交换)
如果进程需要将虚拟页放到物理内存中而此时已经没有空闲的物理页,操作系统必须废弃物理空间中的另一页,为该页让出空间。
如果物理内存中需要废弃的页来自磁盘上的映像或者数据文件,而且没有被写过所以不需要存储,则该页被废弃。如果进程又需要该页,它可以从映像或数据文件中再次加载到内存中。
但是,如果该页已经被改变,操作系统必须保留它的内容以便以后进行访问。这种也叫做 < dirty page ,当它从物理内存中废弃时,被存到一种叫做交换文件的特殊文件中。因为访问交换文件的速度和访问处理器以及物理内存的速度相比很慢,操作系统必须判断是将数据页写到磁盘上还是将它们保留在内存中以便下次访问。
如果决定哪些页需要废弃或者交换的算法效率不高,则会发生颠簸( < thrashing )。这时,页不断地被写到磁盘上,又被读回,操作系统过于繁忙而无法执行实际的工作。例如在图 < 3.1 中,如果物理页号 < 1 经常被访问,那么就不要将它交换到硬盘上。进程正在使用的也叫做工作集 (working set) 。有效的交换方案应该保证所有进程的工作集都在物理内存中。
Linux 使用 < LRU ( Least Recently Used 最近最少使用)的页面技术来公平地选择需要从系统中废弃的页面。这种方案将系统中的每一页都赋予一个年龄,这个年龄在页面存取时改变。页面访问越多,年纪越轻,越少访问,年纪越老越陈旧。陈旧的页面是交换的好候选。
3.1.3 Shared Vitual Memory (共享虚拟内存)
虚拟内存使多个进程可以方便地共享内存。所有的内存访问都是通过页表,每一个进程都有自己的页表。对于两个共享一个物理内存页的进程,这个物理页编号必须出现在两个进程的页表中。
图 3.1 显示了两个共享物理页号 4 的进程。对于进程 X 虚拟页号是 4 ,而对于进程 Y 虚拟页号是 6 。这也表明了共享页的一个有趣的地方:共享的物理页不必存在共享它的进程的虚拟内存空间的同一个地方。
3.1.4 Physical and Vitual Addressing Modes (物理和虚拟寻址模式)
对于操作系统本身而言,运行在虚拟内存中没有什么意义。如果操作系统必须维护自身的页表,这将会是一场噩梦。大多数多用途的处理器同时支持物理地址模式和虚拟地址模式。物理寻址模式不需要页表,处理器在这种模式下不需要进行任何地址转换。 < Linux 核心运行在物理地址模式。
Alpha AXP 处理器没有特殊的物理寻址模式。它将内存空间分为几个区,将其中两个指定为物理映射地址区。核心的地址空间叫做 < KSEG 地址空间,包括从 < 0xfffffc0000000000 向上的所有地址。为了执行连接在 KSEG 的代码(核心代码)或者访问那里的数据,代码必须在核心态执行。 Alpha 上的 Linux 核心连接到从地址 0xfffffc0000310000 执行。
3.1.5 Access Control (访问控制)
页表条目也包括访问控制信息。当处理器使用页表条目将进程的虚拟地址映射到物理地址的时候,它很容易利用访问控制信息控制进程不要用不允许的方式进行访问。
有很多原因你希望限制对于内存区域的访问。一些内存,比如包含执行代码,本质上是只读的代码,操作系统应该禁止进程写它的执行代码。反过来,包括数据的页可以写,但是如果试图执行这段内存应该失败。大多数处理器有两种执行状态:核心态和用户态。你不希望用户直接执行核心态的代码或者存取核心数据结构,除非处理器运行在核心态。
访问控制信息放在 PTE ( page table entry )中,而且和具体处理器相关。图 3.2 显示了 Alpha AXP 的 PTE 。各个位意义如下:
V 有效,这个 < PTE 是否有效
FOE “ < Fault on Execute ” < 试图执行本页代码时,处理器是否要报告 < page fault ,并将控制权传递给操作系统。
FOW “ Fault on Write” 如上,在试图写本页时产生 page fault
FOR “ < fault on read ” 如上,在试图读本页时产生 page fault
ASM 地址空间匹配。用于操作系统清除转换缓冲区中的部分条目
KRE 核心态的代码可以读本页
URE 用户态的代码可以读本页
GII 间隔因子,用于将一整块映射到一个转换缓冲条目而非多个。
KWE 核心态的代码可以写本页
UWE 用户态的代码可以写本页
Page frame number 对于 V 位有效的 PTE ,包括了本 PTE 的物理页编号;对于无效的 PTE ,如果不是 0 ,包括了本页是否在交换文件的信息。
以下两位由 Linux 定义并使用
_PAGE_DIRTY 如果设置,本页需要写到交换文件中。
_PAGE_ACCESSED Linux 使用,标志一页已经访问过
3.2 Caches (高速缓存)
如果你用以上理论模型来实现一个系统,它可以工作,但是不会太高效率。操作系统和处理器的设计师都尽力让系统性能更高。除了使用更快的处理器、内存等,最好的方法是维护有用信息和数据的高速缓存,这会使一些操作更快。 < Linux 使用了一系列和高速缓存相关的内存管理技术:
Buffer Cache : Buffer cache 包含了用于块设备驱动程序的数据缓冲区。这些缓冲区大小固定(例如 512 字节),包括从块设备读出的数据或者要写到块设备的数据。块设备是只能通过读写固定大小的数据块来访问的设备。所有的硬盘都是块设备。块设备用设备标识符和要访问的数据块编号作为索引,用来快速定位数据块。块设备只能通过 < buffer cache 存取。如果数据可以在 < buffer cache 中找到,那就不需要从物理块设备如硬盘上读取,从而使访问加快。
参见 fs/buffer.c
Page Cache 用来加快对磁盘上映像和数据的访问。它用于缓存文件的逻辑内容,一次一页,并通过文件和文件内的偏移来访问。当数据页从磁盘读到内存中时,被缓存到 < page cache 中。
参见 mm/filemap.c
Swap Cache 只有改动过的(或脏 dirty )页才存在交换文件中。只要它们写到交换文件之后没有再次修改,下一次这些页需要交换出来的时候,就不需要再写到交换文件中,因为该页已经在交换文件中了,直接废弃该页就可以了。在一个交换比较厉害的系统,这会节省许多不必要和高代价的磁盘操作。
参见 mm/swap_state.c mm/swapfile.c
Hardware Cache: 硬件高速缓存的常见的实现方法是在处理器里面: PTE 的高速缓存。这种情况下,处理器不需要总是直接读页表,而在需要时把页转换表放在缓存区里。 < CPU 里有转换表缓冲区 < (TLB Translation Look-aside Buffers) ,放置了系统中一个或多个进程的页表条目的缓存的拷贝。
当引用虚拟地址时,处理区试图在 TLB 中寻找。如果找到了,它就直接将虚拟地址转换到物理地址,进而对数据执行正确的操作。如果找不到,它就需要操作系统的帮助。它用信号通知操作系统,发生了 < TLB missing 。一个和系统相关的机制将这个异常转到操作系统相应的代码来处理。操作系统为这个地址映射生成新的 < TLB 条目。当异常清除之后,处理器再次尝试转换虚拟地址,这一次将会成功因为 TLB 中该地址有了一个有效的条目。
高速缓存的副作用(不管是硬件或其他方式的)在于 Linux 必须花大量时间和空间来维护这些高速缓存区,如果这些高速缓存区崩溃,系统也会崩溃。
3.3 Linux Page Tables ( Linux 页表)
Linux 假定了三级页表。访问的每一个页表包括了下一级页表的页编号。图 3.3 显示了一个虚拟地址如何分为一系列字段:每一个字段提供了在一个页表中的偏移量。为了将虚拟地址转换为物理地址,处理器必须取得每一级字段的内容,转换为包括该页表的物理页内的偏移,然后读取下一级页表的页编号。重复三次直到包括虚拟地址的物理地址的页编号找到为止。然后用虚拟地址中的最后一个字段:字节偏移量,在页内查找数据。
Linux 运行的每一个平台都必须提供转换宏,让核心处理特定进程的页表。这样,核心不需要知道页表条目的具体结构或者如何组织。通过这种方式, < Linux 成功地使用了相同的页表处理程序用于 < Alpha 和 Intel x86 处理器,其中 < Alpha 使用三级页表,而 < Intel 使用二级页表。
参见 include/asm/pgtable.h
3.4 Page Allocation and Deallocation ( 页的分配和回收 )
系统中对于物理页有大量的需求。例如,当程序映像加载到内存中的时候,操作系统需要分配页。当程序结束执行并卸载时需要释放这些页。另外为了存放核心相关的数据结构比如页表自身,也需要物理页。这种用于分配和回收页的机制和数据结构对于维护虚拟内存子系统的效率也许是最重要的。
系统中的所有的物理页都使用 mem_map 数据结构来描述。这是一个 mem_map_t 结构的链表,在启动时进行初始化。每一个 mem_map_t (容易混淆的是这个结构也被称为 page 结构)结构描述系统中的一个物理页。重要的字段(至少对于内存管理而言)是:
参见 include/linux/mm.h
count 本页用户数目。如果本页由多个进程共享,计数器大于 1 。
Age 描述本页的年龄。用于决定本页是否可以废弃或交换出去。
Map_nr mem_map_t 描述的物理页编号。
页分配代码使用 free_area 向量来查找空闲的页。整个缓冲管理方案用这种机制来支持。只要用了这种代码,处理器使用的页的大小和物理页的机制就可以无关。 |
|