Windows背景知识之一: 386处理器模式和Windows内存管理
1. 80x86处理器的工作模式
80386处理器有3种工作模式:实模式、保护模式和虚拟86模式。
项目\模式 | 实模式 | 保护模式 | 虚拟86模式 |
CPU运行的主要模式 | 否 | 是 | 否 |
寻址方式 | 段寄存器内容*16 + 段内偏移地址 | 空间高达4G的32位平面寻址 | 段寄存器内容*16 + 段内偏移地址 不过,操作系统利用分页机制将不同的虚拟86的任务的地址空间映射到不同的物理地址上去,这样每个任务都认为自己在使用0~1MB的地址空间 |
内存分页,虚拟内存 | 否 | 支持 | 支持 |
优先级支持 | 否 | 支持 级别分为0~34 | 支持 |
多任务 | 否 | 支持 | 支持 |
运行之下的操作系统 | DOS | Windows | Windows中运行的DOS程序 |
说明 | 老的8086模式 | 主要模式 | 为了在保护模式下执行8086程序而设置的 |
2.Windows的内存管理
DOS的内存安排
DOS操作系统运行于实模式中,由于8086处理器的寻址范围只有1 MB。
- 高端:系统硬件使用的内存安排在高端,地址是从A0000h(即640 KB)开始的384 KB中。
- 低端:而在内存低端,安排了中断向量表和BIOS数据区。
- 中间:剩下从500h开始到A0000h总共不到640 KB的内存是操作系统和应用程序所能够使用的;应用程序不可能使用这640 KB以外的内存。这就是著名的“640KB限制”。但即使在这640 KB中,DOS操作系统又占领了低端的一部分内存,最后剩下600 KB左右的内存才是应用程序真正可以用的。
当80386处理器推出后,可以寻址的内存范围达到了4 GB,利用XMS驱动程序可以访问到所有的4 GB地址空间。但16位的段寻址方式限制了DOS程序,“可见”的内存范围还是停留在00000h到FFFF0h+64 KB的范围内,所有高于1 MB的扩展内存只能通过XMS驱动程序当做数据交换使用,程序的执行空间并没有什么增加。
实模式的内存寻址方式
在实模式下,一个完整的地址由段地址和偏移地址两部分组成。段地址放在16位的段寄存器中,然后在指令中用16位的偏移地址寻址。处理器换算时先将段地址乘以10h,得到段在物理内存中的起始地址;然后加上16位的偏移地址得到实际的物理地址。如xxxx:yyyy格式的虚拟地址在内存中的实际位置是xxxx×10h+yyyy。
在单任务的DOS系统中,一个应用程序可以使用所有的空闲内存。程序退出后,操作系统回收所有的碎片内存并且合并成一个大块内存继续供下一个程序使用。内存合并过程中的一个极端情况是当系统中有多个TSR程序时,早装入内存的TSR卸载后,后装入的TSR会留在内存的中间部位,把空闲内存隔成两个区域。这时应用程序使用的最大内存块只能是这两块内存中较大的一块,无法将它们合并使用。
线性地址和物理地址
80386处理器把4 KB大小的一块内存当做一“页”内存,每页物理内存可以根据“页目录”和“页表”,随意映射到不同的线性地址上。这样,就可以将物理地址不连续的内存的映射连到一起,在线性地址上视为连续。
该机制能够很好的解决内存碎片的合并问题。
页表规定的不仅是地址的映射,同时还规定了页的访问属性,如是否可写、可读和可执行等。比如把代码所在的内存页设置为可读与可执行,那么权限不够的代码向它写数据就会引发保护异常。
Windows的内存安排
Windows系统一般在硬盘上建立大小为物理内存两倍左右的交换文件(文件名在Windows 9x下为Win386.swp,Windows NT下为PageFile.sys)用做虚拟内存。利用80386处理器的内存分页机制,交换文件在寻址上可以很方便地作为物理内存使用。只需在真正访问到的时候将硬盘文件的内容读入物理内存,然后重新将线性地址映射到这块物理内存就可以了。同样道理,被执行的可执行文件也不必真正装入内存,只要在页表中建立映射关系,以后到真正访问到的时候再调入物理内存。
下图展示了Windows的内存安排
如图所示,Windows操作系统通过切换不同的页表内容让线性地址在不同的时间片中映射不同的内容。在物理内存中,操作系统和系统DLL的代码需要供每个应用程序调用,所以在所有的时间片中都必须被映射;用户程序只在自己所属的时间片内被映射;而用户DLL则有选择地被映射。当然,物理内存中只需要一份xxx.dll的代码。
Win32编程中的几个重要概念
- 每个应用程序都有自己的4 GB的寻址空间。该空间可存放操作系统、系统DLL和用户DLL的代码,它们之中有各种函数供应用程序调用。再除去其他的一些空间,余下的是应用程序的代码、数据和可以分配的地址空间。
- 不同应用程序的线性地址空间是隔离的。虽然它们在物理内存中同时存在,但在某个程序所属的时间片中,其他应用程序的代码和数据没有被映射到可寻址的线性地址中,所以是不可访问的。从编程的角度看,程序可以使用4 GB的寻址空间,而且这个空间是“私有”的。
- DLL程序没有自己“私有”的空间。它们总是被映射到其他应用程序的地址空间中,当做其他应用程序的一部分运行。原因很简单,如果它不和其他程序同属一个地址空间,应用程序该如何调用它呢?
那么,80386保护模式的内存管理是如此的麻烦,又是描述表,描述符,又是映射到线性地址,那么Win32的汇编是不是很难写呢?
不是的。Windows操作系统为用户程序“安排好了一切”。具体表现在为用户程序的代码段、数据段和堆栈段全部预定义好了段描述符。这些段的起始地址为0,限长为ffffffff,所以用它们可以直接寻址全部的4 GB地址空间。程序开始执行的时候,CS,DS,ES和SS都已经指向了正确的描述符,在整个程序的生命周期内,程序员不必改动这些段寄存器,也不必关心它们的值究竟是多少(实际上,想改也改不了)。
所以对Win32汇编程序来说,整个源程序中竟然可以不用出现段寄存器的身影。这在DOS汇编编程中是不可想像的。所以说,并不是Win32汇编源代码用不到段寄存器,而是用户在使用中不必去关心段寄存器!
摘自《Win32汇编教程》罗云斌
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律