任務,任務的LDT和TSS
程式是記錄在載體上的指令和資料,其正在執行的一個副本,叫做任務(Task)。如果一個程式有多個副本正在記憶體中運作,那麼他對應多個任務,每一個副本都是一個任務。為了有效地在任務之間進行隔離,處理器建議每個任務都應該具有他自己的描述符表,稱為局部描述符表LDT(Local Descriptor Table)。LDT和GDT一樣也是用來儲存描述符的,但是LDT是隻屬于某個任務的。每個任務是有的段,都應該在LDT中進行描述,和GDT不同的是,LDT的0位也是有效的,也可以使用。

LDT可以有很多個(有多少個任務就有多少個LDT),處理器使用局部描述符寄存器(LDT Register: LDTR)。在一個多任務的系統中,會有很多任務在輪流執行,正在執行中的那個任務,稱為目前任務(Current Task)。因為LDTR隻有一個,是以他用于指向目前任務的LDT,當發生任務切換,LDTR會被自動更新成新的任務的LDT,和GDTR一樣,LDTR包含了32位線性基位址字段和16位段界限。以訓示目前LDT的位置和大小。如果要通路LDT中的一個描述符,和通路GDT的時候是差不多的,也是要向段寄存器傳輸一個16位的段選擇子,隻是和指向GDT的選擇子不同,指向LDT的選擇子的TI位是1。
因為索引号隻能是13位的,是以每個LDT所能容納的描述符個數為213,也就是8192個。又因為每個描述符是8個位元組,是以LDT最大長度是64KB。
同時,為了儲存任務的狀态,并且在下次重新執行的時候恢複他們,每個任務都應該用一個額外的記憶體區域儲存相關資訊,這就叫做任務狀态段(Task State Segment: TSS)。
全局空間和局部空間
每個任務都包含兩個部分:全局部分和私有部分。全局部分是所有任務共有的,含有作業系統的軟體和庫程式,以及可以調用的系統服務和資料。私有部分則是每個任務自己的資料和代碼,與任務要解決的具體問題有關,彼此各不相同。每個任務的LDT可以登記8192個段,GDT可以登記8191個段(0不能用),這樣的話每個使用者程式可以有64TB的總空間。在作業系統中,允許程式使用邏輯位址來通路記憶體,而不是實際位址,是以這64TB記憶體是虛拟位址空間(全局位址空間可以有32TB,一個任務的局部空間為32TB,也就是一個任務的總空間可以是64TB,但是作業系統允許程式的編寫者使用虛拟位址(邏輯位址)來通路記憶體。同一塊記憶體,可以讓多任務,或者是每個任務的不同段來使用。當執行或者通路一個新的段的時候,如果它不在實體記憶體中,而且也沒有空閑的實體記憶體來加載它的時候,作業系統會挑出一個暫時不用的段,把它換到磁盤中,并把空間騰出來配置設定給馬上要通路的段。并修改段描述符,使之指向這一段記憶體空間。當要使用這個段的時候再把段置換回實體記憶體中。)作業系統本身要進行虛拟記憶體管理。
特權級保護概述
X86架構下,Intel引進了4個特權級,分别是0-3,權限從0到3逐次遞減。作業系統處于0特權級,系統服務程式一般在0-2特權級,普通的應用程式一般在3特權級。這裡要特别注意的是:特權級不是指的任務的特權級,而是指的組成任務的各個部分的特權級。比如任務的全局部分一般是0,1和2特權級别的,任務的私有部分一般是3特權級的。
處理器給每個可管理的對象都賦予一個特權級,以決定誰能通路他,確定各種操作的相對安全性。比如系統的一些敏感指令(如hlt,對控制寄存器的讀寫指令,lgdt,ltr等),必須通過具有0特權級的對象來操作。除了敏感指令,I/O端口的讀寫操作也是通過特權管理來進行的,這裡所說的特權管理,通常是指的I/O端口通路許可權。由EFLAGS中的13位和12位決定(I/O Privilege Level: IOPL),它代表着目前任務的I/O特權級别。
- 每個在GDT或者在LDT中的描述符,都有一個DPL位,這就是這個描述符所指的段的特權級又叫做描述符特權級(Descriptor Privilege Level: DPL)。
- 每個段的選擇子的0和1位是一個RPL(Request Privilege Level: RPL)位,對應着目前操作請求的特權級。
- 當處理器正在一個代碼段中取指令和執行指令時,那個代碼段的特權級是目前特權級(Current Privilege Level: CPL)。正在執行的這個代碼段的選擇子位于段寄存器CS中,其最低兩位就是目前特權級的值。
對于資料段,如果一個資料段,其描述符的DPL位2,那麼隻有特權級為0,1和2的程式才能通路他,如果特權級為3的程式通路這個資料段,那麼處理器會阻止并引發異常中斷。也就是在數值上要有:
- CPL<=DPL(數值上比較,目标資料段DPL)
- RPL<=DPL(數值上比較,目标資料段DPL)
對于代碼段,處理器對代碼段的檢查是非常嚴格的,一般控制轉移隻允許發生在兩個特權級相同的代碼段之間。但是處理器允許通過将權限轉移到依從的代碼段或者通過調用門将目前權限變高。但是除了通過門切換或者從門傳回,處理器不允許從特權級高的代碼段轉移到特權級低的代碼段。
如果目前程式想通過直接轉移從特權級低的代碼段到依從的特權級高的代碼段,則必須滿足:
- CPL>=DPL(數值上比較,目标代碼段DPL,代碼段必須是依從的代碼段)
- RPL>=DPL(數值上比較,目标代碼段DPL,代碼段必須是依從的代碼段)
程式還可以通過門來進行代碼段的轉移,其實也是一個描述符。
通常一些核心例程都是特權0級的(特别是那些需要通路硬碟的程式),是以調用門可以給使用者程式便捷的調用例程的手段(使用者程式在特權3級,使用者程式隻要知道例程函數名就可以了,不需要知道例程實作細節,而且可以做一些特權3級做不到的東西)。
調用門描述符給出了例程所在的代碼段的選擇子,通過這個選擇子就可以通路到描述符表相應的代碼段,然後通過門調用實施代碼段描述符的有效性,段界限和特權級的檢查。例程開始偏移是直接在調用門描述符中指定的。
描述符的TYPE位用于表示門的類型,‘1100’表示的是調用門(這裡注意S位,門的S位固定是0,說明這個是一個系統段,當檢查了S為為0後處理器會繼續檢查TYPE為看是什麼系統段(門或者LDT描述符,TSS描述符等))。
描述符P位是有效位,正常來講應該是‘1’,當它是0的時候,調用這樣的門,會導緻處理器産生中斷。但是P=0這對于處理器來說是屬于故障中斷,從中斷處理過程傳回時,處理器還會重新執行引起故障的指令。對于作業系統來說,可以利用這個特性來統計門的調用次數,在中斷程式中,每當某個門調用失敗,就把該門的調用次數+1。
如果使用了call執行調用門,則可能會改變CPL,因為棧段的特權級必須同目前的特權級保持一緻,是以執行調用門時還需要對棧進行切換。也就是從低特權級的棧轉移到高特權級的棧,比如一個特權級為3的程式通過調用門執行特權級為0的代碼段,則棧段要從特權級3轉移到特權級0。這是為了防止因為棧空間不足而産生不可預料的問題,同時也是為了防止資料的交叉引用。為了切換棧,每個任務除了自己固有的棧,還必須額外定義幾個棧,具體數量去決定于目前任務的特權級,要補充比目前特權級高的棧段。這些額外建立的棧段必須放在任務自己的LDT中。同時,還要在TSS中進行登記。
通過調用門使用進階權限的例程時,調用者會傳遞一些參數給例程。一般的方法是通過棧段來傳遞,但是因為切換棧段後棧段的指針會被初始化為一個固定的值,也就是如果你不說,處理器其實并不知道你通過棧傳遞了多少參數給例程,這個時候需要在調用門上說明傳遞了多少個參數,而參數的複制是通過處理器固件完成的,然後根據參數個數來把調用者棧中的資料複制到切換的棧段中去(程式員不需要管棧究竟切換到哪了,隻要知道某個被傳遞的參數在調用門之前是什麼時候壓入棧的,用push和pop指令可以像沒有經過切換棧一樣得到傳遞那個參數,盡管棧指針和棧段已經改變)。
用call far指令通過調用門轉移控制時,如果改變目前特權級,完整的切換棧段的過程:
- S1:使用目标代碼段的DPL到目前任務的TSS中選擇一個棧,包括棧段的選擇子和棧指針。
- S2:從TSS中獨缺所選擇的段的選擇子和棧指針,并用該選擇子和棧指針,并用該選擇子讀取棧段描述符,在此期間,任何違反段界限的行為都會引起處理器引發異常中斷(無效TSS)。
- S3:檢查棧段描述符的特權級和類型,并可能引發處理器異常中斷(無效TSS)。
- S4:臨時儲存目前棧段寄存器SS和棧指針ESP的内容。
- S5:把新的棧段選擇子和棧指針帶入SS和ESP寄存器,切換到新棧。
- S6:将剛才臨時儲存的SS和ESP内容壓入目前棧
- S7:依據調用門描述符“參數個數”字段的訓示,從舊棧中所有的參數都複制到新棧中,如果參數為0則不複制。
- S8:将目前段寄存器CS和指針寄存器EIP壓入新棧(因為是遠調用)。
- S9:從調用門描述符中依次将目标代碼選擇子和段内偏移傳送到CS和ESP寄存器,開始執行調用過程。
使用jmp far來通過調用門轉移控制,則沒有特權級的變化,是以也不會進行棧段的切換,而且不能使用retf來傳回控制到調用者。
帶參數的傳回是用于解決通過棧傳遞參數的過程最後維持棧平衡的問題,過程編寫者知道進入過程前棧中有多少個元素,如果需要把這些元素在結束過程後彈出,則可以使用ret imm16或者retf imm16,這兩條指令都帶16位立即數為操作數,前者為近傳回,後者為遠傳回,而且數值總是為2的倍數或者4的倍數(使用SP則位2的倍數,使用ESP則為4的倍數),代表将控制傳回調用者之前,應當從棧中彈出多少位元組的資料。假如彈出的數值為k,則過程結束後會執行ESP<-ESP+k,相當于彈出了k/4個參數。
要求特權級變化的遠傳回,隻能傳回到較低特權級别上。控制傳回的全部過程如下:
- S1:檢查占中儲存的CS寄存器的内容,根據其RPL字段決定傳回時是否需要改變特權級别。
- S2:從目前棧中讀取CS和EIP的内容,并針對代碼段描述符和代碼段選擇子的RPL段進行特權檢查。從同一特權級傳回時,處理器任然會進行一次特權檢查。
- S3:如果遠傳回指令是帶參數的,則将參數和ESP寄存器的目前值相加,以跳過棧中的參數部分。最後的結果是ESP寄存器指向調用者SS和ESP的壓棧值。注意,retf指令的位元組計數值必須等于調用門的參數個數乘以參數長度。最後恢複調用者調用過程前(包括壓入參數)的棧。
- S4:如果傳回時需要改變特權級,則從棧中将SS和ESP的壓棧值帶入段寄存器SS和指令指針寄存器ESP,切換到調用者的棧,在此期間,一旦檢測到有任何界限違反情況都會引發處理器異常中斷。
- S5:如果傳回時需要改變特權級,檢查DS,ES,FS和GS寄存器的内容,根據他們找到相應的段描述符,要是有任何一個段描述符的DPL高于調用者的特權級(傳回時新的CPL,數值上段描述符DPL<傳回後的CPL),處理器将會把數值0傳入該段寄存器(因為資料段的特權級檢查隻會在把段寄存器的選擇子傳入段寄存器時檢查的,在這之後任何通路資料段指向的記憶體都不會進行特權級檢查,如果目前的資料段特權級高于調用者,不在傳回的時候把0傳給段寄存器而任然不變的話,那麼調用者将有權利使用高特權級的資料段,可能會引發不安全的操作)。
特别需要注意的是,TSS中的SS0,EP0,SS1,EP1,SS2和EP2域都是靜态的,除非軟體進行修改,否則處理器不會改變他們。
其實和安裝GDT描述符差不多(調用門一般為公用例程,是以安裝在GDT上),注意調用門的過程,我們使用的是call far,再進行門調用時,無論是間接遠調用還是絕對遠調用,處理器隻會用選擇子部分,而偏移位址部分會被忽略。在之後的給使用者程式填充門描述符時,一定要記得把門描述符的特權級改為應用程式特權級3,,門調用時候處理器要按以下規則檢查(數值上):
- CPL<=調用門描述符的DPL
- RPL<=調用門描述符的DPL
- CPL>=目标代碼段的DPL
上述規則都滿足,才能成功調用門,否則會引發異常中斷。
這裡還有一個地方值得注意的是處理器對調用者的請求特權級RPL的問題,RPL事實上是處理器和作業系統的一個協定,處理器本身隻會負責檢查特權級的RPL,判斷其是否有權限通路目标段。并不檢查RPL是否是正确的,對RPL的正确性檢查是作業系統的事情。換句話說,作業系統總是會把RPL改為真正發起調用的任務的特權級,以防止低特權級任務通過門等越權操作。為了幫助核心或者作業系統檢查真正調用者的身份,并提供正确的RPL的值,處理器提供了arpl(Adjust RPL Field of Segment Selector)指令,以友善地調整段選擇子的RPL字段,格式為:
arpl r/m16,r16
該指令的目的操作數包含了16位段選擇子的通用寄存器,或者指向一個16位的記憶體單元。源操作數隻能是包含了段選擇子的16位通用寄存器。
該執行執行時,處理器檢查目的操作數的RPL字段,如果它在數值上小于源操作數的RPL字段,則設定ZF标志,并調整目的操作數的RPL字段為源操作數的RPL,否則,清零ZF,且除此之外不進行任何操作。這個指令是典型的作業系統指令,它常用于調整應用程式傳遞給作業系統的段選擇子,使其RPL字段的值和應用程式的特權級相比對,這個指令也可以在應用程式上用。
代碼清單
;代碼清單14-1
;檔案名:c14_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
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 return_point
dw core_code_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
message_3 db 0x0d,0x0a,' Loading user program...',0
do_status db 'Done.',0x0d,0x0a,0
message_6 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
db ' User program terminated,control returned.',0
bin_hex db '0123456789ABCDEF'
;put_hex_dword子過程用的查找表
core_buf times 2048 db 0 ;核心用的緩沖區
esp_pointer dd 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
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
;在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 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] ;通過門顯示資訊(偏移量将被忽略)
mov ebx,message_3
call sys_routine_seg_sel:put_string ;在核心中調用例程不需要通過門
;建立任務控制塊。這不是處理器的要求,而是我們自己為了友善而設立的
mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link ;将任務控制塊追加到TCB連結清單
push dword 50 ;使用者程式位于邏輯50扇區
push ecx ;壓入任務控制塊起始線性位址
call load_relocate_program
mov ebx,do_status
call sys_routine_seg_sel:put_string
mov eax,mem_0_4_gb_seg_sel
mov ds,eax
ltr [ecx+0x18] ;加載任務狀态段
lldt [ecx+0x10] ;加載LDT
mov eax,[ecx+0x44]
mov ds,eax ;切換到使用者程式頭部段
;以下假裝是從調用門傳回。摹仿處理器壓入傳回參數
push dword [0x08] ;調用前的堆棧段選擇子
push dword 0 ;調用前的esp
push dword [0x14] ;調用前的代碼段選擇子
push dword [0x10] ;調用前的eip
retf
return_point: ;使用者程式傳回點
mov eax,core_data_seg_sel ;因為c14.asm是以JMP的方式使用調
mov ds,eax ;用門@TerminateProgram,回到這
;裡時,特權級為3,會導緻異常。
mov ebx,message_6
call sys_routine_seg_sel:put_string
hlt
core_code_end:
;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end: