歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核源碼學習之 內核頁表打印

本學期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文件實現比較方便。

Copyright © Linux教程網 All Rights Reserved