向了第768個表項。
然后函數(shù)開始一個循環(huán)即開始填充從768到1024這256個目錄項的內(nèi)容。
one_md_table_init()函數(shù)根據(jù)pgd找到指向的pmd表。
它同樣在mm/init.c中定義:
static pmd_t * __init one_md_table_init(pgd_t *pgd)
{
pmd_t *pmd_table;
#ifdef CONFIG_X86_PAE
pmd_table = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
set_pgd(pgd, __pgd(__pa(pmd_table) | _PAGE_PRESENT));
if (pmd_table != pmd_offset(pgd, 0))
BUG();
#else
pmd_table = pmd_offset(pgd, 0);
#endif
return pmd_table;
}
可以看出, 如果內(nèi)核不啟用PAE選項, 函數(shù)將通過 pmd_offset返回pgd的地址。因為linux的二級映射模型,本來就是忽略pmd中間目錄表的。
接著又個判斷語句:
>> if (pfn >= max_low_pfn)
>> continue;
這個很關(guān)鍵, max_low_pfn代表著整個物理內(nèi)存一共有多少頁框。 當pfn大于max_low_pfn的時候,表明內(nèi)核已經(jīng)把整個物理內(nèi)存都映射到了系統(tǒng)空間中, 所以剩下有沒被填充的表項就直接忽略了。因為內(nèi)核已經(jīng)可以映射整個物理空間了, 沒必要繼續(xù)填充剩下的表項。
緊接著的第2個for循環(huán),在linux的3級映射模型中,是要設(shè)置pmd表的, 但在2級映射中忽略, 只循環(huán)一次,直接進行頁表pte的設(shè)置。
>> address = pfn * PAGE_SIZE PAGE_OFFSET;
address是個線性地址, 根據(jù)上面的語句可以看出address是從0xc000000開始的,也就是從內(nèi)核空間開始,后面在設(shè)置頁表項屬性的時候會用到它.
>> pte = one_page_table_init(pmd);
根據(jù)pmd分配一個頁表, 代碼同樣在mm/init.c中:
static pte_t * __init one_page_table_init(pmd_t *pmd)
{
if (pmd_none(*pmd)) {
pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE));
if (page_table != pte_offset_kernel(pmd, 0))
BUG();
return page_table;
}
return pte_offset_kernel(pmd, 0);
}
pmd_none宏判斷pmd表是否為空, 如果為空則要利用alloc_bootmem_low_pages分配一個4k大小的物理頁面。 然后通過set_pmd(pmd, __pmd
(__pa(page_table) | _PAGE_TABLE));來設(shè)置pmd表項。page_table顯然屬于線性地址,先通過__pa宏轉(zhuǎn)化為物理地址,在與上_PAGE_TABLE宏,此時它們還是無符號整數(shù),在通過__pmd把無符號整數(shù)轉(zhuǎn)化為pmd類型,經(jīng)過這些轉(zhuǎn)換, 就得到了一個具有屬性的表項, 然后通過set_pmd宏設(shè)置pmd表項.
接著又是一個循環(huán),設(shè)置1024個頁表項。
is_kernel_text函數(shù)根據(jù)前面提到的address來判斷address線性地址是否屬于內(nèi)核代碼段,它同樣在mm/init.c中定義:
static inline int is_kernel_text(unsigned long addr)
{
if (addr >= (unsigned long)_stext && addr <= (unsigned long)__init_end)
return 1;
return 0;
}
_stext, __init_end是個內(nèi)核符號, 在內(nèi)核鏈接的時候生成的, 分別表示內(nèi)核代碼段的開始和終止地址.
如果address屬于內(nèi)核代碼段, 那么在設(shè)置頁表項的時候就要加個PAGE_KERNEL_EXEC屬性,如果不是,則加個PAGE_KERNEL屬性.
#define _PAGE_KERNEL_EXEC \
(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED)
#define _PAGE_KERNEL \
(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_NX)
最后通過set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));來設(shè)置頁表項, 先通過pfn_pte宏根據(jù)頁框號和頁表項的屬性值合并成一個頁表項值,
然戶在用set_pte宏把頁表項值寫到頁表項里。
當pagetable_init()函數(shù)返回后,內(nèi)核已經(jīng)設(shè)置好了內(nèi)核頁表,緊著調(diào)用load_cr3(swapper_pg_dir);
#define load_cr3(pgdir) \
asm volatile("movl %0,%%cr3": :"r" (__pa(pgdir)))
將控制swapper_pg_dir送入控制寄存器cr3. 每當重新設(shè)置cr3時, CPU就會將頁面映射目錄所在的頁面裝入CPU內(nèi)部高速緩存中的TLB部分. 現(xiàn)在內(nèi)存中(實際上是高速緩存中)的映射目錄變了,就要再讓CPU裝入一次。由于頁面映射機制本來就是開啟著的, 所以從這條指令以后就擴大了系統(tǒng)空間中有映射區(qū)域的大小, 使整個映射覆蓋到整個物理內(nèi)存(高端內(nèi)存)除外. 實際上此時swapper_pg_dir中已經(jīng)改變的目錄項很可能還在高速緩存中, 所以還要通過__flush_tlb_all()將高速緩存中的內(nèi)容沖刷到內(nèi)存中,這樣才能保證內(nèi)存中映射目錄內(nèi)容的一致性。
3.4 對如何構(gòu)建頁表的總結(jié)
通過上述對pagetable_init()的剖析, 我們可以清晰的看到, 構(gòu)建內(nèi)核頁表, 無非就是向相應(yīng)的表項寫入下一級地址和屬性。 在內(nèi)核空間保留著一部分內(nèi)存專門用來存放內(nèi)核頁表.當cpu要進行尋址的時候,無論在內(nèi)核空間,還是在用戶空間, 都會通過這個頁表來進行映射。對于這個函數(shù), 內(nèi)核把整個物理內(nèi)存空間都映射完了, 當用戶空間的進程要使用物理內(nèi)存時, 豈不是不能做相應(yīng)的映射了? 其實不會的, 內(nèi)核只是做了映射, 映射不代表使用, 這樣做是內(nèi)核為了方便管理內(nèi)存而已。
四. 實例分析映射機制
4.1示例代碼
通過前面的理論分析,我們通過編寫一個簡單的程序, 來分析內(nèi)核是如何把線性地址映射到物理地址的。
[root@localhost temp]# cat test.c
#include <stdio.h>
void test(void)
{
printf("hello, world.\n");
}
int main(void)
{
test();
}
這段代碼很簡單, 我們故意要main調(diào)用test函數(shù), 就是想看下test函數(shù)的虛擬地址是如何映射成物理地址的。
4.2 段式映射分析
我們先編譯, 在反匯編下test文件
[root@localhost temp]# gcc -o test test.c
[root@localhost temp]# objdump -d test
08048368 <test>:
8048368: 55 push %ebp
8048369: 89 e5 mov %esp,%ebp
804836b: 83 ec 08 sub $0x8,%esp
804836e: 83 ec 0c sub $0xc,%esp
8048371: 68
億恩科技地址(ADD):鄭州市黃河路129號天一大廈608室 郵編(ZIP):450008 傳真(FAX):0371-60123888
聯(lián)系:億恩小凡
QQ:89317007
電話:0371-63322206 本文出自:億恩科技【1tcdy.com】
服務(wù)器租用/服務(wù)器托管中國五強!虛擬主機域名注冊頂級提供商!15年品質(zhì)保障!--億恩科技[ENKJ.COM]
|