歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 一個操作系統的實現(7)-獲取機器內存並進行合理分頁

一個操作系統的實現(7)-獲取機器內存並進行合理分頁

日期:2017/3/1 11:49:02   编辑:關於Linux

在前面的程序中,我們用了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 結束
Copyright © Linux教程網 All Rights Reserved