天天看點

中斷和異常的處理與搶占式多任務

中斷和異常的概述

  • 中斷(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
           

繼續閱讀