1. 程式人生 > >nasm重寫linux-0.11 head.s (博古以通今)

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]