赞
踩
这篇文章不长,主要是末尾的代码长,代码有兴趣可以看,没兴趣的看看文章内容便可。
利用内存的大小设置页表的个数的公式是:页表个数=内存大小÷4MB。
为什么是这样呢?首先,80386一个页的大小是4KB,页是通过页表来找到的,而一个页表中有1024个页,所以一个页表可寻址的物理内存就是4MB,所以页表个数=内存大小÷4MB,这样我们就可以克勤克俭用内存啦。
计算页表个数的代码是这几行:
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
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加一个页表
其中[dwMemSize]中保存着内存大小的数值,获得内存的大小是程序的主要部分。
此程序采用调用15h中断获得内存大小,在调用中断15h之前,需要填充如下寄存器:
①eax:int 15h可完成许多工作,主要由ax的值决定,我们想要获取内存信息,需要将ax赋值为0E820h。
②ebx:放置着“后续值(continuation value)”,第一次调用时ebx必须为0。
③es:di:指向一个地址范围描述符结构ARDS(Address Range Descriptor Structure),BIOS将会填充此结构。
④ecx:es:di所指向的地址范围描述符结构的大小,以字节为单位。无论es:di所指向的结构如何设置,BIOS最多将会填充ecx个字节。不过,通常情况下无论ecx为多大,BIOS只填充20字节,有些BIOS忽略ecx的值,总是填充20字节。
⑤edx 0534D4150h(‘SMAP’)──BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息会被BIOS放置到es:di所指向的结构中。
中断调用之后,结果存放于下列寄存器之中。
①CF:CF=0表示没有错误,否则存在错误。
②eax:0534D4150h(‘SMAP’)。
③es:di:返回的地址范围描述符结构指针,和输入值相同。
④ecx:BIOS填充在地址范围描述符中的字节数量,被BIOS所返回的最小值是20字节。
⑤ebx:这里放置着为等到下一个地址描述符所需要的后续值,这个值的实际形势依赖于具体的BIOS的实现,调用者不必关心它的具体形式,只需在下次迭代时将其原封不动地放置到ebx中,就可以通过它获取下一个地址范围描述符。如果它的值为0,并且CF没有进位,表示它是最后一个地址范围描述符。
上面的几个寄存器值介绍看起来可能很枯燥,没关系,获得内存需要理解的跟上面的关系不大,上面的主要是一些准备工作,主要是理解下面的。
地址范围描述符结构(Address Range Descriptor Structure):
| 偏移 | 名称 | 意义 |
|---|---|---|
| 0 | BaseAddrLow | 基地址的低32位 |
| 4 | BaseAddrHigh | 基地址的高32位 |
| 8 | LengthLow | 长度(字节)的低32位 |
| 12 | LengthHigh | 长度(字节)的高32位 |
| 16 | Type | 这个地址范围的地址类型 |
其中,Type的取值及其意义如下表:
| 取值 | 名称 | 意义 |
|---|---|---|
| 1 | AddressRangeMemory | 这个内存段是一段可以被OS使用的RAM |
| 2 | AddressRangeReserved | 这个地址段正在被使用,或者系统保留所以一定不要被OS使用 |
| 其他 | 未定义 | 保留,为未来使用,任何其他值都必须被OS认为是AddressRangeReserved |
ax=0E820h时调用int 15h得到的不仅仅是内存的大小,还包括对不同内存段的一些描述。而且,这些描述都被保存在一个缓冲区中。所以,在我们调用int 15h之前,必须先有缓冲区。我们可以在每得到一次内存描述时都使用同一个缓冲区,然后对缓冲区里的数据进行处理,也可以将每次得到的数据放进不同的位置,比如一块连续的内存,然后在想要处理它们时再读取。后一种方式可能更方便一些,所以在这里定义了一块256字节的缓冲区,它最多可以存放12个20字节大小的结构体。
_MemChkBuf: times 256 db 0 ; 得到内存数 mov ebx, 0 mov di, _MemChkBuf .loop: mov eax, 0E820h mov ecx, 20 mov edx, 0534D4150h int 15h jc LABEL_MEM_CHK_FAIL add di, 20 inc dword [_dwMCRNumber] cmp ebx, 0 jne .loop jmp LABEL_MEM_CHK_OK LABEL_MEM_CHK_FAIL: mov dword [_dwMCRNumber], 0 LABEL_MEM_CHK_OK:
代码使用了一个循环,一旦CF被置位或者ebx为零,循环将结束。在第一次循环开始之前,eax为0000E820h,ebx为0,ecx为20,edx为0534D4150h,es:di指向_MemChkBuf的开始处。在每一次循环进行时,寄存器di的值将会递增,每次的增量为20字节。另外,eax、ecx和edx的值都不会变,ebx的值我们置之不理。同时,每次循环我们让_dwMCRNumber的值加1,这样到循环结束时它的值会是循环的次数,同时也是地址范围描述符结构的个数。
主要是下面保护模式下的32位代码,添加显示内存信息的过程(包括内存大小是怎么来的的代码)。
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中的成员 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
其中[dwMemSize]这个地址里存的就是内存的大小,看C语言注释可以知道如果BaseAddrLow + LengthLow > MemSize的话MemSize = BaseAddrLow + LengthLow。
这里有两个问题,一个就是为什么要判断BaseAddrLow + LengthLow > MemSize,一个就是为什么MemSize = BaseAddrLow + LengthLow。
判断BaseAddrLow + LengthLow > MemSize是因为代码前面的这两句:
_dwMemSize: dd 0
dwMemSize equ _dwMemSize - $$
可以知道,dwMemSize最开始是为0的,所以BaseAddrLow + LengthLow必须是要大于0的。
MemSize = BaseAddrLow + LengthLow的原因:
内存大小其实是等于Base Address (BaseAddrLow+BaseAddrHigh)+Length(LengthLow+LengthHigh),但是,光一个BaseAddrLow + LengthLow就是32位了,它能表示的内存大小是2^32bit,也就是4GB内存,当时的物理内存不见得有这么大,所以完全可以用BaseAddrLow + LengthLow表示内存大小。
程序中还有一些打印内存信息的代码,在这里不多说,此篇笔记只说明获取内存大小以及计算页表个数的那一部分。
最后再附上完整代码(有兴趣可以看,上面一行基本就是文章的结尾了,代码只是提供给有兴趣看代码的人一个方便):
; ========================================== ; pmtest7.asm ; 编译方法:nasm pmtest7.asm -o pmtest7.com ; ========================================== %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, 4096 * 8 - 1, DA_DRW ; 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: ; 实模式下使用这些符号 ; 字符串 _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] [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 add di, 20 inc dword [_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 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, [_wSPValueInRealMode] 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: 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 jz .no_remainder inc ecx ; 如果余数不为 0 就需增加一个页表 .no_remainder: push ecx ; 暂存页表个数 ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. ; 首先初始化页目录 mov ax, SelectorPageDir ; 此段首地址为 PageDirBase mov es, ax xor edi, edi xor eax, eax mov eax, PageTblBase | PG_P | PG_USU | PG_RWW .1: stosd add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. loop .1 ; 再初始化所有页表 mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase mov es, ax pop eax ; 页表个数 mov ebx, 1024 ; 每个页表 1024 个 PTE 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 mov eax, cr0 or eax, 80000000h mov cr0, eax 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中的成员 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 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]
还有一个头文件%include “lib.inc” :
; ------------------------------------------------------------------------ ; 显示 AL 中的数字 ; ------------------------------------------------------------------------ DispAL: push ecx push edx push edi mov edi, [dwDispPos] mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 mov dl, al shr al, 4 mov ecx, 2 .begin: and al, 01111b cmp al, 9 ja .1 add al, '0' jmp .2 .1: sub al, 0Ah add al, 'A' .2: mov [gs:edi], ax add edi, 2 mov al, dl loop .begin ;add edi, 2 mov [dwDispPos], edi pop edi pop edx pop ecx ret ; DispAL 结束------------------------------------------------------------- ; ------------------------------------------------------------------------ ; 显示一个整形数 ; ------------------------------------------------------------------------ DispInt: mov eax, [esp + 4] shr eax, 24 call DispAL mov eax, [esp + 4] shr eax, 16 call DispAL mov eax, [esp + 4] shr eax, 8 call DispAL mov eax, [esp + 4] call DispAL mov ah, 07h ; 0000b: 黑底 0111b: 灰字 mov al, 'h' push edi mov edi, [dwDispPos] mov [gs:edi], ax add edi, 4 mov [dwDispPos], edi pop edi ret ; DispInt 结束------------------------------------------------------------ ; ------------------------------------------------------------------------ ; 显示一个字符串 ; ------------------------------------------------------------------------ DispStr: push ebp mov ebp, esp push ebx push esi push edi mov esi, [ebp + 8] ; pszInfo mov edi, [dwDispPos] mov ah, 0Fh .1: lodsb test al, al jz .2 cmp al, 0Ah ; 是回车吗? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax jmp .1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [dwDispPos], edi pop edi pop esi pop ebx pop ebp ret ; DispStr 结束------------------------------------------------------------ ; ------------------------------------------------------------------------ ; 换行 ; ------------------------------------------------------------------------ DispReturn: push szReturn call DispStr ;printf("\n"); add esp, 4 ret ; DispReturn 结束--------------------------------------------------------- ; ------------------------------------------------------------------------ ; 内存拷贝,仿 memcpy ; ------------------------------------------------------------------------ ; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); ; ------------------------------------------------------------------------ MemCpy: push ebp mov ebp, esp push esi push edi push ecx mov edi, [ebp + 8] ; Destination mov esi, [ebp + 12] ; Source mov ecx, [ebp + 16] ; Counter .1: cmp ecx, 0 ; 判断计数器 jz .2 ; 计数器为零时跳出 mov al, [ds:esi] ; ┓ inc esi ; ┃ ; ┣ 逐字节移动 mov byte [es:edi], al ; ┃ inc edi ; ┛ dec ecx ; 计数器减一 jmp .1 ; 循环 .2: mov eax, [ebp + 8] ; 返回值 pop ecx pop edi pop esi mov esp, ebp pop ebp ret ; 函数结束,返回 ; MemCpy 结束-------------------------------------------------------------
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。