2009年5月6日 星期三

●memtest86+教學 Part14


圖一
上一篇我們已經知道頁目錄如何建立,並且要另外建立一個指向4個頁目錄基底位址的表格,名稱叫作pdp(page-directory-pointer-table)它非常重要,因為我們現在討論的這種2M分頁方式無論如何一定要建立4GB的頁目錄表,並區分成4等分,每一等分佔用4KByte(4096)的空間,也就是說每一等分負責1GB容量的映射(Mapping);這樣的說法不如來作一張圖(如圖一)會更容易理解。雖然圖中例子是共16G記憶體,但可支援到64G的Mapping。
現在我們來看看誰在使用pdp及頁目錄:
int map_page(unsigned long page)
{
unsigned long i;
struct pde
{
unsigned long addr_lo;
unsigned long addr_hi;
};
extern unsigned char pdp[];
extern struct pde pd2[];
unsigned long window = page >> 19;
if (FLAT (window == mapped_window)) { return 0; }
if (window == 0) { return 0; }
if (!v->pae (window >= 32)) {
/* Fail either we don't have pae support
* or we want an address that is out of bounds
* even for pae. */
return -1; }
/* Compute the page table entries... */
//由於左移符號會無法顯示在文章中,因此我把左移使用shl來表示,右移shr,小於lss
for(i = 0; i lss 1024; i++)
{
pd2[i].addr_lo = ((window & 1) shl 31) + ((i & 0x3ff) shl 21) + 0xE3;
pd2[i].addr_hi = (window shr 1);
}
/*以上這個for迴圈會重新建立pd2~pd3的頁目錄表*/
paging_off();
if (window gtr 1) { paging_on(pdp); }
mapped_window = window;
return 0;
}
這個函式可以說是memtest86分頁技術中最經典的routineㄌ。我們來看誰調用它:
有init.c中的cacheable():
/* Ensure the default set of pages are mapped */
map_page(0);
map_page(0x80000);
及main.c中的do_test():
map_page(v->map[0].pbase_addr)
在cacheable()調用它共2個;第一個map_page(0);代入0結果return 0,而且不會去啟用paging_on(pdp);第二個map_page(0x80000);代入0x80000結果也是return 0,而且不會去啟用paging_on(pdp);然而這只是程式的初始,當然不會去啟用分頁;所以真正會不斷調用它的便是map_page(v->map[0].pbase_addr);而且你會發現pd0~pd3好像只有pd2每次都會重新被計算,其實應該說是pd2及pd3每次都被重新計算,因為它的for迴圈是1024次,然而每個pd佔用512個頁目錄表項。所以我們可以開始推論:假設我們主機板插上6GB的記憶體;當測試0~4G時,並不會啟動分頁模式,當測試範圍為4~6G時;便會進入分頁,並且把pd2~pd3對應到4~6G;也就是說此時cpu access的位址雖然是2~4G,但實際上access 到的卻是實體物理位址4~6G。這樣ㄉ推論沒有說服力;所以一定要實際驗證才行。
順便來看一下paging_on:
static void paging_on(void *pdp)
{
if (!v->pae) return;
__asm__ __volatile__(
/* Load the page table address 此處的%0就是pdp的位址 */
"movl %0, %%cr3\n\t"
/* Enable pae cr4的bit5就是pae位元設為1*/
"movl %%cr4, %%eax\n\t"
"orl $0x00000020, %%eax\n\t"
"movl %%eax, %%cr4\n\t"
/* Enable paging cr0的PG位元設為1*/
"movl %%cr0, %%eax\n\t"
"orl $0x80000000, %%eax\n\t"
"movl %%eax, %%cr0\n\t"
:
: "r" (pdp)
: "ax"
);
}
P.S.
試想,為什麼要分頁,而且還分不同的分頁模式,為什麼要把事情搞的那麼複雜ㄋ,難不成就是要存心搞死我們這些人,難道就沒更好的辦法嗎?回顧歷史,32位元CPU已問世多年,我記得Win95那個時候我的系統記憶體也不過才32MB,但是CPU已經可以Access到4G的空間;所以那時RAM遠比CPU定址能力少得多,為了實現虛擬記憶體,讓作業系統可以多工,每個程式都擁有自己的4G位址空間,所以才搞分頁,因為記憶體容量有限,所以要把很多已執行但卻不會馬上用到的行程(Process)所佔用的空間置換(swap)到硬碟(或其他儲存媒體裝置),使正在執行的行程能有足夠的記體體使用權。曾幾何時,CPU定址仍然是32位元,但DRAM價跌容量升級,使很多使用者的安裝於系統的記憶體容量甚至大於4G,因此時代變了分頁技術也要跟著變,就像我們現在所探討的memtest86,就是因為要能符合時代須求所以要測試比4G容量更大的記憶體,且CPU也已經支援這種技術,所以我們才能借助對memtest86的了解,得知這樣的分頁模式。總之這樣的分頁模式和現行OS所實作的分頁稍有不同,就是因為須求不同的原因或許未來OS設計會將資訊全數載入記憶體,便不需要swap到硬碟,透過PAE這樣的分頁方式來取得資訊,我想一定能更加速系統的運作,但先決條件是DRAM還要更便宜,容量再加大;純假設。
待續‧‧‧


--> 閱讀更多...

●memtest86+教學 Part13






分頁我想應該是memtest86的精華,若能真的把這部分搞懂,我想對IA32(x86)的認識又進入到更深一層的境界了。若你想對整個IA-32的分頁做作通盤認識你可以去參考Intel 64 and IA-32 Architectures Software Developer's Manual - Volume 3A System Programming Guide.pdf。由於一般書籍講到的分頁都是使用4kbyte的分頁模式,而且只講到CR3(或稱 PDBR,page directory base register),然而IA-32的分頁方式還分成好幾種模式;而且支援4K、2M、4M的分頁大小;並可定址到64G (36-BIT PHYSICAL ADDRESSING USING THE PAE PAGING MECHANISM);說的好像很複雜,其實就是在搞CR3、CR4這兩個暫存器(register)。然而其實也沒那麼簡單,無論如何,由於memtest86是採用2M分頁(page size extensions)及支援PAE paging mechanism使可定址到64G;因此我們至少要對這種分頁方式作一番說明:


首先我們先來看看爛豬腳(head.S)如何實作頁目錄,你可能會問不是還要有頁表嗎?這個問題非常好但是我不想在此解答,你直接參考我上面說的那份資料以及Paging Extensions for the Pentium Pro Processor 就可知道為何。

在head.S有這麼一段code:注意;在巨集中使用參數必須前面加上"\"符號
.macro ptes64 start, count=64
.quad \start + 0x0000000 + 0xE3 ;為什麼是E3,下面會解說
.quad \start + 0x0200000 + 0xE3
.quad \start + 0x0400000 + 0xE3
.quad \start + 0x0600000 + 0xE3
.quad \start + 0x0800000 + 0xE3
.quad \start + 0x0A00000 + 0xE3
.quad \start + 0x0C00000 + 0xE3
.quad \start + 0x0E00000 + 0xE3
.if \count-1ptes64 "(\start+0x01000000)",\count-1
.endif
.endm
.macro maxdepth depth=1
.if \depth-1maxdepth \depth-1
.endif
.endm
maxdepth
.balign 4096
.globl pd0
pd0: ptes64 0x0000000000000000
.balign 4096
.globl pd1
pd1: ptes64 0x0000000040000000
.balign 4096
.globl pd2
pd2: ptes64 0x0000000080000000
.balign 4096
.globl pd3
pd3: ptes64 0x00000000C0000000
.balign 4096
.globl pdp
pdp:
.long pd0 + 1
.long 0
.long pd1 + 1
.long 0
.long pd2 + 1
.long 0
.long pd3 + 1
.long 0
上面這段code最重要的就是ptes64那個巨集(macro);若你把pd0:、pd1:、pd2:、pd3:後面的ptes64巨集展開,便會得到0~4GB的頁目錄表,而且每個表項相差2MB。差別在於其每一個頁目錄表項佔用一個quad(8Byte),這和我之前介紹的那本"自己動手寫作業系統"所談到的分頁採用long為頁目錄,相差4個bytes;-而且書面說的也不搞pdp原因如下:
Figure 3-21 shows the format for the page-directory-pointer-table and page-directory entries when 2-MByte pages and extended physicaladdresses are being used.
The major differences in these entries are as follows:
•A page-directory-pointer(pdp)-table entry is added.
•The size of the entries are increased from 32 bits to 64 bits
•The maximum number of entries in a page directory or page table is 512.
•The base physical address field in each entry is extended to 24 bits for 36-bit physical addressing (or extended to MAXPHYADDR-12 bits if MAXPHYADDR is different than 36).
另外針對"至少在各式各樣的Pentium簡介中有四個2M頁[1,2,3,4]"這句話的意思,答案如下:
Figure 3-19 shows how a page-directory-pointer table and page directories can be used to map linear addresses to 2-MByte pages when the PAE paging mechanism enabled. This paging method can be used to map up to 2048 pages (4 page-directory-pointer(pdp)-table entries times 512 page-directory entries) into a 4-GByte linear address space.也就是Figure 3-19中的bit30和bit31。
/*-----------------參考init.c map_page()--------------- *
0xE3 --
* Bit 0 = Present bit. 1 = PDE is present
* Bit 1 = Read/Write. 1 = memory is writable
* Bit 2 = Supervisor/User. 0 = Supervisor only (CPL 0-2)
* Bit 3 = Writethrough. 0 = writeback cache policy
* Bit 4 = Cache Disable. 0 = page level cache enabled
* Bit 5 = Accessed. 1 = memory has been accessed.
* Bit 6 = Dirty. 1 = memory has been written to.
* Bit 7 = Page Size. 1 = page size is 2 MBytes
* --------------------------------------------------*/
透過以上的認知我們要來看其他的code如何來實作分頁‧‧‧待續‧‧‧
--> 閱讀更多...