1. 程式人生 > >作業系統實現之記憶體分頁機制.虛擬空間

作業系統實現之記憶體分頁機制.虛擬空間

這裡我們將把頁目錄表放在0x100000處.頁表也挨著頁目錄表放在0x101000處(第二個頁表.當然在此之前應該把實體記憶體給算出來.這裡可以使用bios中斷來獲取實體記憶體

%include "boot.inc"
section loader vstart=loader_base_addr
;------------全域性描述符表的定義 
gdt_base: 
	 dd 0x00000000    ;全域性描述表.第一個描述符要為空
     dd 0x00000000
code_base: 
     dd 0x0000FFFF
     dd desc_code_high4
data_stack_desc:
	dd 0x0000ffff
	dd desc_data_high4
video_desc:	
	dd 0x80000007   ;段界限 limit=0x7fff.基址位於0xb8000
	dd desc_video_high4
;---------GDT的屬性
gdt_size  equ $-gdt_base  ;GDT大小
gdt_limit equ gdt_size    ;GDT限制
times 60 dq 0  ;預留60個描述符的空位置
;以下定義一個數用來儲存筆記本的記憶體大小,以上一共0x200個位元組(64*8=0x200)
total_mem_bytes  dd 0
;----------定義段選擇子-------------
selector_code equ (0x0001<<3)+ti_gdt+rpl0
selector_data equ (0x0002<<3)+ti_gdt+rpl0
selector_video equ (0x0003<<3)+ti_gdt+rpl0
;定義gdt的指標,前2個位元組為gdt界限,後4個位元組為gdt起始地址
gdt_ptr dw gdt_limit
        dd gdt_base
;-----定義ards結構體數量-------------
 ards_buf times 244 db 0
 ards_nr dw 0		      ;用於記錄ards結構體數量
 
;以上一共0x300個位元組------------------
loader_start:
;-------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 獲取記憶體佈局  -------
   xor ebx, ebx		      ;第一次呼叫時,ebx值要為0
   mov edx, 0x534d4150	      ;edx只賦值一次,迴圈體中不會改變
   mov di, ards_buf	      ;ards結構緩衝區
.e820_mem_get_loop:	      ;迴圈獲取每個ARDS記憶體範圍描述結構
   mov eax, 0x0000e820	      ;執行int 0x15後,eax值變為0x534d4150,所以每次執行int前都要更新為子功能號。
   mov ecx, 20		      ;ARDS地址範圍描述符結構大小是20位元組
   int 0x15
   jc .e820_failed_so_try_e801   ;若cf位為1則有錯誤發生,嘗試0xe801子功能
   add di, cx		      ;使di增加20位元組指向緩衝區中新的ARDS結構位置
   inc word [ards_nr]	      ;記錄ARDS數量
   cmp ebx, 0		      ;若ebx為0且cf不為1,這說明ards全部返回,當前已是最後一個
   jnz .e820_mem_get_loop
;在所有ards結構中,找出(base_add_low + length_low)的最大值,即記憶體的容量。
   mov cx, [ards_nr]	      ;遍歷每一個ARDS結構體,迴圈次數是ARDS的數量
   mov ebx, ards_buf 
   xor edx, edx		      ;edx為最大的記憶體容量,在此先清0
.find_max_mem_area:	      ;無須判斷type是否為1,最大的記憶體塊一定是可被使用
   mov eax, [ebx]	      ;base_add_low
   add eax, [ebx+8]	      ;length_low
   add ebx, 20		      ;指向緩衝區中下一個ARDS結構
   cmp edx, eax		      ;氣泡排序,找出最大,edx暫存器始終是最大的記憶體容量
   jge .next_ards
   mov edx, eax		      ;edx為總記憶體大小
.next_ards:
   loop .find_max_mem_area
   jmp .mem_get_ok
;------  int 15h ax = E801h 獲取記憶體大小,最大支援4G  ------
; 返回後, ax cx 值一樣,以KB為單位,bx dx值一樣,以64KB為單位
; 在ax和cx暫存器中為低16M,在bx和dx暫存器中為16MB到4G。
.e820_failed_so_try_e801:
   mov ax,0xe801
   int 0x15
   jc .e801_failed_so_try88   ;若當前e801方法失敗,就嘗試0x88方法
;1 先算出低15M的記憶體,ax和cx中是以KB為單位的記憶體數量,將其轉換為以byte為單位
   mov cx,0x400	     ;cx和ax值一樣,cx用做乘數
   mul cx 
   shl edx,16
   and eax,0x0000FFFF
   or edx,eax
   add edx, 0x100000 ;ax只是15MB,故要加1MB
   mov esi,edx	     ;先把低15MB的記憶體容量存入esi暫存器備份
;2 再將16MB以上的記憶體轉換為byte為單位,暫存器bx和dx中是以64KB為單位的記憶體數量
   xor eax,eax
   mov ax,bx		
   mov ecx, 0x10000	;0x10000十進位制為64KB
   mul ecx		;32位乘法,預設的被乘數是eax,積為64位,高32位存入edx,低32位存入eax.
   add esi,eax		;由於此方法只能測出4G以內的記憶體,故32位eax足夠了,edx肯定為0,只加eax便可
   mov edx,esi		;edx為總記憶體大小
   jmp .mem_get_ok
;-----------------  int 15h ah = 0x88 獲取記憶體大小,只能獲取64M之內  ----------
.e801_failed_so_try88: 
   ;int 15後,ax存入的是以kb為單位的記憶體容量
   mov  ah, 0x88
   int  0x15
   jc .error_hlt
   and eax,0x0000FFFF
      
   ;16位乘法,被乘數是ax,積為32位.積的高16位在dx中,積的低16位在ax中
   mov cx, 0x400     ;0x400等於1024,將ax中的記憶體容量換為以byte為單位
   mul cx
   shl edx, 16	     ;把dx移到高16位
   or edx, eax	     ;把積的低16位組合到edx,為32位的積
   add edx,0x100000  ;0x88子功能只會返回1MB以上的記憶體,故實際記憶體大小要加上1MB
.mem_get_ok:
   mov [total_mem_bytes], edx	 ;將記憶體換為byte單位後存入total_mem_bytes處。
;-----進入保護模式----------
in al,0x92
or al,0000_0010B
out 0x92,al
;----------載入GDT----------
lgdt [gdt_ptr]
;---------cr0第0位置為1,表示開啟保護模式-----------
mov eax,cr0
or  eax,0x00000001
mov cr0,eax
jmp dword selector_code:p_mode_start
.error_hlt:		      ;出錯則掛起
   hlt
[bits 32]
p_mode_start:
	mov ax,selector_data
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov esp,loader_stack_top
	mov ax,selector_video
	mov gs,ax
	
;建立頁目錄表
call set_page
sgdt [gdt_ptr]  ;載入GDT
mov ebx,[gdt_ptr+2]
or  dword [ebx+0x18+4] ,0xc0000000  ;+4是因為.段描述的高4位元組的段基址是31-24.
;將gdt的基址加上0xc00000000使其成為核心的高地址
add dword [gdt_ptr+2],0xc0000000
add esp,0xc0000000
;-----頁目錄表賦給cr3-----
mov eax,page_dir_table_pos
mov cr3,eax
;開啟cr0的pg位(31位)
mov eax,cr0
or eax,0x80000000
mov cr0, eax
lgdt[gdt_ptr] 
mov byte [gs:160],'V'
jmp $
;------------建立頁目錄表以及頁表
set_page:
	mov ecx,4096
	mov esi,0
.clear_page_dir:
	mov byte [page_dir_table_pos +esi],0
	inc esi
	loop .clear_page_dir
;-------建立PDE-------
create_pde:
	mov eax,page_dir_table_pos
	add eax,0x1000  ;頁表地址
	mov ebx,eax   ;ebx指向第一個頁表地址
	
	or eax, pg_us_u | pg_rw_w | pg_p
	
	mov [page_dir_table_pos +0x0],eax ;將第一個頁表寫到頁目錄表的第一項
	mov [page_dir_table_pos+0xc00],eax ;一個頁表項佔用4位元組,0xc00表示第768個頁表佔用的目錄項,0xc00以上的目錄項用於核心空間
	sub eax,0x1000 
	mov [page_dir_table_pos+4092],eax ;頁目錄表的最後一個頁目錄項指向它自己
	
	
;---------建立頁表項pte-------------
mov ecx,256            ;1M低端記憶體/4k=256
mov esi,0
mov edx,pg_us_u | pg_rw_w |pg_p
.create_pte:
  mov [ebx+esi*4],edx
  add edx,4096
  inc esi
  loop .create_pte
;--------建立核心跟頁表的pde
mov eax,page_dir_table_pos
add eax,0x2000 ;指向第二個頁表
or eax,pg_us_u | pg_rw_w | pg_p
mov ebx,page_dir_table_pos
mov esi,769
mov ecx,254
.create_kernel_pde:
	mov [ebx+esi*4],eax
	inc esi
	add eax,0x1000
	loop .create_kernel_pde
	ret