org 0100h
jmp LABEL_START
;磁盤文件定義
%include "fat12hdr.inc"
%include "load.inc"
%include "pm.inc"
;GDT
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_FLAT_C: Descriptor 0,0fffffh, DA_CR
| DA_32 | DA_LIMIT_4K
LABEL_DESC_FLAT_RW: Descriptor 0,0fffffh, DA_DRW
| DA_32 | DA_LIMIT_4K
LABEL_DESC_VIDEO: Descriptor
0B8000h,0fffffh, DA_DRW |
DA_DPL3 ;顯存首地址
GdtLen equ $ - LABEL_GDT
GdtPtr dw GdtLen - 1
dd BaseOfLoaderPhyAddr + LABEL_GDT
;GDT選擇子
SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT
SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
BaseOfStack equ 0100h
LABEL_START:
mov ax,cs
mov ds,ax
mov es,ax
mov gs,ax
mov ss,ax
mov sp,BaseOfStack
mov dh,0
call DispStrRealMode
;得到內存數
mov ebx,0 ;ebx=後續值,開始時需爲0
mov di,_MemChkBuf ;es:di指向一個地址範圍描述符結構(ARDS)
.MemChkLoop:
mov eax,0E820h
mov ecx,20
mov edx,0534D4150h ;edx
= 'SMAP'
int 15h
jc .MemChkFail ;CF進位,表示有錯誤
add di,20
inc dword [_dwMCRNumber] ;dwMCRNumber
= ARDS的個數
cmp ebx,0 ;ebx置且CF未進位,則表示它是最後一個地址描述符
jne .MemChkLoop
jmp .MemChkOk
.MemChkFail:
mov dword [_dwMCRNumber],0
.MemChkOk:
;在A盤的根目錄尋找KERNEL.BIN 根目錄32位條目記錄了文件的各項屬性
; 下面在 A 盤的根目錄尋找 KERNEL.BIN
mov word [wSectorNo],
SectorNoOfRootDirectory
xor ah, ah ;
┓
xor dl, dl ; ┣
軟驅復位
int 13h ;
┛
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootDirSizeForLoop],
0 ; ┓
jz LABEL_NO_KERNELBIN ;
┣ 判斷根目錄區是不是已經讀完, 如果讀完表示沒有找到 KERNEL.BIN
dec word
[wRootDirSizeForLoop] ; ┛
mov ax, BaseOfKernelFile
mov es,
ax ; es
<- BaseOfKernelFile
mov bx,
OffsetOfKernelFile ; bx <-
OffsetOfKernelFile 於是, es:bx =
BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h +
OffsetOfKernelFile
mov ax,
[wSectorNo] ; ax
<- Root Directory 中的某 Sector 號
mov cl, 1
call ReadSector
mov si,
KernelFileName ; ds:si ->
"KERNEL BIN"
mov di,
OffsetOfKernelFile ; es:di ->
BaseOfKernelFile:???? = BaseOfKernelFile*10h+????
cld
mov dx, 10h
LABEL_SEARCH_FOR_KERNELBIN:
cmp dx,
0 ;
┓
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;
┣ 循環次數控制, 如果已經讀完了一個 Sector, 就跳到下一個 Sector
dec dx ;
┛
mov cx, 11
LABEL_CMP_FILENAME:
cmp cx,
0 ; ┓
jz LABEL_FILENAME_FOUND ;
┣ 循環次數控制, 如果比較了 11 個字符都相等, 表示找到
dec cx ;
┛
lodsb ;
ds:si -> al
cmp al, byte
[es:di] ; if al == es:di
jz LABEL_GO_ON
jmp LABEL_DIFFERENT
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME ; 繼續循環
LABEL_DIFFERENT:
and di,
0FFE0h ;
else┓ 這時di的值不知道是什麼, di &= e0 爲了讓它是
20h 的倍數
add di,
20h ;
┃
mov si,
KernelFileName ;
┣ di += 20h 下一個目錄條目
jmp LABEL_SEARCH_FOR_KERNELBIN;
┛
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
add word [wSectorNo], 1
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
LABEL_NO_KERNELBIN:
mov dh,
2 ; "No
KERNEL."
call DispStrRealMode ;
顯示字符串
%ifdef _LOADER_DEBUG_
mov ax,
4c00h ; ┓
int 21h ;
┛沒有找到 KERNEL.BIN, 回到 DOS
%else
jmp $ ;
沒有找到 KERNEL.BIN, 死循環在這裏
%endif
LABEL_FILENAME_FOUND: ;
找到 KERNEL.BIN 後便來到這裏繼續
mov ax, RootDirSectors
and di,
0FFF0h ; di ->
當前條目的開始
push eax
mov eax, [es : di +
01Ch] ; ┓
mov dword [dwKernelSize],
eax ; ┛保存 KERNEL.BIN 文件大小
pop eax
add di,
01Ah ; di -> 首
Sector
mov cx, word [es:di]
push cx ;
保存此 Sector 在 FAT 中的序號
add cx, ax
add cx,
DeltaSectorNo ; 這時 cl 裏面是 LOADER.BIN 的起始扇區號 (從 0
開始數的序號)
mov ax, BaseOfKernelFile
mov es,
ax ; es
<- BaseOfKernelFile
mov bx,
OffsetOfKernelFile ; bx <-
OffsetOfKernelFile 於是, es:bx =
BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h +
OffsetOfKernelFile
mov ax,
cx ; ax
<- Sector 號
LABEL_GOON_LOADING_FILE:
push ax ;
┓
push bx ;
┃
mov ah,
0Eh ; ┃
每讀一個扇區就在 "Loading " 後面打一個點, 形成這樣的效果:
mov al,
'.' ; ┃
mov bl,
0Fh ; ┃
Loading ......
int 10h ;
┃
pop bx ;
┃
pop ax ;
┛
mov cl, 1
call ReadSector
pop ax ;
取出此 Sector 在 FAT 中的序號
call GetFATEntry
cmp ax, 0FFFh
jz LABEL_FILE_LOADED
push ax ;
保存 Sector 在 FAT 中的序號
mov dx, RootDirSectors
add ax, dx
add ax, DeltaSectorNo
add bx, [BPB_BytsPerSec]
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
call KillMotor ; 關閉軟驅馬達
mov dh,
1 ;
"Ready."
call DispStrRealMode ;
顯示字符串
;下面準備跳入保護模式
lgdt [GdtPtr]
;關中斷
cli
;打開地址線A20
in al,92h
or al,00000010b
out 92h,al
;準備切換到保護模式
mov eax,cr0
or eax,1
mov cr0,eax
;真正進入保護模式
jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr
+ LABEL_PM_START)
jmp $
;============================================================================
;變量
;----------------------------------------------------------------------------
wRootDirSizeForLoop dw RootDirSectors ;
Root Directory 佔用的扇區數
wSectorNo dw 0 ;
要讀取的扇區號
bOdd db 0 ;
奇數還是偶數
dwKernelSize dd 0 ;
KERNEL.BIN 文件大小
;=======================================================================
;字符串部分
KernelFileName db "KERNEL
BIN",0
MessageLength equ 9
LoadMessage db "Loading
"
Message1 db "Ready.
"
Message2 db "No KERNEL"
;=======================================================================
;函數部分
;=======================================================================
;DispStrRealMode dh爲序號
DispStrRealMode:
mov ax,MessageLength
mul dh
add ax,LoadMessage
mov bp,ax ;
mov ax,ds ;es:bp
= 串地址
mov es,ax ;
mov cx,MessageLength ;cx=串長度
mov ax,01301h
mov bx,0007h
mov dl,0
add dh,3 ;從第3行往下顯示
int 10h ;int 10h
ret
;----------------------------------------------------------------------------
; 函數名: ReadSector
;----------------------------------------------------------------------------
; 作用:
; 從序號(Directory Entry 中的 Sector 號)爲 ax 的的 Sector
開始, 將 cl 個 Sector 讀入 es:bx 中
ReadSector:
;
-----------------------------------------------------------------------
; 怎樣由扇區號求扇區在磁盤中的位置 (扇區號 -> 柱面號,
起始扇區, 磁頭號)
;
-----------------------------------------------------------------------
; 設扇區號爲 x
;
┌ 柱面號 = y >> 1
;
x
┌ 商 y ┤
; -------------- =>
┤
└ 磁頭號 = y & 1
;
每磁道扇區數
│
;
└ 餘 z => 起始扇區號 = z + 1
push bp
mov bp, sp
sub esp,
2 ;
闢出兩個字節的堆棧區域保存要讀的扇區數: byte [bp-2]
mov byte [bp-2], cl
push bx ;
保存 bx
mov bl,
[BPB_SecPerTrk] ; bl: 除數
div bl ;
y 在 al 中, z 在 ah 中
inc ah ;
z ++
mov cl,
ah ; cl
<- 起始扇區號
mov dh,
al ; dh
<- y
shr al,
1 ; y
>> 1 (其實是 y/BPB_NumHeads,
這裏BPB_NumHeads=2)
mov ch,
al ; ch
<- 柱面號
and dh,
1 ; dh
& 1 = 磁頭號
pop bx ;
恢復 bx
; 至此, "柱面號, 起始扇區, 磁頭號" 全部得到
^^^^^^^^^^^^^^^^^^^^^^^^
mov dl,
[BS_DrvNum] ; 驅動器號 (0 表示 A
盤)
.GoOnReading:
mov ah,
2 ; 讀
mov al, byte
[bp-2] ; 讀 al 個扇區
int 13h
jc .GoOnReading ;
如果讀取錯誤 CF 會被置爲 1, 這時就不停地讀, 直到正確爲止
add esp, 2
pop bp
ret
;----------------------------------------------------------------------------
; 函數名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
; 找到序號爲 ax 的 Sector 在 FAT 中的條目, 結果放在 ax 中
; 需要注意的是, 中間需要讀 FAT 的扇區到 es:bx 處, 所以函數一開始保存了 es 和
bx
GetFATEntry:
push es
push bx
push ax
mov ax,
BaseOfKernelFile ; ┓
sub ax,
0100h ; ┣ 在 BaseOfKernelFile
後面留出 4K 空間用於存放 FAT
mov es,
ax ; ┛
pop ax
mov byte [bOdd], 0
mov bx, 3
mul bx ;
dx:ax = ax * 3
mov bx, 2
div bx ;
dx:ax / 2 ==> ax
<- 商, dx <- 餘數
cmp dx, 0
jz LABEL_EVEN
mov byte [bOdd], 1
LABEL_EVEN:;偶數
xor dx,
dx ; 現在 ax 中是
FATEntry 在 FAT 中的偏移量. 下面來計算 FATEntry 在哪個扇區中(FAT佔用不止一個扇區)
mov bx, [BPB_BytsPerSec]
div bx ;
dx:ax / BPB_BytsPerSec
==> ax <-
商 (FATEntry 所在的扇區相對於 FAT
來說的扇區號)
; dx
<- 餘數 (FATEntry 在扇區內的偏移)。
push dx
mov bx,
0 ; bx
<- 0 於是, es:bx = (BaseOfKernelFile
- 100):00 = (BaseOfKernelFile - 100) * 10h
add ax,
SectorNoOfFAT1 ; 此句執行之後的 ax 就是 FATEntry
所在的扇區號
mov cl, 2
call ReadSector ;
讀取 FATEntry 所在的扇區, 一次讀兩個, 避免在邊界發生錯誤, 因爲一個 FATEntry 可能跨越兩個扇區
pop dx
add bx, dx
mov ax, [es:bx]
cmp byte [bOdd], 1
jnz LABEL_EVEN_2
shr ax, 4
LABEL_EVEN_2:
and ax, 0FFFh
LABEL_GET_FAT_ENRY_OK:
pop bx
pop es
ret
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 函數名: KillMotor
;----------------------------------------------------------------------------
; 作用:
; 關閉軟驅馬達
KillMotor:
push dx
mov dx, 03F2h
mov al, 0
out dx, al
pop dx
ret
;----------------------------------------------------------------------------
;從此以後的代碼在保護模式下執行
;32位代碼段你,由實模式跳入
[SECTION .s32]
ALIGN 32
[BITS 32]
LABEL_PM_START:
mov ax,SelectorVideo
mov gs,ax
mov ax,SelectorFlatRW
mov ds,ax
mov es,ax
mov fs,ax
mov ss,ax
mov esp,TopOfStack
push szMemChkTitle
call DispStr
add esp,4
call DispMemInfo
call SetupPaging
mov ah,0Fh
mov al,'P'
mov [gs:((80*0 +39) *
2)],ax
call InitKernel
;jmp $
jmp SelectorFlatC:KernelEntryPointPhyAddr ;進入內核!!!!!!
%include "lib.inc"
; 顯示內存信息
--------------------------------------------------------------
DispMemInfo:
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
;
---------------------------------------------------------------------------
; 啓動分頁機制
--------------------------------------------------------------
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, SelectorFlatRW
mov es, ax
mov edi,
PageDirBase ; 此段首地址爲 PageDirBase
xor eax, eax
mov eax, PageTblBase |
PG_P | PG_USU | PG_RWW
.1:
stosd
add eax,
4096 ; 爲了簡化, 所有頁表在內存中是連續的.
loop .1
; 再初始化所有頁表
pop eax ;
頁表個數
mov ebx,
1024 ; 每個頁表 1024 個 PTE
mul ebx
mov ecx,
eax ; PTE個數 = 頁表個數 * 1024
mov edi,
PageTblBase ; 此段首地址爲 PageTblBase
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
; 分頁機制啓動完畢
----------------------------------------------------------
; InitKernel
---------------------------------------------------------------------------------
; 將 KERNEL.BIN 的內容經過整理對齊後放到新的位置
; 遍歷每一個 Program Header,根據 Program Header
中的信息來確定把什麼放進內存,放到什麼位置,以及放多少。
;
--------------------------------------------------------------------------------------------
InitKernel:
xor esi,esi
mov cx,word
[BaseOfKernelFilePhyAddr +
2CH] ;ecx<-pElfHdr->e_phnum
movzx ecx,cx
mov esi,[BaseOfKernelFilePhyAddr
+ 1Ch]
add esi,BaseOfKernelFilePhyAddr
.Begin:
mov eax,[esi + 0]
cmp eax,0
jz .NoAction
push dword [esi + 010h]
mov eax,[esi + 04h]
add eax,BaseOfKernelFilePhyAddr
push eax
push dword [esi + 08h]
call MemCpy
add esp,12
.NoAction:
add esi,020h
dec ecx
jnz .Begin
ret
;32位data段
[SECTION .data1]
ALIGN 32
LABEL_DATA:
; 實模式下使用這些符號
; 字符串
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow
LengthHigh Type", 0Ah, 0
_szRAMSize: db "RAM size:", 0
_szReturn: db 0Ah, 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
;
;; 保護模式下使用這些符號
szMemChkTitle equ BaseOfLoaderPhyAddr
+ _szMemChkTitle
szRAMSize equ BaseOfLoaderPhyAddr
+ _szRAMSize
szReturn equ BaseOfLoaderPhyAddr
+ _szReturn
dwDispPos equ BaseOfLoaderPhyAddr
+ _dwDispPos
dwMemSize equ BaseOfLoaderPhyAddr
+ _dwMemSize
dwMCRNumber equ BaseOfLoaderPhyAddr
+ _dwMCRNumber
ARDStruct equ BaseOfLoaderPhyAddr
+ _ARDStruct
dwBaseAddrLow equ BaseOfLoaderPhyAddr
+ _dwBaseAddrLow
dwBaseAddrHigh equ BaseOfLoaderPhyAddr
+ _dwBaseAddrHigh
dwLengthLow equ BaseOfLoaderPhyAddr
+ _dwLengthLow
dwLengthHigh equ BaseOfLoaderPhyAddr
+ _dwLengthHigh
dwType equ BaseOfLoaderPhyAddr
+ _dwType
MemChkBuf equ BaseOfLoaderPhyAddr
+ _MemChkBuf
; 堆棧就在數據段的末尾
StackSpace: times 1024 db 0
TopOfStack equ BaseOfLoaderPhyAddr
+ $ ; 棧頂
; SECTION .data1 之結束
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^