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