内存管理与保护模式
本实验讨论x86采用的内存管理模式:从分段实模式到分段保护模式,再到分页虚拟内存管理,并介绍进入保护模式、启动分页机制、以及获取内存大小的基本方法。
1 x86的内存管理
内存的分段(segmentation)管理符合程序的逻辑结构,利于程序的保护和动态控制。分页(paging)则最适合虚拟内存的管理需要。目前主流操作系统的内存管理采用的是分页方法(如类Unix),也有采用段页式的(如Wiondows)。
Intel 8086支持不带保护功能的分段内存管理,80286开始引入带保护功能的分段内存管理,80386又引入了支持虚拟内存的分页内存管理,但其分页是建立在分段基础上的。IA-32和x64处理器,都支持段页式内存管理。
1.1 实模式
Intel的16位处理器8086,采用的是分段内存管理,CPU中有CS、DS、SS和ES四个16位段寄存器,作为基地址,分别用于生成代码、数据、堆栈和其他段的物理地址:
20位物理地址 = 16位段寄存器值*16(或左移4位) + 16位偏移量
2016寻址空间只有(2=)1MB,最大段长为(2=)64KB。
因为这样的CPU只能生成和访问真实的物理内存地址,所以被称为实地址模式(real-address mode),简称为实模式(real mode)。
不过这种分段方法并不是现代的分段技术,没有提供任何内存保护功能,不能阻止内存的越界访问。而且8086 CPU也没有提供任何特权分级,谁都可以任意改变CS、DS和SS寄存器中的值,从而可以执行/访问内存任何地址的指令/数据,完全没有安全可言。因此在8086 CPU上,是不可能构建现代操作系统的。
1.2 保护模式
1982年推出的16位的80286处理器,在x86体系中首次引入了(分段式的)内存保护机制,称之为保护模式(protected mode)。不过80286的保护模式只支持24位的地址空间
1616+824(最多只能访问16MB内存)和16位的界限大小(最大段长为2=64KB或2=2=16MB),且只能从实模式进入保护模式,而不能从保护模式返回实模式。
1985年推出的32位的80386处理器,其保护模式支持32位的地址空间,寻址空间达
32到(2=)4GB。但是为了与分段式的8086兼容,80386并没有使用简单的线性地址空间,而是采用了基于分段的分页内存管理方法(其中的分页管理是80386新增加的)。这里的分段是必须的,分页则是可选的。
1(分段
分段将处理器可寻址的内存空间(称为线性地址空间)划分为较小的受保护的地址空间(称为段),段可用于容纳程序的代码、数据和堆栈,或系统的数据结构(如TSS或LDT)。可以为每个运行的程序指派各自的段集。分段机制还允许将段分类,从而可限制在某个特定段类上能够执行的操作。
为了定位特定段中的字节,必须提供一个逻辑地址(也称为远指针),它包含一个段选择符和一个偏移量。段选择符是段唯一的标识符,它提供段描述符在描述符
中的偏移量。段描述符指定段的大小、访问权限和特权级、段类型、段在线性地址空间中的起始地址(基址)。段的基址+偏移量=处理器线性地址空间中的线性地址。
2(分页
如果不使用分页,则IA-32处理器的线性地址空间直接映射到处理器的物理地址空间,不支持虚拟内存管理。对IA-32处理器,只有分页机制才支持虚拟内存,使处理器的线性地址空间可以大于内存的物理地址空间。
但是IA-32处理器的分页是建立在分段的基础之上的,即IA-32处理器采用的是段页式
内存管理方法。在使用分页管理时,每个段被分成若干等长的页(对IA-32处理器,典型的页帧大小是4KB),这些页可以存放在内存中,也可以存放在磁盘上(即支持虚拟内存)。
当程序或任务试图访问线性地址空间中的地址位置时,处理器利用页目录和页表,将线性地址转换为物理地址。
为了节省内存空间,IA-32处理器采用的是两级分页方式。如果采用单级分页,对4KB
1220(=2)页帧大小和4GB地址空间,需要1M(=2)个表项,每个表项4B则整个页表就需要4MB空间。由于一般的进程并不需要使用整个4GB的空间,所以大多数表项都空着的,
10非常浪费。如果采用二级分页,一级为页目录(Page Directory,PD),有(2=)1024个页目录项(Page Directory Entry,PDE)。每个页目录项指向一个二级页表(Page Table,PT),
1020它也有(2=)1024个页表项(Page Table Entry,PTE),总共也是(1024*1024=2=)1M个页。但对空页目录项,不必创建其对应的页表,可以节省空间。
31 22 21 12 11 0
页目录项序号dir 页表项序号table 页帧内偏移量offset
4KB分页时的线性地址格式
31 12 11 10 9 8 7 6 5 4 3 2 1 0
I P P U R P 页表起始物理地址的高20位 Ignored g A C W / / P S n D T S W
4KB分页时的PDE(页目录项)格式
其中:
, P = Present存在(=0:不在内存中,其余各位忽略、=1:在内存中,其余各位有意义) , R/W = Read / Write读/写(=0:只读、=1:可写)
, U/S = User / Supervisor用户/超级管理员(=0:系统权限、=1:用户权限) , PWT = Page-level Write-Through页级直接写
, PCD = Page-level Cache Disable页级禁用缓存
, A = Accessed访问过(0:未访问过、1:已访问过)
, Ign = Ignored被忽略
, PS = Page Size页大小(0:4KB、1:4MB)
, Ignored被忽略
31 12 11 10 9 8 7 6 5 4 3 2 1 0
P P P U R
页帧起始物理地址的高20位 Ignored G A D A C W / / P
T D T S W
4KB分页时的PTE(页表项)格式
其中:
, D = Dirty脏(即对应页被软件改写过)(0:未脏、1:已脏)
, PAT = 用于确定内存类型
, G = Global全局(0:局部、1:全局)
如果采用4MB页帧大小,则只需要单级分页(只需要页目录)即可。
31 22 21 0
页目录dir 偏移量offset
4MB分页时的线性地址格式
分段分页
逻辑地址,线性地址,物理地址 映射映射
2 IA-32的寻址方式
分段不分页
逻辑地址,线性地址,物理地址映射
IA-32的分段保护模式下的逻辑地址由一个16位的段选择符和一个32位的偏移量构成。
2.1 段选择符
80386的8个通用寄存器和指令指针寄存器及标志寄存器都扩展成了32位(如EAX、ESP、EIP、EFLAGS,对应寄存器名前增加了一个字母E,代表Extended扩展),但是仍然保留原来的段寄存器为16位不变,只是另外增加了两个新的16位辅助段寄存器FS和GS(字母F和G没有特殊的含义,只是跟随在已有段寄存器字母C[S]、D[S]、E[S]之后的两个英文字母而已),一共有6个段寄存器:代码段的CS、数据段的DS、堆栈段的SS和3个辅助段寄存器——ES、FS和GS。
80386的主要寄存器
但是在32位的保护模式下,这些16位的段寄存器的
,不再是实模式下(最大64KB段长的)段基址,而是一种包含地址描述符结构数组(段表)的索引(index,下标)和特权级(privilege level)等设置的一种数据结构——段选择符(segment selector)。
这里的索引(偏移值)指向全局或局部段描述符表(段表)中的一个(描述符)表项,因索引/偏移值为16位,而每个段描述符占8B,所以该偏移值的低3位必须为0(另外用作
1613TI和RPL),只有段寄存器的高13位才有效,因此段描述符表中最多可有(2/8=2=)8192(=8K)个表项。
其实在IA-32处理器中,除了6个可见的16位段寄存器外,还有隐藏不可见的与每个段寄存器对应的(64位)影子结构,用于实现分段管理中的保护功能。
2.2 段描述符
段选择符指向段描述符表(segment descriptor table)中的一个段描述符表项,段描述符用于分段内存管理中的地址生成和保护。在段描述符(表项)中,包含32位段基址、20位段界限和12个控制位,共计64位(8B)。为了与80286(48位[6B]的)段描述符中的24位基址和16位界限兼容,所以80386段描述符中的32位基址和20位界限在8字节的结构中并不是连续的(高8位基址和高4位界限被放在了新增加的2个高位字节中)。
B31~B24 G D/B 0 AVL L19~L16 3 P DPL S Type B23~B16 2
B15~B0 1
L15~L0 0 15 0
描述符
其中:
, L = limit界限(20位段的界限)
, B = base基址(32位段的基地址)
, Type(类型)占4个二进制位,从低到高依次为(对代码段或数据段描述符):
, A = access访问(=0:未被访问、=1:已被访问)
, RW = read/write读/写[数据段](=0:只读不可写[数据段]/只执行不可读[代码段]、
=1:可读写[数据段]/可执行和读[代码段])
, ED/C = extend direction/conforming伸展方向/一致(=0:向上[高地址]伸展[数据段]/
非一致[代码段]、1:向下[低地址]伸展[堆栈段]/一致 [代码段])
, E = code代码(0:数据段[包括堆栈]、1:代码段)
, S = descriptor type描述符类型(=0:系统描述符、=1:代码段或数据段描述符) , DPL = descriptor privilege level描述符特权级(0~3:本段的特权级为0~3) , P = present存在(0:段不存在,描述符无意义、1:段存在,描述符含有效基址和界限) , AVL = available for System系统可用(始终设为0)
, D/B = default operation size默认操作大小(0:16位段、1:32位段)
12, G = Granularity 粒度/段长的单位(=0:字节、=1:4KB=2B)
下面是全局描述符的结构定义:
struct gdt_entry
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
因为段描述符中的控制位G可以指定段长的单位,G=0时单位为字节,G=1时的单位
122020+12 为4KB=2B。所以段描述符中20位段界限,最大(段长)可以是(2=)1MB或(2= 32 2=)4GB。(使用x86处理器的)Linux采用的是分页内存管理,不支持内存的分段管理,就是利用粒度为4KB的段界限,将32位处理器所支持的整个4GB内存分为了一个段,绕开了x86默认的分段管理机制。
如果描述符中的S控制位=0,则为系统描述符,包括如下两类:
, 系统段描述符
, LDT(Local Descriptor-Table,局部描述符表)段描述符
, TSS(Task-State Segment,任务状态段)描述符
, 门描述符
, 调用门(call-gate)描述符
, 中断门(interrupt-gate)描述符
, 陷阱门(trap-gate)描述符
, 任务门(task-gate)描述符
系统描述符类型
Type字段 描述 数值 11 10 9 8
0 0 0 0 0 保留
1 0 0 0 1 16位TSS(可用)
2 0 0 1 0 LDT
3 0 0 1 1 16位TSS(忙)
4 0 1 0 0 16位调用门
5 0 1 0 1 任务门
6 0 1 1 0 16位中断门
7 0 1 1 1 16位陷阱门
8 1 0 0 0 保留
9 1 0 0 1 32位TSS(可用)
10 1 0 1 0 保留
11 1 0 1 1 32位TSS(忙)
12 1 1 0 0 32位调用门
13 1 1 0 1 保留
14 1 1 1 0 32位中断门
15 1 1 1 1 32位陷阱门
2.3 描述符表
, GDT(Global Descriptor Table,全局描述符表)——是线性空间里的一种数据结构(本身不是一个段),每个系统都必须定义唯一一个GDT,可被系统中的所有程序和任务使用。GDT的基线性地址(8B对齐)和界限必须(使用LGDT指令)装入GDTR寄存器。GDT的界限值为字节数。
, LDT(Local Descriptor Table,局部描述符表)——本身是一个段,可定义若干个,可被多个任务共享。LDT位于LDT类型的系统段,GDT中必须包含一个LDT的段描述符。LDT使用其段选择符访问。为了在访问LDT时消除地址转换,LDT的段选择符、基线性地址、界限、访问权限被存储在LDTR寄存器中。
IDTR = Interrupt Descriptor Table Register,中断描述符表寄存器
3 保护模式
IA-32架构的CPU支持4种操作模式:
, 保护模式(protected mode)——这是处理器的天然态(native state)。 , 虚拟8086模式(virtual-8086 mode)——不是一种实际的处理器模式,只是指在保
护模式下可以直接执行实模式8086软件的能力。
, 实地址模式(Real-address mode)——实现8086处理器的编程环境。在加电或重
启时,处理器被置于实地址模式。
, SMM(System Management Mode,系统管理模式)——为操作系统或执行程序提
供一种实现平台特定功能(如电源管理和系统安全)的透明机制。
3.1 进入保护模式
1(进入保护模式的主要步骤
, 准备GDT
, 准备GDTR指针(48位GDT参数:16位界限与32位基址) , 用LGDT指令将GDT参数加载到寄存器GDTR , 关闭中断
, 打开A20地址线
, 置CR0寄存器的PE位
, 跳转进入保护模式
2(常量与宏
, NASM宏的定义与使用
, 宏的定义格式:
%macro 宏名 参数个数
„„ ; (含诸参数的)指令序列,在其中用%i来表示参数i
%endmacro
, 宏的使用方法:
宏名 参数1, 参数2, „„
, (段)描述符格式:
B31~B24 G D/B 0 AVL L19~L16 3
P DPL S Type B23~B16 2
B15~B0 1
L15~L0 0
15 0
描述符格式
D/B=1:32位段
DPL=0~3:描述符特权级
, 描述符类型常量与用于生成描述符(和门)宏的包含文件代码(pm.inc) ;----------------------------------------------------------------------------
; 在下列类型值命名中:
; DA_ : Descriptor Attribute 描述符属性
; D : 数据段
; C : 代码段
; S : 系统段
; R : 只读
; RW : 读写
; A : 已访问
; 其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
; 描述符类型
DA_32 EQU 4000h ; 32 位段
DA_DPL0 EQU 00h ; DPL = 0 DA_DPL1 EQU 20h ; DPL = 1 DA_DPL2 EQU 40h ; DPL = 2 DA_DPL3 EQU 60h ; DPL = 3
; 存储段描述符类型
DA_DR EQU 90h ; 存在的只读数据段类型值 DA_DRW EQU 92h ; 存在的可读写数据段属性值 DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 DA_C EQU 98h ; 存在的只执行代码段属性值 DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值
; 系统段描述符类型
DA_LDT EQU 82h ; 局部描述符表段类型值 DA_TaskGate EQU 85h ; 任务门类型值
DA_386TSS EQU 89h ; 可用 386 任务状态段类型值
DA_386CGate EQU 8Ch ; 386 调用门类型值
DA_386IGate EQU 8Eh ; 386 中断门类型值
DA_386TGate EQU 8Fh ; 386 陷阱门类型值
; RPL(Requested Privilege Level): 请求特权级,用于特权检查。
;
; TI(Table Indicator): 引用描述符表指示位
; TI=0 指示从全局描述符表GDT中读取描述符;
; TI=1 指示从局部描述符表LDT中读取描述符。
;
;----------------------------------------------------------------------------
; 选择符类型值说明
; 其中:
; SA_ : Selector Attribute
SA_RPL0 EQU 0 ; ?
SA_RPL1 EQU 1 ; ? RPL
SA_RPL2 EQU 2 ; ?
SA_RPL3 EQU 3 ; ?
SA_TIG EQU 0 ; ?TI
SA_TIL EQU 4 ; ?
;----------------------------------------------------------------------------
; 宏 -------------------------------------------------------------------------------------------------
;
; 描述符
; usage: Descriptor Base, Limit, Attr ; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro ; 共 8 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移1
dw %1 ; 选择子
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性
dw ((%2 >> 16) & 0FFFFh) ; 偏移2
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3(汇编源代码
, 测试程序(pmtest1.asm)
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT(定义全局描述符表。注意,为防止误操作,首个GDT表项必须为空) ; 段基址, 段界限, 属性
Descriptor 0, 0, 0 ; 空描述符 LABEL_GDT:
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 ; GDT 结束
; 定义48位的GDT参数结构GdtPtr(16位界限 + 32位基地址) GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择符(定义代码段和显存段在GDT中的偏移量)
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT ; = 64
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; = 128
; END of [SECTION .gdt]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs ; 设置DS/ES/SS = CS
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 100h ; 设置 SP = 100h
; 初始化 32 位代码段描述符中的基址部分
xor eax, eax ; EAX = 0
mov ax, cs
shl eax, 4 ; EAX = 代码段基址
add eax, LABEL_SEG_CODE32 ; EAX + 偏移地址 = 32位代码段起始地址
mov word [LABEL_DESC_CODE32 + 2], ax ; B0~15 = AX
shr eax, 16 ; EAX >> 16(AX = EAX的高16位)
mov byte [LABEL_DESC_CODE32 + 4], al ; B16~23 = AL
mov byte [LABEL_DESC_CODE32 + 7], ah ; B24~31 = AH
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <-- GDT基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <-- GDT基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b ; or al, 2(关闭:and al,0FDh)
out 92h, al
; 准备切换到保护模式(置CR0的PE位为1)
mov eax, cr0
or eax, 1 ; PE = 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把SelectorCode32 装入cs,
; 并跳转到Code32Selector:0处 ; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择符(目的)
mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $ ; 死循环
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
4(代码解释
, 机器指令LGDT将48位GDT参数(参见下图)加载到寄存器GDTR中,我们代
码中的GDT参数,用结构变量GdtPtr表示。
47 16 15 0
32位GDT基址 16位GDT界限
下面是相关代码:
; 加载 GDTR
lgdt [GdtPtr]
, 打开A20地址线
由于历史原因,虽然8086处理器只有20位地址线(即只能寻址1MB地址空间),但是如果试图访问超过1MB的地址时,系统并不会发生异常,而只是回卷(wrap)寻址(忽略序号为20的第21位及以上的地址数据)。在推出80286后,不再回卷,可访问超过1MB以上的地址空间。但为了保证对老软件的兼容,IBM想出了一个办法——使用Intel 8042键盘控制器(后来改用92h号端口的第二个二进制位)来控制20号(及以上)地址(Address)位,称为A20线门(A20 line gate)或门A20(Gate-A20))。当A20打开时,不回卷,可访问超过1MB以上的地址空间;当A20关闭时,则回卷,只能寻址1MB地址空间。
在系统启动时,主板上的系统BIOS会打开A20进行内存检测,但在将控制转交给操作系统(磁盘引导扇区)之前,会关闭A20。
为了使用大于1MB的内存空间,在进入保护模式之前,我们先必须打开A20。下面是相关代码:
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
, 置CR0寄存器的PE位
CR0是80386引入的4个32位的控制寄存器(Control Register)CR0~CR3之一,包含若干控制处理器操作模式和状态的系统控制标志。其中的最低位PE(Protection Enable,保护允许/激活保护)用于允许/禁止保护状态。
相关代码:
; 准备切换到保护模式(置CR0的PE位为1)
mov eax, cr0
or eax, 1
mov cr0, eax
控制寄存器
, 跳转进入保护模式
虽然置CR0的PE=1后,系统已经允许在保护模式,但是此时的CS仍然是16位实模式下的值,必须通过一个特殊的跳转指令,从我们程序前面的16位代码([SECTION .s16])跳转到后面的32位代码([SECTION .s32])。至此,才算真正完成从实模式到保护模式的转换。
相关代码:
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs,
; 并跳转到 Code32Selector:0 处 注意,其中jmp指令的红色的数据类型说明符dword(32位的双字)是NASM特有的。 4(编译运行
编译:nasm pmtest1.asm -o pmtest1.bin
写入:用FloppyWriter或WinHex将pmtest1.bin写入软盘映像的引导扇区,并在该扇区的结束处(1FEh)添加启动扇区的结束标55h和0aah
测试:用Bochs虚拟机测试
结果:运行结果如下图所示:
3.2 启动分页机制
进入保护模式,只是启动了分段机制,可以实现保护功能。但是如果要实现虚拟内存管理,还需要启动可选的分页机制。
1(启动分页机制的主要步骤
, 准备PD和PT
, 让CR3指向PD
, 置CR0寄存器的PG位
跳转启动分页机制 , 短
2(汇编源代码
需要在原pm.inc中增加如下常量定义:
; 为段描述符中属性字对应控制位G(15b)的十六进制值,用于构造描述符
DA_LIMIT_4K EQU 8000h ; 段界限粒度为 4K 字节
;----------------------------------------------------------------------------
; 分页机制使用的常量说明
; 为页目录项PDE和页表项PTE中字对应属性位的值,用于表项的初始化
;----------------------------------------------------------------------------
PG_P EQU 1 ; 页存在属性位
PG_RWR EQU 0 ; R/W 属性位值, 读/执行
PG_RWW EQU 2 ; R/W 属性位值, 读/写/执行
PG_USS EQU 0 ; U/S 属性位值, 系统级
PG_USU EQU 4 ; U/S 属性位值, 用户级
;----------------------------------------------------------------------------
下面是进入保护模式并启动分页机制的测试程序的源代码(pmtest2.asm):
其中
, 红色部分为启动分页机制的代码
, 绿色部分为返回实模式的代码
, 其余部分为进入保护模式的代码
%include "pm.inc" ; 常量, 宏, 以及一些说明
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M+4K
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限, 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW; Normal 描述符
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW ; Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K ; Page Tables
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32 ; 非一致代码段, 32 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16 LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择符
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT SelectorPageTbl equ LABEL_DESC_PAGE_TBL - LABEL_GDT SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0; 进入保护模式后显示此字符串 OffsetPMMessage equ PMMessage - $$ DataLen equ $ - LABEL_DATA ; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [SPValueInRealMode], sp
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把SelectorCode32装入cs,
; 并跳转到Code32Selector:0处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ?
and al, 11111101b ; ? 关闭 A20 地址线
out 92h, al ; ?
sti ; 开中断
mov ax, 4c00h ; ?
int 21h ; ?回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32]
LABEL_SEG_CODE32:
call SetupPaging
mov ax, SelectorData
mov ds, ax ; 数据段选择符
mov ax, SelectorVideo
mov gs, ax ; 视频段选择符
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择符
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
; 到此停止
jmp SelectorCode16:0
; 启动分页机制 --------------------------------------------------------------
SetupPaging:
; 为简化处理, 所有线性地址对应相等的物理地址.
; 首先初始化页目录
mov ax, SelectorPageDir; 此段首地址为 PageDirBase
mov es, ax
mov ecx, 1024 ; 共 1K 个表项
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd ; 存储双字串:EAX -> ES:EDI, EDI += 4, ECX--
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表 (1K个, 4M内存空间)
mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
mov es, ax
mov ecx, 1024 * 1024 ; 共 1M 个页表项, 也即有 1M 个页
xor edi, edi
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW .2:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
mov eax, PageDirBase ; CR3 => 页目录起始地址
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax ; CR0的PG位=1
jmp short .3
.3:
nop
ret
; 分页机制启动完毕 ----------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式 [SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and eax, 7FFFFFFEh ; PE=0, PG=0
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
汇编成pmtest2.com后在Windows的命令行窗口执行,不会有任何输出。用VMware下的Ubuntu或WinHex放进MyOS.img中,在Boches虚拟机上的DOS环境下执行,结果显示为:
3(代码解释
我们采用基本的4KB分页大小,并将页目录PD的开始地址PageDirBase定在200000h(2M)处,大小为4KB(1K个目录项)。将页表PT的开始地址PageTblBase定在201000h(2M+4K)处,紧跟在PD之后,大小为1KB*4K(1M个表项)。参见下表:
PageDirBase (200000h) PageTblBase (201000h)
L PDE
0 PDE 1024 第0帧 个
4096B PDE „„
PDE 每
个第1帧 PTE 页4096B PTE 表 对
应 „„ 1 共 0 1 共 „„ PTE 2 M1 4个PTE 0 个页 2 PTE 页 , 每个页表 4 4 个含1024个 第1023帧 G „„ 页PTE B 4096B 表 线, PTE 性占 地 用 址 4 空M „„ 间 B„„ 内 存
空PTE 间 PTE
第1048575帧 „„ 4096B 4GB PTE H 在全局描述符表GDT中增加了PD和PT的段描述符。下面这两个表的表项格式:
31 12 11 10 9 8 7 6 5 4 3 2 1 0
I P P U R P 页表物理地址的高20位 Ignored g A C W / / P S n D T S W
4KB分页时的PDE(页目录项)格式
31 12 11 10 9 8 7 6 5 4 3 2 1 0
P P P U R 页帧物理地址的高20位(帧号) Ignored G A D A C W / / P
T D T S W
4KB分页时的PTE(页表项)格式
我们用处理器的存储(双字)串指令STOSD来初始化这两个表:
stosd ; 存储双字串:EAX -> ES:EDI, EDI += 4, ECX--
该指令将EAX中的值存储到ES:EDI指定的内存地址中,然后让EDI加4、ECX减1,用LOOP语句进行循环,直到ECX减为0。
对页目录,其中的ES = 段选择符SelectorPageDir,其基址为PageDirBase(200000h = 2M),EDI的初值为0,ECX的初值为1024(个页目录表项)。
页目录的表项PDE的初值:EAX = PageTblBase | PG_P | PG_USU | PG_RWW,每循环一次EAX加4096(4K),相当于对应页表的物理地址的高20位加1(即每个页表的大小只有4KB,占1个页)。我们的程序只有唯一的一个页表段,含1024个页表。
对页表,ES = 段选择符SelectorPageTbl,其基址为PageTblBase(201000h = 2M + 4K),EDI的初值也为0,但ECX的初值为1024*1024(共1M个页表项,占4MB空间,对应于4GB的线性地址空间)。
页表项PTE的初值:EAX = PG_P | PG_USU | PG_RWW(即从第0帧开始),每循环一次EAX加4096(4K),相当于对应帧的物理地址高20位加1(即帧号加1)。
在准备好PD和PT后,我们让三号控制寄存器CR3指向页目录的起始地址,并设零号控制寄存器CR0的最高位PG(Paging,分页)为1,即可启动分页机制:
mov eax, PageDirBase
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
其中,PDBR = Page-Directory Base Register 页目录基址寄存器。
4 获取内存大小
4.1 int 12h
PC机在8086实模式下,可以调用BIOS 12h中断来确定内存容量(最大640KB),返回值放在AX中,以1KB(1024B)为单位列出可用的RAM空间。例如,用Debug执行int 12h,结果AX=280h=640,即实模式下可用的内容容量为640KB。参见下图:
4.2 int 15h, AX = E801h
对现代的PC机(80386+),还可以通过调用BIOS中断15h的E801h号功能来确定内
存的容量。
返回参数:
, CF = 0:成功、CF = 1:出错(CF为标志寄存器中的进位标志)
, AX = 1MB~16MB之间的扩展内存,以KB为单位,最大值为3C00h(对应于15MB)
, BX = 16MB以上的扩展内存,以64KB为单位 , CX = 1MB~16MB之间的配置内存,以KB为单位 , DX = 16MB以上的配置内存,以64KB为单位 例如(ms1.asm)
org 100h ; 可汇编成COM文件
mov ax,0E801h ; 功能号
int 15h ; 中断调用
jc LB_fail ; 出错跳转
push dx ; 保存DX
push cx ; 保存CX
push bx ; 保存BX
call DispVal ; 显示AX
pop ax ; 恢复BX
call DispVal ; 显示BX
pop ax ; 恢复CX
call DispVal ; 显示CX
pop ax ; 恢复DX
call DispVal ; 显示DX
ret ; 返回
LB_fail: ; 调用失败时显示“Failed!”字符串
mov bp,FailMsg ; BP=当前串的偏移地址
mov ax,ds ; ES:BP = 串地址
mov es,ax ; 置ES=DS
mov cx,7 ; CX = 串长(=9)
mov ax,1301h ; AH = 13h(功能号)、AL = 01h(光标置于串尾)
mov bx,000Fh ; 页号为0(BH = 0) 黑底白字(BL = 0Fh)
mov dx,0 ; 列号=0(DL=0) 行号=0(DH=0)
int 10h ; 显示中断
ret ; 退出程序
; 定义字符串常量
FailMsg: db "Failed!" ; 中断调用失败时显示的字符串 ; 显示数据值十六进制串函数
DispVal: ; 显示16位整数值串(以AX为传递参数)
mov dx,ax ; 保存传递参数AX的值
; 显示高4位
and ax,0F000h ; 取出最高的4位
shr ax,12 ; 右移12位
call ShowChar ; 调用显示字符函数
; 显示中高4位
mov ax,dx ; 恢复传递参数AX的值
and ax,0F00h ; 取出中高的4位
shr ax,8 ; 右移8位
call ShowChar ; 调用显示字符函数
; 显示中低4位
mov ax,dx ; 恢复传递参数AX的值
and ax,0F0h ; 取出中低的4位
shr ax,4 ; 右移4位
call ShowChar ; 调用显示字符函数
; 显示低4位
mov ax,dx ; 恢复传递参数AX的值
and ax,0Fh ; 取出最低的4位
call ShowChar ; 调用显示字符函数
; 显示空格符
mov al,20h ; AL = 空格符
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
ret
; 显示单个十六进制字符函数
ShowChar: ; 显示一个十六进制数字符:0~9、A~F(以AL为传递参数)
cmp al,10 ; AL < 10 ?
jl digital ; AL < 10:跳转到digital
add al,7 ; AL >= 10:显示字母( = 数值+=37h)
digital: ; 数字
add al,30h ; 数字字符 = 数值+=30h
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
ret
汇编成ms1.com后在Windows的命令行窗口执行,会输出失败字符串。用WinImage将其放进MyOS.img中,在Boches虚拟机上的DOS环境下执行,我的机器上显示为:
即:
, 1MB~16MB之间的扩展内存 = 3C00h*1KB = 15360KB = 15MB , 16MB以上的扩展内存 = 100h*64KB = 256*64KB = 16384KB = 16MB。
, 总内存容量为1MB+15MB+16MB = 32MB???
原因在于我们使用了Bochs虚拟机的默认内存大小设置(=32MB),可以修改Bochs虚拟机的配置文件,将原来的megs: 32改大一些(如1GB)megs: 1024。参加下图:
重新运行虚拟机,结果如下图所示:
, 1MB~16MB之间的扩展内存 = 3C00h*1KB = 15360KB = 15MB
, 16MB以上的扩展内存 = 3F00h*64KB = 16128*64KB = 1032192KB = 1008MB。 , 总内存容量为1MB+15MB+1008MB = 1024MB = 1GB
如果将ms1.com写入U盘的引导扇区,并用它启动计算机,在我的电脑上显示为:
3C00 DE30 3C00 DE30 因DE30h * 64 KB = 56880 * 64 KB = 3555 MB,所以再 + 16MB = 3571 MB = 3.48GB,与WinXP显示的一致。
4.3 int 15h, EAX = E820h
对现代的PC机还可以通过(多次)调用BIOS中断15h的E820h号功能来确定内存的容量和分布。
入口参数:
, EAX = 功能号E820h
, EBX = 后续值,初值必须=0
, ES:DI = 缓冲区地址
, ECX = 缓冲区字节数(必须>=20,一般取为20)
, EDX = 534D4150h("SMAP")为校验标志
出口参数:
, CF = 0:成功、CF = 1:出错(CF为标志寄存器中的进位标志)
, EAX = 534D4150h("SMAP")
, ES:DI = 入口时的值
, ECX = 填充的字节数(>=20)
, EBX =后续值,若=0则内存段的地址范围描述符已读完
地址范围描述符结构(Address Range Discriptor Structure,ARDS):
ARDS
偏移量 数据类型 字节数 描述
0 QWORD 8 基址
8 QWORD 8 长度
16 DWORD 4 类型
其中的地址范围类型的取值:
:OS可用的RAM内存 , 1
, 2:保留,OS不可用(如系统ROM、存储映像设备)
, 3:ACPI(Advanced Configuration and Power Management Interface,高级配置和电
源管理接口)回收内存(读ACPI表后OS可用)
, 4:ACPI NVS内存(OS必需在NVS会话之间保存该内存)
, 其余值:未定义,视为保留
例如(ms2.asm):
org 100h ; 可汇编成COM文件
;org 7C00h ; 用于引导扇区
mov ax,cs ; DS = CS, SS = CS
mov ds,ax
mov ss,ax
mov sp,100h-4 ; 可汇编成COM文件
;mov sp,7C00h-4 ; 用于引导扇区
mov es,ax ; ES = CS
mov ebx,0 ; EBX = 0 (初始值)
mov di,Buf ; ES:DI = 返回值的缓冲区起始地址 LB_loop: ; 调用15h中断的E820h功能获取内存容量
mov eax,0E820h ; 功能号
mov ecx,20 ; 缓冲区大小
mov edx,534D4150h ; "SMAP" 校验标志
int 15h ; 中断调用
jc LB_fail ; 出错跳转
add di,20 ; 缓冲区指针后移20个字节
inc word [Numb] ; 内存分段数加一
cmp ebx,0 ; EBX = 0?
jne LB_loop ; EBX != 0:继续调用
mov di, Buf ; EBX = 0:结束,DI = 缓冲区起始地址(用于显示)
jmp LB_OK ; 跳转到LB_OK,开始显示返回值
LB_fail: ; 调用失败时显示“Failed!”字符串
mov bp,FailMsg ; BP=当前串的偏移地址
mov ax,ds ; ES:BP = 串地址
mov es,ax ; 置ES=DS
mov cx,7 ; CX = 串长(=9)
mov ax,1301h ; AH = 13h(功能号)、AL = 01h(光标置于串尾)
mov bx,000Fh ; 页号为0(BH = 0) 黑底白字(BL = 0Fh)
mov dx,0 ; 列号=0(DL=0) 行号=0(DH=0)
int 10h ; 显示中断
mov word [Numb],0 ; 置内存分段数=0(不显示返回值)
ret ; 退出程序
LB_OK: ; 循环显示返回段值
cmp word [Numb],0 ; Numb = 0?
je LB_out ; Numb = 0:退出程序
mov cx,20 ; CX = 20(行内循环初值) LB_lloop: ; 行内循环
mov al, byte [di] ; AL = 当前字节值
call DispByte ; 调用显示字节十六进制值的函数
inc di ; DI++
dec cx ; CX--
jnz LB_lloop ; 行内循环
dec word [Numb] ; Numb--
call DispCrnl ; 回车换行
jnz LB_OK ; 显示下一个内存段值 LB_out: ; 退出程序
ret ; 返回
; 显示回车换行符
DispCrnl:
mov al,0Ah ; AL = 换行符
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
mov al,0Dh ; AL = 回车符
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
ret
; 显示字节数据值十六进制串函数
DispByte: ; 显示字节数值串(以AL为传递参数)
mov dl,al ; 保存传递参数AX的值
; 显示高4位
and al,0F0h ; 取出高4位
shr al,4 ; 右移4位
call ShowChar ; 调用显示字符函数
; 显示低4位
mov al,dl ; 恢复传递参数AX的值
and al,0Fh ; 取出低4位
call ShowChar ; 调用显示字符函数
; 显示空格符
mov al,20h ; AL = 空格符
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
; 如果当前字节的序号%8=0,则多显示一个空格(分隔结构中的字段)
mov ax,21 ; AX = 21 - CX
sub ax,cx
and ax,7 ; AX & 0111 b
cmp ax,0 ; AX % 8 = 0 ?
jnz LB_ret ; != 0:返回、= 0:再显示一个空格符
mov al,20h ; AL = 空格符
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
LB_ret:
ret
; 显示单个十六进制字符函数
ShowChar: ; 显示一个十六进制数字符:0~9、A~F(以AL为传递参数)
cmp al,10 ; AL < 10 ?
jl digital ; AL < 10:跳转到digital
add al,7 ; AL >= 10:显示字母( = 数值+=37h)
digital: ; 数字
add al,30h ; 数字字符 = 数值+=30h
mov ah,0Eh ; 功能号(以电传方式显示单个字符)
mov bl,0 ; 对文本方式置0
int 10h ; 调用10H号中断
ret
; 定义变量和缓冲区
Numb dw 0 ; 内存分段数,初值=0
FailMsg: db "Failed!" ; 中断调用失败时显示的字符串
Buf: times 160 db 0 ; 存放中断返回值的缓冲区
汇编成ms2.com后在Windows的命令行窗口执行,会输出失败字符串。用WinImage
将其放进MyOS.img中,在(内存设为1GB的)Boches虚拟机上的DOS环境下执行,我
的机器上显示为:
ARDS
序号 基址 长度(字节数) 类型
1 00000000 0009F000(636KB) 1
2 0009F000 00001000(4KB) 2
3 000E8000 00018000(96KB) 2
4 00100000 3FEF0000(1023MB) 1
5 3FFF0000 00010000(64KB) 3
6 FFFC0000 00040000(256KB) 2
如果将ms2.asm开始处的org和SP赋值改为7C00h和7C00h-4,重新编译后写入U盘
的引导扇区,并用它启动计算机,在我的台式电脑上显示的结果为:
ARDS
序号 基址 长度(字节数) 类型 1 00000000 0009E800(634KB) 1 2 0009E800 00001800(6KB) 2 3 000E0000 00020000(128KB) 2 4 00100000 DF202000(3570MB) 1 5 DF302000 00058000(352KB) 4 6 DF35A000 00246000(2328KB) 2 7 DF5A0000 00011000(68KB) 4 8 DF5B1000 00014000(80KB) 2 9 DF5C5000 00002000(8KB) 1 10 DF5C7000 00001000(4KB) 3 11 DF5C8000 00008000(32KB) 2 12 DF5D0000 0000A000(40KB) 4 13 DF5DA000 0005C000(368KB) 2 14 DF636000 00043000(268KB) 4 15 DF679000 00187000(1564KB) 1 16 100000000 11F800000(4600MB) 1 17 FED1C000 00004000(16KB) 2 18 FF0000000 01000000(16MB) 2
可用的内存空间为:634 KB + 3570 MB + 8 KB + 1564 KB + 4600 MB = 8172 MB。
在我的笔记本电脑上显示的结果为:
ARDS
序号 基址 长度(字节数) 类型 1 00000000 0009D400(629KB) 1 2 0009D400 00002C00(11KB) 2 3 000E0000 00020000(128KB) 2
4 00100000 774A7000(1909MB) 1
5 775A7000 00048000(288KB) 4
6 775EF000 0000B000(44KB) 3
7 775FA000 00003000(12KB) 4
8 775FD000 00025000(148KB) 2
9 77622000 00008000(32KB) 4
10 7762A000 00003000(12KB) 2
11 7762D000 00005000(20KB) 4
12 77632000 00020000(40KB) 2
13 77652000 00043000(268KB) 4
14 77695000 0016B000(1452KB) 1
15 79E00000 02200000(34MB) 2
16 E0000000 10000000(256MB) 2
17 FED1C000 00004000(16KB) 2
18 FF0000000 01000000(16MB) 2
可用的内存空间为:629 KB + 1909 MB + 1452 KB = 1911 MB。
实验7 , 了解x86的内存管理方式。
, 了解IA-32的寻址方式。
, 掌握进入保护模式的步骤。
, 实现进入保护模式的测试程序pmtest1.asm。
实验8 , 掌握启动分页机制的步骤。
, 实现启动分页机制的测试程序pmtest2.asm。
, 实现获取内存容量的两个程序ms1.asm和ms2.asm。 , 修改ms2.asm:
, 使其只保存和显示类型为1的行。
, 并对每行中字段数据的显示,从低位字节在前,改为高位字节在前。
, (选做)最后计算并显示可用内存的总容量。
例如:
在内存大小设置为1024MB的Bochs虚拟机上:
在内存大小为8GB的台式机上: