[Linux內存管理-分頁機制]—把一個虛擬地址轉換爲物理地址

    由於內核在不同的CPU上運行,甚至包括目前的64位機器。Linux內核提供了4級頁表的管理機制,它可以兼容各種架構的CPU。

    一個虛擬地址會被分爲5個部分:頁全局目錄PGD(Page Global Directory),頁上級目錄PUD (Page Upper Directory),頁中間目錄PMD(Page Middle Directory,頁表PT (Page Table)以及 偏移量offset,其中的表項叫頁表項PTE(Page table entry)。也就是說一個線性地址中除去偏移量,分別存放了4級目錄表項的索引值。

   具體的線性地址翻譯成物理地址的過程是:

     (1)首先從進程地址描述符中(mm_struct)中讀取pgd字段的內容。它就是頁全局目錄的起始地址;

     (2)然後,頁全局目錄起始地址+頁全局目錄索引---->頁上級目錄的起始地址;

     (3)頁上級目錄+頁上級目錄索引---->頁中間目錄的起始地址;

     (4)頁中間目錄的起始地址+頁中間目錄的索引---->頁表起始地址;

     (5)頁表起始地址+索引---->頁表項;

     (6)從頁表項中取出物理頁的基址,加上偏移量可以得到最終的物理地址。


以2級頁表管理機制做一個原理性的說明,4級頁表管理類似:

2級頁表管理機制原理:



對於4級頁表管理機制:

那麼線性地址被分成五部分:



頁表:

頁表項的集合形成了頁表。在一級頁表內,頁表項連續存放。在虛擬地址轉爲爲物理地址過程中,沒訪問一次頁表就需要訪問一次內存。


頁表項:

每個頁表項的信息分爲兩部分:頁框基地址和屬性,對於我們查找物理地址來說有用的部分是頁表基地址。




內核相關部分代碼分析:

PAGE_SZIE  

/* page.h:PAGE_SHIFT determines the page size */  
#define PAGE_SHIFT	12
#define PAGE_SIZE	(_AC(1, UL) << PAGE_SHIFT)
#define PAGE_MASK	(~(PAGE_SIZE-1))
其中PAGE_SIZE表明了一個page的大小(2^12字節,4K大小)。我們用的是PAGE_MASK爲 11111111111111111111000000000000(20位“1”,12位’0‘)實際上能夠達到取物理地址基址和屏蔽頁內地址的作用。

PGD

/*pgtable-2level.h*/
#define PGDIR_SHIFT	22
#define PGDIR_SIZE	(1UL << PGDIR_SHIFT)
#define PGDIR_MASK	(~(PGDIR_SIZE-1))

#define PTRS_PER_PGD<span style="white-space:pre">	</span>1024

/*pgtable.h*/
#define pgd_index(address)	(((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))
/* to find an entry in a page-table-directory */
#define pgd_offset(mm, addr)	((mm)->pgd + pgd_index(addr))

從以上代碼可以看出:完成轉換步驟中的(1)(2)從mm_struct中獲取了PGD起始地址;又從線性地址的高10位作爲PGD的offset;從而獲得了PUD 的其實地址。

說明:從源代碼可以看出,對於不同平臺 以及64位平臺,每級頁表的偏移量定義都是不相同的,主要用PGD_SHIFT 控制。但是計算原理都一樣。

PUD

#define pud_index(address) (((address) >> PUD_SHIFT) & (PTRS_PER_PUD-1))
#define pud_offset(pgd, address) ((pud_t *) pgd)

PMD

/*pgtable.h*/
#define pmd_index(address) (((address) >> PMD_SHIFT) & (PTRS_PER_PMD-1))
#define pmd_offset(pud, address) ((pmd_t *) pud + pmd_index(address))

PTE

/*pgtable.h*/
#define pte_index(address) (((address) >> PAGE_SHIFT) & (PTRS_PER_PTE-1))
#define pmd_deref(pmd) (pmd_val(pmd) & _SEGMENT_ENTRY_ORIGIN)
#define pte_offset(pmd, addr) ((pte_t *) pmd_deref(*(pmd)) + pte_index(addr))
#define pte_offset_kernel(pmd, address) pte_offset(pmd,address)

類型檢查:

/*
 * These are used to make use of C type-checking..
 */
typedef struct { unsigned long	pte;	} pte_t;
typedef struct { unsigned long	ste[64];} pmd_t;
typedef struct { pmd_t		pue[1]; } pud_t;
typedef struct { pud_t		pge[1];	} pgd_t;
typedef struct { unsigned long	pgprot;	} pgprot_t;
typedef struct page *pgtable_t;

#define pte_val(x)	((x).pte)
#define pmd_val(x)	((x).ste[0])
#define pud_val(x)	((x).pue[0])
#define pgd_val(x)	((x).pge[0])

其實pte、pgd、pud、pmd都是unsigned Long類型,但是Linux爲了實現更嚴格的類型檢查。用作指針的時候用pgd_t * ,當取地址的時候用pgd_val(x)。



內核模塊實現虛擬地址轉換爲物理地址代碼:

/*get_physic_addr.c */ 

內核版本:Linux-2.6.35

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/mm.h>
#include<linux/mm_types.h>
#include<asm/pgtable.h>
#include<linux/vmalloc.h>
#include<linux/sched.h>




static  unsigned long  vaddr_to_paddr(struct mm_struct *mm,unsigned long vaddr)
{
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;


    unsigned long paddr=0;
    unsigned long page_addr=0;
    unsigned long page_offset=0;
//    struct mm_struct *mm=current->mm;


    pgd=pgd_offset(mm,vaddr);  /*獲得addr對應的pgd的地址*/
    if(pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
            goto out; 


    pud=pud_offset(pgd,vaddr);
    if(pud_none(*pud) || unlikely(pud_bad(*pud)))
            goto out; 


    pmd=pmd_offset(pud,vaddr);
    if(pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
            goto out; 


    
    pte=pte_offset_kernel(pmd,vaddr);
    if(pte_none(*pte))
        goto out;
   
    page_addr=pte_val(*pte) & PAGE_MASK;
    page_offset= vaddr & ~PAGE_MASK;
    paddr=page_addr | page_offset;
    printk("page_addr=0x%lx,page_offset=0x%lx\n",page_addr,page_offset);
    printk("vaddr=%lx,paddr=%lx\n",vaddr,paddr);


out:
    return paddr;
}






static int __init get_physic_init(void)
{
    printk("<1>get_physic_addr Modules running.....\n");
    
    unsigned long vaddr=0;


    vaddr=(unsigned long)vmalloc(1000*sizeof(char));
    if(vaddr==0){
        printk("vmalloc failed...\n");
        return 0;
    }
    vaddr_to_paddr(current->mm,vaddr);


    vfree((void*)vaddr);
    return 0;
}


static void __exit get_physic_exit(void)
{
    printk("<1>get_phyisc_addr Modules exit\n");


}




module_init(get_physic_init);
module_exit(get_physic_exit);


MODULE_LICENSE("GPL");



/*Makefile*/

obj-m:=get_physic_addr.o


CURRENT_PATH=$(shell pwd)
LINUX_KERNEL_PATH=/usr/src/kernels/$(shell uname -r)

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) m=$(CURRENT_PATH) clean



結果:


學習參考:http://edsionte.com/techblog/archives/3435


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章