1. 程式人生 > >核心態訪問使用者態地址

核心態訪問使用者態地址

一、 與頁相關的資料結構及巨集的定義
    分頁機制是硬體對分頁的支援,這是虛擬記憶體管理的硬體基礎。要想使這種硬體機制充分發揮其功能,必須有相應軟體的支援,我們來看一下Linux所定義的一些主要資料結構,其分佈在include/asm-i386/目錄下的page.hpgtable.hpgtable-2level.h三個檔案中。
 1. 表項的定義
   如上所述,PGDPMDPT表的表項都佔4個位元組,因此,把它們定義為無符號長整數,分別叫做pgd_tpmd_tpte_t(pte Page table Entry),在page.h中定義如下:
    typedef struct { unsignedlong pte_low; } pte_t;
    typedef struct { unsignedlong pmd; } pmd_t;
typedef struct { unsigned long pgd; }pgd_t;
    typedefstruct { unsigned long pgprot; } pgprot_t;
    可以看出,

Linux沒有把這幾個型別直接定義長整數而是定義為一個結構,這是為了讓gcc在編譯時進行更嚴格的型別檢查。另外,還定義了幾個巨集來訪問這些結構的成分,這也是一種面向物件思想的體現:
   #definepte_val(x)      ((x).pte_low)
   #define pmd_val(x)     ((x).pmd)
   #define pgd_val(x)      ((x).pgd)
      從圖2.13可以看出,對這些表項應該定義成位段,但核心並沒有這樣定義,而是定義了一個頁面保護結構pgprot_t和一些巨集:
   typedefstruct { unsigned long pgprot; } pgprot_t;
   #definepgprot_val(x)   ((x).pgprot)
  欄位
pgprot的值與圖2.13表項的低12位相對應,其中的9位對應09位,在pgtalbe.h中定義了對應的巨集:
  #define _PAGE_PRESENT   0×001  
  #define _PAGE_RW        0×002
  #define _PAGE_USER      0×004
  #define _PAGE_PWT       0×008
  #define _PAGE_PCD       0×010
  #define _PAGE_ACCESSED  0×020
  #define _PAGE_DIRTY     0×040
  #define _PAGE_PSE       0×080  /* 4 MB (or 2MB) page, Pentium+, if present.. */
  #define _PAGE_GLOBAL    0×100  /* Global TLB entry PPro+ */
    在你閱讀原始碼的過程中你能體會到,把標誌位定義為巨集而不是位段更有利於編碼。

    另外,頁目錄表及頁表在pgtable.h中定義如下:
     externpgd_t swapper_pg_dir[1024];
     externunsigned long pg0[1024];
     swapper_pg_dir為臨時頁目錄表,是在核心編譯的過程中被靜態初始化的。pg0為初始化過程中使用的一臨時頁表。
 

2.線性地址域的定義       
(1)頁偏移量的位數
  #define PAGE_SHIFT      12
  #define PAGE_SIZE       (1UL << PAGE_SHIFT)
     #define PTRS_PER_PTE    1024
  #define PAGE_MASK       (~(PAGE_SIZE-1))
   其中PAGE_SHIFT巨集定義了頁偏移量的位數為12,因此頁大小PAGE_SIZE2124096位元組; PTRS_PER_PTE為頁表的項數;最後PAGE_MASK值定義為0xfffff000,用以遮蔽掉偏移量域的所有位(12位)。
(2)PGDIR_SHIFT
   #define PGDIR_SHIFT     22
   #define PTRS_PER_PGD    1024
   #define PGDIR_SIZE      (1UL << PGDIR_SHIFT)
   #define PGDIR_MASK      (~(PGDIR_SIZE-1))
   PGDIR_SHIFT是頁表所能對映區域線性地址的位數,它的值為2212位的偏移量加上10位的頁表);PTRS_PER_PGD為頁目錄目錄項數;PGDIR_SIZE為頁目錄的大小,222,即4MBPGDIR_MASK0xffc00000,用於遮蔽偏移量位與頁表域的所有位。
3PMD_SHIFT
#definePMD_SHIFT       22
#definePTRS_PER_PMD    1
   PMD_SHIFT為中間目錄表對映的地址位數,其值也為22,但是對於兩級頁表結構,讓其目錄項個數為1,這就使得中間目錄在指標序列中的位置被儲存,以便同樣的程式碼在32位系統和64位系統下都能使用。後面的討論我們不再提及中間目錄。
 
3  對頁目錄及頁表的處理
   page.hpgtable.hpgtable-2level.h三個檔案中還定義有大量的巨集,用以對頁目錄、頁表及表項的處理,我們在此介紹一些主要的巨集和函式。
 
  3.1.表項值的確定
   staticinline int pgd_none(pgd_t pgd)          { return 0; }
   staticinline int pgd_present(pgd_t pgd)       { return 1; }
  
   #definepte_present(x)  ((x).pte_low &(_PAGE_PRESENT | _PAGE_PROTNONE))
     pgd_none()函式直接返回0,表示尚未為這個頁目錄建立對映,所以頁目錄項為空。pgd_present()函式直接返回1,表示對映雖然還沒有建立,但頁目錄所對映的頁表肯定存在於記憶體(即頁表必須一直在記憶體)。
pte_present巨集的值為10,表示P標誌位。如果頁表項不為0,但標誌位為0,則表示對映已經建立,但所對映的物理頁面不在記憶體。
  3.2. 清相應表的表項:
  #definepgd_clear(xp)                          do { } while (0)
  #definepte_clear(xp)   do { set_pte(xp,__pte(0)); } while (0)
  pgd_clear巨集實際上什麼也不做,定義它可能是為了保持程式設計風格的一致。pte_clear就是把0寫到頁表表項中。
 3.3.對頁表表項標誌值進行操作的巨集。
 這些巨集的程式碼在pgtable.h檔案中,表2.2給出巨集名及其功能。
  2.2 對頁表表項標誌值進行操作的巨集及其功能

      巨集名   

功能

  Set_pte()
  
  把一個具體的值寫入表項
  
  Pte_read()
  
  返回User/Supervisor標誌值(由此可以得知是否可以在使用者態下訪問此頁)
  
  Pte _write()
  
  如果Present標誌和Read/Write標誌都為1,則返回1(此頁是否存在並可寫)
  
  Pte _exec()
  
  返回User/Supervisor標誌值
  
  Pte _dirty()
  
  返回Dirty標誌的值(說明此頁是否被修改過)
  
  Pte _young()
  
  返回Accessed標誌的值(說明此頁是否被存取過) 
  
  Pte _wrprotect()
  
  清除Read/Write標誌
  
  Pte _rdprotect()
  
  清除User/Supervisor標誌
  
  Pte _mkwrite
  
  設定Read/Write標誌
  
  Pte _mkread
  
  設定User/Supervisor標誌
  
  Pte _mkdirty()
  
  Dirty標誌置1
  
  Pte _mkclean()   Dirty標誌置0
  
  Pte _mkyoung   Accessed標誌置1
  
  Pte _mkold()   Accessed標誌置0
  
  Pte _modify(p,v)   把頁表表項p的所有存取許可權設定為指定的值v
  
  mk_pte()   把一個線性地址和一組存取許可權合併來建立一個32位的頁表表項
  
  pte _pte_phys()   把一個實體地址與存取許可權合併來建立一個頁表表項
  
  pte _page()   從頁表表項返回頁的線性地址
  


   實際上頁表的處理是一個複雜的過程,在這裡我們僅僅讓讀者對軟硬體如何結合起來有一個初步的認識。
三、模組程式設計舉例

結合上面的介紹,我們編寫一個核心模組,把一個給定的虛地址轉換為記憶體的實體地址:

  1. /*****************************************************************
  2. 檔名:mem.c
  3. 輸入引數:
  4. pid 接收待查詢程序的PID
  5. va 接收待查詢的虛擬地址
  6. *****************************************************************/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/pgtable.h>
#include <asm/page.h>
MODULE_LICENSE("GPL");
static int pid;
static unsigned long va;
module_param(pid,int,0644);
module_param(va,ulong,0644);
static int find_pgd_init(void)
{
unsigned long pa = 0;
struct task_struct *pcb_tmp = NULL;
pgd_t *pgd_tmp = NULL;
pud_t *pud_tmp = NULL;
pmd_t *pmd_tmp = NULL;
pte_t *pte_tmp = NULL;

printk("<0>""PAGE_OFFSET = 0x%lx\n",PAGE_OFFSET);
printk("<0>""PGDIR_SHIFT = %d\n",PGDIR_SHIFT);
printk("<0>""PUD_SHIFT = %d\n",PUD_SHIFT);
printk("<0>""PMD_SHIFT = %d\n",PMD_SHIFT);
printk("<0>""PAGE_SHIFT = %d\n",PAGE_SHIFT);
printk("<0>""PTRS_PER_PGD = %d\n",PTRS_PER_PGD);
printk("<0>""PTRS_PER_PUD = %d\n",PTRS_PER_PUD);
printk("<0>""PTRS_PER_PMD = %d\n",PTRS_PER_PMD);
printk("<0>""PTRS_PER_PTE = %d\n",PTRS_PER_PTE);
printk("<0>""PAGE_MASK = 0x%lx\n",PAGE_MASK);

if (!(pcb_tmp = pid_task(find_vpid(pid), PIDTYPE_PID))) {
printk("<0>""Can't find the task %d .\n",pid);
return 0;
}
printk("<0>""pgd = 0x%p\n",pcb_tmp->mm->pgd);

/*
判斷給出的地址va是否合法
(va&lt;vm_end)*/
if(!find_vma(pcb_tmp->mm,va)){
printk("<0>""virt_addr 0x%lx not available.\n",va);
return 0;
}
pgd_tmp = pgd_offset(pcb_tmp->mm,va);
printk("<0>""pgd_tmp = 0x%p\n",pgd_tmp);
printk("<0>""pgd_val(*pgd_tmp) = 0x%lx\n",pgd_val(*pgd_tmp));

if(pgd_none(*pgd_tmp)){
printk("<0>""Not mapped in pgd.\n");
return 0;
}
pud_tmp = pud_offset(pgd_tmp,va);
printk("<0>""pud_tmp = 0x%p\n",pud_tmp);
printk("<0>""pud_val(*pud_tmp) = 0x%lx\n",pud_val(*pud_tmp));

if(pud_none(*pud_tmp)){
printk("<0>""Not mapped in pud.\n");
return 0;
}
pmd_tmp = pmd_offset(pud_tmp,va);
printk("<0>""pmd_tmp = 0x%p\n",pmd_tmp);
printk("<0>""pmd_val(*pmd_tmp) = 0x%lx\n",pmd_val(*pmd_tmp));

if(pmd_none(*pmd_tmp)){
printk("<0>""Not mapped in pmd.\n");
return 0;
}
/*
在這裡,把原來的pte_offset_map()改成了
pte_offset_kernel*/
pte_tmp = pte_offset_kernel(pmd_tmp,va);
printk("<0>""pte_tmp = 0x%p\n",pte_tmp);
printk("<0>""pte_val(*pte_tmp) = 0x%lx\n",pte_val(*pte_tmp));

if(pte_none(*pte_tmp)){
printk("<0>""Not mapped in pte.\n");
return 0;
}

if(!pte_present(*pte_tmp)){
printk("<0>""pte not in RAM.\n");
return 0;
}
pa = (pte_val(*pte_tmp)& PAGE_MASK) |(va & ~PAGE_MASK);
printk("<0>""virt_addr 0x%lx in RAM is 0x%lx .\n",va,pa);
printk("<0>""contect in 0x%lx is 0x%lx\n",pa, *(unsigned long *)((char *)pa + PAGE_OFFSET));      

//此處有問題,只有低端記憶體才會有可以線性對映,我想應用copy_to_user
return 0;
}


static void find_pgd_exit(void)

      printk("<0>""Goodbye!\n");
}
module_init(find_pgd_init);
module_exit(find_pgd_exit);

Makefile如下:

obj-m := pgd.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
      $(MAKE) -C $(KDIR) M=$(PWD) modules

測試:開啟gedit, 再開啟工作管理員,檢視gedit的程序號pid=12749,
右鍵檢視其記憶體對映,找到一個有效的虛擬地址va=0xb8041000,然後:
sudo insmod mem.ko pid=12749 va=0xb8041000

如果你的核心是2.6.24以後的,需要將find_task_by_pid改為find_task_by_vpid