nasm重寫linux-0.11 head.s (博古以通今)
;檔名:followking/boot/head.s
;本檔案改寫linux-0.11/boot/head.s,目的是為了體驗整個系統構建的過程。
;我是看著趙炯《Linux核心0.11完全註釋》編寫的。不過,我是編寫程式碼,有疑問再看。
;我用的nasm的語法格式。我想寫一個作業系統,現在覺得最簡單的方式莫過於
;先把前輩的實現的東西重新實現一遍。等到對這個問題有更深刻認識的時候,
;再重新思考,寫出有自己特色的系統。
;作者:hk0625
;開始時間: 2010年03月20號星期六 20:57
;完成時間: 2010年03月21號星期天 11:16
;最後修改時間: 2010年04月07日星期三 11:47
;修改理由:
;(1) 2010年03月26號星期五 20:51, 我把原來有下劃線的全域性變數做了修改
;(除_start外),理由現在的編譯器對c程式碼編譯後有關識別符號已經不再加下劃線了。
;地點:北化1#宿舍樓426
;下面let's try
;head.s含有32位啟動程式碼。
;注意!32位啟動程式碼是從絕對地址0x00000000開始的,這裡也同樣是頁目錄將存在的地方
;因此這裡的啟動程式碼將被頁目錄覆蓋掉。
[section .text]
global idt, gdt, pg_dir, tmp_floppy_area, _start
extern stack_start, main, printk
_start:
pg_dir: ;頁目錄將會存放在這裡。
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; mov ss, ax
lss esp, [stack_start]
; mov esp, 0xff00 ;測試用的。
call setup_idt
call setup_gdt
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; mov ss, ax
lss esp, [stack_start]
; mov esp, 0xff00 ;測試用的。
;下面測試A20地址線是否已經開啟。採用的方法是向記憶體地址0x000000處寫入任意一個
;數值,然後看記憶體地址0x100000(1M)處是否也是這個數值。如果相同,就一直比較下去
;也即是迴圈。呵呵,這裡不太明白為什麼會是這樣。
xor eax, eax
lb: inc eax
mov [0x000000], eax
cmp [0x100000], eax
je lb
;注意!在下面這段程式中,486應該將位16置位,以檢查在超級使用者模式下的防寫,
;此後“verify_area()”呼叫中就不需要了。486的使用者通常也會想將NE(#5)置位,
;一邊對數學協處理器的出錯使用 int 16。
mov eax, cr0
and eax, 0x80000011
or eax, 2
mov cr0, eax
call check_x87
jmp after_page_tables
; 依賴ET標誌的正確性來檢測287/387存在與否。
check_x87:
fninit
fstsw ax
cmp al, 0
je lf
mov eax, cr0
xor eax, 6
mov cr0, eax
ret
align 2
lf: db 0xDB, 0xE4
ret
;下面這段是設定中斷描述符表子程式 setup_idt
;將中斷描述符表idt設定成具有256個項,並都指向ignore_int中斷門。然後載入中斷
;描述符表暫存器(用lidt指令)。真正實用的中斷門以後在安裝。當我們在其他地方認為
;一切都正常時在開啟中斷。蓋子程式將會被頁表覆蓋掉。
setup_idt:
mov edx, ignore_int - $$ ;lea edx, ignore_int
mov eax, 0x00080000
mov ax, dx
mov dx, 0x8E00
mov edi, idt - $$ ;lea edx, _idt
mov ecx, 256
rp_sidt:
mov [edi], eax
mov [edi+4], edx
add edi, 8
dec ecx
jne rp_sidt
lidt [idt_descr - $$]
ret
;設定全域性描述表項setup_gdt
;這個子程式設定一個新的全域性描述表項gdt,並載入。此時僅建立了兩個表項,
;與前面的一樣。該子程式只有兩行。
;該子程式將被頁表覆蓋。
setup_gdt:
lgdt [gdt_descr - $$]
ret
;這裡將核心的記憶體頁表直接放在頁目錄之後,使用了4個表來定址16Mb的實體記憶體。
;如果你有多於16Mb的記憶體,就需要在這裡進行擴充修改。
times 4096 - ($ - $$) db 0 ;resb 0x1000 ;0rg 0x1000
pg0:
times 4096 db 0 ;resb 0x1000 ;org 0x2000
pg1:
times 4096 db 0 ;resb 0x1000 ;org 0x3000
pg2:
times 4096 db 0 ;resb 0x1000 ;org 0x4000
pg3:
times 4096 db 0 ;resb 0x1000 ;org 0x5000
;當DMA(直接儲存器訪問)不能訪問緩衝塊時,下面的tmp_floppy_area記憶體塊
;就可供軟盤驅動程式使用。其地址需要對其調整,這樣就不會跨越64kB邊界。
tmp_floppy_area:
times 1024 db 0 ;resb 1024
after_page_tables:
push 0
push 0
push 0
push L6 - $$
push main
; push L6 - $$ ;除錯用的,接下來死迴圈。
jmp setup_paging
; call setup_paging
; push 0
; push 0
; jmp main
L6:
jmp $ ;應該不會從main中返回這裡,不過為了以防萬一,
;在這裡設一條指令吧。
;下面是預設的中斷“向量控制代碼”
int_msg:
db "Unknown interrupt. in the head.s", 0xa, 0x00
align 2
ignore_int:
push eax
push ecx
push edx
push ds
push es
push fs
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
; mov ss, ax
push int_msg - $$
call printk ;在/kernel/printk.c中。
pop eax
pop fs
pop es
pop ds
pop edx
pop ecx
pop eax
jmp $
iret
;這個子程式通過設定控制暫存器cr0的標誌(PG位31)來啟動對記憶體的分頁處理功能,
;並設定各個頁表的內容,以恆等對映前16MB的物理內純。分頁假定不會產生非法的
;地址對映(也即在只有4Mb的機器上設定出大於4Mb的記憶體地址)。
align 2
setup_paging:
mov ecx, 5*1024
xor eax, eax
xor edi, edi
cld
rep
stosd
mov word [pg_dir + 0x00 - $$], pg0 + 7 - $$
mov word [pg_dir + 0x04 - $$], pg1 + 7 - $$
mov word [pg_dir + 0x08 - $$], pg2 + 7 - $$
mov word [pg_dir + 0x0c - $$], pg3 + 7 - $$
mov edi, pg3+4092 - $$
mov eax, 0xfff007
std
lb2: stosd ;這裡存在問題
sub eax, 0x1000
jge lb2
xor eax, eax
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
ret
align 2
dw 0
idt_descr:
dw 256*8-1
dd idt - $$
align 2
dw 0
gdt_descr:
dw 256*8-1
dd gdt - $$
align 8
times 0x6000 - ($ - $$) db 0
idt: times 256 dd 0, 0
gdt: dd 0x00000000
dd 0x00000000
dd 0x00000fff
dd 0x00c09a00
dd 0x00000fff
dd 0x00c09200
dd 0x00000000
dd 0x00000000
times 252 dd 0, 0
length equ $ - $$
[section .data]
[section .bss]