一个操作系统的实现(7)-获取机器内存并进行合理分页

在前面的程序中,我们用了4MB的空间来存放页表,并用它映射了4GB的内存空间,而我们的物理内存不见得有这么大,这显然是太浪费了。如果我们的内存总数只有16MB的话,只是页表就占用了25%的内存空间。而实际上,如果仅仅是对等映射的话,16MB的内存只要4个页表就够了。所以,我们有必要知道内存有多大,然后根据内存大小确定多少页表是够用的。而且,一个操作系统也必须知道内存的容量,以便进行内存管理。

克勤克俭用内存

这里利用中断15h来获取计算机的内存。

在调用中断15h之前,我们需要填充下列寄存器:

  • e a x int 15h可完成许多工作,主要由ax的值决定,我们想要获取内存信息,需要将ax赋值为0E820h。
  • e b x 放置着“后续值(continuation value)”,第一次调用时ebx必须为0。
  • e s : d i 指向一个地址范围描述符结构ARDS(Address Range Descriptor Structure),BIOS将会填充此结构。
  • e c x es:di所指向的地址范围描述符结构的大小,以字节为单位。无论es:di所指向的结构如何设置,BIOS最多将会填充ecx个字节。不过,通常情况下无论ecx为多大,BIOS只填充20字节,有些BIOS忽略ecx的值,总是填充20字节。
  • e d x 0534D4150h(‘SMAP’)──BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息会被BIOS放置到es:di所指向的结构中。

调用中断15h之后,结果存放于下列寄存器中:

  • C F CF=0表示没有错误,否则存在错误。
  • e a x 0534D4150h(‘SMAP’)。
  • e s : d i 返回的地址范围描述符结构指针,和输入值相同。
  • e c x BIOS填充在地址范围描述符中的字节数量,被BIOS所返回的最小值是20字节。
  • e b x 这里放置着为等到下一个地址描述符所需要的后续值,这个值的实际形势依赖于具体的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字节的缓冲区(代码第65行),它最多可以存放12个20字节大小的结构体。我们现在还不知道它到底够不够用,这个大小仅仅是凭猜测设定。我们将把每次得到的内存信息连续写入这块缓冲区,形成一个结构体数组。然后在保护模式下把它们读出来,显示在屏幕上,并且凭借它们得到内存的容量。

下面是调用中断15h的代码:

 65 _MemChkBuf:     times   256     db      0
...

111         ; 得到内存数
112         mov     ebx, 0
113         mov     di, _MemChkBuf
114 .loop:
115         mov     eax, 0E820h
116         mov     ecx, 20
117         mov     edx, 0534D4150h
118         int     15h
119         jc      LABEL_MEM_CHK_FAIL
120         add     di, 20
121         inc     dword [_dwMCRNumber]
122         cmp     ebx, 0
123         jne     .loop
124         jmp     LABEL_MEM_CHK_OK
125 LABEL_MEM_CHK_FAIL:
126         mov     dword [_dwMCRNumber], 0
127 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位代码中添加显示内存信息的过程。

305 DispMemSize:
306         push    esi
307         push    edi
308         push    ecx
309
310         mov     esi, MemChkBuf
311         mov     ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一个ARDS
312 .loop:                            ;{
313         mov     edx, 5            ;  for(int j=0;j<5;j++) //每次得到一个ARDS中的成员
314         mov     edi, ARDStruct    ;  {//依次显示BaseAddrLow,BaseAddrHigh,LengthLow,
315 .1:                               ;             LengthHigh,Type
316         push    dword [esi]       ;
317         call    DispInt           ;    DispInt(MemChkBuf[j*4]); //显示一个成员
318         pop     eax               ;
319         stosd                     ;    ARDStruct[j*4] = MemChkBuf[j*4];
320         add     esi, 4            ;
321         dec     edx               ;
322         cmp     edx, 0            ;
323         jnz     .1                ;  }
324         call    DispReturn        ;  printf("\n");
325         cmp     dword [dwType], 1 ;  if(Type == AddressRangeMemory)
326         jne     .2                ;  {
327         mov     eax, [dwBaseAddrLow];
328         add     eax, [dwLengthLow];
329         cmp     eax, [dwMemSize]  ;    if(BaseAddrLow + LengthLow > MemSize)
330         jb      .2                ;
331         mov     [dwMemSize], eax  ;    MemSize = BaseAddrLow + LengthLow;
332 .2:                               ;  }
333         loop    .loop             ;}
334                                   ;
335         call    DispReturn        ;printf("\n");
336         push    szRAMSize         ;
337         call    DispStr           ;printf("RAM size:");
338         add     esp, 4            ;
339                                   ;
340         push    dword [dwMemSize] ;
341         call    DispInt           ;DispInt(MemSize);
342         add     esp, 4            ;
343
344         pop     ecx
345         pop     edi
346         pop     esi
347         ret

对照右边注释中的C代码,可以很容易了解这段代码的目的:程序的主题是一个循环,循环的次数为地址范围描述符结构(下文用ARDStruct代替)的个数,每次循环将会读取一个ARDStruct。首先打印其中每一个成员的各项,然后根据当前结构的类型,得到可以被操作系统使用的内存的上限。结果会被存放在变量dwMemSize中,并在此模块的最后打印到屏幕。

其中,

DispInt函数定义如下:

 42 ;; 显示一个整形数
 43 DispInt:
 44         mov     eax, [esp + 4]
 45         shr     eax, 24
 46         call    DispAL
 47
 48         mov     eax, [esp + 4]
 49         shr     eax, 16
 50         call    DispAL
 51
 52         mov     eax, [esp + 4]
 53         shr     eax, 8
 54         call    DispAL
 55
 56         mov     eax, [esp + 4]
 57         call    DispAL
 58
 59         mov     ah, 07h                 ; 0000b: 黑底    0111b: 灰字
 60         mov     al, ‘h‘
 61         push    edi
 62         mov     edi, [dwDispPos]
 63         mov     [gs:edi], ax
 64         add     edi, 4
 65         mov     [dwDispPos], edi
 66         pop     edi
 67
 68         ret
 69 ;; DispInt 结束

DispStr定义如下:

 71 ;; 显示一个字符串
 72 DispStr:
 73         push    ebp
 74         mov     ebp, esp
 75         push    ebx
 76         push    esi
 77         push    edi
 78
 79         mov     esi, [ebp + 8]  ; pszInfo
 80         mov     edi, [dwDispPos]
 81         mov     ah, 0Fh
 82 .1:
 83         lodsb
 84         test    al, al
 85         jz      .2
 86         cmp     al, 0Ah ; 是回车吗?
 87         jnz     .3
 88         push    eax
 89         mov     eax, edi
 90         mov     bl, 160
 91         div     bl
 92         and     eax, 0FFh
 93         inc     eax
 94         mov     bl, 160
 95         mul     bl
 96         mov     edi, eax
 97         pop     eax
 98         jmp     .1
 99 .3:
100         mov     [gs:edi], ax
101         add     edi, 2
102         jmp     .1
103
104 .2:
105         mov     [dwDispPos], edi
106
107         pop     edi
108         pop     esi
109         pop     ebx
110         pop     ebp
111         ret
112 ;; DispStr 结束
113
114 ;; 换行
115 DispReturn:
116         push    szReturn
117         call    DispStr                 ;printf("\n");
118         add     esp, 4
119
120         ret
121 ;; DispReturn 结束

DispInt和DispStr函数连同DispAL、DispReturn被放在了lib.inc中,并且通过如下语句包含进pmtest7.asm中:

%include "lib.inc"

这与直接把代码写进这个位置的效果是一样的,把他们单独放到一个文件有利于阅读。

在DispInt中,[esp+4]即为已经入栈的参数,函数通过4次对DispAL的调用显示了一个整数,并且最后显示一个灰色的字母“h”。函数DispStr通过一个循环来显示字符串,每一次复制一个字符入显存,遇到\0则结束循环。同时,DispStr加入了对回车的处理,遇到0Ah就会从下一行的开始处继续显示。由于这一点,DispReturn也做了简化,通过DispStr来处理回车。

在以前的程序中,我们用edi保存当前的显示位置,从这个程序开始,我们改为用变量dwDispPos来保存。这样我们就可以放心地使用edi这个寄存器。

至此,我们新增的内容已经准备得差不多了,另外还需要提到的一点是,在数据段中,几乎每个变量都有类似的两个符号,比如:

 57 _dwMemSize:                     dd      0

 73 dwMemSize               equ     _dwMemSize      - $$

在实模式下应使用_dwMemSize,而在保护模式下应使用dwMemSize。因为程序是在实模式下编译的,地址只适用于实模式,在保护模式下,数据的地址应该是其相对于段基址的偏移。

接下来就是调用DispMemSize来显示内存信息啦:

238         push    szMemChkTitle
239         call    DispStr
240         add     esp, 4
241
242         call    DispMemSize             ; 显示内存信息

在调用DispMemSize之前,我们显示了一个字符串作为将要打印的内存信息的表格头。现在来看看结果:

上面的结果图,总共有六段内存被列出来,对列出的内存情况解释如下表:

内存段 属性 是否可被OS使用
00000000h~0009EFFFh AddressRangeMemory
0009F000h~0009FFFFh AddressRangeReserved 不可
000E8000h~000FFFFFh AddressRangeReserved 不可
00100000h~01FEFFFFh AddressRangeMemory
01FF0000h~01FFFFFFh 未定义 不可
FFFC0000h~FFFFFFFFh AddressRangeReserved 不可

从上面可以看出,操作系统能够使用的最大内存地址是01FEFFFFh,所以此极其拥有32MB-16KB的内存。而且幸运的是,我们指定的256字节的内存MemChkBuf是够用的。

你可能没有想到,得到内存容量还要这么多代码,不过,实际上我们除了得到了内存的大小,还得到了可用内存的分布信息。由于历史原因,系统可用内存分布得并不连续,所以在使用的时候,我们要根据得到的信息小心行事。

内存容量得到了,你是否还记得我们为什么要得到内存?我们是为了节约使用,不再初始化所有PDE和所有页表。现在,我们已经可以根据内存大小计算应初始化多少PDE以及多少页表,下面来修改一下函数SetupPaging。

249 ; 启动分页机制 --------------------------------------------------------------
250 SetupPaging:
251         ; 根据内存大小计算应初始化多少PDE以及多少页表
252         xor     edx, edx
253         mov     eax, [dwMemSize]
254         mov     ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
255         div     ebx
256         mov     ecx, eax        ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
257         test    edx, edx
258         jz      .no_remainder
259         inc     ecx             ; 如果余数不为 0 就需增加一个页表
260 .no_remainder:
261         push    ecx             ; 暂存页表个数
262
263         ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
264
265         ; 首先初始化页目录
266         mov     ax, SelectorPageDir     ; 此段首地址为 PageDirBase
267         mov     es, ax
268         xor     edi, edi
269         xor     eax, eax
270         mov     eax, PageTblBase | PG_P  | PG_USU | PG_RWW
271 .1:
272         stosd
273         add     eax, 4096               ; 为了简化, 所有页表在内存中是连续的.
274         loop    .1
275
276         ; 再初始化所有页表
277         mov     ax, SelectorPageTbl     ; 此段首地址为 PageTblBase
278         mov     es, ax
279         pop     eax                     ; 页表个数
280         mov     ebx, 1024               ; 每个页表 1024 个 PTE
281         mul     ebx
282         mov     ecx, eax                ; PTE个数 = 页表个数 * 1024
283         xor     edi, edi
284         xor     eax, eax
285         mov     eax, PG_P  | PG_USU | PG_RWW
286 .2:
287         stosd
288         add     eax, 4096               ; 每一页指向 4K 的空间
289         loop    .2
290
291         mov     eax, PageDirBase
292         mov     cr3, eax
293         mov     eax, cr0
294         or      eax, 80000000h
295         mov     cr0, eax
296         jmp     short .3
297 .3:
298         nop
299
300         ret
301 ; 分页机制启动完毕 ----------------------------------------------------------

在函数的开头,我们就用内存大小除以4MB来得到应初始化的PDE的个数(同时也是页表的个数)。在初始化页表的时候,通过刚刚计算出的页表个数乘以1024(每个页表含1024个PTE)得出要填充的PTE个数,然后通过循环完成对它的初始化。

这样一来,页表所占的空间就小得多,在本例中,32MB的内存实际上只要32KB的页表就够了,所以在GDT中,这样初始化页表段:

LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096*8-1,DA_DRW

这样,程序所需的内存空间就小了许多。

源代码

; ==========================================
; 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]

lib.inc:

;; 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 结束
时间: 2024-10-09 11:15:57

一个操作系统的实现(7)-获取机器内存并进行合理分页的相关文章

一个操作系统的实现(1)-准备工作

今天开始看<Orange'S:一个操作系统的实现>一书.这里是ubuntu 16.04下开发环境的搭建以及实现一个最小的操作系统(准确地说应该是一个引导扇区). 工欲善其事,必先利其器.自制一个操作系统需要的工具如下: 汇编编译器NASM 虚拟机算计Bochs 软盘绝对扇区读写工具(这里直接使用强大的dd) 现在就来安装这些工具 安装NASM 通过源代码安装,官网下载最新版源代码,当前最新的是nasm-2.12.01版本,下面的内容以此来演示. 个人软件一般安装到/usr/local下,所以压

如何正确查看Linux机器内存使用情况

如何正确查看Linux机器内存使用情况 背景 ??只要工作上涉及到Linux机器,基本上都会有这样一个需求,查看内存使用情况,但是怎么看才正确呢?之前使用的是top命令,一直存在一个误区. 为什么top命令看内存会有误区? ??top是个很好用的系统分析工具,可以实时查看进程,cpu使用率,内存使用率等情况,有点像windows下的任务管理器.我以前一直以为top看到的就是真正的内存使用情况,后来baidugoogle好久,才发现自己图样.= =|| 首先看下top命令后展示出来的内存使用情况,

java获取cpu,内存,磁盘等信息

原文:java获取cpu,内存,磁盘等信息 源代码下载地址:http://www.zuidaima.com/share/1550463331306496.htm package com.zuidaima.util; import java.io.File; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.List; imp

一个操作系统的实现(4)-认识LDT

看到这里,你应该已经很了解GDT了,如果还不了解GDT.请看这篇文章:一个操作系统的实现(2)-认识保护模式,认识保护模式那篇文章的最后详细介绍了由16位寻址升级到32位寻址而引入的GDT. LDT(Local Descriptor Table):从名字上面就可以看出来它与GDT(Gobal Descriptor Table)的区别.GDT是全局描述符表,LDT是局部描述符表(相对于GDT). 下面仍然是从代码的角度讲解什么是LDT.主要讲解在上一节的基础上增加的代码.在文章的最后会附上所有代码

SD卡路径问题以及如何获取SDCard 内存

昨天在研究拍照后突破的存储路径的问题,开始存储路径写死为:    private String folder = "/sdcard/DCIM/Camera/"(SD卡上拍照程序的图片存储路径); 后来发现这样写虽然一般不会出错,但不是很好,因为不同相机,可能路径会出问题.较好的方法是通过Environment 来获取路径,最后给出一个例子,教你怎样获取SDCard 的内存,显示出来告诉用户.讲述的内容如下:     0.获取sd卡路径.      1.讲述 Environment 类.

Android学习笔记-获取手机内存,SD卡存储空间。

前面介绍到如何保存数据到手机内存或者SD卡,但是问题是,在保存以前,我们还需要对他们的空间(可用空间),进行判断,才可以进行后续操作,所以,本节我们就介绍如何获取手机内存以及Sd卡的空间. //这时获取手机内存的 // File path = Environment.getDataDirectory(); //这时获取SD卡的空间 File path = Environment.getExternalStorageDirectory(); StatFs stat = new StatFs(pat

简介(1)-概述、一个简单的时间获取客户程序

1.概述 大多数网络应用划分:客户(client)和服务器(server) 一些复杂的网络应用:异步回调通信,即服务器向客户发起请求消息. 协议栈:应用协议.TCP协议.IP协议.以太网协议 局域网(local area network,LAN),广域网(wide area network,WAN). 路由器是广域网的架构设备. 因特网:当今最大的广域网. POSIX:一种被多数厂商采纳的标准. 2.一个简单的时间获取客户程序 1)创建套接字 socket函数 2)指定服务器的IP地址和端口 s

Android---35---openFileInput、openFileOutput获取手机内存中的数据

openFileOutput和openFileInput 获取手机内存中的文件而不是SD卡中的. Context提供了两个方法来打开本应用程序的数据文件夹里的文件I/O流. openFIleInput(String name):name文件对应的输入流 openFileOutput(String name,int mode):name文件对应的输出流 其中输出流中的第二个参数表示打开文件的模式,也可以称作权限: MODE_PRIVATE:该文件只能被当前程序读写 MODE_APPEND:以追加的

获取android 内存大小

//来源于  http://www.linuxidc.com/Linux/2013-03/81232.htm public class memInfo { // 获得可用的内存  public static long getmem_UNUSED(Context mContext) { long MEM_UNUSED; // 得到ActivityManager ActivityManager am = (ActivityManager) mContext.getSystemService(Cont