分頁機制概述
分頁其實就是記憶體塊的映射管理。在我們之前的章節中,我們都是使用的分段管理模式,處理器中負責分段的部件是段部件,段管理機制是Intel處理器最基本的處理機制,在任何時候都是無法關閉的。而當開啟了分頁管理之後,處理器會把4GB的記憶體分成長度相同的段,也就是說用長度固定的頁來代替長度不一的段。頁的配置設定由處理器固件來進行,可以實作非常高效的操作。頁的最小機關是4KB,也就是說4GB的記憶體可以分成1048576個頁(頁的實體位址的低12位全是0)。
在分頁模式下,作業系統可以建立一個為所有任務共用的4GB虛拟記憶體空間。也可以為每一個任務建立一個獨立的4GB虛拟記憶體空間,當一個程式加載的時候,作業系統既要在虛拟記憶體空間中配置設定空間,而且也要在實體記憶體中配置設定相應的也頁面。另外,如果允許頁共享,多個段或者多個程式可以用同一個頁來存放各自的資料。4GB虛拟記憶體空間隻是一個用來訓示記憶體使用狀況的一個機制,當作業系統加載一個程式并且建立為任務時,作業系統在虛拟記憶體空間中尋找空閑的段,并映射到空閑的頁中取去。然後,到真正加載程式的時候,再把原本屬于段的資料按頁的尺寸拆分,分開寫入相應的頁中。頁最大的好處就是友善作業系統進行記憶體管理,特别是虛拟記憶體管理,每個任務都可以有4GB虛拟記憶體,但是假如機器沒有那麼大的記憶體,作業系統也可以認為确實存在那麼大的記憶體,當一個程式使用的記憶體超過了實際的實體記憶體,那麼作業系統就會搜尋那些暫時用不到的頁,并且把他們轉移到磁盤中,并且調入馬上要使用的頁。(當然這種操作非常地花費時間,這就是為什麼小記憶體的電腦會有很嚴重的卡頓現象)。
Intel處理器的最基礎的分頁管理機制就是二級管理機制。(當然現在更新的處理器支援更複雜的分頁操作,但是教材也沒有提及。)如果每個操使用直接分頁管理(也就是把4GB的記憶體直接分成1048576個頁),這需要1048576個表項,每個表項是4位元組,是以映射表的總大小是4MB。事實上程式往往用不到4GB的記憶體,是以1級映射是一個很嚴重的浪費。但是如果采用階層化分頁管理,那麼就會産生巨大的記憶體節約。所謂Intel的分頁管理,其實就是把頁拆成3個部分(頁目錄,頁表和頁)。
頁目錄(Page Directory Table,PDT) 的實體位址由PDBR(Page Directory Base Register,也就是CR3寄存器) 指定,每個任務都可以有自己的頁目錄。每個任務的TSS段就有自己的CR3的實體位址。每次進行任務切換的時候,CR3的内容都會被替換改為新任務的CR3域中的實體位址。頁目錄也是一個的頁。但是他裡面存放的是頁表的位址,是以頁目錄可以指向1024個頁表。
頁表也可以指向1024個頁,每個項也是4個位元組,同樣頁表的大小也是頁的大小。
處理器有頁部件,專門負責線性位址到實體位址的轉換工作。它首先将段部件送來的32位線性位址截成3段,分别是高10位(頁目錄的索引),中間的10位(頁表的索引)和低12位(頁内偏移)。作業系統要負責填寫頁目錄和頁表位址,然後程式的記憶體通路就可以像上圖那樣進行轉換了。

頁目錄、頁表和CR3的填寫
- P(Present)是存在位,為“1”時,表示頁表或者頁位于記憶體中,否則,表示頁表或者頁不在記憶體中,必須先予以建立,或者從磁盤調入記憶體後方可使用。
-
RW(Read/Write)是讀/寫位,為“0”時表示這樣的頁隻能讀取,為“1”時,可讀可寫。
-US(User/Supervisor)是使用者/管理位。為“1”時,允許所有特權級别的程式通路;為“0”時,隻允許特權級為0,1,2的程式通路,特權級為3的程式不能通路。
- PWT(Page-Level Write-Through)是頁級通寫位,和高速緩存有關,“通寫”是處理器高速緩存的一種工作方式,這意味用來間接決定是否采用此種方式來改善頁面的通路效率。
- A(Accessed)是通路位。該位由處理器固件來設定,用來訓示此表項所指向的頁是否被通路過。(這個坑爹的屬性将會在練習題中展現)。
- D(Dirty)是髒位。該位由處理器固件來設定,用來訓示此表項所指向的頁是否被寫入資料。(和A位一樣,練習題會有展現)。
- PAT(Page Attribute Table)頁屬性表支援位。此位涉及更為複雜的分頁系統。和頁高速緩存有關。
- G(Global)是全局位,用來隻是該表項指向的頁是否為全局屬性。如果頁是全局屬性的,那麼,他将一直在高速緩存中一直儲存(位址轉換速度會加快)。因為頁高速緩存的緩存容量有限,隻能存放頻繁使用的那些表項。而且,當因為任務切換等原因改變CR3寄存器的内容時,整個頁高速緩存的内容都會被重新整理。
- AVL位被處理器忽略,軟體可以使用。
代碼清單
;代碼清單16-2
;檔案名:c16.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
;-------------------------------------------------------------------------------
reserved times 256*500 db 0 ;保留一個空白區,以示範分頁
;-------------------------------------------------------------------------------
ReadDiskData db '@ReadDiskData'
times 256-($-ReadDiskData) db 0
PrintDwordAsHex db '@PrintDwordAsHexString'
times 256-($-PrintDwordAsHex) db 0
salt_end:
message_0 db 0x0d,0x0a,
db ' ............User task is running with '
db 'paging enabled!............',0x0d,0x0a,0
space db 0x20,0x20,0
;-------------------------------------------------------------------------------
[bits 32]
;-------------------------------------------------------------------------------
start:
mov ebx,message_0
call far [PrintString]
xor esi,esi
mov ecx,88
.b1:
mov ebx,space
call far [PrintString]
mov edx,[esi*4]
call far [PrintDwordAsHex]
inc esi
loop .b1
call far [TerminateProgram] ;退出,并将控制權傳回到核心
;-------------------------------------------------------------------------------
program_end:
;代碼清單16-1
;檔案名:c16_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 ;0x800b8000段的選擇子
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
;-------------------------------------------------------------------------------
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
;-------------------------------------------------------------------------------
allocate_a_4k_page: ;配置設定一個4KB的頁
;輸入:無
;輸出:EAX=頁的實體位址
push ebx
push ecx
push edx
push ds
mov eax,core_data_seg_sel
mov ds,eax
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 sys_routine_seg_sel:put_string
hlt ;沒有可以配置設定的頁,停機
.b2:
shl eax,12 ;乘以4096(0x1000)
pop ds
pop edx
pop ecx
pop ebx
ret
;-------------------------------------------------------------------------------
alloc_inst_a_page: ;配置設定一個頁,并安裝在目前活動的
;層級分頁結構中
;輸入:EBX=頁的線性位址
push eax
push ebx
push esi
push ds
mov eax,mem_0_4_gb_seg_sel
mov ds,eax
;檢查該線性位址所對應的頁表是否存在
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 ds
pop esi
pop ebx
pop eax
retf
;-------------------------------------------------------------------------------
create_copy_cur_pdir: ;建立新頁目錄,并複制目前頁目錄内容
;輸入:無
;輸出:EAX=新頁目錄的實體位址
push ds
push es
push esi
push edi
push ebx
push ecx
mov ebx,mem_0_4_gb_seg_sel
mov ds,ebx
mov es,ebx
call allocate_a_4k_page
mov ebx,eax
or ebx,0x00000007
mov [0xfffffff8],ebx
mov esi,0xfffff000 ;ESI->目前頁目錄的線性位址
mov edi,0xffffe000 ;EDI->新頁目錄的線性位址
mov ecx,1024 ;ECX=要複制的目錄項數
cld
repe movsd
pop ecx
pop ebx
pop edi
pop esi
pop es
pop ds
retf
;-------------------------------------------------------------------------------
terminate_current_task: ;終止目前任務
;注意,執行此例程時,目前任務仍在
;運作中。此例程其實也是目前任務的
;一部分
mov eax,core_data_seg_sel
mov ds,eax
pushfd
pop edx
test dx,0100_0000_0000_0000B ;測試NT位
jnz .b1 ;目前任務是嵌套的,到.b1執行iretd
jmp far [program_man_tss] ;程式管理器任務
.b1:
iretd
sys_routine_end:
;===============================================================================
SECTION core_data vstart=0 ;系統核心的資料段
;-------------------------------------------------------------------------------
pgdt dw 0 ;用于設定和修改GDT
dd 0
page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff
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 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_0 db ' Working in system core,protect mode.'
db 0x0d,0x0a,0
message_1 db ' Paging is enabled.System core is mapped to'
db ' address 0x80000000.',0x0d,0x0a,0
message_2 db 0x0d,0x0a
db ' System wide CALL-GATE mounted.',0x0d,0x0a,0
message_3 db '********No more pages********',0
message_4 db 0x0d,0x0a,' Task switching...@_@',0x0d,0x0a,0
message_5 db 0x0d,0x0a,' Processor HALT.',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
;任務控制塊鍊
tcb_chain dd 0
;核心資訊
core_next_laddr dd 0x80100000 ;核心空間中下一個可配置設定的線性位址
program_man_tss dd 0 ;程式管理器的TSS描述符選擇子
dw 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
;清空目前頁目錄的前半部分(對應低2GB的局部位址空間)
mov ebx,0xfffff000
xor esi,esi
.b1:
mov dword [es:ebx+esi*4],0x00000000
inc esi
cmp esi,512
jl .b1
;以下開始配置設定記憶體并加載使用者程式
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,0xfffff000 ;使之4KB對齊
add ebx,0x1000
test eax,0x00000fff ;程式的大小正好是4KB的倍數嗎?
cmovnz eax,ebx ;不是。使用湊整的結果
mov ecx,eax
shr ecx,12 ;程式占用的總4KB頁數
mov eax,mem_0_4_gb_seg_sel ;切換DS到0-4GB的段
mov ds,eax
mov eax,[ebp+12*4] ;起始扇區号
mov esi,[ebp+11*4] ;從堆棧中取得TCB的基位址
.b2:
mov ebx,[es:esi+0x06] ;取得可用的線性位址
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
push ecx
mov ecx,8
.b3:
call sys_routine_seg_sel:read_hard_disk_0
inc eax
loop .b3
pop ecx
loop .b2
;在核心位址空間内建立使用者任務的TSS
mov eax,core_data_seg_sel ;切換DS到核心資料段
mov ds,eax
mov ebx,[core_next_laddr] ;使用者任務的TSS必須在全局空間上配置設定
call sys_routine_seg_sel:alloc_inst_a_page
add dword [core_next_laddr],4096
mov [es:esi+0x14],ebx ;在TCB中填寫TSS的線性位址
mov word [es:esi+0x12],103 ;在TCB中填寫TSS的界限值
;在使用者任務的局部位址空間内建立LDT
mov ebx,[es:esi+0x06] ;從TCB中取得可用的線性位址
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov [es:esi+0x0c],ebx ;填寫LDT線性位址到TCB中
;建立程式代碼段描述符
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f800 ;4KB粒度的代碼段描述符,特權級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 ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+76],cx ;填寫TSS的CS域
;建立程式資料段描述符
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f200 ;4KB粒度的資料段描述符,特權級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 ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+84],cx ;填寫TSS的DS域
mov [es:ebx+72],cx ;填寫TSS的ES域
mov [es:ebx+88],cx ;填寫TSS的FS域
mov [es:ebx+92],cx ;填寫TSS的GS域
;将資料段作為使用者任務的3特權級固有堆棧
mov ebx,[es:esi+0x06] ;從TCB中取得可用的線性位址
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+80],cx ;填寫TSS的SS域
mov edx,[es:esi+0x06] ;堆棧的高端線性位址
mov [es:ebx+56],edx ;填寫TSS的ESP域
;在使用者任務的局部位址空間内建立0特權級堆棧
mov ebx,[es:esi+0x06] ;從TCB中取得可用的線性位址
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c09200 ;4KB粒度的堆棧段描述符,特權級0
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0000B ;設定選擇子的特權級為0
mov ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+8],cx ;填寫TSS的SS0域
mov edx,[es:esi+0x06] ;堆棧的高端線性位址
mov [es:ebx+4],edx ;填寫TSS的ESP0域
;在使用者任務的局部位址空間内建立1特權級堆棧
mov ebx,[es:esi+0x06] ;從TCB中取得可用的線性位址
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0b200 ;4KB粒度的堆棧段描述符,特權級1
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001B ;設定選擇子的特權級為1
mov ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+16],cx ;填寫TSS的SS1域
mov edx,[es:esi+0x06] ;堆棧的高端線性位址
mov [es:ebx+12],edx ;填寫TSS的ESP1域
;在使用者任務的局部位址空間内建立2特權級堆棧
mov ebx,[es:esi+0x06] ;從TCB中取得可用的線性位址
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0d200 ;4KB粒度的堆棧段描述符,特權級2
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基位址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010B ;設定選擇子的特權級為2
mov ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+24],cx ;填寫TSS的SS2域
mov edx,[es:esi+0x06] ;堆棧的高端線性位址
mov [es:ebx+20],edx ;填寫TSS的ESP2域
;重定位SALT
mov eax,mem_0_4_gb_seg_sel ;通路任務的4GB虛拟位址空間時用
mov es,eax
mov eax,core_data_seg_sel
mov ds,eax
cld
mov ecx,[es:0x0c] ;U-SALT條目數
mov edi,[es: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 [es:edi-256],eax ;将字元串改寫成偏移位址
mov ax,[esi+4]
or ax,0000000000000011B ;以使用者程式自己的特權級使用調用門
;故RPL=3
mov [es: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+11*4] ;從堆棧中取得TCB的基位址
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中
mov ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov [es:ebx+96],cx ;填寫TSS的LDT域
mov word [es:ebx+0],0 ;反向鍊=0
mov dx,[es:esi+0x12] ;段長度(界限)
mov [es:ebx+102],dx ;填寫TSS的I/O位圖偏移域
mov word [es:ebx+100],0 ;T=0
mov eax,[es:0x04] ;從任務的4GB位址空間擷取入口點
mov [es:ebx+32],eax ;填寫TSS的EIP域
pushfd
pop edx
mov [es:ebx+36],edx ;填寫TSS的EFLAGS域
;在GDT中登記TSS描述符
mov eax,[es:esi+0x14] ;從TCB中擷取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
;建立使用者任務的頁目錄
;注意!頁的配置設定和使用是由頁位圖決定的,可以不占用線性位址空間
call sys_routine_seg_sel:create_copy_cur_pdir
mov ebx,[es:esi+0x14] ;從TCB中擷取TSS的線性位址
mov dword [es:ebx+28],eax ;填寫TSS的CR3(PDBR)域
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_0
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
;準備打開分頁機制
;建立系統核心的頁目錄表PDT
;頁目錄表清零
mov ecx,1024 ;1024個目錄項
mov ebx,0x00020000 ;頁目錄的實體位址
xor esi,esi
.b1:
mov dword [es:ebx+esi],0x00000000 ;頁目錄表項清零
add esi,4
loop .b1
;在頁目錄内建立指向頁目錄自己的目錄項
mov dword [es:ebx+4092],0x00020003
;在頁目錄内建立與線性位址0x00000000對應的目錄項
mov dword [es:ebx+0],0x00021003 ;寫入目錄項(頁表的實體位址和屬性)
;建立與上面那個目錄項相對應的頁表,初始化頁表項
mov ebx,0x00021000 ;頁表的實體位址
xor eax,eax ;起始頁的實體位址
xor esi,esi
.b2:
mov edx,eax
or edx,0x00000003
mov [es:ebx+esi*4],edx ;登記頁的實體位址
add eax,0x1000 ;下一個相鄰頁的實體位址
inc esi
cmp esi,256 ;僅低端1MB記憶體對應的頁才是有效的
jl .b2
.b3: ;其餘的頁表項置為無效
mov dword [es:ebx+esi*4],0x00000000
inc esi
cmp esi,1024
jl .b3
;令CR3寄存器指向頁目錄,并正式開啟頁功能
mov eax,0x00020000 ;PCD=PWT=0
mov cr3,eax
mov eax,cr0
or eax,0x80000000
mov cr0,eax ;開啟分頁機制
;在頁目錄内建立與線性位址0x80000000對應的目錄項
mov ebx,0xfffff000 ;頁目錄自己的線性位址
mov esi,0x80000000 ;映射的起始位址
shr esi,22 ;線性位址的高10位是目錄索引
shl esi,2
mov dword [es:ebx+esi],0x00021003 ;寫入目錄項(頁表的實體位址和屬性)
;目标單元的線性位址為0xFFFFF200
;将GDT中的段描述符映射到線性位址0x80000000
sgdt [pgdt]
mov ebx,[pgdt+2]
or dword [es:ebx+0x10+4],0x80000000
or dword [es:ebx+0x18+4],0x80000000
or dword [es:ebx+0x20+4],0x80000000
or dword [es:ebx+0x28+4],0x80000000
or dword [es:ebx+0x30+4],0x80000000
or dword [es:ebx+0x38+4],0x80000000
add dword [pgdt+2],0x80000000 ;GDTR也用的是線性位址
lgdt [pgdt]
jmp core_code_seg_sel:flush ;重新整理段寄存器CS,啟用高端線性位址
flush:
mov eax,core_stack_seg_sel
mov ss,eax
mov eax,core_data_seg_sel
mov ds,eax
mov ebx,message_1
call sys_routine_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 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 .b4
;對門進行測試
mov ebx,message_2
call far [salt_1+256] ;通過門顯示資訊(偏移量将被忽略)
;為程式管理器的TSS配置設定記憶體空間
mov ebx,[core_next_laddr]
call sys_routine_seg_sel:alloc_inst_a_page
add dword [core_next_laddr],4096
;在程式管理器的TSS中設定必要的項目
mov word [es:ebx+0],0 ;反向鍊=0
mov eax,cr3
mov dword [es:ebx+28],eax ;登記CR3(PDBR)
mov word [es:ebx+96],0 ;沒有LDT。處理器允許沒有LDT的任務。
mov word [es:ebx+100],0 ;T=0
mov word [es:ebx+102],103 ;沒有I/O位圖。0特權級事實上不需要。
;建立程式管理器的TSS描述符,并安裝到GDT中
mov eax,ebx ;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 [program_man_tss+4],cx ;儲存程式管理器的TSS描述符選擇子
;任務寄存器TR中的内容是任務存在的标志,該内容也決定了目前任務是誰。
;下面的指令為目前正在執行的0特權級任務“程式管理器”後補手續(TSS)。
ltr cx
;現在可認為“程式管理器”任務正執行中
;建立使用者任務的任務控制塊
mov ebx,[core_next_laddr]
call sys_routine_seg_sel:alloc_inst_a_page
add dword [core_next_laddr],4096
mov dword [es:ebx+0x06],0 ;使用者任務局部空間的配置設定從0開始。
mov word [es:ebx+0x0a],0xffff ;登記LDT初始的界限到TCB中
mov ecx,ebx
call append_to_tcb_link ;将此TCB添加到TCB鍊中
push dword 50 ;使用者程式位于邏輯50扇區
push ecx ;壓入任務控制塊起始線性位址
call load_relocate_program
mov ebx,message_4
call sys_routine_seg_sel:put_string
call far [es:ecx+0x14] ;執行任務切換。
mov ebx,message_5
call sys_routine_seg_sel:put_string
hlt
core_code_end:
;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end: