啟用分頁機制
當Linux啟動時,首先運行在實模式下,隨後就要轉到保護模式下運行。因為在第二章段機制中,我們已經介紹了Linux對段的設置,在此我們主要討論與分頁機制相關的問題。Linux內核代碼的入口點就是/arch/i386/kernel/head.S中的startup_32。
1.頁表的初步初始化:
/*
* The page tables are initialized to only 8MB here - the final page
* tables are set up later depending on memory size.
*/
.org 0x2000
ENTRY(pg0)
.org 0x3000
ENTRY(pg1)
/*
* empty_zero_page must immediately follow the page tables ! (The
* initialization loop counts until empty_zero_page)
*/
.org 0x4000
ENTRY(empty_zero_page)
/*
* Initialize page tables
*/
movl $pg0-__PAGE_OFFSET,%edi /* initialize page tables */
movl $007,%eax /* "007" doesn't mean with right to kill, but
PRESENT+RW+USER */
2: stosl
add $0x1000,%eax
cmp $empty_zero_page-__PAGE_OFFSET,%edi
jne 2b
內核的這段代碼執行時,因為頁機制還沒有啟用,還沒有進入保護模式,因此指令寄存器EIP中的地址還是物理地址,但因為pg0中存放的是虛擬地址(想想gcc編譯內核以後形成的符號地址都是虛擬地址),因此,“$pg0-__PAGE_OFFSET ”獲得pg0的物理地址,可見pg0存放在相對於內核代碼起點為0x2000的地方,即物理地址為0x00102000,而pg1的物理地址則為0x00103000。Pg0和pg1這個兩個頁表中的表項則依次被設置為0x007、0x1007、0x2007等。其中最低的三位均為1,表示這兩個頁為用戶頁,可寫,且頁的內容在內存中(參見圖2.24)。所映射的物理頁的基地址則為0x0、0x1000、0x2000等,也就是物理內存中的頁面0、1、2、3等等,共映射2K個頁面,即8MB的存儲空間。由此可以看出,Linux內核對物理內存的最低要求為8MB。緊接著存放的是empty_zero_page頁(即零頁),零頁存放的是系統啟動參數和命令行參數,具體內容參見第十三章。
2.啟用分頁機制:
/*
* This is initialized to create an identity-mapping at 0-8M (for bootup
* purposes) and another mapping of the 0-8M area at virtual address
* PAGE_OFFSET.
*/
.org 0x1000
ENTRY(swapper_pg_dir)
.long 0x00102007
.long 0x00103007
.fill BOOT_USER_PGD_PTRS-2,4,0
/* default: 766 entries */
.long 0x00102007
.long 0x00103007
/* default: 254 entries */
.fill BOOT_KERNEL_PGD_PTRS-2,4,0
/*
* Enable paging
*/
3:
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
jmp 1f /* flush the prefetch-queue */
1:
movl $1f,%eax
jmp *%eax /* make sure eip is relocated */
1:
我們先來看這段代碼的功能。這段代碼就是把頁目錄swapper_pg_dir的物理地址裝入控制寄存器cr3,並把cr0中的最高位置成1,這就開啟了分頁機制。
但是,啟用了分頁機制,並不說明Linux內核真正進入了保護模式,因為此時,指令寄存器EIP中的地址還是物理地址,而不是虛地址。“jmp 1f”指令從邏輯上說不起什麼作用,但是,從功能上說它起到丟棄指令流水線中內容的作用(這是Intel在i386技術資料中所建議的),因為這是一個短跳轉,EIP中還是物理地址。緊接著的mov和jmp指令把第二個標號為1的地址裝入EAX寄存器並跳轉到那兒。在這兩條指令執行的過程中, EIP還是指向物理地址“1MB+某處”。因為編譯程序使所有的符號地址都在虛擬內存空間中,因此,第二個標號1的地址就在虛擬內存空間的某處((PAGE_OFFSET+某處),於是,jmp指令執行以後,EIP就指向虛擬內核空間的某個地址,這就使CPU轉入了內核空間,從而完成了從實模式到保護模式的平穩過渡。
然後再看頁目錄swapper_pg_dir中的內容。從前面的討論我們知道pg0和pg1這兩個頁表的起始物理地址分別為0x00102000和0x00103000。從圖2?.22可知,頁目錄項的最低12位用來描述頁表的屬性。因此,在swapper_pg_dir中的第0和第1個目錄項0x00102007、0x00103007,就表示pg0和pg1這兩個頁表是用戶頁表、可寫且頁表的內容在內存。
接著,把swapper_pg_dir中的第2~767共766個目錄項全部置為0。因為一個頁表的大小為4KB,每個表項占4個字節,即每個頁表含有1024個表項,每個頁的大小也為4KB,因此這768個目錄項所映射的虛擬空間為768?1024?4K=3G,也就是swapper_pg_dir表中的前768個目錄項映射的是用戶空間。
最後,在第768和769個目錄項中又存放pg0和pg1這兩個頁表的地址和屬性,而把第770~1023共254個目錄項置0。這256個目錄項所映射的虛擬地址空間為256?1024?4K=1G,也就是swapper_pg_dir表中的後256個目錄項映射的是內核空間。
由此可以看出,在初始的頁目錄swapper_pg_dir中,用戶空間和內核空間都只映射了開頭的兩個目錄項,即8MB的空間,而且有著相同的映射,如圖6.6所示。
圖6.6 初始頁目錄swapper_pg_dir的映射圖
讀者會問,內核開始運行後運行在內核空間,那麼,為什麼把用戶空間的低區(8M)也進行映射,而且與內核空間低區的映射相同?簡而言之,是為了從實模式到保護模式的平穩過渡。具體地說,當CPU進入內核代碼的起點startup_32後,是以物理地址來取指令的。在這種情況下,如果頁目錄只映射內核空間,而不映射用戶空間的低區,則一旦開啟頁映射機制以後就不能繼續執行了,這是因為,此時CPU中的指令寄存器EIP仍指向低區,仍會以物理地址取指令,直到以某個符號地址為目標作絕對轉移或調用子程序為止。所以,Linux內核就采取了上述的解決辦法。