?? head.s.bak
字號:
;/* passed
; * linux/boot/head.s
; *
; * (C) 1991 Linus Torvalds
; */
.386p
.model flat
;/*
; * head.s 含有32 位啟動代碼。
; *
; * 注意!!! 32 位啟動代碼是從絕對地址0x00000000 開始的,這里也同樣
; * 是頁目錄將存在的地方,因此這里的啟動代碼將被頁目錄覆蓋掉。
; *
; */
extrn _stack_start:far ptr,_main_rename:far,_printk:far ptr
public _idt,_gdt,_pg_dir,_tmp_floppy_area
.code
_pg_dir: ;// 頁目錄將會存放在這里。
_startup_32: ;// 以下5行設置各個數據段寄存器。指向gdt數據段描述符項
mov eax,10h
;// 再次注意!!! 這里已經處于32 位運行模式,因此這里的$0x10 并不是把地址0x10 裝入各
;// 個段寄存器,它現在其實是全局段描述符表中的偏移值,或者更正確地說是一個描述符表
;// 項的選擇符。有關選擇符的說明請參見setup.s 中的說明。這里$0x10 的含義是請求特權
;// 級0(位0-1=0)、選擇全局描述符表(位2=0)、選擇表中第2 項(位3-15=2)。它正好指向表中
;// 的數據段描述符項。(描述符的具體數值參見前面setup.s )。下面代碼的含義是:
;// 置ds,es,fs,gs 中的選擇符為setup.s 中構造的數據段(全局段描述符表的第2 項)=0x10,
;// 并將堆棧放置在數據段中的_stack_start 數組內,然后使用新的中斷描述符表和全局段
;// 描述表.新的全局段描述表中初始內容與setup.s 中的完全一樣。
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
lss esp,_stack_start ;// 表示_stack_start -> ss:esp,設置系統堆棧。
;// stack_start 定義在kernel/sched.c,69 行。
call setup_idt ;// 調用設置中斷描述符表子程序。
call setup_gdt ;// 調用設置全局描述符表子程序。
mov eax,10h ;// reload all the segment registers
mov ds,ax ;// after changing gdt. CS was already
mov es,ax ;// reloaded in 'setup_gdt'
mov fs,ax ;// 因為修改了gdt,所以需要重新裝載所有的段寄存器。
mov gs,ax ;// CS 代碼段寄存器已經在setup_gdt 中重新加載過了。
lss esp,_stack_start
;// 以下5行用于測試A20 地址線是否已經開啟。采用的方法是向內存地址0x000000 處寫入任意
;// 一個數值,然后看內存地址0x100000(1M)處是否也是這個數值。如果一直相同的話,就一直
;// 比較下去,也即死循環、死機。表示地址A20 線沒有選通,結果內核就不能使用1M 以上內存。
xor eax,eax
l1: inc eax ;// check that A20 really IS enabled
mov ds:[0],eax ;// loop forever if it isn't
cmp ds:[100000h],eax
je l1 ;// '1b'表示向后(backward)跳轉到標號1 去。
;// 若是'5f'則表示向前(forward)跳轉到標號5 去。
;/*
;* 注意! 在下面這段程序中,486 應該將位16 置位,以檢查在超級用戶模式下的寫保護,
;* 此后"verify_area()"調用中就不需要了。486 的用戶通常也會想將NE(;//5)置位,以便
;* 對數學協處理器的出錯使用int 16。
;*/
;// 下面這段程序用于檢查數學協處理器芯片是否存在。方法是修改控制寄存器CR0,在假設
;// 存在協處理器的情況下執行一個協處理器指令,如果出錯的話則說明協處理器芯片不存
;// 在,需要設置CR0 中的協處理器仿真位EM(位2),并復位協處理器存在標志MP(位1)。
mov eax,cr0 ;// check math chip
and eax,80000011h ;// Save PG,PE,ET
;/* "orl $0x10020,%eax" here for 486 might be good */
or eax,2 ;// set MP
mov cr0,eax
call check_x87
jmp after_page_tables
;/*
;* 我們依賴于ET 標志的正確性來檢測287/387 存在與否。
;*/
check_x87:
fninit
fstsw ax
cmp al,0
je l2 ;/* no coprocessor: have to set bits */
mov eax,cr0 ;// 如果存在的則向前跳轉到標號1 處,否則改寫cr0。
xor eax,6 ;/* reset MP, set EM */
mov cr0,eax
ret
align 2 ;// 這里".align 2"的含義是指存儲邊界對齊調整。
l2: ;// 即按4 字節方式對齊內存地址。
db 0DBh,0E4h ;/* 287 協處理器碼。 */
ret
;/*
; * 下面這段是設置中斷描述符表子程序setup_idt
; *
; * 將中斷描述符表idt 設置成具有256 個項,并都指向ignore_int 中斷門。然后加載
; * 中斷描述符表寄存器(用lidt 指令)。真正實用的中斷門以后再安裝。當我們在其它
; * 地方認為一切都正常時再開啟中斷。該子程序將會被頁表覆蓋掉。
; */
setup_idt:
lea edx,ignore_int ;// 將ignore_int 的有效地址(偏移值)值 edx 寄存器
mov eax,00080000h ;// 將選擇符0x0008 置入eax 的高16 位中。
mov ax,dx ;/* selector = 0x0008 = cs */
;// 偏移值的低16 位置入eax 的低16 位中。此時eax 含
;// 有門描述符低4 字節的值。
mov dx,8E00h ;/* interrupt gate - dpl=0, present */
;// 此時edx 含有門描述符高4 字節的值。
lea edi,_idt
mov ecx,256
rp_sidt:
mov [edi],eax ;// 將啞中斷門描述符存入表中。
mov [edi+4],edx
add edi,8 ;// edi 指向表中下一項。
dec ecx
jne rp_sidt
lidt fword ptr idt_descr ;// 加載中斷描述符表寄存器值。
ret
;/*
; * 下面這段是設置全局描述符表項setup_gdt
; *
; * 這個子程序設置一個新的全局描述符表gdt,并加載。此時僅創建了兩個表項,與前
; * 面的一樣。該子程序只有兩行,“非常的”復雜,所以當然需要這么長的注釋了:)。
; */
setup_gdt:
lgdt fword ptr gdt_descr ;// 加載全局描述符表寄存器(內容已設置好,見232-238 行)。
ret
;/*
; * Linus 將內核的內存頁表直接放在頁目錄之后,使用了4 個表來尋址16 Mb 的物理內存。
; * 如果你有多于16 Mb 的內存,就需要在這里進行擴充修改。
; */
;// 每個頁表長為4 Kb 字節,而每個頁表項需要4 個字節,因此一個頁表共可以存放1000 個,
;// 表項如果一個表項尋址4 Kb 的地址空間,則一個頁表就可以尋址4 Mb 的物理內存。頁表項
;// 的格式為:項的前0-11 位存放一些標志,如是否在內存中(P 位0)、讀寫許可(R/W 位1)、
;// 普通用戶還是超級用戶使用(U/S 位2)、是否修改過(是否臟了)(D 位6)等;表項的位12-31
;// 是頁框地址,用于指出一頁內存的物理起始地址。
org 1000h ;// 從偏移0x1000 處開始是第1 個頁表(偏移0 開始處將存放頁表目錄)。
pg0:
org 2000h
pg1:
org 3000h
pg2:
org 4000h
pg3:
org 5000h ;// 定義下面的內存數據塊從偏移0x5000 處開始。
;/*
; * 當DMA(直接存儲器訪問)不能訪問緩沖塊時,下面的tmp_floppy_area 內存塊
; * 就可供軟盤驅動程序使用。其地址需要對齊調整,這樣就不會跨越64kB 邊界。
; */
_tmp_floppy_area:
db 1024 dup(0) ;// 共保留1024 項,每項1 字節,填充數值0 。
;// 下面這幾個入棧操作(pushl)用于為調用/init/main.c 程序和返回作準備。
;// 前面3 個入棧指令不知道作什么用的,也許是Linus 用于在調試時能看清機器碼用的.。
;// 139 行的入棧操作是模擬調用main.c 程序時首先將返回地址入棧的操作,所以如果
;// main.c 程序真的退出時,就會返回到這里的標號L6 處繼續執行下去,也即死循環。
;// 140 行將main.c 的地址壓入堆棧,這樣,在設置分頁處理(setup_paging)結束后
;// 執行'ret'返回指令時就會將main.c 程序的地址彈出堆棧,并去執行main.c 程序去了。
after_page_tables:
push 0 ;// These are the parameters to main :-)
push 0 ;// 這些是調用main 程序的參數(指init/main.c)。
push 0
push L6 ;// return address for main, if it decides to.
push _main_rename ;// '_main'是編譯程序對main 的內部表示方法。
jmp setup_paging
L6:
jmp L6 ;// main should never return here, but
;// just in case, we know what happens.
;/* 下面是默認的中斷“向量句柄” :-) */
int_msg:
db "Unknown interrupt\n\r" ;// 定義字符串“未知中斷(回車換行)”。
align 2 ;// 按4 字節方式對齊內存地址。
ignore_int:
push eax
push ecx
push edx
push ds ;// 這里請注意!!ds,es,fs,gs 等雖然是16 位的寄存器,但入棧后
push es ;// 仍然會以32 位的形式入棧,也即需要占用4 個字節的堆棧空間。
push fs
mov eax,10h ;// 置段選擇符(使ds,es,fs 指向gdt 表中的數據段)。
mov ds,ax
mov es,ax
mov fs,ax
push int_msg ;// 把調用printk 函數的參數指針(地址)入棧。
call _printk ;// 該函數在/kernel/printk.c 中。
;// '_printk'是printk 編譯后模塊中的內部表示法。
pop eax
pop fs
pop es
pop ds
pop edx
pop ecx
pop eax
iret ;// 中斷返回(把中斷調用時壓入棧的CPU 標志寄存器(32 位)值也彈出)。
;/*
; * Setup_paging
; *
; * 這個子程序通過設置控制寄存器cr0 的標志(PG 位31)來啟動對內存的分頁處理
; * 功能,并設置各個頁表項的內容,以恒等映射前16 MB 的物理內存。分頁器假定
; * 不會產生非法的地址映射(也即在只有4Mb 的機器上設置出大于4Mb 的內存地址)。
; *
; * 注意!盡管所有的物理地址都應該由這個子程序進行恒等映射,但只有內核頁面管
; * 理函數能直接使用>1Mb 的地址。所有“一般”函數僅使用低于1Mb 的地址空間,或
; * 者是使用局部數據空間,地址空間將被映射到其它一些地方去-- mm(內存管理程序)
; * 會管理這些事的。
; *
; * 對于那些有多于16Mb 內存的家伙- 太幸運了,我還沒有,為什么你會有:-)。代碼就
; * 在這里,對它進行修改吧。(實際上,這并不太困難的。通常只需修改一些常數等。
; * 我把它設置為16Mb,因為我的機器再怎么擴充甚至不能超過這個界限(當然,我的機
; * 器很便宜的:-))。我已經通過設置某類標志來給出需要改動的地方(搜索“16Mb”),
; * 但我不能保證作這些改動就行了 :-( )
; */
align 2 ;// 按4 字節方式對齊內存地址邊界。
setup_paging: ;// 首先對5 頁內存(1 頁目錄+ 4 頁頁表)清零
mov ecx,1024*5 ;/* 5 pages - pg_dir+4 page tables */
xor eax,eax
xor edi,edi ;/* pg_dir is at 0x000 */
;// 頁目錄從0x000 地址開始。
pushf ;// VC內匯編使用cld和std后,需要自己恢復DF的值
cld
rep stosd
;// 下面4 句設置頁目錄中的項,我們共有4 個頁表所以只需設置4 項。
;// 頁目錄項的結構與頁表中項的結構一樣,4 個字節為1 項。參見上面的說明。
;// "$pg0+7"表示:0x00001007,是頁目錄表中的第1 項。
;// 則第1 個頁表所在的地址= 0x00001007 & 0xfffff000 = 0x1000;第1 個頁表
;// 的屬性標志= 0x00001007 & 0x00000fff = 0x07,表示該頁存在、用戶可讀寫。
mov eax,_pg_dir
mov [eax],pg0+7 ;/* set present bit/user r/w */
mov [eax+4],pg1+7 ;/* --------- " " --------- */
mov [eax+8],pg2+7 ;/* --------- " " --------- */
mov [eax+12],pg3+7 ;/* --------- " " --------- */
;// 下面6 行填寫4 個頁表中所有項的內容,共有:4(頁表)*1024(項/頁表)=4096 項(0 - 0xfff),
;// 也即能映射物理內存4096*4Kb = 16Mb。
;// 每項的內容是:當前項所映射的物理內存地址+ 該頁的標志(這里均為7)。
;// 使用的方法是從最后一個頁表的最后一項開始按倒退順序填寫。一個頁表的最后一項
;// 在頁表中的位置是1023*4 = 4092。因此最后一頁的最后一項的位置就是$pg3+4092。
mov edi,pg3+4092 ;// edi -> 最后一頁的最后一項。
mov eax,00fff007h ;/* 16Mb - 4096 + 7 (r/w user,p) */
;// 最后1 項對應物理內存頁面的地址是0xfff000,
;// 加上屬性標志7,即為0xfff007.
std ;// 方向位置位,edi 值遞減(4 字節)。
L3: stosd ;/* fill pages backwards - more efficient :-) */
sub eax,00001000h ;// 每填寫好一項,物理地址值減0x1000。
jge L3 ;// 如果小于0 則說明全添寫好了。
popf
;// 設置頁目錄基址寄存器cr3 的值,指向頁目錄表。
xor eax,eax ;/* 頁目錄表(pg_dir)在0x0000 處。 */
mov cr3,eax ;/* cr3 - page directory start */
;// 設置啟動使用分頁處理(cr0 的PG 標志,位31)
mov eax,cr0
or eax,80000000h ;// 添上PG 標志。
mov cr0,eax ;/* set paging (PG) bit */
ret ;/* this also flushes prefetch-queue */
;// 在改變分頁處理標志后要求使用轉移指令刷新預取指令隊列,這里用的是返回指令ret。
;// 該返回指令的另一個作用是將堆棧中的main 程序的地址彈出,并開始運行/init/main.c
;// 程序。本程序到此真正結束了。
align 2 ;// 按4 字節方式對齊內存地址邊界。
dw 0
;//下面兩行是lidt 指令的6 字節操作數:長度,基址。
idt_descr:
dw 256*8-1
dd _idt ;// idt contains 256 entries
align 2
dw 0
;// 下面兩行是lgdt 指令的6 字節操作數:長度,基址。
gdt_descr:
dw 256*8-1 ;// so does gdt (not that that's any
dd _gdt ;// magic number, but it works for me :^)
align 4 ;// 按8 字節方式對齊內存地址邊界。
_idt:
DQ 256 dup(0) ;// idt is uninitialized // 256 項,每項8 字節,填0。
;// 全局表。前4 項分別是空項(不用)、代碼段描述符、數據段描述符、系統段描述符,
;// 其中系統段描述符linux 沒有派用處。后面還預留了252 項的空間,用于放置所創建
;// 任務的局部描述符(LDT)和對應的任務狀態段TSS 的描述符。
;// (0-nul, 1-cs, 2-ds, 3-sys, 4-TSS0, 5-LDT0, 6-TSS1, 7-LDT1, 8-TSS2 etc...)
_gdt:
DQ 0000000000000000h ;/* NULL descriptor */
DQ 00c09a0000000fffh ;/* 16Mb */ // 代碼段最大長度16M。
DQ 00c0920000000fffh ;/* 16Mb */ // 數據段最大長度16M。
DQ 0000000000000000h ;/* TEMPORARY - don't use */
DQ 252 dup(0) ;/* space for LDT's and TSS's etc */
end
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -