本學期Linux內核實驗最後是打印內核頁表,線性地址----物理地址
我看到這個實驗題目的時候想到的就是這個init函數(因為這部分當時就是我講的^_^),這個函數是初始化linux內核頁表的,也就是將32位系統中3G以上的896M線性地址映射到物理地址的0-896M,在其調用者paging_init函數中還處理了其他的情況,比如固定映射之類的。那屬於高端內存映射那一塊的內容,目前我們先看一下如何將內核頁表3G~3G+896M的線性地址對應的物理地址打印出來。
一下的源碼是linux2.6.11版本的,應該是和白皮書上的是對應的版本
static void __initkernel_physical_mapping_init(pgd_t *pgd_base)
{
unsignedlong pfn;
pgd_t*pgd;
pmd_t*pmd;
pte_t*pte;
intpgd_idx, pmd_idx, pte_ofs;
/*因為內核的線性地址空間是從0xC0000000開始的,所以這裡我們只需要初始化內核全局頁目錄從0x300開始的項*/
pgd_idx= pgd_index(PAGE_OFFSET); /*768*/
pgd= pgd_base + pgd_idx; /*pgd指向當前的目錄項 */
pfn= 0; /*需要被映射的物理頁框號,從物理地址0開始 */
/*初始化從768開始的每個頁全局目錄項,PTRS_PER_PGD為總項數1024 */
for(; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
pmd= one_md_table_init(pgd);
if(pfn >= max_low_pfn) /*max_low_pfn代表被內核直接映射的最後一個物理頁框的頁框號*/
continue;
/*初始化每個頁中間目錄項,前面說到啟用了物理地址擴展的32位x86系統中,使用三級映射,
而沒有啟用物理地址擴展的32位系統,其實只使用了其中的兩級,雖然在軟件結構中PMD依然存在,
但實際只是一個擺設。內核通過將PTRS_PER_PMD設為1,並且在one_md_table_init初始化PMD的函數中
直接將PMD的第一項初始化為指向其地址的PGD項本身,完成了一個"原地"的映射。也就是說,
此時的每一個頁目錄項,既表示一個頁中間目錄描述符,也表示一個頁表 */
for(pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++,pmd_idx++) {
unsignedint address = pfn * PAGE_SIZE + PAGE_OFFSET;
/*Map with big pages if possible, otherwise create normal page tables. */
if(cpu_has_pse) {
unsignedint address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET +PAGE_SIZE-1;
if(is_kernel_text(address) || is_kernel_text(address2))
set_pmd(pmd,pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
else
set_pmd(pmd,pfn_pmd(pfn, PAGE_KERNEL_LARGE));
pfn+= PTRS_PER_PTE;
}else {
pte= one_page_table_init(pmd);
/*最後初始化每個頁表項,也就是每個物理頁框的描述符。注意pfn++表示頁框號依次加1,
而其初始值為0,也就是把物理地址從0開始的頁框,直接映射到內核線性地址0xC0000000開始的空間內
,映射的方式與臨時內核頁表相似,只不過范圍更大了 */
for(pte_ofs = 0;
pte_ofs < PTRS_PER_PTE && pfn< max_low_pfn;
pte++, pfn++, pte_ofs++, address +=PAGE_SIZE) {
if(is_kernel_text(address))
set_pte(pte,pfn_pte(pfn, PAGE_KERNEL_EXEC));
else
set_pte(pte,pfn_pte(pfn, PAGE_KERNEL));
}
}
}
}
}
注:上面的注釋是針對沒有開啟PAE模式的,如果開啟PAE,就是 4----512----512----也就是上面的768變為3,PTRS_PER_PGD為512 PTRS_PER_PMD為1,PTRS_PER_PTE為512
看完這個函數,我們來想一下怎麼打印內核頁表,我的系統是默認開啟PAE的,也就是內存中是2M的頁和4KB的頁並存的,頁目錄和頁表的組織形式是:
Cr3--àPDPT--àpmd [--àpte]-àpage 中間的pte之所以要加方括號,表示的是這一級不一定有,即如果對應的是2M的頁,那麼這個pmd中存放的就是2M頁的物理地址和標志位,此時page的大小是2M;如果有這一級,那麼對應的頁是4KB的。這個從數量上很好理解,pmd一個表項對應的是2M的線性地址空間,如果頁的大小是2M那麼pmd相當於是頁表,如果頁的大小是4KB,那麼有512項,正好需要借助再加上一級的pte(512)項來表示,這也就是上面函數對應的在pmd下判斷是否有2M頁的結構。
所以打印函數可以寫成這樣了:
for (; pgd_idx < PTRS_PER_PGD;pgd_idx++) {
unsignedlong pgd_cur = pgd_idx * PGDIR_SIZE;
pmd= pmd_offset((pud_t *)(pgd_base + pgd_idx), pgd_cur);
for(pmd_idx = 0; pmd_idx < 448/*PTRS_PER_PMD*/; pmd_idx++) {
unsignedlong pmd_cur = pgd_cur + pmd_idx * PMD_SIZE;
if(pmd_present(pmd[pmd_idx])){
pte= pte_offset_kernel((pmd_t *)(pmd + pmd_idx), pmd_cur);
pte1= pmd_val(*((pmd_t *)(pmd + pmd_idx)));
if((((unsignedlong)pte1)>>7)&0x1)
{
//打印2M頁
}
else
{
for(pte_ofs = 0; pte_ofs < PTRS_PER_PTE; pte_ofs++) {
unsignedlong pg_cur = pmd_cur + pte_ofs * PAGE_SIZE;
if(pte_present(pte[pte_ofs]))
{
//打印4kb頁
}
}
}
}
}
}
因為打印的內容很多,用seq文件實現比較方便。