任務切換

任務切換的方法
第一個方法就是借助中斷來進行任務切換,這是現代搶占式多任務的基礎。在實模式下,記憶體最低端1KB是中斷向量表,儲存着256個中斷處理過程的段位址和偏移位址。在保護模式下,處理器不再使用中斷向量表,而是使用中斷描述符表。中段描述符表和GDT,LDT是一樣的,用于儲存描述符,唯一不同的地方是,他儲存的是門描述符,包括中斷門,陷阱門和任務門。中斷門和陷阱門允許在任務内實施中斷處理,轉到全局空間去執行一些系統級的管理工作,本質上,也是任務内的控制轉移行為。但是,在中斷發生的時候,如果該中斷号對應的門是任務門,那麼必須進行任務切換(中斷目前任務的執行,保護目前任務的現場,并轉移到另一個任務去執行)。
任務門的樣子如上,任務門也是一個系統段,是以S一定是0,TYPE位是0101表明是任務門。任務門的P位來表示這個門是否有效,當P=0時候,不允許通過此門來進行任務切換,DPL是任務門描述符的特權級,但是對因為中斷而發起的任務切換不起作用,處理器不按特權級施加任何保護。
當中斷發生的時候,處理器用中斷号乘以8作為索引通路中段描述符表,當這個門是任務門時,則處理器會取出新任務的TSS選擇子,然後用TSS的選擇子通路GDT,取出TSS描述符。然後,處理器通路新的TSS,從中恢複在TSS登記的所有東西。然後,TR開始指向新任務的TSS,一旦新任務開始執行,則處理器固件會自動将TSS描述符的B位設定為1(表示在忙)。
當中斷發生的時候,可以執行正常的中斷處理過程。也可以進行任務切換。這兩者都使用iret指令傳回。前者是傳回到同一個任務的不同代碼段,後者是傳回到被中斷的任務。處理器通過EFLAGS位來區分這兩者的差別。
本質上,因為現在中斷的描述符全部都是門,是以都是可以根據NT位來确定如何進行傳回的。如果一個任務的切換是因為中斷發起的,發生任務切換以後,舊任務的TSS描述符的B位不會改變,處理器将現場的TSS的所有需要填寫的項都填寫好,然後把舊任務的TSS描述符選擇子寫入新任務的任務連接配接域中(0x00)處,同時将新任務EFLAGS寄存器的NT位置變為1,以允許新任務傳回舊任務的操作,同時還要把新任務的TSS描述符的B位設定為1(忙)。
回到iret指令上來,當處理器看見iret指令,則會立馬檢查NT位,如果這個位是0,那麼就按一般的中斷傳回操作進行;如果這個位是1,那麼就會傳回原先被中斷的任務的位置繼續進行舊任務,同時把NT位複原,并把切換前的任務的TSS描述符的B位設定為0。然後TR指向原來被切換的任務的TSS,進行任務切換。
處理器切換任務的方式有4種:
- 目前程式,任務或者過程執行控制轉移到GDT内某個TSS描述符(使用jmp或者call進行切換)。
- 目前程式,任務或者過程執行控制轉移到GDT或者目前LDT内某個任務門描述符(任務門可以在任務的LDT上,TSS一定要在GDT上)(使用jmp或者call進行切換)。
- 一個異常或者中斷發生時候,中斷号指向中斷描述表内的任務門。
- 在EFLAGS寄存器的NT位置為的情況下,目前任務執行一個iret指令。
由call指令發起的任務切換類似于因為中斷發起的任務切換,也就是說,call指令發起的任務切換是嵌套的,目前任務(舊任務)TSS描述符的B位保持原來的“1”不變。EFLAGS寄存器的NT位也不會發生變化。新任務的TSS描述符的B位置1,EFLAGS的NT位也置1,表示此任務嵌套于其他任務中,同時,TSS任務連接配接域的内容改為舊任務的TSS描述符選擇子。
和call指令不同,使用jmp指令發起的任務切換,不會形成任務的嵌套關系。執行任務切換的時候,目前任務(舊任務)的TSS描述符清零,變為非忙狀态,EFLAGS寄存器的NT位不變;新任務的TSS描述符的B位置1,進入忙的狀态,EFLAGS寄存器的NT位保持從TSS中加載時的狀态度不變。
不管如何,任務是不可以重入的,本質上來說,就是發起任務切換的時候,新任務的TSS描述符的B位不能是1,處理器每次切換任務都會檢查TSS描述符的B位。
在進行任務切換的時候,處理器執行以下操作:
- 使用JMP或者CALL指令的操作數(注意任務門的選擇子問題,和調用門一樣,進行遠調用的時候任務門會忽略32位的偏移位址,直接用16位的選擇子部分),任務門或者目前任務的TSS任務連接配接域取得新任務的TSS描述符選擇子。
- 檢查是否允許從目前任務切換到新任務,資料通路的特權級檢查适用于jmp和call指令,目前(舊)任務的CPL和新任務段的RPL必須在數值上小于或者等于目标TSS或者任務門的DPL。異常,中斷(除了由int n指令發起的中斷)和iret指令引起的任務切換忽略任務門或者TSS描述符的DPL,對于以int n發起的任務切換,要檢查DPL。
- 檢查新任務的TSS描述符是否被标記為有效(P=1),而且界限要大于103,同時檢查任務的B位,除了iret指令發起的切換,其他切換B位要等于0(防止任務重入),iret指令發起的切換B=1。
- 檢查目前任務(舊任務)和新任務的TSS,以及所有在任務切換時要用到的段描述符已經安排到系統記憶體中。
- 如果任務切換是由jmp或者iret發起的,處理器清除目前(舊)任務的B位,如果是由call,異常或者中斷發起的,那麼B位就維持原來的狀态。
- 如果任務切換是由iret發起的,那麼處理器建立EFLAGS寄存器的副本并且把NT位清除,如果是其他三種方式發起的切換,那麼副本中的NT位保持不變。
- 儲存目前任務狀态到他的TSS中(TSS所需要的所有副本)。
- 如果任務切換是由call,異常或者中斷發起的,則處理器從新任務加載的EFLAGS寄存器的NT位置1;如果是由iret或者jmp發起的,則NT位保持不變。
- 如果任務是由call,jmp,異常或者中斷發起的,則處理器将新任務的TSS描述符的B位置1;如果由iret發起,則B位保持不變。
- 用新任務的TSS選擇子和TSS描述符加載到任務寄存器TR(注意描述符的基位址,段界限和屬性加載到TR的高速緩存器中了)。
- 新任務的TSS狀态資料被加載到處理器(TSS裡面有的都要加載)。載入期間隻要有一個發生故障,架構狀态會被破壞(無法撤銷)。所謂架構狀态就是從一個狀态轉移到另一個狀态是一确定的,不會出現不可預知的狀況。
- 段選擇子對應的描述符也會被加載,與加載和驗證新環境有關的任務錯誤都會破壞架構狀态,如果前面1-10步出現了不可恢複的錯誤,處理器将不能完成任務切換,并且確定處理器傳回到執行發起任務切換的那條指令前的狀态,如果在11步出現了不可恢複的錯誤,架構狀态将會被破壞。如果在12步(送出點)以後發生了不可恢複錯誤,處理器将會完成任務切換并且在開始執行新任務之前産生一個相應的異常。
- 開始執行新任務。任務切換的時候,新任務的特權級别不是從那個被挂起的任務繼承來的。新任務的特權級别都是由其段寄存器CS的低2位決定的,而該寄存器的内容取自新任務的TSS。因為每個任務都有自己的獨立位址空間和任務狀态段TSS,是以任務之間是彼此隔離的。隻需要用特權級規則控制對TSS的通路就行,軟體不需要在任務切換時進行顯式的特權級檢查。
代碼清單
;代碼清單15-2
;檔案名:c15.asm
;檔案說明:使用者程式
;===============================================================================
SECTION header vstart=0
program_length dd program_end ;程式總長度#0x00
head_len dd header_end ;程式頭部的長度#0x04
stack_seg dd 0 ;用于接收堆棧段選擇子#0x08
stack_len dd 1 ;程式建議的堆棧大小#0x0c
;以4KB為機關
prgentry dd start ;程式入口#0x10
code_seg dd section.code.start ;代碼段位置#0x14
code_len dd code_end ;代碼段長度#0x18
data_seg dd section.data.start ;資料段位置#0x1c
data_len dd data_end ;資料段長度#0x20
;-------------------------------------------------------------------------------
;符号位址檢索表
salt_items dd (header_end-salt)/256 ;#0x24
salt: ;#0x28
PrintString db '@PrintString'
times 256-($-PrintString) db 0
TerminateProgram db '@TerminateProgram'
times 256-($-TerminateProgram) db 0
ReadDiskData db '@ReadDiskData'
times 256-($-ReadDiskData) db 0
header_end:
;===============================================================================
SECTION data vstart=0
message_1 db 0x0d,0x0a
db '[USER TASK]: Hi! nice to meet you,'
db 'I am run at CPL=',0
message_2 db 0
db '.Now,I must exit...',0x0d,0x0a,0
data_end:
;===============================================================================
[bits 32]
;===============================================================================
SECTION code vstart=0
start:
;任務啟動時,DS指向頭部段,也不需要設定堆棧
mov eax,ds
mov fs,eax
mov eax,[data_seg]
mov ds,eax
mov ebx,message_1
call far [fs:PrintString]
mov ax,cs
and al,0000_0011B
or al,0x0030
mov [message_2],al
mov ebx,message_2
call far [fs:PrintString]
call far [fs:TerminateProgram] ;退出,并将控制權傳回到核心
code_end:
;-------------------------------------------------------------------------------
SECTION trail
;-------------------------------------------------------------------------------
program_end:
;代碼清單15-1
;檔案名:c15_core.asm
;檔案說明:保護模式微型核心程式
;以下常量定義部分。核心的大部分内容都應當固定
core_code_seg_sel equ 0x38 ;核心代碼段選擇子
core_data_seg_sel equ 0x30 ;核心資料段選擇子
sys_routine_seg_sel equ 0x28 ;系統公共例程代碼段的選擇子
video_ram_seg_sel equ 0x20 ;視訊顯示緩沖區的段選擇子
core_stack_seg_sel equ 0x18 ;核心堆棧段選擇子
mem_0_4_gb_seg_sel equ 0x08 ;整個0-4GB記憶體的段的選擇子
;-------------------------------------------------------------------------------
;以下是系統核心的頭部,用于加載核心程式
core_length dd core_end ;核心程式總長度#00
sys_routine_seg dd section.sys_routine.start
;系統公用例程段位置#04
core_data_seg dd section.core_data.start
;核心資料段位置#08
core_code_seg dd section.core_code.start
;核心代碼段位置#0c
core_entry dd start ;核心代碼段入口點#10
dw core_code_seg_sel
;===============================================================================
[bits 32]
;===============================================================================
SECTION sys_routine vstart=0 ;系統公共例程代碼段
;-------------------------------------------------------------------------------
;字元串顯示例程
put_string: ;顯示0終止的字元串并移動光标
;輸入:DS:EBX=串位址
push ecx
.getc:
mov cl,[ebx]
or cl,cl
jz .exit
call put_char
inc ebx
jmp .getc
.exit:
pop ecx
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位數
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: ;正常顯示字元
push es
mov eax,video_ram_seg_sel ;0xb8000段的選擇子
mov es,eax
shl bx,1
mov [es:bx],cl
pop es
;以下将光标位置推進一個字元
shr bx,1
inc bx
.roll_screen:
cmp bx,2000 ;光标超出螢幕?滾屏
jl .set_cursor
push ds
push es
mov eax,video_ram_seg_sel
mov ds,eax
mov es,eax
cld
mov esi,0xa0 ;小心!32位模式下movsb/w/d
mov edi,0x00 ;使用的是esi/edi/ecx
mov ecx,1920
rep movsd
mov bx,3840 ;清除螢幕最底一行
mov ecx,80 ;32位程式應該使用ECX
.cls:
mov word[es:bx],0x0720
add bx,2
loop .cls
pop es
pop ds
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=邏輯扇區号
;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
retf ;段間傳回
;-------------------------------------------------------------------------------
;彙編語言程式是極難一次成功,而且調試非常困難。這個例程可以提供幫助
put_hex_dword: ;在目前光标處以十六進制形式顯示
;一個雙字并推進光标
;輸入:EDX=要轉換并顯示的數字
;輸出:無
pushad
push ds
mov ax,core_data_seg_sel ;切換到核心資料段
mov ds,ax
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
pop ds
popad
retf
;-------------------------------------------------------------------------------
allocate_memory: ;配置設定記憶體
;輸入:ECX=希望配置設定的位元組數
;輸出:ECX=起始線性位址
push ds
push eax
push ebx
mov eax,core_data_seg_sel
mov ds,eax
mov eax,[ram_alloc]
add eax,ecx ;下一次配置設定時的起始位址
;這裡應當有檢測可用記憶體數量的指令
mov ecx,[ram_alloc] ;傳回配置設定的起始位址
mov ebx,eax
and ebx,0xfffffffc
add ebx,4 ;強制對齊
test eax,0x00000003 ;下次配置設定的起始位址最好是4位元組對齊
cmovnz eax,ebx ;如果沒有對齊,則強制對齊
mov [ram_alloc],eax ;下次從該位址配置設定記憶體
;cmovcc指令可以避免控制轉移
pop ebx
pop eax
pop ds
retf
;-------------------------------------------------------------------------------
set_up_gdt_descriptor: ;在GDT内安裝一個新的描述符
;輸入:EDX:EAX=描述符
;輸出:CX=描述符的選擇子
push eax
push ebx
push edx
push ds
push es
mov ebx,core_data_seg_sel ;切換到核心資料段
mov ds,ebx
sgdt [pgdt] ;以便開始處理GDT
mov ebx,mem_0_4_gb_seg_sel
mov es,ebx
movzx ebx,word [pgdt] ;GDT界限
inc bx ;GDT總位元組數,也是下一個描述符偏移
add ebx,[pgdt+2] ;下一個描述符的線性位址
mov [es:ebx],eax
mov [es: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 es
pop ds
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
;-------------------------------------------------------------------------------
terminate_current_task: ;終止目前任務
;注意,執行此例程時,目前任務仍在
;運作中。此例程其實也是目前任務的
;一部分
pushfd
mov edx,[esp] ;獲得EFLAGS寄存器内容
add esp,4 ;恢複堆棧指針
mov eax,core_data_seg_sel
mov ds,eax
test dx,0100_0000_0000_0000B ;測試NT位
jnz .b1 ;目前任務是嵌套的,到.b1執行iretd
mov ebx,core_msg1 ;目前任務不是嵌套的,直接切換到
call sys_routine_seg_sel:put_string
jmp far [prgman_tss] ;程式管理器任務
.b1:
mov ebx,core_msg0
call sys_routine_seg_sel:put_string
iretd
sys_routine_end:
;===============================================================================
SECTION core_data vstart=0 ;系統核心的資料段
;-------------------------------------------------------------------------------
pgdt dw 0 ;用于設定和修改GDT
dd 0
ram_alloc dd 0x00100000 ;下次配置設定記憶體時的起始位址
;符号位址檢索表
salt:
salt_1 db '@PrintString'
times 256-($-salt_1) db 0
dd put_string
dw sys_routine_seg_sel
salt_2 db '@ReadDiskData'
times 256-($-salt_2) db 0
dd read_hard_disk_0
dw sys_routine_seg_sel
salt_3 db '@PrintDwordAsHexString'
times 256-($-salt_3) db 0
dd put_hex_dword
dw sys_routine_seg_sel
salt_4 db '@TerminateProgram'
times 256-($-salt_4) db 0
dd terminate_current_task
dw sys_routine_seg_sel
salt_item_len equ $-salt_4
salt_items equ ($-salt)/salt_item_len
message_1 db ' If you seen this message,that means we '
db 'are now in protect mode,and the system '
db 'core is loaded,and the video display '
db 'routine works perfectly.',0x0d,0x0a,0
message_2 db ' System wide CALL-GATE mounted.',0x0d,0x0a,0
bin_hex db '0123456789ABCDEF'
;put_hex_dword子過程用的查找表
core_buf times 2048 db 0 ;核心用的緩沖區
cpu_brnd0 db 0x0d,0x0a,' ',0
cpu_brand times 52 db 0
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0
;任務控制塊鍊
tcb_chain dd 0
;程式管理器的任務資訊
prgman_tss dd 0 ;程式管理器的TSS基位址
dw 0 ;程式管理器的TSS描述符選擇子
prgman_msg1 db 0x0d,0x0a
db '[PROGRAM MANAGER]: Hello! I am Program Manager,'
db 'run at CPL=0.Now,create user task and switch '
db 'to it by the CALL instruction...',0x0d,0x0a,0
prgman_msg2 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am glad to regain control.'
db 'Now,create another user task and switch to '
db 'it by the JMP instruction...',0x0d,0x0a,0
prgman_msg3 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am gain control again,'
db 'HALT...',0
core_msg0 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'CALL instruction or an exeception/ interrupt,'
db 'should use IRETD instruction to switch back...'
db 0x0d,0x0a,0
core_msg1 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'JMP instruction, should switch to Program '
db 'Manager directly by the JMP instruction...'
db 0x0d,0x0a,0
core_data_end:
;===============================================================================
SECTION core_code vstart=0
;-------------------------------------------------------------------------------
fill_descriptor_in_ldt: ;在LDT内安裝一個新的描述符
;輸入:EDX:EAX=描述符
; EBX=TCB基位址
;輸出:CX=描述符的選擇子
push eax
push edx
push edi
push ds
mov ecx,mem_0_4_gb_seg_sel
mov ds,ecx
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 ds
pop edi
pop edx
pop eax
ret
;-------------------------------------------------------------------------------
load_relocate_program: ;加載并重定位使用者程式
;輸入: PUSH 邏輯扇區号
; PUSH 任務控制塊基位址
;輸出:無
pushad
push ds
push es
mov ebp,esp ;為通路通過堆棧傳遞的參數做準備
mov ecx,mem_0_4_gb_seg_sel
mov es,ecx
mov esi,[ebp+11*4] ;從堆棧中取得TCB的基位址
;以下申請建立LDT所需要的記憶體
mov ecx,160 ;允許安裝20個LDT描述符
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x0c],ecx ;登記LDT基位址到TCB中
mov word [es:esi+0x0a],0xffff ;登記LDT初始的界限到TCB中
;以下開始加載使用者程式
mov eax,core_data_seg_sel
mov ds,eax ;切換DS到核心資料段
mov eax,[ebp+12*4] ;從堆棧中取出使用者程式起始扇區号
mov ebx,core_buf ;讀取程式頭部資料
call sys_routine_seg_sel:read_hard_disk_0
;以下判斷整個程式有多大
mov eax,[core_buf] ;程式尺寸
mov ebx,eax
and ebx,0xfffffe00 ;使之512位元組對齊(能被512整除的數低
add ebx,512 ;9位都為0
test eax,0x000001ff ;程式的大小正好是512的倍數嗎?
cmovnz eax,ebx ;不是。使用湊整的結果
mov ecx,eax ;實際需要申請的記憶體數量
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x06],ecx ;登記程式加載基位址到TCB中
mov ebx,ecx ;ebx -> 申請到的記憶體首位址
xor edx,edx
mov ecx,512
div ecx
mov ecx,eax ;總扇區數
mov eax,mem_0_4_gb_seg_sel ;切換DS到0-4GB的段
mov ds,eax
mov eax,[ebp+12*4] ;起始扇區号
.b1:
call sys_routine_seg_sel:read_hard_disk_0
inc eax
loop .b1 ;循環讀,直到讀完整個使用者程式
mov edi,[es:esi+0x06] ;獲得程式加載基位址
;建立程式頭部段描述符
mov eax,edi ;程式頭部起始線性位址
mov ebx,[edi+0x04] ;段長度
dec ebx ;段界限
mov ecx,0x0040f200 ;位元組粒度的資料段描述符,特權級3
call sys_routine_seg_sel:make_seg_descriptor
;安裝頭部段描述符到LDT中
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;設定選擇子的特權級為3
mov [es:esi+0x44],cx ;登記程式頭部段選擇子到TCB
mov [edi+0x04],cx ;和頭部内
;建立程式代碼段描述符
mov eax,edi
add eax,[edi+0x14] ;代碼起始線性位址
mov ebx,[edi+0x18] ;段長度
dec ebx ;段界限
mov ecx,0x0040f800 ;位元組粒度的代碼段描述符,特權級3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;設定選擇子的特權級為3
mov [edi+0x14],cx ;登記代碼段選擇子到頭部
;建立程式資料段描述符
mov eax,edi
add eax,[edi+0x1c] ;資料段起始線性位址
mov ebx,[edi+0x20] ;段長度
dec ebx ;段界限
mov ecx,0x0040f200 ;位元組粒度的資料段描述符,特權級3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;設定選擇子的特權級為3
mov [edi+0x1c],cx ;登記資料段選擇子到頭部
;建立程式堆棧段描述符
mov ecx,[edi+0x0c] ;4KB的倍率
mov ebx,0x000fffff
sub ebx,ecx ;得到段界限
mov eax,4096
mul ecx
mov ecx,eax ;準備為堆棧配置設定記憶體
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;得到堆棧的高端實體位址
mov ecx,0x00c0f600 ;位元組粒度的堆棧段描述符,特權級3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;設定選擇子的特權級為3
mov [edi+0x08],cx ;登記堆棧段選擇子到頭部
;重定位SALT
mov eax,mem_0_4_gb_seg_sel ;這裡和前一章不同,頭部段描述符
mov es,eax ;已安裝,但還沒有生效,故隻能通
;過4GB段通路使用者程式頭部
mov eax,core_data_seg_sel
mov ds,eax
cld
mov ecx,[es:edi+0x24] ;U-SALT條目數(通過通路4GB段取得)
add edi,0x28 ;U-SALT在4GB段内的偏移
.b2:
push ecx
push edi
mov ecx,salt_items
mov esi,salt
.b3:
push edi
push esi
push ecx
mov ecx,64 ;檢索表中,每條目的比較次數
repe cmpsd ;每次比較4位元組
jnz .b4
mov eax,[esi] ;若比對,則esi恰好指向其後的位址
mov [es:edi-256],eax ;将字元串改寫成偏移位址
mov ax,[esi+4]
or ax,0000000000000011B ;以使用者程式自己的特權級使用調用門
;故RPL=3
mov [es:edi-252],ax ;回填調用門選擇子
.b4:
pop ecx
pop esi
add esi,salt_item_len
pop edi ;從頭比較
loop .b3
pop edi
add edi,256
pop ecx
loop .b2
mov esi,[ebp+11*4] ;從堆棧中取得TCB的基位址
;建立0特權級堆棧
mov ecx,4096
mov eax,ecx ;為生成堆棧高端位址做準備
mov [es:esi+0x1a],ecx
shr dword [es:esi+0x1a],12 ;登記0特權級堆棧尺寸到TCB
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;堆棧必須使用高端位址為基位址
mov [es:esi+0x1e],eax ;登記0特權級堆棧基位址到TCB
mov ebx,0xffffe ;段長度(界限)
mov ecx,0x00c09600 ;4KB粒度,讀寫,特權級0
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
;or cx,0000_0000_0000_0000 ;設定選擇子的特權級為0
mov [es:esi+0x22],cx ;登記0特權級堆棧選擇子到TCB
mov dword [es:esi+0x24],0 ;登記0特權級堆棧初始ESP到TCB
;建立1特權級堆棧
mov ecx,4096
mov eax,ecx ;為生成堆棧高端位址做準備
mov [es:esi+0x28],ecx
shr [es:esi+0x28],12 ;登記1特權級堆棧尺寸到TCB
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;堆棧必須使用高端位址為基位址
mov [es:esi+0x2c],eax ;登記1特權級堆棧基位址到TCB
mov ebx,0xffffe ;段長度(界限)
mov ecx,0x00c0b600 ;4KB粒度,讀寫,特權級1
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001 ;設定選擇子的特權級為1
mov [es:esi+0x30],cx ;登記1特權級堆棧選擇子到TCB
mov dword [es:esi+0x32],0 ;登記1特權級堆棧初始ESP到TCB
;建立2特權級堆棧
mov ecx,4096
mov eax,ecx ;為生成堆棧高端位址做準備
mov [es:esi+0x36],ecx
shr [es:esi+0x36],12 ;登記2特權級堆棧尺寸到TCB
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;堆棧必須使用高端位址為基位址
mov [es:esi+0x3a],ecx ;登記2特權級堆棧基位址到TCB
mov ebx,0xffffe ;段長度(界限)
mov ecx,0x00c0d600 ;4KB粒度,讀寫,特權級2
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010 ;設定選擇子的特權級為2
mov [es:esi+0x3e],cx ;登記2特權級堆棧選擇子到TCB
mov dword [es:esi+0x40],0 ;登記2特權級堆棧初始ESP到TCB
;在GDT中登記LDT描述符
mov eax,[es:esi+0x0c] ;LDT的起始線性位址
movzx ebx,word [es:esi+0x0a] ;LDT段界限
mov ecx,0x00408200 ;LDT描述符,特權級0
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [es:esi+0x10],cx ;登記LDT選擇子到TCB中
;建立使用者程式的TSS
mov ecx,104 ;tss的基本尺寸
mov [es:esi+0x12],cx
dec word [es:esi+0x12] ;登記TSS界限值到TCB
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x14],ecx ;登記TSS基位址到TCB
;登記基本的TSS表格内容
mov word [es:ecx+0],0 ;反向鍊=0
mov edx,[es:esi+0x24] ;登記0特權級堆棧初始ESP
mov [es:ecx+4],edx ;到TSS中
mov dx,[es:esi+0x22] ;登記0特權級堆棧段選擇子
mov [es:ecx+8],dx ;到TSS中
mov edx,[es:esi+0x32] ;登記1特權級堆棧初始ESP
mov [es:ecx+12],edx ;到TSS中
mov dx,[es:esi+0x30] ;登記1特權級堆棧段選擇子
mov [es:ecx+16],dx ;到TSS中
mov edx,[es:esi+0x40] ;登記2特權級堆棧初始ESP
mov [es:ecx+20],edx ;到TSS中
mov dx,[es:esi+0x3e] ;登記2特權級堆棧段選擇子
mov [es:ecx+24],dx ;到TSS中
mov dx,[es:esi+0x10] ;登記任務的LDT選擇子
mov [es:ecx+96],dx ;到TSS中
mov dx,[es:esi+0x12] ;登記任務的I/O位圖偏移
mov [es:ecx+102],dx ;到TSS中
mov word [es:ecx+100],0 ;T=0
mov dword [es:ecx+28],0 ;登記CR3(PDBR)
;通路使用者程式頭部,擷取資料填充TSS
mov ebx,[ebp+11*4] ;從堆棧中取得TCB的基位址
mov edi,[es:ebx+0x06] ;使用者程式加載的基位址
mov edx,[es:edi+0x10] ;登記程式入口點(EIP)
mov [es:ecx+32],edx ;到TSS
mov dx,[es:edi+0x14] ;登記程式代碼段(CS)選擇子
mov [es:ecx+76],dx ;到TSS中
mov dx,[es:edi+0x08] ;登記程式堆棧段(SS)選擇子
mov [es:ecx+80],dx ;到TSS中
mov dx,[es:edi+0x04] ;登記程式資料段(DS)選擇子
mov word [es:ecx+84],dx ;到TSS中。注意,它指向程式頭部段
mov word [es:ecx+72],0 ;TSS中的ES=0
mov word [es:ecx+88],0 ;TSS中的FS=0
mov word [es:ecx+92],0 ;TSS中的GS=0
pushfd
pop edx
mov dword [es:ecx+36],edx ;EFLAGS
;在GDT中登記TSS描述符
mov eax,[es:esi+0x14] ;TSS的起始線性位址
movzx ebx,word [es:esi+0x12] ;段長度(界限)
mov ecx,0x00408900 ;TSS描述符,特權級0
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [es:esi+0x18],cx ;登記TSS選擇子到TCB
pop es ;恢複到調用此過程前的es段
pop ds ;恢複到調用此過程前的ds段
popad
ret 8 ;丢棄調用本過程前壓入的參數
;-------------------------------------------------------------------------------
append_to_tcb_link: ;在TCB鍊上追加任務控制塊
;輸入:ECX=TCB線性基位址
push eax
push edx
push ds
push es
mov eax,core_data_seg_sel ;令DS指向核心資料段
mov ds,eax
mov eax,mem_0_4_gb_seg_sel ;令ES指向0..4GB段
mov es,eax
mov dword [es: ecx+0x00],0 ;目前TCB指針域清零,以訓示這是最
;後一個TCB
mov eax,[tcb_chain] ;TCB表頭指針
or eax,eax ;連結清單為空?
jz .notcb
.searc:
mov edx,eax
mov eax,[es: edx+0x00]
or eax,eax
jnz .searc
mov [es: edx+0x00],ecx
jmp .retpc
.notcb:
mov [tcb_chain],ecx ;若為空表,直接令表頭指針指向TCB
.retpc:
pop es
pop ds
pop edx
pop eax
ret
;-------------------------------------------------------------------------------
start:
mov ecx,core_data_seg_sel ;令DS指向核心資料段
mov ds,ecx
mov ecx,mem_0_4_gb_seg_sel ;令ES指向4GB資料段
mov es,ecx
mov ebx,message_1
call sys_routine_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 sys_routine_seg_sel:put_string
mov ebx,cpu_brand
call sys_routine_seg_sel:put_string
mov ebx,cpu_brnd1
call sys_routine_seg_sel:put_string
;以下開始安裝為整個系統服務的調用門。特權級之間的控制轉移必須使用門
mov edi,salt ;C-SALT表的起始位置
mov ecx,salt_items ;C-SALT表的條目數量
.b3:
push ecx
mov eax,[edi+256] ;該條目入口點的32位偏移位址
mov bx,[edi+260] ;該條目入口點的段選擇子
mov cx,1_11_0_1100_000_00000B ;特權級3的調用門(3以上的特權級才
;允許通路),0個參數(因為用寄存器
;傳遞參數,而沒有用棧)
call sys_routine_seg_sel:make_gate_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+260],cx ;将傳回的門描述符選擇子回填
add edi,salt_item_len ;指向下一個C-SALT條目
pop ecx
loop .b3
;對門進行測試
mov ebx,message_2
call far [salt_1+256] ;通過門顯示資訊(偏移量将被忽略)
;為程式管理器的TSS配置設定記憶體空間
mov ecx,104 ;為該任務的TSS配置設定記憶體
call sys_routine_seg_sel:allocate_memory
mov [prgman_tss+0x00],ecx ;儲存程式管理器的TSS基位址
;在程式管理器的TSS中設定必要的項目
mov word [es:ecx+96],0 ;沒有LDT。處理器允許沒有LDT的任務。
mov word [es:ecx+102],103 ;沒有I/O位圖。0特權級事實上不需要。
mov word [es:ecx+0],0 ;反向鍊=0
mov dword [es:ecx+28],0 ;登記CR3(PDBR)
mov word [es:ecx+100],0 ;T=0
;不需要0、1、2特權級堆棧。0特級不
;會向低特權級轉移控制。
;建立TSS描述符,并安裝到GDT中
mov eax,ecx ;TSS的起始線性位址
mov ebx,103 ;段長度(界限)
mov ecx,0x00408900 ;TSS描述符,特權級0
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [prgman_tss+0x04],cx ;儲存程式管理器的TSS描述符選擇子
;任務寄存器TR中的内容是任務存在的标志,該内容也決定了目前任務是誰。
;下面的指令為目前正在執行的0特權級任務“程式管理器”後補手續(TSS)。
ltr cx
;現在可認為“程式管理器”任務正執行中
mov ebx,prgman_msg1
call sys_routine_seg_sel:put_string
mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link ;将此TCB添加到TCB鍊中
push dword 50 ;使用者程式位于邏輯50扇區
push ecx ;壓入任務控制塊起始線性位址
call load_relocate_program
call far [es:ecx+0x14] ;執行任務切換。和上一章不同,任務切
;換時要恢複TSS内容,是以在建立任務
;時TSS要填寫完整
;重新加載并切換任務
mov ebx,prgman_msg2
call sys_routine_seg_sel:put_string
mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link ;将此TCB添加到TCB鍊中
push dword 50 ;使用者程式位于邏輯50扇區
push ecx ;壓入任務控制塊起始線性位址
call load_relocate_program
jmp far [es:ecx+0x14] ;執行任務切換
mov ebx,prgman_msg3
call sys_routine_seg_sel:put_string
hlt
core_code_end:
;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end: