1.如何由内存推出页表数量
假设内存空间为32MB, 由于每个页是4KB, 则我们需要8K个页表,而每个页表实际占4个字节,所以一共是页表所占内存为32KB
分配内存依然是从零开始的,但是里面有一些内存是不能用的,我们只能说自己线性地址指向的内存有部分可用,可用的最大值的地址是01FF0000h, 所以说可以用的内存都被指向到了
;======================================
;pmtest7.asm
;编译方法:nasm pmtest7.asm -o pmtest7.bin
;======================================
%include "pm.inc"; 常量,宏 以及一些说明
;org 07c00h
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 4k
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096*8-1, DA_DRW ;32KB的页表
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C;
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32;
LABEL_DESC_VIDEO : Descriptor 0B8000h, 0ffffh, DA_DRW; 显存首地址
;GDT 就是一个数组结构
GdtLen equ $-LABEL_GDT ;GDT长度
GdtPtr dw GdtLen - 1 ;GDT 界限
dd 0 ; GDT基地址
;GdtPtr也是一个数据结构 前2字节是GDT界限 后4字节是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:
;实模式下使用这些符号
;字符串
_szPMMessage: db "In Protect Mode now.^_^", 0Ah, 0Ah, 0; 在保护模式中显示
_szMemChkTitle db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0; 进入保护模式后显示
_szRAMSize db "RAM size:", 0
_szReturn db 0Ah, 0
; 变量
_wSPValueInRealMode dw 0
_dwMCRNumber: dd 0 ; Memory Check Result
_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_dwMemSize: dd 0
_ARDStruct: ; Address Range Descriptor Structure
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
_MemChkBuf: times 256 db 0
; 保护模式下使用这些符号
szPMMessage equ _szPMMessage - $$
szMemChkTitle equ _szMemChkTitle - $$
szRAMSize equ _szRAMSize - $$
szReturn equ _szReturn - $$
dwDispPos equ _dwDispPos - $$
dwMemSize equ _dwMemSize - $$
dwMCRNumber equ _dwMCRNumber - $$
ARDStruct equ _ARDStruct - $$
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
MemChkBuf equ _MemChkBuf - $$
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]
;这是一个16位代码段 这个程序修改了gdt中的一些值 然后执行跳转到第三个section
[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 [_wSPValueInRealMode],sp ;是从零开始的?
; 得到内存数
mov ebx, 0
mov di, _MemChkBuf
.loop:
mov eax, 0E820h
mov ecx, 20
mov edx, 0534D4150h
int 15h
jc LABEL_MEM_CHK_FAIL ;CF=1 发生了错误
add di, 20
inc dword [_dwMCRNumber] ;_dwMCRNumber每次循环加一 作为循环次数
cmp ebx, 0
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0 ;循环次数清零
LABEL_MEM_CHK_OK:
;初始化16位的代码段描述符
mov ax, cs
movzx eax, ax
; xor eax, eax
; mov ax, cs
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ;从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [_wSPValueInRealMode] ;指针调到了堆栈中 返回到实模式
in al, 92h
and al, 11111101b; 关闭A20地址线
out 92h, al
sti; 开中断
mov ax, 4c00h;
int 21h; 回到dos
;end of section .s16
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
;call SetupPaging
mov ax, SelectorData
mov ds, ax; 数据段选择子
mov ax, SelectorData
mov es, ax
mov ax, SelectorVideo; 视频段选择子
mov gs, ax
mov ax, SelectorStack
mov ss, ax; 堆栈段选择子
mov esp, TopOfStack
;下面显示一个字符串
push szPMMessage
call DispStr
add esp, 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ; 显示内存信息
call SetupPaging ; 启动分页机制
; 到此停止
jmp SelectorCode16:0
;-------------------------------------------------------------------------
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
div ebx
mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
test edx, edx ; 测试edx 是否为空 edx是放余数的位置
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加一个页表
.no_remainder:
push ecx ; 暂存页表个数
; 为简化处理 所有线性地址对应相等的物理地址
;首先初始化页目录
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 ;取得双字mov [edi], eax, edi = edi+4;
add eax, 4096; 为了简化 所有页表在内存中都是连续的
loop .1
;再初始化所有页表(1K个 4M内存空间)
mov ax, SelectorPageTbl; 此段首地址为 pagetblbase
mov es, ax
pop eax ; 页表个数从堆栈里弹出来
mov ebx, 1024
mul ebx
mov ecx, eax; PTE个数 1024*页表个数
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
mov cr3, eax ;cr3 指向页目录表
mov eax, cr0
or eax, 80000000h
mov cr0, eax ;PG = 1 分页机制生效
jmp short .3
.3:
nop
ret
;
;分页机制启动完毕
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一个ARDS
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++) //每次得到一个ARDS中的成员 每个四字节 共20字节
mov edi, ARDStruct ; {//依次显示BaseAddrLow,BaseAddrHigh,LengthLow,
.1: ; LengthHigh,Type
push dword [esi] ;
call DispInt ; DispInt(MemChkBuf[j*4]); //显示一个成员
pop eax ;
stosd ; ARDStruct[j*4] = MemChkBuf[j*4];
add esi, 4 ;
dec edx ;
cmp edx, 0 ;
jnz .1 ; }
call DispReturn ; printf("\n");
cmp dword [dwType], 1 ; if(Type == AddressRangeMemory)
jne .2 ; {
mov eax, [dwBaseAddrLow];
add eax, [dwLengthLow];
cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
jb .2 ;
mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
.2: ; }
loop .loop ;}
;
call DispReturn ;printf("\n");
push szRAMSize ;
call DispStr ;printf("RAM size:");
add esp, 4 ;
;
push dword [dwMemSize] ;
call DispInt ;DispInt(MemSize);
add esp, 4 ;
pop ecx
pop edi
pop esi
ret
%include "lib.inc"
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 al, 11111110b
and eax, 7ffffffeh
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY; 段地址会在程序开始处被设置为正确的值
Code16Len equ $ - LABEL_SEG_CODE16
;end of section .s16code