中斷和異常的概述
- 中斷(Interrupt)中斷包括硬體中斷和軟中斷。硬體中斷是由外圍裝置發出的中斷信号引發的,以請求處理器提供服務。當I/O接口發出中斷請求的時候,會被像8259A和I/O APIC這樣的中斷寄存器手機,并發送給處理器。硬體中斷完全是随機産生的,與處理器的執行并不同步。當中斷發生的時候,處理器要先執行完目前的指令(指的是正在執行的指令),然後才能對中斷進行處理。
軟中斷是由int n指令引發的中斷處理器,n是中斷号(類型碼)。
- 異常(Exception)異常就是内部中斷。内部中斷是處理器内部産生的中斷,表示在指令執行的時候遇到了錯誤。當處理器執行一條非法指令(引用一個不合标準的段,任務切換的時候TSS選擇子不是有效的,通路了一個沒有登記的頁等等)。簡單來說就是指令不能正常執行的時候,将引發這種類型的中斷。
異常分為三種:
- 程式錯誤異常,指處理器在執行指令的過程中,檢測到了程式中的錯誤,并由此引發的錯誤。
- 軟體引發的異常。這類異常通常是into,int3和bound指令主動發起的,這些指令允許在指令流的目前點上檢查實施異常處理跌條件是否滿足。比如一個例子,into指令在執行的時候,将檢查EFLAGS寄存器的OF标志位,如果滿足為“1”的條件,那麼就引發異常。
- 機器檢查異常。這種異常是處理器型号相關的,也就是說,每種處理器都不太一樣。無論如何,處理器提供了一種對硬體晶片内部和總線處理進行檢查的機制,當檢測到有錯誤的時候,将引發此異常。
根據異常情況的性質和嚴重性,異常又分為以下三種,并分别實施不同的特權保護。
- 故障(Faults)。故障通常是可以糾正的。最典型的就是處理器執行一個記憶體通路指令的時候,發現那個段或者頁不在記憶體中(P=0),此時,可以在異常處理程式中予以糾正(配置設定記憶體,或者執行磁盤的換入換出操作)。傳回時,程式可以重新啟動并且不失連續性。當故障發生的時候,處理器把及其狀态恢複到引發故障的那條指令之前的狀态,在進入異常處理時,壓入棧中的傳回位址(CS和EIP的内容)是指向引起故障的那條指令的,而不像通常那樣指向下一條指令。虛拟記憶體管理就是以異常為基礎的。
- 陷阱(Traps)。陷阱中斷通常在執行了解惑陷條件的指令立即産生,如果陷阱條件成立的話。陷阱通常用于調試目的,比如單步中斷指令int3和溢出檢測指令into。陷阱中斷允許程式或者任務從中斷處理過程傳回後繼續執行不失連續性。當陷阱異常發生的時候,轉入異常處理程式之前,處理器在棧中壓入陷阱截獲指令的下一條指令位址。
- 終止(Aborts)。終止标志着最嚴重的錯誤,諸如硬體錯誤,系統表(GDT,LDT等)中的資料不一緻或者無效。這種錯誤發生的時候,程式或者任務都不可能重新啟動。
對于某些異常,處理器在轉入異常處理程式之前,會在目前棧中壓入一個稱為錯誤代碼的數值,幫助程式進一步診斷異常産生的位置和原因。

中斷描述符表,中斷門和陷阱門
在保護模式下,處理器不是用的中斷向量表來進行中斷的,取而代之的是中斷描述符表(Interrupt Descriptor Table,IDT),中斷描述符表存放的是中斷門,陷阱門和任務門。其中中斷門和陷阱門是隻能放在IDT中。和IVT不一樣的是,IDT不要求必須位于記憶體的最低端。在處理器内部,有一個48位的中斷描述符表寄存器(Interrupt Descriptor Table Register,IDTR),儲存着中斷描述符表在記憶體中的線性基位址和界限,IDTR隻有一個,和GDTR的儲存格式是一樣的。中斷門,陷阱門描述符格式和中段描述符表寄存器的結構如下:
中斷描述符表IDT可以位于記憶體的任何地方,隻要IDTR指向了它,整個終端系統就可以正常的工作。為了利用高速緩存使處理器的工作性能最大化,處理器建議IDT的基位址是8位元組對齊的。處理器複位的時候,IDTR的基位址部分是0,界限部分是0xFFFF(和GDTR是一樣的)。處理器隻識别256個中斷,是以LDT通常隻用2KB。和GDT不一樣的是,IDT的第一個槽位可以不是0描述符。
在保護模式下處理器執行中斷的時候,先根據相應的中斷号乘以8加上IDT的基位址得到相應的中段描述符的位置(如果有頁映射也是根據頁的映射規則來找到相應的描述符),和通過調用門試試的控制轉移一樣,處理器也要對中斷和異常處理程式進行特權級的保護。但是在中斷和異常的特權級檢查中有特殊的情況。因為中斷和異常的理想兩沒有RPL,是以處理器在進入中斷或者異常處理程式的時候,或者通過人物們發起任務切換的時候,不檢查RPL。和普通的門調用一樣,CPL要在數值上小于等于目标代碼段的DPL才可以執行代碼段的切換,但是對于門的DPL的檢查中,除了軟中斷int n和單步中斷int3以及into引發的中斷和異常外,處理器不對門的DPL進行特權級檢查,如果是以上三種中斷指令引發的中斷,則要求CPL<=門描述符的DPL。(主要是為了防止軟中斷引發的越權操作)。
如果發生了特權級的轉變(比如從局部空間轉移到了全局空間)。那麼要進行棧切換。壓棧順序如下:
- 根據處理器的特權級别,從目前任務的TSS中取得棧段選擇子和棧指針。處理器把舊的棧的選擇子和棧指針壓入新棧。如果中斷處理程式的特權級别和目前特權級别一緻。則不用轉換棧。
- 處理器把EFLGAS壓入棧,然後把CS壓棧,然後再壓棧EIP。
- 如果有錯誤代碼的異常,處理器還要将錯誤代碼壓入新棧,緊挨着EIP之後。
中斷門和陷阱門的差別就是對IF位的處理不同。通過中斷門進入中斷處理程式的收,EFLAGS寄存器的IF位被處理器自動清零。以禁止嵌套的中斷,當中斷傳回的時候,從棧中恢複EFLAGS的原始狀态。陷阱中斷的優先級比較低,當通過陷阱門進入中斷處理程式的時候,EFLAGS寄存器的IF位不變,以允許其他中斷的優先處理。EFLAGS寄存器的IF位僅影響硬體中斷,對NMI,異常和int n形式的中斷不起作用。
和GDT一樣,如果要通路的位置超過了IDT的界限,那麼就會産生正常保護異常(#GP)。
中斷任務
中斷任務切換指的是通過在IDT的任務門發起的任務切換。硬體中斷發生是客觀的,可以用中斷來實作搶占式的多任務系統(硬體排程機制,代價很大,需要儲存大量的機器狀态,現代作業系統都是用的軟切換)。
可以想一下,如果在某個任務中發出了雙重中斷(#DF),是一種終止類型的中斷,如果把雙重中斷的處理程式定義成任務,那麼當雙重故障發生的時候,可以執行任務切換傳回核心,并且抹去出錯程式,回收其記憶體空間,然後執行其他排程,這樣會非常的自然。
中斷機制使用任務門有以下特點:
- 被中斷的程式或者任務的整個環境被儲存到TSS中。
- 切換的新任務有自己的棧和虛拟記憶體空間,防止系統因為出錯而崩潰。
中斷或者異常發起的任務切換,不再儲存CS和EIP的狀态,但是任務切換後,如果有錯誤代碼,還是要把錯誤代碼壓入新任務要棧中。要注意的是,任務是不可以重入的,在執行中斷任務之後和執行其iret之前,必須關中斷,以防止因為相同的中斷而産生正常保護異常(#GP)。
錯誤代碼
錯誤代碼的高16位是不用的。
- EXT位表示,異常是由外部事件引發的(External Event)。此位是1的時候,表示異常是由NMI,硬體中斷引發的。
- IDT位用于訓示描述符的位置(Descriptor Location)。為1時則表示段選擇子的索引部分是存在于中段描述符IDT的;為0時,則表示在GDT或者LDT中。
- TI位僅僅在IDT為0的時候才有意義,當此位是0時,則表示段的選擇子的索引部分是存在于GDT的,否則在LDT。
當錯誤代碼全都是0的時候,這表示異常的産生并非是由于引用一個段産生的(比如也有可能是頁錯誤,通路了一個沒有登記的頁),也有可能是因為應用了一個空描述符的時候發生的。需要注意的是,在執行iret指令從中斷處理程式傳回的時候,處理器并不會自動彈出錯誤代碼,對于那些會壓入錯誤代碼的處理過程來說,在執行iret時,必須把錯誤代碼彈出。
特别注意,對于外部異常(通過處理器引腳觸發),以及用軟中斷指令int n引發的異常,處理器不會壓入錯誤代碼,即使是有錯誤代碼的異常。配置設定給外部中斷的向量号在31~255之間,處于特殊目的,外部的8259A或者I/O APIC晶片可能給出一個0~19的向量号,比如13(正常異常保護#GP),并希望進行異常處理。在這種情況下處理器不會壓入錯誤代碼。如果用軟中斷有意引發的異常,也不會壓入錯誤代碼。
代碼清單
使用者程式
;代碼清單17-3
;檔案名:c17_1.asm
;檔案說明:使用者程式
program_length dd program_end ;程式總長度#0x00
entry_point dd start ;程式入口點#0x04
salt_position dd salt_begin ;SALT表起始偏移量#0x08
salt_items dd (salt_end-salt_begin)/256 ;SALT條目數#0x0C
;-------------------------------------------------------------------------------
;符号位址檢索表
salt_begin:
PrintString db '@PrintString'
times 256-($-PrintString) db 0
TerminateProgram db '@TerminateProgram'
times 256-($-TerminateProgram) db 0
ReadDiskData db '@ReadDiskData'
times 256-($-ReadDiskData) db 0
PrintDwordAsHex db '@PrintDwordAsHexString'
times 256-($-PrintDwordAsHex) db 0
salt_end:
message_0 db ' User task A->;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;'
db 0x0d,0x0a,0
;-------------------------------------------------------------------------------
[bits 32]
;-------------------------------------------------------------------------------
start:
mov ebx,message_0
call far [PrintString]
jmp start
call far [TerminateProgram] ;退出,并将控制權傳回到核心
;-------------------------------------------------------------------------------
program_end:
;代碼清單17-4
;檔案名:c17_2.asm
;檔案說明:使用者程式
program_length dd program_end ;程式總長度#0x00
entry_point dd start ;程式入口點#0x04
salt_position dd salt_begin ;SALT表起始偏移量#0x08
salt_items dd (salt_end-salt_begin)/256 ;SALT條目數#0x0C
;-------------------------------------------------------------------------------
;符号位址檢索表
salt_begin:
PrintString db '@PrintString'
times 256-($-PrintString) db 0
TerminateProgram db '@TerminateProgram'
times 256-($-TerminateProgram) db 0
ReadDiskData db '@ReadDiskData'
times 256-($-ReadDiskData) db 0
PrintDwordAsHex db '@PrintDwordAsHexString'
times 256-($-PrintDwordAsHex) db 0
salt_end:
message_0 db ' User task B->$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$'
db 0x0d,0x0a,0
;-------------------------------------------------------------------------------
[bits 32]
;-------------------------------------------------------------------------------
start:
mov ebx,message_0
call far [PrintString]
jmp start
call far [TerminateProgram] ;退出,并将控制權傳回到核心
;-------------------------------------------------------------------------------
program_end:
保護模式微型核心程式
;代碼清單17-2
;檔案名:c17_core.asm
;檔案說明:保護模式微型核心程式
;-------------------------------------------------------------------------------
;以下定義常量
flat_4gb_code_seg_sel equ 0x0008 ;平坦模型下的4GB代碼段選擇子
flat_4gb_data_seg_sel equ 0x0018 ;平坦模型下的4GB資料段選擇子
idt_linear_address equ 0x8001f000 ;中斷描述符表的線性基位址
;-------------------------------------------------------------------------------
;以下定義宏
%macro alloc_core_linear 0 ;在核心空間中配置設定虛拟記憶體
mov ebx,[core_tcb+0x06]
add dword [core_tcb+0x06],0x1000
call flat_4gb_code_seg_sel:alloc_inst_a_page
%endmacro
;-------------------------------------------------------------------------------
%macro alloc_user_linear 0 ;在任務空間中配置設定虛拟記憶體
mov ebx,[esi+0x06]
add dword [esi+0x06],0x1000
call flat_4gb_code_seg_sel:alloc_inst_a_page
%endmacro
;===============================================================================
SECTION core vstart=0x80040000
;以下是系統核心的頭部,用于加載核心程式
core_length dd core_end ;核心程式總長度#00
core_entry dd start ;核心代碼段入口點#04
;-------------------------------------------------------------------------------
[bits 32]
;-------------------------------------------------------------------------------
;字元串顯示例程(适用于平坦記憶體模型)
put_string: ;顯示0終止的字元串并移動光标
;輸入:EBX=字元串的線性位址
push ebx
push ecx
cli ;硬體操作期間,關中斷
.getc:
mov cl,[ebx]
or cl,cl ;檢測串結束标志(0)
jz .exit ;顯示完畢,傳回
call put_char
inc ebx
jmp .getc
.exit:
sti ;硬體操作完畢,開放中斷
pop ecx
pop ebx
retf ;段間傳回
;-------------------------------------------------------------------------------
put_char: ;在目前光标處顯示一個字元,并推進
;光标。僅用于段内調用
;輸入:CL=字元ASCII碼
pushad
;以下取目前光标位置
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
in al,dx ;高字
mov ah,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
in al,dx ;低字
mov bx,ax ;BX=代表光标位置的16位數
and ebx,0x0000ffff ;準備使用32位尋址方式通路顯存
cmp cl,0x0d ;回車符?
jnz .put_0a
mov ax,bx ;以下按回車符處理
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor
.put_0a:
cmp cl,0x0a ;換行符?
jnz .put_other
add bx,80 ;增加一行
jmp .roll_screen
.put_other: ;正常顯示字元
shl bx,1
mov [0x800b8000+ebx],cl ;在光标位置處顯示字元
;以下将光标位置推進一個字元
shr bx,1
inc bx
.roll_screen:
cmp bx,2000 ;光标超出螢幕?滾屏
jl .set_cursor
cld
mov esi,0x800b80a0 ;小心!32位模式下movsb/w/d
mov edi,0x800b8000 ;使用的是esi/edi/ecx
mov ecx,1920
rep movsd
mov bx,3840 ;清除螢幕最底一行
mov ecx,80 ;32位程式應該使用ECX
.cls:
mov word [0x800b8000+ebx],0x0720
add bx,2
loop .cls
mov bx,1920
.set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
mov al,bh
out dx,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
mov al,bl
out dx,al
popad
ret
;-------------------------------------------------------------------------------
read_hard_disk_0: ;從硬碟讀取一個邏輯扇區(平坦模型)
;EAX=邏輯扇區号
;EBX=目标緩沖區線性位址
;傳回:EBX=EBX+512
cli
push eax
push ecx
push edx
push eax
mov dx,0x1f2
mov al,1
out dx,al ;讀取的扇區數
inc dx ;0x1f3
pop eax
out dx,al ;LBA位址7~0
inc dx ;0x1f4
mov cl,8
shr eax,cl
out dx,al ;LBA位址15~8
inc dx ;0x1f5
shr eax,cl
out dx,al ;LBA位址23~16
inc dx ;0x1f6
shr eax,cl
or al,0xe0 ;第一硬碟 LBA位址27~24
out dx,al
inc dx ;0x1f7
mov al,0x20 ;讀指令
out dx,al
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬碟已準備好資料傳輸
mov ecx,256 ;總共要讀取的字數
mov dx,0x1f0
.readw:
in ax,dx
mov [ebx],ax
add ebx,2
loop .readw
pop edx
pop ecx
pop eax
sti
retf ;遠傳回
;-------------------------------------------------------------------------------
;彙編語言程式是極難一次成功,而且調試非常困難。這個例程可以提供幫助
put_hex_dword: ;在目前光标處以十六進制形式顯示
;一個雙字并推進光标
;輸入:EDX=要轉換并顯示的數字
;輸出:無
pushad
mov ebx,bin_hex ;指向核心位址空間内的轉換表
mov ecx,8
.xlt:
rol edx,4
mov eax,edx
and eax,0x0000000f
xlat
push ecx
mov cl,al
call put_char
pop ecx
loop .xlt
popad
retf
;-------------------------------------------------------------------------------
set_up_gdt_descriptor: ;在GDT内安裝一個新的描述符
;輸入:EDX:EAX=描述符
;輸出:CX=描述符的選擇子
push eax
push ebx
push edx
sgdt [pgdt] ;取得GDTR的界限和線性位址
movzx ebx,word [pgdt] ;GDT界限
inc bx ;GDT總位元組數,也是下一個描述符偏移
add ebx,[pgdt+2] ;下一個描述符的線性位址
mov [ebx],eax
mov [ebx+4],edx
add word [pgdt],8 ;增加一個描述符的大小
lgdt [pgdt] ;對GDT的更改生效
mov ax,[pgdt] ;得到GDT界限值
xor dx,dx
mov bx,8
div bx ;除以8,去掉餘數
mov cx,ax
shl cx,3 ;将索引号移到正确位置
pop edx
pop ebx
pop eax
retf
;-------------------------------------------------------------------------------
make_seg_descriptor: ;構造存儲器和系統的段描述符
;輸入:EAX=線性基位址
; EBX=段界限
; ECX=屬性。各屬性位都在原始
; 位置,無關的位清零
;傳回:EDX:EAX=描述符
mov edx,eax
shl eax,16
or ax,bx ;描述符前32位(EAX)構造完畢
and edx,0xffff0000 ;清除基位址中無關的位
rol edx,8
bswap edx ;裝配基址的31~24和23~16 (80486+)
xor bx,bx
or edx,ebx ;裝配段界限的高4位
or edx,ecx ;裝配屬性
retf
;-------------------------------------------------------------------------------
make_gate_descriptor: ;構造門的描述符(調用門等)
;輸入:EAX=門代碼在段内偏移位址
; BX=門代碼所在段的選擇子
; CX=段類型及屬性等(各屬
; 性位都在原始位置)
;傳回:EDX:EAX=完整的描述符
push ebx
push ecx
mov edx,eax
and edx,0xffff0000 ;得到偏移位址高16位
or dx,cx ;組裝屬性部分到EDX
and eax,0x0000ffff ;得到偏移位址低16位
shl ebx,16
or eax,ebx ;組裝段選擇子部分
pop ecx
pop ebx
retf
;-------------------------------------------------------------------------------
allocate_a_4k_page: ;配置設定一個4KB的頁
;輸入:無
;輸出:EAX=頁的實體位址
push ebx
push ecx
push edx
xor eax,eax
.b1:
bts [page_bit_map],eax
jnc .b2
inc eax
cmp eax,page_map_len*8
jl .b1
mov ebx,message_3
call flat_4gb_code_seg_sel:put_string
hlt ;沒有可以配置設定的頁,停機
.b2:
shl eax,12 ;乘以4096(0x1000)
pop edx
pop ecx
pop ebx
ret
;-------------------------------------------------------------------------------
alloc_inst_a_page: ;配置設定一個頁,并安裝在目前活動的
;層級分頁結構中
;輸入:EBX=頁的線性位址
push eax
push ebx
push esi
;檢查該線性位址所對應的頁表是否存在
mov esi,ebx
and esi,0xffc00000
shr esi,20 ;得到頁目錄索引,并乘以4
or esi,0xfffff000 ;頁目錄自身的線性位址+表内偏移
test dword [esi],0x00000001 ;P位是否為“1”。檢查該線性位址是
jnz .b1 ;否已經有對應的頁表
;建立該線性位址所對應的頁表
call allocate_a_4k_page ;配置設定一個頁做為頁表
or eax,0x00000007
mov [esi],eax ;在頁目錄中登記該頁表
.b1:
;開始通路該線性位址所對應的頁表
mov esi,ebx
shr esi,10
and esi,0x003ff000 ;或者0xfffff000,因高10位是零
or esi,0xffc00000 ;得到該頁表的線性位址
;得到該線性位址在頁表内的對應條目(頁表項)
and ebx,0x003ff000
shr ebx,10 ;相當于右移12位,再乘以4
or esi,ebx ;頁表項的線性位址
call allocate_a_4k_page ;配置設定一個頁,這才是要安裝的頁
or eax,0x00000007
mov [esi],eax
pop esi
pop ebx
pop eax
retf
;-------------------------------------------------------------------------------
create_copy_cur_pdir: ;建立新頁目錄,并複制目前頁目錄内容
;輸入:無
;輸出:EAX=新頁目錄的實體位址
push esi
push edi
push ebx
push ecx
call allocate_a_4k_page
mov ebx,eax
or ebx,0x00000007
mov [0xfffffff8],ebx
invlpg [0xfffffff8]
mov esi,0xfffff000 ;ESI->目前頁目錄的線性位址
mov edi,0xffffe000 ;EDI->新頁目錄的線性位址
mov ecx,1024 ;ECX=要複制的目錄項數
cld
repe movsd
pop ecx
pop ebx
pop edi
pop esi
retf
;-------------------------------------------------------------------------------
general_interrupt_handler: ;通用的中斷處理過程
push eax
mov al,0x20 ;中斷結束指令EOI
out 0xa0,al ;向從片發送
out 0x20,al ;向主片發送
pop eax
iretd
;-------------------------------------------------------------------------------
general_exception_handler: ;通用的異常處理過程
mov ebx,excep_msg
call flat_4gb_code_seg_sel:put_string
hlt
;-------------------------------------------------------------------------------
rtm_0x70_interrupt_handle: ;實時時鐘中斷處理過程
pushad
mov al,0x20 ;中斷結束指令EOI
out 0xa0,al ;向8259A從片發送
out 0x20,al ;向8259A主片發送
mov al,0x0c ;寄存器C的索引。且開放NMI
out 0x70,al
in al,0x71 ;讀一下RTC的寄存器C,否則隻發生一次中斷
;此處不考慮鬧鐘和周期性中斷的情況
;找目前任務(狀态為忙的任務)在連結清單中的位置
mov eax,tcb_chain
.b0: ;EAX=連結清單頭或目前TCB線性位址
mov ebx,[eax] ;EBX=下一個TCB線性位址
or ebx,ebx
jz .irtn ;連結清單為空,或已到末尾,從中斷傳回
cmp word [ebx+0x04],0xffff ;是忙任務(目前任務)?
je .b1
mov eax,ebx ;定位到下一個TCB(的線性位址)
jmp .b0
;将目前為忙的任務移到鍊尾
.b1:
mov ecx,[ebx] ;下遊TCB的線性位址
mov [eax],ecx ;将目前任務從鍊中拆除
.b2: ;此時,EBX=目前任務的線性位址
mov edx,[eax]
or edx,edx ;已到連結清單尾端?
jz .b3
mov eax,edx
jmp .b2
.b3:
mov [eax],ebx ;将忙任務的TCB挂在連結清單尾端
mov dword [ebx],0x00000000 ;将忙任務的TCB标記為鍊尾
;從鍊首搜尋第一個空閑任務
mov eax,tcb_chain
.b4:
mov eax,[eax]
or eax,eax ;已到鍊尾(未發現空閑任務)
jz .irtn ;未發現空閑任務,從中斷傳回
cmp word [eax+0x04],0x0000 ;是空閑任務?
jnz .b4
;将空閑任務和目前任務的狀态都取反
not word [eax+0x04] ;設定空閑任務的狀态為忙
not word [ebx+0x04] ;設定目前任務(忙)的狀态為空閑
jmp far [eax+0x14] ;任務轉換
.irtn:
popad
iretd
;-------------------------------------------------------------------------------
terminate_current_task: ;終止目前任務
;注意,執行此例程時,目前任務仍在
;運作中。此例程其實也是目前任務的
;一部分
;找目前任務(狀态為忙的任務)在連結清單中的位置
mov eax,tcb_chain
.b0: ;EAX=連結清單頭或目前TCB線性位址
mov ebx,[eax] ;EBX=下一個TCB線性位址
cmp word [ebx+0x04],0xffff ;是忙任務(目前任務)?
je .b1
mov eax,ebx ;定位到下一個TCB(的線性位址)
jmp .b0
.b1:
mov word [ebx+0x04],0x3333 ;修改目前任務的狀态為“退出”
.b2:
hlt ;停機,等待程式管理器恢複運作時,
;将其回收
jmp .b2
;-------------------------------------------------------------------------------
pgdt dw 0 ;用于設定和修改GDT
dd 0
pidt dw 0
dd 0
;任務控制塊鍊
tcb_chain dd 0
core_tcb times 32 db 0 ;核心(程式管理器)的TCB
page_bit_map db 0xff,0xff,0xff,0xff,0xff,0xff,0x55,0x55
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
page_map_len equ $-page_bit_map
;符号位址檢索表
salt:
salt_1 db '@PrintString'
times 256-($-salt_1) db 0
dd put_string
dw flat_4gb_code_seg_sel
salt_2 db '@ReadDiskData'
times 256-($-salt_2) db 0
dd read_hard_disk_0
dw flat_4gb_code_seg_sel
salt_3 db '@PrintDwordAsHexString'
times 256-($-salt_3) db 0
dd put_hex_dword
dw flat_4gb_code_seg_sel
salt_4 db '@TerminateProgram'
times 256-($-salt_4) db 0
dd terminate_current_task
dw flat_4gb_code_seg_sel
salt_item_len equ $-salt_4
salt_items equ ($-salt)/salt_item_len
excep_msg db '********Exception encounted********',0
message_0 db ' Working in system core with protection '
db 'and paging are all enabled.System core is mapped '
db 'to address 0x80000000.',0x0d,0x0a,0
message_1 db ' System wide CALL-GATE mounted.',0x0d,0x0a,0
message_3 db '********No more pages********',0
core_msg0 db ' System core task running!',0x0d,0x0a,0
bin_hex db '0123456789ABCDEF'
;put_hex_dword子過程用的查找表
core_buf times 512 db 0 ;核心用的緩沖區
cpu_brnd0 db 0x0d,0x0a,' ',0
cpu_brand times 52 db 0
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0
;-------------------------------------------------------------------------------
fill_descriptor_in_ldt: ;在LDT内安裝一個新的描述符
;輸入:EDX:EAX=描述符
; EBX=TCB基位址
;輸出:CX=描述符的選擇子
push eax
push edx
push edi
mov edi,[ebx+0x0c] ;獲得LDT基位址
xor ecx,ecx
mov cx,[ebx+0x0a] ;獲得LDT界限
inc cx ;LDT的總位元組數,即新描述符偏移位址
mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx ;安裝描述符
add cx,8
dec cx ;得到新的LDT界限值
mov [ebx+0x0a],cx ;更新LDT界限值到TCB
mov ax,cx
xor dx,dx
mov cx,8
div cx
mov cx,ax
shl cx,3 ;左移3位,并且
or cx,0000_0000_0000_0100B ;使TI位=1,指向LDT,最後使RPL=00
pop edi
pop edx
pop eax
ret
;-------------------------------------------------------------------------------
load_relocate_program: ;加載并重定位使用者程式
;輸入: PUSH 邏輯扇區号
; PUSH 任務控制塊基位址
;輸出:無
pushad
mov ebp,esp ;為通路通過堆棧傳遞的參數做準備
;清空目前頁目錄的前半部分(對應低2GB的局部位址空間)
mov ebx,0xfffff000
xor esi,esi
.b1:
mov dword [ebx+esi*4],0x00000000
inc esi
cmp esi,512
jl .b1
mov eax,cr3
mov cr3,eax ;重新整理TLB
;以下開始配置設定記憶體并加載使用者程式
mov eax,[ebp+40] ;從堆棧中取出使用者程式起始扇區号
mov ebx,core_buf ;讀取程式頭部資料
call flat_4gb_code_seg_sel:read_hard_disk_0
;以下判斷整個程式有多大
mov eax,[core_buf] ;程式尺寸
mov ebx,eax
and ebx,0xfffff000 ;使之4KB對齊
add ebx,0x1000
test eax,0x00000fff ;程式的大小正好是4KB的倍數嗎?
cmovnz eax,ebx ;不是。使用湊整的結果
mov ecx,eax
shr ecx,12 ;程式占用的總4KB頁數
mov eax,[ebp+40] ;起始扇區号
mov esi,[ebp+36] ;從堆棧中取得TCB的基位址
.b2:
alloc_user_linear ;宏:在使用者任務位址空間上配置設定記憶體
push ecx
mov ecx,8
.b3:
call flat_4gb_code_seg_sel:read_hard_disk_0
inc eax
loop .b3
pop ecx
loop .b2
;在核心位址空間内建立使用者任務的TSS
alloc_core_linear ;宏:在核心的位址空間上配置設定記憶體
;使用者任務的TSS必須在全局空間上配置設定
mov [esi+0x14],ebx ;在TCB中填寫TSS的線性位址
mov word [esi+0x12],103 ;在TCB中填寫TSS的界限值
;在使用者任務的局部位址空間内建立LDT
alloc_user_linear ;宏:在使用者任務位址空間上配置設定記憶體
mov [esi+0x0c],ebx ;填寫LDT線性位址到TCB中
;建立程式代碼段描述符
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f800 ;4KB粒度的代碼段描述符,特權級3
call flat_4gb_code_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;設定選擇子的特權級為3
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+76],cx ;填寫TSS的CS域
;建立程式資料段描述符
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f200 ;4KB粒度的資料段描述符,特權級3
call flat_4gb_code_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;設定選擇子的特權級為3
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+84],cx ;填寫TSS的DS域
mov [ebx+72],cx ;填寫TSS的ES域
mov [ebx+88],cx ;填寫TSS的FS域
mov [ebx+92],cx ;填寫TSS的GS域
;将資料段作為使用者任務的3特權級固有堆棧
alloc_user_linear ;宏:在使用者任務位址空間上配置設定記憶體
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+80],cx ;填寫TSS的SS域
mov edx,[esi+0x06] ;堆棧的高端線性位址
mov [ebx+56],edx ;填寫TSS的ESP域
;在使用者任務的局部位址空間内建立0特權級堆棧
alloc_user_linear ;宏:在使用者任務位址空間上配置設定記憶體
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c09200 ;4KB粒度的堆棧段描述符,特權級0
call flat_4gb_code_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0000B ;設定選擇子的特權級為0
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+8],cx ;填寫TSS的SS0域
mov edx,[esi+0x06] ;堆棧的高端線性位址
mov [ebx+4],edx ;填寫TSS的ESP0域
;在使用者任務的局部位址空間内建立1特權級堆棧
alloc_user_linear ;宏:在使用者任務位址空間上配置設定記憶體
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0b200 ;4KB粒度的堆棧段描述符,特權級1
call flat_4gb_code_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001B ;設定選擇子的特權級為1
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+16],cx ;填寫TSS的SS1域
mov edx,[esi+0x06] ;堆棧的高端線性位址
mov [ebx+12],edx ;填寫TSS的ESP1域
;在使用者任務的局部位址空間内建立2特權級堆棧
alloc_user_linear ;宏:在使用者任務位址空間上配置設定記憶體
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0d200 ;4KB粒度的堆棧段描述符,特權級2
call flat_4gb_code_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010B ;設定選擇子的特權級為2
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+24],cx ;填寫TSS的SS2域
mov edx,[esi+0x06] ;堆棧的高端線性位址
mov [ebx+20],edx ;填寫TSS的ESP2域
;重定位U-SALT
cld
mov ecx,[0x0c] ;U-SALT條目數
mov edi,[0x08] ;U-SALT在4GB空間内的偏移
.b4:
push ecx
push edi
mov ecx,salt_items
mov esi,salt
.b5:
push edi
push esi
push ecx
mov ecx,64 ;檢索表中,每條目的比較次數
repe cmpsd ;每次比較4位元組
jnz .b6
mov eax,[esi] ;若比對,則esi恰好指向其後的位址
mov [edi-256],eax ;将字元串改寫成偏移位址
mov ax,[esi+4]
or ax,0000000000000011B ;以使用者程式自己的特權級使用調用門
;故RPL=3
mov [edi-252],ax ;回填調用門選擇子
.b6:
pop ecx
pop esi
add esi,salt_item_len
pop edi ;從頭比較
loop .b5
pop edi
add edi,256
pop ecx
loop .b4
;在GDT中登記LDT描述符
mov esi,[ebp+36] ;從堆棧中取得TCB的基位址
mov eax,[esi+0x0c] ;LDT的起始線性位址
movzx ebx,word [esi+0x0a] ;LDT段界限
mov ecx,0x00408200 ;LDT描述符,特權級0
call flat_4gb_code_seg_sel:make_seg_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [esi+0x10],cx ;登記LDT選擇子到TCB中
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov [ebx+96],cx ;填寫TSS的LDT域
mov word [ebx+0],0 ;反向鍊=0
mov dx,[esi+0x12] ;段長度(界限)
mov [ebx+102],dx ;填寫TSS的I/O位圖偏移域
mov word [ebx+100],0 ;T=0
mov eax,[0x04] ;從任務的4GB位址空間擷取入口點
mov [ebx+32],eax ;填寫TSS的EIP域
pushfd
pop edx
mov [ebx+36],edx ;填寫TSS的EFLAGS域
;在GDT中登記TSS描述符
mov eax,[esi+0x14] ;從TCB中擷取TSS的起始線性位址
movzx ebx,word [esi+0x12] ;段長度(界限)
mov ecx,0x00408900 ;TSS描述符,特權級0
call flat_4gb_code_seg_sel:make_seg_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [esi+0x18],cx ;登記TSS選擇子到TCB
;建立使用者任務的頁目錄
;注意!頁的配置設定和使用是由頁位圖決定的,可以不占用線性位址空間
call flat_4gb_code_seg_sel:create_copy_cur_pdir
mov ebx,[esi+0x14] ;從TCB中擷取TSS的線性位址
mov dword [ebx+28],eax ;填寫TSS的CR3(PDBR)域
popad
ret 8 ;丢棄調用本過程前壓入的參數
;-------------------------------------------------------------------------------
append_to_tcb_link: ;在TCB鍊上追加任務控制塊
;輸入:ECX=TCB線性基位址
cli
push eax
push ebx
mov eax,tcb_chain
.b0: ;EAX=連結清單頭或目前TCB線性位址
mov ebx,[eax] ;EBX=下一個TCB線性位址
or ebx,ebx
jz .b1 ;連結清單為空,或已到末尾
mov eax,ebx ;定位到下一個TCB(的線性位址)
jmp .b0
.b1:
mov [eax],ecx
mov dword [ecx],0x00000000 ;目前TCB指針域清零,以訓示這是最
;後一個TCB
pop ebx
pop eax
sti
ret
;-------------------------------------------------------------------------------
start:
;建立中斷描述符表IDT
;在此之前,禁止調用put_string過程,以及任何含有sti指令的過程。
;前20個向量是處理器異常使用的
mov eax,general_exception_handler ;門代碼在段内偏移位址
mov bx,flat_4gb_code_seg_sel ;門代碼所在段的選擇子
mov cx,0x8e00 ;32位中斷門,0特權級
call flat_4gb_code_seg_sel:make_gate_descriptor
mov ebx,idt_linear_address ;中斷描述符表的線性位址
xor esi,esi
.idt0:
mov [ebx+esi*8],eax
mov [ebx+esi*8+4],edx
inc esi
cmp esi,19 ;安裝前20個異常中斷處理過程
jle .idt0
;其餘為保留或硬體使用的中斷向量
mov eax,general_interrupt_handler ;門代碼在段内偏移位址
mov bx,flat_4gb_code_seg_sel ;門代碼所在段的選擇子
mov cx,0x8e00 ;32位中斷門,0特權級
call flat_4gb_code_seg_sel:make_gate_descriptor
mov ebx,idt_linear_address ;中斷描述符表的線性位址
.idt1:
mov [ebx+esi*8],eax
mov [ebx+esi*8+4],edx
inc esi
cmp esi,255 ;安裝普通的中斷處理過程
jle .idt1
;設定實時時鐘中斷處理過程
mov eax,rtm_0x70_interrupt_handle ;門代碼在段内偏移位址
mov bx,flat_4gb_code_seg_sel ;門代碼所在段的選擇子
mov cx,0x8e00 ;32位中斷門,0特權級
call flat_4gb_code_seg_sel:make_gate_descriptor
mov ebx,idt_linear_address ;中斷描述符表的線性位址
mov [ebx+0x70*8],eax
mov [ebx+0x70*8+4],edx
;準備開放中斷
mov word [pidt],256*8-1 ;IDT的界限
mov dword [pidt+2],idt_linear_address
lidt [pidt] ;加載中斷描述符表寄存器IDTR
;設定8259A中斷控制器
mov al,0x11
out 0x20,al ;ICW1:邊沿觸發/級聯方式
mov al,0x20
out 0x21,al ;ICW2:起始中斷向量
mov al,0x04
out 0x21,al ;ICW3:從片級聯到IR2
mov al,0x01
out 0x21,al ;ICW4:非總線緩沖,全嵌套,正常EOI
mov al,0x11
out 0xa0,al ;ICW1:邊沿觸發/級聯方式
mov al,0x70
out 0xa1,al ;ICW2:起始中斷向量
mov al,0x04
out 0xa1,al ;ICW3:從片級聯到IR2
mov al,0x01
out 0xa1,al ;ICW4:非總線緩沖,全嵌套,正常EOI
;設定和時鐘中斷相關的硬體
mov al,0x0b ;RTC寄存器B
or al,0x80 ;阻斷NMI
out 0x70,al
mov al,0x12 ;設定寄存器B,禁止周期性中斷,開放更
out 0x71,al ;新結束後中斷,BCD碼,24小時制
in al,0xa1 ;讀8259從片的IMR寄存器
and al,0xfe ;清除bit 0(此位連接配接RTC)
out 0xa1,al ;寫回此寄存器
mov al,0x0c
out 0x70,al
in al,0x71 ;讀RTC寄存器C,複位未決的中斷狀态
sti ;開放硬體中斷
mov ebx,message_0
call flat_4gb_code_seg_sel:put_string
;顯示處理器品牌資訊
mov eax,0x80000002
cpuid
mov [cpu_brand + 0x00],eax
mov [cpu_brand + 0x04],ebx
mov [cpu_brand + 0x08],ecx
mov [cpu_brand + 0x0c],edx
mov eax,0x80000003
cpuid
mov [cpu_brand + 0x10],eax
mov [cpu_brand + 0x14],ebx
mov [cpu_brand + 0x18],ecx
mov [cpu_brand + 0x1c],edx
mov eax,0x80000004
cpuid
mov [cpu_brand + 0x20],eax
mov [cpu_brand + 0x24],ebx
mov [cpu_brand + 0x28],ecx
mov [cpu_brand + 0x2c],edx
mov ebx,cpu_brnd0 ;顯示處理器品牌資訊
call flat_4gb_code_seg_sel:put_string
mov ebx,cpu_brand
call flat_4gb_code_seg_sel:put_string
mov ebx,cpu_brnd1
call flat_4gb_code_seg_sel:put_string
;以下開始安裝為整個系統服務的調用門。特權級之間的控制轉移必須使用門
mov edi,salt ;C-SALT表的起始位置
mov ecx,salt_items ;C-SALT表的條目數量
.b4:
push ecx
mov eax,[edi+256] ;該條目入口點的32位偏移位址
mov bx,[edi+260] ;該條目入口點的段選擇子
mov cx,1_11_0_1100_000_00000B ;特權級3的調用門(3以上的特權級才
;允許通路),0個參數(因為用寄存器
;傳遞參數,而沒有用棧)
call flat_4gb_code_seg_sel:make_gate_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [edi+260],cx ;将傳回的門描述符選擇子回填
add edi,salt_item_len ;指向下一個C-SALT條目
pop ecx
loop .b4
;對門進行測試
mov ebx,message_1
call far [salt_1+256] ;通過門顯示資訊(偏移量将被忽略)
;初始化建立程式管理器任務的任務控制塊TCB
mov word [core_tcb+0x04],0xffff ;任務狀态:忙碌
mov dword [core_tcb+0x06],0x80100000
;核心虛拟空間的配置設定從這裡開始。
mov word [core_tcb+0x0a],0xffff ;登記LDT初始的界限到TCB中(未使用)
mov ecx,core_tcb
call append_to_tcb_link ;将此TCB添加到TCB鍊中
;為程式管理器的TSS配置設定記憶體空間
alloc_core_linear ;宏:在核心的虛拟位址空間配置設定記憶體
;在程式管理器的TSS中設定必要的項目
mov word [ebx+0],0 ;反向鍊=0
mov eax,cr3
mov dword [ebx+28],eax ;登記CR3(PDBR)
mov word [ebx+96],0 ;沒有LDT。處理器允許沒有LDT的任務。
mov word [ebx+100],0 ;T=0
mov word [ebx+102],103 ;沒有I/O位圖。0特權級事實上不需要。
;建立程式管理器的TSS描述符,并安裝到GDT中
mov eax,ebx ;TSS的起始線性位址
mov ebx,103 ;段長度(界限)
mov ecx,0x00408900 ;TSS描述符,特權級0
call flat_4gb_code_seg_sel:make_seg_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [core_tcb+0x18],cx ;登記核心任務的TSS選擇子到其TCB
;任務寄存器TR中的内容是任務存在的标志,該内容也決定了目前任務是誰。
;下面的指令為目前正在執行的0特權級任務“程式管理器”後補手續(TSS)。
ltr cx
;現在可認為“程式管理器”任務正執行中
;建立使用者任務的任務控制塊
alloc_core_linear ;宏:在核心的虛拟位址空間配置設定記憶體
mov word [ebx+0x04],0 ;任務狀态:空閑
mov dword [ebx+0x06],0 ;使用者任務局部空間的配置設定從0開始。
mov word [ebx+0x0a],0xffff ;登記LDT初始的界限到TCB中
push dword 50 ;使用者程式位于邏輯50扇區
push ebx ;壓入任務控制塊起始線性位址
call load_relocate_program
mov ecx,ebx
call append_to_tcb_link ;将此TCB添加到TCB鍊中
;建立使用者任務的任務控制塊
alloc_core_linear ;宏:在核心的虛拟位址空間配置設定記憶體
mov word [ebx+0x04],0 ;任務狀态:空閑
mov dword [ebx+0x06],0 ;使用者任務局部空間的配置設定從0開始。
mov word [ebx+0x0a],0xffff ;登記LDT初始的界限到TCB中
push dword 100 ;使用者程式位于邏輯100扇區
push ebx ;壓入任務控制塊起始線性位址
call load_relocate_program
mov ecx,ebx
call append_to_tcb_link ;将此TCB添加到TCB鍊中
.core:
mov ebx,core_msg0
call flat_4gb_code_seg_sel:put_string
;這裡可以編寫回收已終止任務記憶體的代碼
jmp .core
core_code_end:
;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end:
硬碟主引導扇區代碼
;代碼清單17-1
;檔案名:c17_mbr.asm
;檔案說明:硬碟主引導扇區代碼
;設定堆棧段和棧指針
core_base_address equ 0x00040000 ;常數,核心加載的起始記憶體位址
core_start_sector equ 0x00000001 ;常數,核心的起始邏輯扇區号
;===============================================================================
SECTION mbr vstart=0x00007c00
mov ax,cs
mov ss,ax
mov sp,0x7c00
;計算GDT所在的邏輯段位址
mov eax,[cs:pgdt+0x02] ;GDT的32位實體位址
xor edx,edx
mov ebx,16
div ebx ;分解成16位邏輯位址
mov ds,eax ;令DS指向該段以進行操作
mov ebx,edx ;段内起始偏移位址
;跳過0#号描述符的槽位
;建立1#描述符,保護模式下的代碼段描述符
mov dword [ebx+0x08],0x0000ffff ;基位址為0,界限0xFFFFF,DPL=00
mov dword [ebx+0x0c],0x00cf9800 ;4KB粒度,代碼段描述符,向上擴充
;建立2#描述符,保護模式下的資料段和堆棧段描述符
mov dword [ebx+0x10],0x0000ffff ;基位址為0,界限0xFFFFF,DPL=00
mov dword [ebx+0x14],0x00cf9200 ;4KB粒度,資料段描述符,向上擴充
;初始化描述符表寄存器GDTR
mov word [cs: pgdt],23 ;描述符表的界限
lgdt [cs: pgdt]
in al,0x92 ;南橋晶片内的端口
or al,0000_0010B
out 0x92,al ;打開A20
cli ;中斷機制尚未工作
mov eax,cr0
or eax,1
mov cr0,eax ;設定PE位
;以下進入保護模式... ...
jmp dword 0x0008:flush ;16位的描述符選擇子:32位偏移
;清流水線并串行化處理器
[bits 32]
flush:
mov eax,0x00010 ;加載資料段(4GB)選擇子
mov ds,eax
mov es,eax
mov fs,eax
mov gs,eax
mov ss,eax ;加載堆棧段(4GB)選擇子
mov esp,0x7000 ;堆棧指針
;以下加載系統核心程式
mov edi,core_base_address
mov eax,core_start_sector
mov ebx,edi ;起始位址
call read_hard_disk_0 ;以下讀取程式的起始部分(一個扇區)
;以下判斷整個程式有多大
mov eax,[edi] ;核心程式尺寸
xor edx,edx
mov ecx,512 ;512位元組每扇區
div ecx
or edx,edx
jnz @1 ;未除盡,是以結果比實際扇區數少1
dec eax ;已經讀了一個扇區,扇區總數減1
@1:
or eax,eax ;考慮實際長度≤512個位元組的情況
jz pge ;EAX=0 ?
;讀取剩餘的扇區
mov ecx,eax ;32位模式下的LOOP使用ECX
mov eax,core_start_sector
inc eax ;從下一個邏輯扇區接着讀
@2:
call read_hard_disk_0
inc eax
loop @2 ;循環讀,直到讀完整個核心
pge:
;準備打開分頁機制。從此,再也不用在段之間轉來轉去,實在暈乎~
;建立系統核心的頁目錄表PDT
mov ebx,0x00020000 ;頁目錄表PDT的實體位址
;在頁目錄内建立指向頁目錄表自己的目錄項
mov dword [ebx+4092],0x00020003
mov edx,0x00021003 ;MBR空間有限,後面盡量不使用立即數
;在頁目錄内建立與線性位址0x00000000對應的目錄項
mov [ebx+0x000],edx ;寫入目錄項(頁表的實體位址和屬性)
;此目錄項僅用于過渡。
;在頁目錄内建立與線性位址0x80000000對應的目錄項
mov [ebx+0x800],edx ;寫入目錄項(頁表的實體位址和屬性)
;建立與上面那個目錄項相對應的頁表,初始化頁表項
mov ebx,0x00021000 ;頁表的實體位址
xor eax,eax ;起始頁的實體位址
xor esi,esi
.b1:
mov edx,eax
or edx,0x00000003
mov [ebx+esi*4],edx ;登記頁的實體位址
add eax,0x1000 ;下一個相鄰頁的實體位址
inc esi
cmp esi,256 ;僅低端1MB記憶體對應的頁才是有效的
jl .b1
;令CR3寄存器指向頁目錄,并正式開啟頁功能
mov eax,0x00020000 ;PCD=PWT=0
mov cr3,eax
;将GDT的線性位址映射到從0x80000000開始的相同位置
sgdt [pgdt]
mov ebx,[pgdt+2]
add dword [pgdt+2],0x80000000 ;GDTR也用的是線性位址
lgdt [pgdt]
mov eax,cr0
or eax,0x80000000
mov cr0,eax ;開啟分頁機制
;将堆棧映射到高端,這是非常容易被忽略的一件事。應當把核心的所有東西
;都移到高端,否則,一定會和正在加載的使用者任務局部空間裡的内容沖突,
;而且很難想到問題會出在這裡。
add esp,0x80000000
jmp [0x80040004]
;-------------------------------------------------------------------------------
read_hard_disk_0: ;從硬碟讀取一個邏輯扇區
;EAX=邏輯扇區号
;DS:EBX=目标緩沖區位址
;傳回:EBX=EBX+512
push eax
push ecx
push edx
push eax
mov dx,0x1f2
mov al,1
out dx,al ;讀取的扇區數
inc dx ;0x1f3
pop eax
out dx,al ;LBA位址7~0
inc dx ;0x1f4
mov cl,8
shr eax,cl
out dx,al ;LBA位址15~8
inc dx ;0x1f5
shr eax,cl
out dx,al ;LBA位址23~16
inc dx ;0x1f6
shr eax,cl
or al,0xe0 ;第一硬碟 LBA位址27~24
out dx,al
inc dx ;0x1f7
mov al,0x20 ;讀指令
out dx,al
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬碟已準備好資料傳輸
mov ecx,256 ;總共要讀取的字數
mov dx,0x1f0
.readw:
in ax,dx
mov [ebx],ax
add ebx,2
loop .readw
pop edx
pop ecx
pop eax
ret
;-------------------------------------------------------------------------------
pgdt dw 0
dd 0x00008000 ;GDT的實體/線性位址
;-------------------------------------------------------------------------------
times 510-($-$$) db 0
db 0x55,0xaa