《汇编语言:基于X86处理器》第11章 MS-Windows编程(3)
本章展示的是如何用32 位Microsoft Windows API进行控制台窗口编程。应用编程接口(API:ApplicationProgramming Interface)是类型、常数和函数的集合体,它提供了一种用计算机代码操作对象的方式。本章将讨论文本I/O、颜色选择、时间与日期、数据文件I/O,以及内存管理的API 函数。同时,还包括了本书 64 位链接库Irvine64代码的一些例子。
11.3 动态内存分配
动态内存分配(dynamic memoryallocation),又被称为堆分配(heap allocation),是编程语言使用的一种技术,用于在创建对象、数组和其他结构时预留内存。比如在 Java 语言中,下面的语句就会为 String对象保留内存:
String str = new String("abcde");
同样的,在 C++中,对变量使用大小属性就可以为一个整数数组分配空间:
int size;
cin >> size; //用户输入大小
int array[] = new int[size];
C、C++和Java都有内置运行时堆管理器来处理程序请求的存储分配和释放。程序启动时,堆管理器常常从操作系统中分配一大块内存,并为存储块指针创建空闲列表(free list)。当接收到一个分配请求时,堆管理器就把适当大小的内存块标识为已预留,并返回指向该块的指针。之后,当接收到对同一个块的删除请求时,堆就释放该内存块,并将其返回到空闲列表。每次接收到新的分配请求,堆管理器就会扫描空闲列表,寻找第一个可用的、且容量足够大的内存块来响应请求。
汇编语言程序有两种方法进行动态分配。方法一,通过系统调用从操作系统获得内存块。方法二,实现自己的堆管理器来服务更小的对象提出的请求。本节展示的是如何实现第一种方法。示例程序为 32 位保护模式的应用程序。
利用表 11-11 中的几个 Win32 API 函数就可以从 Windows 中请求多个不同大小的内存块。表中所有的函数都会覆盖通用寄存器,因此程序可能想要创建封装过程来实现重要寄存器的人栈和出栈操作。若需进一步了解存储管理,请在 Microsoft 在线文档中查阅MemoryManagement Reference
表11-11 堆相关函数 | |
函数 | 描述 |
GetProcessHeap | 用EAX返回程序现存堆区域的32位整数句柄。如果函数成功,则EAX中的返回值为堆句柄。如果函数失败,则EAX中的返回值为NULL |
HeapAlloc | 从堆中分配内存块。如果成功,EAX中的返回值就为内存块的地址。如果失败,则EAX中的返回值为 NULL |
HeapCreate | 创建新堆,并使其对调用程序可用。如果函数成功,则EAX中的返回值为新创建堆的句柄。如果失败,则 EAX 的返回值为NULL |
HeapDestroy | 销毁指定堆对象,并使其句柄无效。如果函数成功,则EAX中的返回值为非零 |
HeapFree | 释放之前从堆中分配的内存块,该堆由其地址和堆句柄进行标识。如果内存块释放成功,则返回值为非零 |
HeapReAlloc | 对堆中内存块进行再分配和调整大小。如果函数成功,则返回值为指向再分配内存块的指针。如果函数失败,且没有指定HEAP_GENERATE_EXCEPTIONS,则返回值为NULL |
HeapSize | 返回之前通过调用HeapAlloc或 HeapReAlloc 分配的内存块的大小。如果函数成功,则 EAX包含被分配内存块的字节数。如果函数失败,则返回值为SIZET1(SIZET等于指针能指向的最大字节数) |
GetProcessHeap如果使用的是当前程序的默认堆,那么GetProcessHeap 就足够了这个函数没有参数,EAX中的返回值就是堆句柄:
GetProcessHeap PROTO
示例调用:
.data
hHeap HANDLE ?
.code
INVOKE GetProcessHeap
.IF eax == NULL ;不能获取句柄jmp quit
.ELSEmov hHeap, eax ;句柄 OK
.ENDIF
HeapCreate HeapCreate能为当前程序创建一个新的私有堆:
HeapCreate PROTO,flOptions:DWORD, ;堆分配选项dwInitialSize:DWORD, ;按字节初始化堆大小dwMaximumSize:DWORD ;最大堆字节数
flOptions设置为NULL。dwInitialSize设置为初始堆字节数,其值的上限为下一页的边界。如果 HeapAlloc 的调用超过了初始堆大小,那么堆最大可以扩展到dwMaximumSize参数中指定的大小(上限为下一页的边界)。调用后,EAX 中的返回值为空就表示堆未创建成功。HeapCreate 的调用示例如下:
HEAP_START = 2000000 ;2MB
HEAP_MAX = 400000000 ;400M
.data
hHeap HANDLE ? ;堆句柄
.code
INVOKE HeapCreate, 0, HEAP_START, HEAP_MAX
.IF eax == NULL ;堆未创建call WriteWindowsMsg ;显示错误消息jmp quit
.ELSEmov hHeap, eax ;句柄OK
.ENDIF
HeapDestroyHeapDeatroy销毁一个已存在的私有堆(由HeapCreate创建)。需向其传递堆句柄:
HeapDestroy PROTO,hHeap:DWORD ;堆句柄
如果堆销毁失败,则 EAX 等于NULL。下面为示例调用,其中使用了11.1.4 节描述的WriteWindowsMsg 过程:
.data
hHeap HANDLE ? ;堆句柄
.code
INVOKE HeapDestroy, hHeap
.IF eax == NULLcall WriteWindowsMsg ;显示错误消息
.ENDIF
HeapAlloc HeapAlloc从已存在堆中分配一个内存块:
HeapAlloc PROTO,hHeap:HANDLE, ;现有堆内存块的句柄dwFlags:DWORD, ;堆分配控制标志dwBytes:DWORD ;分配的字节数
需传递下述参数:·
●hHeap,32位堆句柄,该堆由GetProcessHeap或HeapCreate初始化。
●dwFlags,一个双字,包含了一个或多个标志值。可以选择将其设置为HEAP_ZERO MEMORY,即设置内存块为全零。
●dwBytes,一个双字,表示堆分配的字节数。
如果 HeapAlloc 成功,则 EAX 包含指向新存储区的指针;如果失败,则 EAX 中的返回值为NULL。下面的代码用 hHeap 标识一个堆,从该堆中分配了一个1000 字节的数组,并将数组初始化为全零:
.data
hHeap HANDLE ? ;椎句柄
pArray DWORD ? ;数组指针
.code
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, 1000
.IF eax == NULLmWrite "HeapAlloc failed"jmp quit
.ELSEmov pArray,eax
.ENDIF
HeapFree 函数 HeapFree 释放之前从堆中分配的一个内存块,该堆由其地址和堆句柄标识:
HeapFree PROTO,hHeap:HANDLE,dwFlags:DWORD,lpMem:DWORD
第一个参数是包含该内存块的堆的句柄。第二个参数通常为零,第三个参数是指向将被释放内存块的指针。如果内存块释放成功,则返回值非零。如果该块不能被释放,则函数返回零。示例调用如下:
INVOKE HeapFree, hHeap;0, pArray
Error Handling 若在调用HeapCreate、HeapDestroy或GetProcessHeap时遇到错误,可以通过调用API 函数GetLastError 来获得详细信息。还可以调用Irvine32链接库的函数WriteWindowsMsg。HeapCreate调用示例如下:
.IF eax == NULL ;失败?call WriteWindowsMsg ;显示错误信息
.ELSEmov hHeap, eax ;成功
.ENDIF
反之,函数 HeapAlloc 在失败时不会设置系统错误码,因此也就无法调用 GetLastError或WriteWindowsMsg。
11.3.1 HeapTest程序
下面的程序示例(Heaptest1.asm)使用动态内存分配创建并填充了一个1000 字节的数组:
;11.3.1_HeapTest1.asm 11.3.1 HeapTest程序
;下面的程序示例(Heaptest1.asm)使用动态内存分配创建并填充了一个1000 字节的数组:INCLUDE Irvine32.inc;使用动态内存分配,本程序分配并填充一个字节数组
.data
ARRAY_SIZE = 1000
FILL_VAL EQU 0FFhhHeap HANDLE ? ;程序堆句柄
pArray DWORD ? ;内存块指针
newHeap DWORD ? ;新堆句柄
str1 BYTE "Heap size is: ", 0.code
main PROCINVOKE GetProcessHeap ;获取程序堆句柄.IF eax == NULL ;如果失败,显示消息call WriteWindowsMsgjmp quit.ELSEmov hHeap, eax ;成功.ENDIFcall allocate_arrayjnc arrayOk ;失败(CF=1)?call WriteWindowsMsgcall Crlfjmp quitarrayOk: ;成功填充数组call fill_arraycall display_arraycall Crlf;释放数组INVOKE HeapFree, hHeap, 0, pArrayquit:INVOKE ExitProcess, 0
main ENDP
;---------------------------------------------------------
;动态分配数组空间。
;接收:EAX=程序堆句柄
;返回:如果内存分配成功,则CF=0。
;----------------------------------------------------------
allocate_array PROC USES eaxINVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, ARRAY_SIZE.IF eax == NULLstc ;返回 CF=1.ELSEmov pArray, eax ;保存指针clc ;返回CF=0.ENDIFret
allocate_array ENDP
;---------------------------------------------------------
;用一个字符填充整个数组。
;接收:无
;返回:无
;----------------------------------------------------------
fill_array PROC USES ecx edx esimov ecx, ARRAY_SIZE ;循环计数器mov esi, pArray ;指向数组
L1: mov BYTE PTR [esi], FILL_VAL ;填充每个字节inc esi ;下一个位置loop L1ret
fill_array ENDP
;---------------------------------------------------------
;显示数组。
;接收:无
;返回:无
;----------------------------------------------------------
display_array PROC USES eax ebx ecx esimov ecx, ARRAY_SIZE ;循环计数器mov esi, pArray ;指向数组
L1: mov al, [esi] ;取出一个字节mov ebx, TYPE BYTEcall WriteHexB ;显示该字节inc esi ;下一个位置loop L1ret
display_array ENDP
END main
运行调试:
下面的示例(Heaptest2.asm)采用动态内存分配重复分配大块内存,直到超过堆大小。
;11.3.1_HeapTest2.asm 11.3.1 HeapTest程序
;下面的示例(Heaptest2.asm)采用动态内存分配重复分配大块内存,直到超过堆大小。INCLUDE Irvine32.inc.data
HEAP_START = 2000000 ;2MB
HEAP_MAX = 400000000 ;400MB
BLOCK_SIZE = 500000 ;0.5MBhHeap HANDLE ? ;堆句柄
pData DWORD ? ;块指针
str1 BYTE 0dh, 0ah, "Memory allocation failed", 0dh, 0ah, 0.code
main PROCINVOKE HeapCreate, 0, HEAP_START, HEAP_MAX.IF eax == NULL ;失败?call WriteWindowsMsgcall Crlfjmp quit.ELSEmov hHeap, eax ;成功.ENDIFmov ecx, 2000 ;循环计数器
L1: call allocate_block ;分配一个块.IF Carry? ;失败?mov edx, OFFSET str1 ;显示消息call WriteStringjmp quit.ELSEmov al, '.' ;否:打印一个点来显示进度call WriteChar.ENDIF;call free_block ;允许/禁止本行loop L1
quit:INVOKE HeapDestroy, hHeap ;销毁堆.IF eax == NULL ;失败?call WriteWindowsMsg ;是:错误消息call Crlf.ENDIFINVOKE ExitProcess, 0
main ENDP
allocate_block PROC USES ecx;分配一个块,并填充为全零.INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, BLOCK_SIZE.IF eax == NULLstc ;返回CF = 1.ELSEmov pData, eax ;保存指针clc ;返回CF = 0.ENDIFret
allocate_block ENDP
free_block PROC USES ecxINVOKE HeapFree, hHeap, 0, pDataret
free_block ENDPEND main
运行调试:
11.3.2 本节回顾
1.在C、C++和Java上下文中,堆分配的另一个术语是什么?
答:动态内存分配。
2.请说明 GetProcessHeap函数。
答:用EAX为程序现有堆返回一个32位整数句柄。
3.请说明 HeapAlloc 函数。
答:从堆中分配一个内存块。
4.给出 HeapCreate 函数的一个示例调用。
HEAP_START = 2000000 ;2MB
HEAP_MAX = 400000000 ;400M
.data
hHeap HANDLE ?
.code
INVOKE HeapCreate, 0, HEAP_START, HEAP_MAX
5.调用HeapDestroy时,如何标识将被销毁的内存块?
答:(与堆句柄一起)传递内存块指针。
11.4 x86存储管理
本节将对 Windows 32 位存储管理进行简要说明,展示它是如何使用 x86 处理器直接内置功能的。重点关注的是存储管理的两个主要方面:
●将逻辑地址转换为线性地址
●将线性地址转换为物理地址(分页)
下面先简单回顾一下第2章介绍过的一些x86存储管理术语:
●多任务处理(multitasking)允许多个程序(或任务)同时运行。处理器在所有运行程序中划分其时间。
●段(segments)是可变大小的内存区,用于让程序存放代码或数据。
●分段(segmentation)提供了分隔内存段的方法。它允许多个程序同时运行又不会相互干扰。
●段描述符(segment descriptor)是一个64 位的值,用于标识和描述一个内存段。它包含的信息有段基址、访问权限、段限长、类型和用法。
现在再增加两个新术语:
●段选择符(segmentselector)是保存在段寄存器(CS、DS、SSESFS或GS)中的一个16 位数值。
●逻辑地址(logicaladdress)就是段选择符加上一个32位的偏移量。
本书一直都忽略了段寄存器,因为用户程序从来不会直接修改这些寄存器,所以只关注了 32 位数据偏移量。但是,从系统程序员的角度来看,段寄存器是很重要的,因为它们包含了对内存段的直接引用。
11.4.1 线性地址
1.逻辑地址转换为线性地址
多任务操作系统允许几个程序(任务)同时在内存中运行。每个程序都有自己唯一的数据区。假设现有3个程序,每个程序都有一个变量的偏移地址为200h,那么,怎样区分这3个变量而不进行共享? x86 解决这个问题的方法是,用一步或两步处理过程将每个变量的偏移量转换为唯一的内存地址。
第一步,将段值加上变量偏移量形成线性地址(linear address)。这个线性地址可能就是该变量的物理地址。但是像MS-Windows和Linux这样的操作系统采用了分页(paging)功能,它使得程序能使用比可用物理空间更大的线性空间。这种情况下,就必需采用第二步页转换(page translation),将线性地址转换为物理地址。页转换将在 11.4.2 节介绍。
首先了解一下处理器如何用段和选择符来确定变量的线性地址。每个段选择符都指向一个段描述符(位于描述符表中),其中包含了该内存段的基地址。如图11-6所示,逻辑地址中的32位偏移量加上段基址就形成了32位的线性地址,
线性地址线性地址是一个32位整数,其范围为OFFFFFFFFh,它表示一个内存位置。如果禁止分页功能,那么线性地址也就是目标数据的物理地址。
2.分页
分页是x86处理器的一-个重要功能,它使得计算机能运行在其他情况下无法装人内存的一组程序。处理器初始只将部分程序加载到内存,而程序的其他部分仍然留在硬盘上。程序使用的内存被分割成若干小区域,称为页(page),通常一页大小为4KB。当每个程序运行时处理器会选择内存中不活跃的页面替换出去,而将立即会被请求的页加载到内存。
操作系统通过维护一个页目录(pagedirectory)和一组页表(page table)来持续跟踪当前内存中所有程序使用的页面。当程序试图访问线性地址空间内的一个地址时,处理器会自动将线性地址转换为物理地址。这个过程被称为页转换(page translation)。如果被请求页当前不在内存中,则处理器中断程序并产生一个页故障(page fault)。操作系统将被请求页从硬盘复制到内存,然后程序继续执行。从应用程序的角度看,页故障和页转换都是自动发生的。
使用Microsoft Windows工具任务管理器(taskmanager)就可以查看物理内存和虚拟内存的区别。图 11-7 所示计算机的物理内存为256MB。任务管理器的 Commit Charge 框内为当前可用的虚拟内存总量。虚拟内存的限制为633MB,大大高于计算机的物理内存。
图11-7 Windows任务管理器示例
3.描述符表
段描述符可以在两种表内找到:全局描述符表(global description table)和局部描述符表(local description table)。
全局描述符表(GDT) 开机过程中,当操作系统将处理器切换到保护模式时,会创建唯一一张 GDT,其基址保存在GDTR(全局描述符表寄存器)中。表中的表项(称为段描述符)指向段。操作系统可以选择将所有程序使用的段保存在GDT中。
局部描述符表(LDT) 在多任务操作系统中,每个任务或程序通常都分配有自己的段描述符表,称为LDT。LDTR寄存器保存的是程序LDT的地址。每个段描述符都包含了段在线性地址空间内的基地址。一般,段与段之间是相互区分的。如图11-8所示,图中有三个不同的逻辑地址,这些地址选择了LDT中三个不同的表项。这里,假设禁止分页,因此线性地址空间也是物理地址空间。
4.段描述符详细信息
除了段基址,段描述符还包含了位映射字段来说明段限长和段类型。只读类型段的一个例子就是代码段。如果程序试图修改只读段,则会产生处理器故障。
段描述符可以包含保护等级,以便保护操作系统数据不被应用程序访问。下面是对每个描述符字段的说明:
基址: 32位整数,定义段在4GB线性地址空间中的起始地址。
特权级: 每个段都可以分配一个特权级,特权级范围从0到 3,其中0级为最高级,一般用于操作系统核心代码。如果特权级数值高的程序试图访问特权级数值低的段,则发生处理器故障。
段类型: 说明段的类型并指定段的访问类型以及段生长的方向(向上或向下)。数据(包括堆栈)段可以是可读类型或读/写类型,其生长方向可以是向上的也可以是向下的。代码段可以是只执行类型或执行/只读类型。
段存在标志: 这一位说明该段当前是否在物理内存中。
粒度标志: 确定对段限长字段的解释。如果该位清零,则段限长以字节为单位。如果该位置1,则段限长的解释单位为4096字节。
段限长: 这个20位的整数指定段大小。按照粒度标志,这个字段有两种解释:
●该段有多少字节,范围为1~IMB。
●该段包含多少个4096字节,允许段大小的范围为4KB~4GB。
11.4.2 页转换
若允许分页,则处理器必须将 32位线性地址转换为 32 位物理地址²。这个过程会用到3种结构:
●页目录:一个数组,最多可包含 1024 个 32 位页目录项。
●页表:一个数组,最多可包含 1024 个32 位页表项。
●页:4KB或4MB的地址空间。
为了简化下面的叙述,假设页面大小为 4KB:
线性地址分为三个字段:页目录表项指针、页表项指针和页内偏移量。控制寄存器CR3)保存了页目录的起始地址。如图11-9所示,处理器在进行线性地址到物理地址的转换时,采用如下步骤:
1)线性地址引用线性地址空间中的一个位置。
2)线性地址中10位的目录字段是页目录项的索引。页目录项包含了页表的基址。
3)线性地址中 10 位的页表字段是页表的索引,该页表由页目录项指定。索引到的页表项包含了物理内存中页面的基址。
4)线性地址中 12 位的偏移量字段与页面基址相加,生成的恰好是操作数的物理地址。
操作系统可以选择让所有的运行程序和任务使用一个页目录,或者选择让每个任务使用一个页目录,还可以选择为两者的组合。
Windows虚拟机管理器
现在对IA-32如何管理内存已经有了总体了解,那么看看Windows 如何处理内存管理可能也会令人感兴趣。下面这段文字转自 Microsoft 在线文档:
虚拟机管理器(VMM)是Windows内核中的32位保护模式操作系统。它创建运行、监视和终止虚拟机。它管理内存、进程、中断和异常。它与虚拟设备(virtualdevice)一起工作,使得它们能拦截中断和故障,以此来控制对硬件和已安装软件的访问。VMM和虚拟设备运行在特权级为0的单一32位平坦模式地址空间中。系统创建两个全局描述符表项(段描述符),一个是代码段的,一个是数据段的。段固定在线性地址0。VMM 提供多线程和抢先多任务处理。通过共享运行应用程序的虚拟机之间的CPU时间,它可以同时运行多个应用程序。
在上面的文字中,可以将虚拟机解释为Intel中的过程或任务。它包含了程序代码、支撑软件、内存和寄存器。每个虚拟机都被分配了自己的地址空间、I/0端口空间、中断向量表和局部描述符表。运行于虚拟8086模式的应用程序特权级为3。Windows中保护模式程序的特权级为0和3。
11.4.3 本节回顾
1.术语解释:
a.多任务 b.分段
答:(a)多任务允许同时运行多个程序(或任务)。处理器将其时间分割给所有的运行程序。
(b)分段是指隔离内存段与内存段的方法。它使得多个程序能同时运行且不会相互干扰。
2.术语解释:
a.段选择符 b.逻辑地址
答:(a)段选择符是保存在段寄存器(CS、DS、SS、ES、FS或GS)中的16位值。
(b)逻辑地址选择符与32位偏移量的组合。
3.(真/假):段选择符指向段描述符表的一个表项。
答:真
4.(真/假):段描述符包含了段的基地址。
答:真
5.(真/假):段选择符是32位的。
答:假
6.(真/假):段描述符不包含段大小信息。
答:假
11.5 本章
小结表面上看,32 位控制台模式程序的外观和行为就像运行在文本模式下的16 位MS-DOS程序。这两种类型的程序都从标准输人读,向标准输出写,支持命令行重定向,还可以显示彩色文本。但是,深人了解会发现,Win32 控制台与 MS-DOS 程序是有很大不同的。Win32运行于 32 位保护模式,而 MS-DOS 运行于实地址模式。Win32 程序可以调用图形Windows应用程序使用的函数库内的函数。而 MS-DOS 程序只局限于BIOS 的一个小子集,以及从出现IBM-PC 后就存在的MS-DOS中断。
Windows API函数使用的字符集类型:8位的ASCII/ANSI 字符集和16 位的Unicode字符集。
API 函数使用的标准MS-Windows 数据类型必须转换为MASM 数据类型(参见表11-1)。
控制台句柄为32位整数,用于控制台窗口的输人/输出。函数GetStdHandle获取控制台句柄。进行高级控制台输入,调用函数ReadConsole;进行高级控制台输出,调用WriteConsole。创建和打开文件时,调用 CreateFile。读文件时,调用ReadFile;写文件时,调用 WriteFile。CloseHandle 关闭一个文件。移动文件指针,调用 SetFilePointer。
要操作控制台屏幕缓冲区,调用 SetConsoleScreenBufferSize。要改变文本颜色,调用SetConsoleTextAttribute。本章的程序WriteColors演示了函数 WriteConsoleOutputAttribute和WriteConsoleOutputCharacter。
要获取系统时间,调用 GetLocalTime;要设置时间,调用SetLocalTime。这两个函数都要使用SYSTEMTIME 结构。本章的GetDateTime函数示例用64位整数返回日期和时间,指明从1601 年1 月1 日开始经过了多少个100 纳秒。函数 TimerStart 和 TimerStop 可用来创建一个简单的秒表计时器。
创建图形MS-Windows应用程序时,用该程序的主窗口类信息填充WNDCLASS结构。创建 WinMain 过程获取当前过程的句柄、加载图标和光标、注册程序的主窗口、创建主窗口、显示和更新主窗口,并开始接收和发送消息的循环。
WinProc 过程负责处理输入的 Windows 消息,一般由用户行为激活,比如点击鼠标或者按键。本章的示例程序处理了WM LBUTTONDOWNWMCREATE和WM CLOSE消息。当检测到相应事件时,就会显示弹出消息。
动态内存分配,或堆分配是保留和释放用户程序所用内存的工具。汇编语言程序有两种方法来实现动态内存分配。第一种,进行系统调用,从操作系统获得内存块。第二种,实现自己的堆管理器来响应小型对象的请求。下面是动态内存分配最重要的 Win32 API调用:
●GetProcessHeap返回程序已存在内存堆区域的32位整数句柄。
●HeapAlloc从堆中分配一个内存块。
●HeapCreate 新建一个堆。
●HeapDestroy销毁一个堆。
●HeapFree 释放之前从堆分配出去的内存块。
●HeapReAlloc 从堆中重新分配内存块,并重新定义块大小。●HeapSize返回之前分配的内存块的大小。
本章的内存管理小节主要涉及两个问题:将逻辑地址转换为线性地址,以及将线性地址转换为物理地址。
逻辑地址中的选择符指向段描述符表的表项,这个表项又指向线性空间内的一个段。段描述符包含了段信息,如段大小和访问类型。描述符表有两种:唯一的全局描述符表(GDT),以及一个或多个局部描述符表(LDT)。
分页是x86处理器的一个重要功能,它使得计算机能运行在其他情况下无法装入内存的一组程序。处理器初始只将部分程序加载到内存,同时,程序的其他部分仍然留在硬盘上。处理器利用页目录、页表和页面生成数据的物理地址。页目录包含了页表指针。页表包含了页面指针。
阅读 若想进一步阅读了解 Windows编程,下面的书籍可能会有所帮助:
Mark Russinovich 和David Solomon,《Windows Internals》,第1、2部分,MicrosoftPress,2012.
Barry Kauler,《 Windows Assembly Language and System Programming 》,CMPBooks,1997.
Charles Petzold,《Programming Windows》,第5版,Microsoft Press, 1998