linux 和windows 内存管理区别

在 80 年代初,IBM 推出的 IBM PC 机采用了 Intel 16 位的 8088 处理器,该处
理器可以访问最多 1MB 的存储器。当应用程序需要的存储量(包括数据和代码)大于
1MB 时,为解决存储需求矛盾而出现了几种解决方案。第一种是由Lotus、Intel 和
Microsoft 共同制定的 EMS
规范。利用这一规范,应用程序可以在计算机中附加的存储器硬件卡和常规内存的
某 64K
固定长度的内存之间交换数据。另外一种解决方案是应用程序代码段的覆盖技术。
利用这一技术,应用程序的某个代码段在某一段时间不需要时,覆盖管理程序就
可以 挥τ贸绦蛑械魅肫渌 拇 攵味 哺钦庖淮 攵巍5笔钡?Borland 和 Microso
ft 的 C/C++
编译器中均提供了这一技术。利用这一技术,开发人员可以方便地编写大型的应用
程序。

随后,Intel 的 80286 处理器问世,该处理器可以访问 16MB 的存储空间,但只能
在保护模式下才能访问除 1MB 常规内存之外的 15MB 内存。为了满足新的存储需求
,Microsoft 又推出了新的存储访问规范,即 XMS。这一技术可让应用程序在实模式
下运行时访问额外的内存。在今天的
DOS 和 Windows 操作系统中,这一规范的驱动程序 HIMEM.SYS还在这些系统中继续
使用着。

当 32 位的 80386 处理器问世时,在该处理器的支持下,为 PC 设计多任务操作系
统已经成为可能。当 80386 处理器运行在保护模式时,每个应用程序均可以拥有自
己的 4GB
地址空间,并且可保证每个应用程序的地址空间不会互相重叠,显然,这对多任务
操作系统来说具有相当重要的意义。这种支持多任务的内存技术就是普遍使用在现代
僮飨低持械男槟饽诖婕际酢

Linux 和 Windows NT 均采用了基于分页技术的虚拟内存管理。尽管它们提供了诸
如内存映射、需求装载、写时复制等与虚拟内存相关的技术,但是在虚拟内存管理上
怯幸恍┫灾 牟煌

本章介绍 Linux 和 Windows NT 在虚拟内存技术实现上的异同。

3.1 基本概念

3.1.1 虚拟内存模型

Linux 和 Windows NT
中的虚拟内存采用“分页”机制实现。分页机制将虚拟地址空间和物理地址空间划
分为大小相同的块,这样的块称为“内存页”或简称为“页”。通过虚拟内存地址空
涞囊秤胛锢淼刂房占渲械囊持 涞挠成洌 忠郴 瓶墒迪中槟饽诖娴刂返轿锢砟诖娴
刂分 涞淖 弧T?80x86
处理器系统中,内存页的大小为 4K。每个进程具有自己的内存页表,页表中保存了
进程的虚拟内存页和物理内存页之间的对应关系,根据页表中的信息,CPU 可以将虚
拟内存区域映射为相应的物理内存区域。

页表中的每个页表项对应于一个虚拟内存页,包含了对应的物理内存页编号、页表
项的有效标志以及相应的物理页访问控制属性。访问控制属性指定了页是只读页、只
匆场⒖啥量尚匆郴故强芍葱写 胍常 庥欣 谑迪帜诖姹;ぁ⒛诖娼换灰约澳诖嬗成
涞燃际酢

进程虚拟地址包含两个信息,一个是虚拟页帧编号,作为访问进程页表的索引;另
外一个是偏移量。当进程访问某虚拟地址时,CPU 根据虚拟页帧编号从进程的页表中
获得对应的物理页帧编号,物理页帧编号乘以 4K
(内存页的大小)就是线性物理地址的基地址,再加上虚拟地址的偏移量就是实际
的物理地址。在实际的 CPU 中,这种页表转化通常是分级的,例如,在 80x86 CPU
中,页表分为两级,见图 3-1。



图 3-1 虚拟地址到物理地址的转换
80x86 的虚拟地址由三部分组成:页目录索引、页表索引和页内偏移。寄存器 CR3
包含有当前任务的页目录表地址。根据页目录表地址和线性地址高10 位(页目录表
项索引)可得到页表地址,页表项索引包含在线性地址的中间 10 位中,对应的页表
项包含有物理地址的高 20
位,加上线性地址中的低 12 位偏移量,就是实际的物理地址。

由于 CPU 只能访问系统 RAM
中的物理内存,而系统中的物理内存量通常远远低于进程的虚拟内存地址空间的大
小,因此,进程的虚拟页不可能全部映射到物理内存中。虚拟内存的另外一个特点就
强梢灾唤 鼻笆褂玫某绦蚩榛蚴 菘楸A粼谖锢砟诖嬷校 溆嗟某绦蚩榛蚴 菘樵虮
A粼诖排躺希 匾 保 僮飨低掣涸
在磁盘和内存之间交换程序块。这种操作称为“交换”。

如果进程访问的虚拟地址和对应页表项的属性相冲突,对该虚拟地址的访问将产生
一个页故障异常,一般而言,页故障的发生有如下几种情况:


无效的虚拟内存地址,在进程页表中不存在对应的虚拟内存页表项。这通常是由于
非法的虚拟内存地址而导致,这时,操作系统将因为无效的地址访问而终止进程。


发生内存访问冲突。在虚拟内存上进行的操作违反了内存页的读写属性或特权属性
。例如,向只读的代码页写入数据时将发生这类的页故障。这种情况下,操作系统将
蛭 扌У牡刂贩梦识 罩菇 獭


虚拟内存地址对应的页表项无效。表明虚拟内存页并没有对应的物理内存页,这可
能是因为进程要访问的物理地址当前不在物理内存中,这时,操作系统负责将所需的
诖嬉匙叭胛锢砟诖妗

对页故障的恰当处理可实现内存映射、需求分页、写时复制以及内存交换。如图 3-
2 所示是进程访问数据时的一般处理过程。



图 3-2 访问数据时的一般处理过程

需要说明的是,操作系统内核本身的存储管理一般不采取虚拟内存的方式,内核或
微内核一般运行在“物理地址模式”,CPU 不必在这种模式下进行地址转换。

3.1.2 内存映射和需求分页

既然进程的内存页可以不和物理内存页对应,那么就可以保存在磁盘文件中。如果
我们在虚拟内存和磁盘文件之间建立一个关系,就可以象访问内存那样访问磁盘文件
饩褪悄诖嬗成涞幕 靖拍睢5苯 谭梦室桓鲇成涞轿募 诚蟮男槟饽诖娴刂肥保
⑸ 彻收希 僮飨低巢痘褚彻收现
后,可以将对应的文件数据装入物理内存,然后建立真正有效的虚拟内存页表项,
这时,对虚拟内存的访问就相当于对文件数据的访问。

内存映射可以用来优化对大型文件的操作。在 Windows NT
中,还利用内存映射文件实现了内存共享。在初始化进程时,也可以利用内存映射
实现“需求分页”,也就是说,进程的代码、数据没有必要在一开始就全部装入到物
砟诖嬷校 挥械苯 痰闹葱行枰 囟ǖ氖 莼虼 胧辈糯映绦蛴诚笪募 凶叭搿U
庵旨际蹩纱蟠蠼档统绦蜃叭氲氖奔洹


Linux 和 Windows NT 均支持内存映射和需求分页。

3.1.3 写时复制

利用虚拟内存,不同进程的虚拟内存页可以映射到同一个物理内存页。如果相关的
进程都只是读取这些数据而不写入的话,这种映射关系可以一直存在下去,同时节省
宋锢砟诖妫 跎倭宋锢砟诖嬉车姆峙淇 T?Linux 中,利用 fork
建立子进程时,系统并不分配实际的物理内存,只是复制父进程的页表,从而提高
了 fork
系统调用的执行效率。但是,如果这些进程要写入同一物理地址怎么办?这时,操
作系统可以捕获这一情况的发生,并分配一个新的物理内存页,然后复制原有的内容
偌绦 慈氩僮鳎 饩褪恰靶词备粗啤奔际酢T诟粗埔潮硎保 僮飨低吃は冉 潮硐
畹氖粜陨柚梦 欢粒 苯 滔蚋眯槟
地址写入时,将发生内存访问的页故障,这时操作系统可捕获这一异常并执行写时
复制操作。

Linux 和 Windows NT 均支持写时复制技术。

3.2 进程地址空间

3.2.1 Linux 的进程地址空间

图 3-3 给出了 Linux 进程的地址空间划分。



图 3-3 Linux 的进程地址空间划分

Linux 进程全部可用的地址空间为 3GB(从 0x00000000 到 0xC0000000)。这 3GB
的地址空间划分如下:

第一个区间:正文段

这一区间保存进程的代码,即正文(text)段。对该段数据的写入,将导致产生页
故障,对该页故障的默认处理程序会发送 SEGV 信号。如果捕获 SEGV 信号,则有可
能向代码段中写入数据。

第二个区间:初始化的数据段

在进程的正文段之后,保存进程初始化后的数据(initialized data)段。这种数
据对应于 C 程序的全局变量和静态变量。

第三个区间:未初始化的数据段

在初始化的数据段之后,是进程的未初始化数据(uninitialized data)段。

第四个区间:malloc 后的内存区

在所有的数据段之后,是进程通过 malloc 函数调用分配的内存。

第五个区间:空闲内存区

该空闲内存区是留待分配的内存区。利用 brk 或 sbrk 可在该区间内分配内存,进
而扩大第四个区间。

第六个区间:共享库

该区间是准备来装载共享库的区间。一般而言,共享库的装载地址在 0.5GB 到 3GB
之间。

第七个区间:栈

该区间是进程的栈区间,栈顶在 0xBFFFFFFF 地址处,随函数调用向下伸展。

第八个区间:内核数据区

从 0xC0000000 开始到 0xFFFFFFFF结束的内存区间映射到了内核的代码和数据,对
这一区间的访问需要高的特权级,进程不可用。

3.2.2 Windows NT 的进程地址空间

图 3-4 显示了 Windows NT 的进程地址空间划分。



图 3-4 Windows NT 的进程地址空间划分

第一个区间:0x00000000 到 0x0000FFFF

进程地址空间底部的 64KB 范围由 Windows NT 保留,可帮助程序员捕获 NULL(空
)指针赋值。任何对该区间地址的访问将导致“一般保护性错误”。

第二个区间:0x00010000 到 0x7FFEFFFF

该区间(大小为 2GB - 64K - 64KB = 2,147,352,576)是进程的私有地址空间。当
Win32 进程装入时,它要求访问的系统动态链接库(DLL),包括 KERNEL32.DLL、U
SER32.DLL、GDI32.DLL 和 ADVAPI32.DLL 等,均装入这个区间。系统还利用这个区
间映射进程的内存映射文件。

第三个区间:0x7FFF0000 到 0x7FFFFFFF

这个区间和第一区间(0x00000000 到 0x0000FFFF)的功能类似,大小也为 64KB,
系统保留该区间以便捕获该范围内的指针赋值。任何对该区间地址的访问将导致“一
般保护性错误”。

第三个区间:0x80000000 到 0xFFFFFFFF

该区间(2GB)是 Windows NT Executive、内核和设备驱动程序装入的地方。访问
该区间的代码必须具有足够的特权级,因此,一般的应用程序是不能访问该区间地址
模 魏味愿们 涞刂返姆梦式 贾隆耙话惚;ば源砦蟆薄

Windows NT 保留 2GB 的地址空间显然有些大了,这主要是因为 MIPS R4000 CPU
需要保留这些范围的空间,为了跨平台移植的方便,Microsoft 对所有平台的 Win32
均保留了这 2GB 地址空间。

3.3 虚拟内存的使用

3.3.1 Linux 中虚拟内存的使用

Linux 内核为每个进程维护虚拟内存地址的信息。参见图 3-5,Linux 进程的虚拟
地址空间由一个个的区域来描述。



图 3-5 Linux 进程的虚拟内存

在装入程序映象文件时,系统根据程序映象文件中的信息为进程的代码段和数据段
分别建立相应的虚拟内存区域(图 3-5 所示的即为某个程序映象装入后的代码段和数
据段)。如果该程序用到了任何一个共享库,则共享库也必须装入进程的虚拟地址空
间。利用 ldd
命令可查看一个程序的共享库使用情况和装入的地址:

[WeiYM@goldfield src]$ ldd testgui
libvgagl.so.1 => /lib/libvgagl.so.1 (0x40005000)
libvga.so.1 => /lib/libvga.so.1 (0x40013000)
libpthread.so.0 => /lib/libpthread.so.0 (0x40037000)
libc.so.6 => /lib/libc.so.6 (0x40044000)
libm.so.6 => /lib/libm.so.6 (0x400ea000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00000000)

前面提到,Linux 利用需求分页技术优化程序映象的装入。在装入程序映象时,Lin
ux 并不将映象装入物理内存,相反,可执行文件只是被链接到进程的虚拟地址空间
中。随着程序的运行,被引用的程序部分会由操作系统自动装入物理内存。

在程序的执行过程中,可利用如下的系统调用修改进程的虚拟内存:


brk 或 sbrk:利用该系统调用可修改进程数据段的大小,可用于内存的动态分配。


mmap:利用该函数可将文件映射到进程的虚拟地址空间。系统将增加新的内存区域
数据结构,但并不实际装入文件数据,而是利用需求分页技术,当需要真正访问数据
辈抛叭氲轿锢砟诖妗:?mmap 相关的系统调用还有 munmap 和 msync,munmap 可取
消文件的内存映射关系,msync
可将任何对内存映射中所做的修改实际写入到对应的文件中。


mremap:利用该系统调用可改变某个虚拟内存区域的大小,包括扩大内存区域或减
小内存区域。在 Linux 中,它的作用并不限制于文件映射内存区。利用该函数可有效
实现 ANSI C 的 realloc 函数。


mlock 或 mlockall:利用这些系统调用可以禁止对某个虚拟内存区域,或整个虚拟
内存(包括代码、数据和堆栈段、共享库、用户空间的内核数据、共享内存和内存映
射文件等)的分页操作。禁止分页的内存页可以驻留在 RAM 中,直到调用 munlock
或 munlockall
为止。将内存页锁定到 RAM 中的方法经常用于两种场合:实时系统和需要高度安全
性的数据处理。对实时系统而言,分页机制可能导致严重的不可预料的时间延迟,因
此禁止分页有利于实时处理
。对高度安全性的数据处理来说,禁止分页可避免将重要的数据写入交换文件而导
致可能的数据窃取。


getrlimit、getrusage 和 setrlimit:利用这些系统调用可查看或设置进程的资源
限制以及资源使用情况,包括数据段大小、堆栈段大小、共享内存大小和锁定内存大
小等。


getpagesize:利用该系统调用可获得系统内存页的单位大小。

在程序中,通常利用 ANSI C 的标准内存处理函数从进程堆或栈中分配内存,例如
:malloc、free,alloca 等。

Linux 的虚拟内存使用有一个特点,即分配的虚拟内存区域由内核管理,并在适当
的时候自动分配给相应的物理内存区。除非要使用特殊的功能,例如内存映射和锁定
δ埽 讨恍枥 ?ANSI C 的函数在进程堆中分配和使用内存。

3.3.2 Windows NT 中虚拟内存的使用

在 Windows NT 中,进程虚拟地址空间也以链表的形式管理,数据结构和 Linux 类
似,但虚拟内存的使用有些复杂。进程在初始化时,系统自动为进程建立一个堆,和
Linux 一样,进程也使用 malloc 或 C++ 的 new
操作符从这个堆中分配和使用虚拟内存,而相应的物理内存区则由堆管理程序在适
当的时候分配,在这点上,Linux 和 Windows NT 具有相似之处,但 Windows NT 提
供了一些比 Linux 复杂、但灵活一些的虚拟内存管理方案:


虚拟内存的两阶段使用方案。首先在地址空间中保留虚拟内存区域,然后再提交物
理内存。第一个步骤实际是在进程的地址空间中保留了一定量的虚拟内存区域,但尚
春腿魏挝锢砟诖婊蚪换晃募 ㄔ?Windows NT 中称为页面文件,并和物理内存 RAM
一起统称为物理存储)建立关系,也就是说,进程页表中没有对应的页表项。这时
如果进程访问这些内存地址,将发生页故障。该方案的第二个步骤真正建立相应的进
桃潮硐睿 槟饽诖婧臀锢泶娲⒘ 翟谝黄稹U庵址桨傅挠诺闶墙 炭梢宰杂煽刂坪
问苯 槟饽诖媲 虻囊徊糠趾臀锢泶
储联系在一起。这两个步骤均由一个 Win32 API 完成,即 VirtualAlloc,当然,
进程也可以只调用该函数一次而完成所有步骤。在 Windows NT 中,物理存储是最重
要的虚拟内存概念。当某个虚拟内存区域提交物理存储之后,不管这些被提交的物理
娲⑹?
RAM,还是页面文件中的部分,还是后面要讲到的内存映射文件的一部分,一旦提交
了物理存储,应用程序就可以访问对应的虚拟内存区域了。操作系统和 CPU 一同完
成访问内存页和系统 RAM 之间必要的交换工作。


私有堆方案。私有堆是指除了系统在初始化时为进程建立的默认堆之外,进程还可
以建立自己的私有堆,并从该私有堆中分配空间。利用虚拟堆时,进程不需要关心虚
饽诖婧臀锢泶娲⒅ 涞墓叵怠 icrosoft 提供私有堆的出发点有三个:


在多线程的进程中,默认情况下所有线程均使用同一个默认堆,由于多任务特点,
线程必须互斥访问默认堆,即保证线程对默认堆的串行化访问,从而导致有稍微的性
芙档汀H绻 叱探 ⒆约旱乃接卸眩 蚩梢员苊馍鲜鲂阅芙档汀


建立私有堆可以实现有效的内存管理方案。因为在堆中的频繁内存分配和释放极容
易造成堆的碎片化,从而容易导致分配失败。利用私有堆则可以针对分配对象实现有
У亩压芾矸桨浮


内存访问保护。为关键的数据结构建立单独的私有堆,可以避免因错误访问而导致
的数据结构混乱。

除了上述和 Linux 在虚拟内存管理上的不同外,Windows NT 的虚拟内存管理同样
提供了内存映射和内存锁定等功能:


内存映射。Windows NT 提供的内存映射功能基本上和 Linux 一样,但建立映射时
的步骤要复杂一些:


利用 CreateFile 创建或打开文件核心对象;相当于 Linux 的 open 系统调用。


利用 CreateFileMapping 创建文件映射核心对象;Linux 中没有这个步骤。


利用 MapViewOfFile 利用文件映射核心对象将文件数据映射到进程的地址空间;2
和 3 这两个步骤相当于 Linux 中的 mmap 系统调用。


利用 UnmapViewOfFile 解除文件数据到进程地址空间的映射关系;相当于 Linux
中的 munmap 系统调用。


释放文件映射核心对象;Linux 中没有这个步骤。


释放文件核心对象;相当于 Linux 中的 close 系统调用。


内存锁定。和 Linux 类似地,有两个 Win32 API 函数可以实现内存锁定(Virtual
Lock 和 VirtualUnlock),即将虚拟内存区域对应的物理存储锁定在系统的 RAM 中
。但是,和 Linux 不同的是,Windows NT 进程无法将所有和进程相关的内存锁定在
RAM 中,这包括系统
DLL、设备驱动程序、栈页面、堆等,因此,锁定进程的一部分内存并不能提高进程
的实时性。但是,这种特性可以使用在设备驱动程序中。

3.3.3 Windows NT 对 16 位应用程序的支持

Windows NT 对 16 位应用程序的支持是通过虚拟机实现的,这包括两种虚拟机:虚
拟 DOS 机(VDM)和虚拟 Win16 机。

在 Windows NT 启动前,它首先获取一个 DOS 环境的“快照”,以备将来使用。当
系统正常启动之后运行某个 DOS 程序,Windows NT 就会为每个 DOS 程序建立一个
单独的 VDM,然后将启动时获取的 DOS 环境快照复制到新的 VDM,这样,所有的 DOS

程序将拥有和启动时一样的设备驱动程序以及 TSR(终止并驻留程序)。

对于 16 位 Windows 应用程序来说,问题更加复杂一些。主要是因为 16 位 Windo
ws 应用程序的内存模型是分段内存模型,而 32 位 Windows NT 的内存模型是线性
内存模型(又称为平面内存模型)。

有时 16 位应用程序和 32 位应用程序之间需要进行交互。例如,16 位应用程序要
通过 32 位的驱动程序访问设备。但是,这两种应用程序处理 16 位数据和 32
位数据的方式非常不同,并且这两种应用程序所采用的不同内存模型也使得很难用
通常的办法实现这两种应用程序之间的交互。

为此,Windows NT 采用了称为“chunk”层的模型处理这两种应用程序之间的不一
致和不兼容。图 3-6 表明了 16 位应用程序和 32 位应用程序是如何通过 chunk 层
进行交互的。



图 3-6 Windows NT 中 16 位应用程序和 32 位应用程序的互操作

可以看到, API 层的三个组件(KERNEL、GDI、USER)在提供自身服务的同时,还
提供转换服务。每个 API 组件负责转换处于自身区域中的数据和地址。

多数转换过程是直接的,Windows NT 简单地将寄存器数据(如32 位应用程序的 EA
X)转换为适当的寄存器(如 16 位应用程序的 DX 和 AX)。thunk
层还为不同的应用程序建立新的堆栈以便满足应用程序的函数调用要求。地址转换
则需要稍微多一些的工作,并且会花费非常多的时间。每次 Windows NT 在进行地址
转换时,都要执行多次分段内存模型中选择符(相当于段地址)的装载,这增加了处
砥鞯亩钔飧旱!

3.4 共享内存

共享内存是一种进程间通信机制。Linux 和 Windows NT 均提供这一机制,虽然它
们在本质上是一样的,均通过将两个进程的部分虚拟地址区域映射到同一物理内存区
蚨 迪郑 饬礁霾僮飨低程峁└ 没У慕涌诓煌 inux 的共享内存技术遵循 UN
IX System V
的共享内存接口,提供了标准的 UNIX 进程间通信能力;而 Windows NT 的共享内
存技术是通过内存映射文件进行的。从本质上讲,Windows NT 提供的共享内存技术几
乎是唯一的进程间通信机制(不包括各种同步机制),其他的共享机制,例如,通过
WM_COPYDATA,WM_SETTEXT
等消息在不同进程间传送数据时,在内部依然采用基于内存映射文件的共享内存技
术而实现。从某种意义上讲,共享内存技术是最为重要的进程间通信机制。这小节重
憬彩稣饬礁霾僮飨低吃诠蚕砟诖婕际跎系牟煌

3.4.1 Linux 的共享内存

Linux 中的共享内存实现非常直观。在利用共享内存时,参与通信的进程首先通过
系统调用 shmget (建立并)获得共享内存对象的标识符,然后通过系统调用 shmat
将自己的虚拟内存区域附加到共享内存对象上。同一个进程可以以不同的读写权限
,将同一个共享内存对象附加到自己不同的虚拟内存区域上。当某个进程不再利用共
砟诖媸保 孟低车饔?shmdt 将自己的虚拟地址区域从该链表中移去。

当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换空间
中。

3.4.2 Windows NT 的共享内存

在前面提到,Windows NT 在创建内存映射文件时,首先要创建文件映射核心对象,
文件映射核心对象是由内核管理的,进程可以通过该对象的名称而访问它,例如:

HANDLE hFileMap = CreateFileMapping (..., “MyFileMapObj”);

当两个或多个进程在调用 CreateFileMapping 函数指定相同的对象名称,则它们获
得的对象句柄所代表的文件映射对象是同一个对象。这时,如果不同的进程均通过相
同的文件映射核心对象调用 MapViewOfFile 建立各自的内存映射关系的话,这些进
程之间的共享内存就实现了。



图 3-7 Windows NT 的共享内存

当共享内存关系建立之后,任何一个进程在该映射内存上的写操作将立即体现在其
他进程的映射内存上。

从前面的内存映射文件的建立过程可以看到,利用磁盘上的有效文件核心对象可以
建立一个共享内存关系,但是,如果在每次建立共享内存关系之前,均需要打开一个
导实奈募 诵亩韵蟮幕埃 蚕砟诖娴氖褂镁突岷懿环奖悖 挥腥魏瘟榛钚钥裳浴N
耍琖indows NT
可以通过系统的页面文件建立内存映射关系,只需在 CreateFileMapping 函数调用
中传递一个“无效”的文件句柄,即 0xFFFFFFFF。

从理论上讲,Windows NT 通过将不同进程的指定虚拟内存区域提交到同一个物理存
储而实现共享内存。利用普通文件时,物理存储就是普通文件本身,利用页面文件时
,物理存储就是页面文件。如图 3-6 所示,是 Windows NT 通过内存映射文件建立
共享内存关系的示意图。

3.5 内存交换管理

3.5.1 Linux 的内存交换

当物理内存出现不足时,Linux 内存管理子系统需要释放部分物理内存页。这一任
务由内核的交换守护进程 kswaped 完成,该内核守护进程实际是一个内核线程,它的
任务就是保证系统中具有足够的空闲页,从而使内存管理子系统能够有效运行。

在系统启动时,这一守护进程由内核的 init 进程启动。当内核的交换定时器到期
时,该进程开始运行。如果 kswaped 发现系统中的空闲页很少,该进程将按照下面的
三种方法减少系统使用的物理页:

1. 减少缓冲区和页高速缓存的大小。页高速缓存中包含内存映射文件的页,可能
包含一些系统不再需要的页,类似地,缓冲区高速缓存中也可能包含从物理设备中读
〉幕蛐慈胛锢砩璞傅氖 荩 庑┗撼迩 部赡懿辉傩枰 虼耍 饬礁龈咚倩捍婵捎
美词头懦隹障幸场

2. 将System V 共享内存页交换出物理内存。系统通过将共享内存页交换到交换文
件而释放物理内存。

3. 将页交换出物理内存或丢弃。kswaped 首先选择可交换的进程,或其中某些页
可从内存中交换出或丢弃的进程。可执行映象的大部分内容可从磁盘映象中获取,因
耍 庑┮晨啥 Q《ㄒ 换坏慕 讨 螅琇inux
将把该进程的一小部分页交换出内存,而大部分不会被交换,另外,被锁定的页也
不会被交换。

根据被释放的页数目,kswaped 会自动调节交换定时器的间隔,以便能够有足够的
时间释放更多的页而保证足够的空闲页。

交换文件中的页是经过修改的页(通过在页表项中设置相应的位而标志该页为“脏
”页),则当进程再次访问该页时,操作系统必须从交换文件中将该页交换到物理内


3.5.2 Linux 的交换空间

Linux 可以利用文件系统中通常的文件作为交换文件,也可以利用某个分区进行交
换操作,因此,通常把交换文件或交换分区称为“交换空间”。在交换分区上的交换
僮鹘峡欤 媒换晃募 煞奖愀谋浣换豢占浯笮  inux 还可以使用多个交换分
区或交换文件进行交换操作。

作为交换空间的交换文件实际就是通常的文件,但文件的扇区必须是连续的,也即
,文件中必须没有“洞”,另外,交换文件必须保存在本地硬盘上。由于内核要利用
换豢占浣 锌焖俚哪诖嬉辰换唬 虼耍 唤 腥魏挝募 惹 募觳椋 衔 惹
橇 摹S捎谡庖辉 颍 换晃募
能包含洞。可用下面的命令建立无洞的交换文件:

$ dd if=/dev/zero of=/extra-swap bs=1024 count=2048
2048+0 records in
2048+0 records out

交换分区和其他分区没有什么不同,可象建立其他分区一样建立交换分区。但该分
区不包含任何文件系统。分区类型对内核来讲并不重要,但最好设置为 Linux Swap
类型(即类型 82)。

通过系统调用 swapon/swapoff 或系统命令 swapon/swapoff 可以将指定的交换空
间投入使用或禁止使用。

3.5.3 Windows NT 的页面文件

和 Linux 不同,Windows NT 用于交换的交换空间只能是文件,Windows NT 自动管
理用于交换的页面文件。Windows NT 不要求该文件处于连续的扇区中,因而可能导
致交换操作上的性能降低。为了提高 Windows NT 的交换性能,可采取以下措施:

1.尽量在非引导分区(Windows NT 系统目录所在的分区)上建立页面文件。

2.尽量在处于不同物理磁盘的多个分区上建立多个页面文件。

3.尽量选择建立一个最小值和最大值相等的页面文件,降低页面文件碎片化的可能
性。

有关页面文件的设置,可双击控制面板的“性能”图标进行设置。如图 3-6。



图 3-8 配置 Windows NT 的虚拟内存选项

3.5.4 交换空间大小的选择原则

通常对交换空间或页面文件的大小选取有种种误解,比较简单的说法是建立一个大
小为 RAM 数量的两倍的页面文件,这种说法实际不正确,交换空间的大小应当根据具
体情况而定,可依据如下的原则(这一原则对 Linux 和 Windows NT 均适用):

1. 估计需要的内存总量。运行想同时运行的所有程序,并利用系统工具估计所需
的内存总量,只需大概估计(在 Linux 下可利用 free、top 或 ps;在 Windows NT
下可利用性能监视器)。

2. 增加一些安全性余量,大概 4MB 到 16MB。

3. 减去已有的物理内存数量,然后将所得数据圆整为 MB,这就是应当的交换空间
大小。

4. 如果得到的交换空间大小远远大于物理内存量,则说明需要增加物理内存数量
,否则系统性能会因为过分的页交换而下降。

当计算的结果说明不需要任何交换空间时,也有必要使用交换空间。Windows NT 永
远需要至少 2MB 的交换空间;Linux
从性能的角度出发,会在磁盘空闲时将某些页交换到交换空间中,以便减少必要时
的交换时间。另外,如果在不同的磁盘上建立多个交换空间,有可能提高页交换的速
龋 馐且蛭 承┯才糖 骺稍诓煌 拇排躺贤 苯 卸列床僮鳌

3.6 高速缓存

不管在硬件设计中,还是在软件设计中,高速缓存可以为系统性能带来极大的提高
,在操作系统设计中也不例外。

高速缓存的基本概念就是在系统 RAM 中保存最频繁使用的信息,从而当进程在访问
这些信息时,不必每次均进行实际的、费时的 I/O 处理,而只需从 RAM 中读取这些
信息,从而节省操作时间,提高系统性能。

Linux 和 Windows NT 均为提供系统性能而使用了大量的高速缓存,涉及到内存管
理、文件系统、输入输出等方面。这里讲述与内存管理有关的高速缓存,其他的高速
捍婊 平 谒婧蟮恼陆谥薪彩觥

3.6.1 Linux 和内存管理相关的高速缓存

Linux 和内存管理相关的高速缓存主要有页高速缓存和交换高速缓存,这两种高速
缓存的功能可描述如下:


页高速缓存:这一高速缓存用来加速对磁盘上的映象和数据的访问,它用来缓存某
个文件的逻辑内容,并通过文件的 VFS
索引节点(有关索引节点的内容可参阅第四章)和偏移量访问。经内存映射的文件
每次只读取一页内容,读取后的页保存在页缓存中,利用页缓存,可提高文件的访问
俣取5币炒哟排躺隙恋轿锢砟诖媸保 突捍嬖谝掣咚倩捍嬷校 毕低骋 幽诖嬗成湮
募 卸寥∧骋呈保 紫仍谝郴捍嬷胁
找,如果发现该页保存在缓存中,则可以免除实际的文件读取,而只需从页缓存中
读取;如果该页不在缓存中,则必须从实际的文件系统映象中读取页,Linux 内核首
先分配物理页然后从磁盘读取页内容。如果可能,Linux
还会预先读取文件中下一页内容,这样,如果进程要连续访问页,则下一页的内容
不必再次从文件中读取了,而只需从页缓存中读取。随着映象的读取和执行,页缓存
械哪谌菘赡芑嵩龆啵 馐保琇inux 可移走不再需要的页。当系统中可用的物理内存
量变小时,Linux
也会通过缩小页缓存的大小而释放更多的物理内存页。


交换高速缓存:只有修改后(脏)的页才保存在交换文件中。修改后的页写入交换
文件后,如果该页再次被交换但未被修改时,就没有必要写入交换文件,相反,只需
靡场=换桓咚倩捍媸导拾 艘桓鲆潮硐盍幢恚 低车拿扛鑫锢硪扯杂σ桓鲆潮
硐睢6越换怀龅囊常 靡潮硐畎
存该页的交换文件信息,以及该页在交换文件中的位置信息。如果某个交换页表项
非零,则表明保存在交换文件中的对应物理页没有被修改。如果这一页在后续的操作
斜恍薷模 虼τ诮换换捍嬷械囊潮硐畋磺辶恪5?Linux
需要从物理内存中交换出某个页时,它首先分析交换缓存中的信息,如果缓存中包
含该物理页的一个非零页表项,则说明该页交换出内存后还没有被修改过,这时,系
持恍瓒 靡场


硬件高速缓存:常见的硬件缓存是对页表项的缓存,这一工作实际由处理器完成,
其操作和具体的处理器硬件有关,有关该高速缓存的内容,可参阅处理器的有关文献


3.6.2 Windows NT 的高速缓存

Windows NT 的高速缓存管理器为整个 I/O 子系统处理缓存,在系统内存中为文件
系统和网络组件提供高速缓存服务。Windows NT 高速缓存的一大特点是能够系统对物
理内存的需求而动态减少或增加高速缓存的大小。我们将在第五章中详细讨论这一高
速缓存。

3.7 小结

Linux 和 Windows NT 均使用了虚拟内存技术。本章重点讲述了和虚拟内存相关的
一些概念,诸如内存映射、需求分页和写时复制等。在这些基本概念之后,我们对比
?Linux 和 Windows NT 在虚拟内存管理的一些细节上的不同:


Linux 和 Windows NT 在面对相同的进程地址空间大小时,对内存布局的使用方式
不同。Windows NT 实际只为进程准备了 2GB 弱的可用虚拟地址空间,而 Linux 中的
进程地址空间使用则更为灵活些。


在虚拟内存的使用上,Linux 内核为用户管理了非常多的细节问题,用户可以以为
自己真正拥有 4GB 地址空间一样利用这些内存,而不用关心虚拟内存是否尚未提交物
理存储等等的问题。Windows NT
则为程序员提供了多种虚拟内存使用方案,虽然这些方案的使用有些复杂,但却提
供了一定程度上的灵活性。


Windows NT 也支持 DOS 和 Win16 程序的执行,但为了提供这种兼容性,Windows
NT 的内存管理付出了极高的性能代价。


Linux 和 Windows NT 虽然均提供了内存共享技术,但它们的实现有些细微的差别
。Linux 提供给用户的接口非常简单,只需将自己的虚拟内存空间区域附加到共享内
存对象之上。Windows NT 通过内存映射文件提供共享内存机制,从使用上讲,略显复
杂。


Linux 的内存交换管理灵活性很强,用户可以在普通的文件系统上建立“无洞”的
文件作为交换空间,还可以使用多个交换文件,从而可以动态增加交换文件。Linux
也提供了利用交换分区作为交换空间的方法,这一方法是优选的交换空间方案。Windo
ws NT
的页面文件很难逃脱碎片化的危险,从而可能降低交换操作的性能,为了保证 Wind
ows NT 采用无碎片的页面文件,必须采取一定的措施。


高速缓存是计算机系统提高性能的常用方法,不管在硬件设计上,还是在软件设计
上,高速缓存对系统性能的提高有着至关重要的作用。Linux 和 Windows NT 在高速
缓存的使用上有一些共同之处,均尽量采用系统的全部空闲 RAM 作为高速缓存区域,
但 Linux
在高速缓存管理上有一些独到之处,这是 Linux 性能之所以高的根本原因所在。