1. 程式人生 > >linux記憶體學習筆記(二)——裝置地址到使用者空間

linux記憶體學習筆記(二)——裝置地址到使用者空間

系統呼叫mmap(使用者空間使用)

caddr_t mmap(caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);

prot,指定訪問許可權,

PROT_READ(可讀),

PROT_WRITE(可寫)

PROT_EXEC(可執行)

PROT_NONE(不可訪問)

caddr_t,實際上是 void *;

驅動中的mmap(核心空間)

int(*mmap)(struct file *,struct vm_area_struct *);

VMA,即vm_area_struct,用於描述一個虛擬記憶體區域

struct vm_area_struct{

       struct mm_struct *vm_mm;

       unsigned long vm_start;

       unsigned long vm_end;

       pgprot_t vm_page_prot;

       unsigned long vm_flags;

       struct vm_operations_struct *vm_ops;

       unsigned long vm_pgoff;

       struct file *vm_file;

       void *vm_private_data;

       ……

       }

當用戶進行mmap()系統呼叫後,儘管VMA已經產生,但是核心卻不會呼叫VMA操作集中的open()函式。通常需要在驅動的 mmap()函式中顯示呼叫 vma->vm_ops->open()。

使用模板

static int xxx_mmap(struct file *filp,struct vm_area_struct *vma)

{

       if(remap_pfn_range(vma,vma->vma_start,vm->vm_pgoff,vma->vm_end-

vma->vm_start,vma->vm_page_prot))

return –EAGAIN;

              vma->vm_ops=&xxx_rempa_vm_ops;

              xxx_vma_open(vma);//顯式呼叫

              return 0;

       }

       void xxx_vma_open(struct vm_area_struct *vma)

       {

              ……

              printk(KERN_NOTICE “xxx_VMA open”);

       }

       void xxx_vma_close(struct vm_area_struct *vma)

       {

              ……

              printk(KERN_NOTICE “xxx VMA close.\n”);

       }

       static struct vm_operations_struct xxx_remap_vm_ops={

              .open=xxx_vma_open,

              .close=xxx_vma_close,

              ……

       };

 模板說明:

(1)       VMA的資料成員是核心根據使用者的請求自動填充的。

(2)       vm_pgoff,這個成員是要對映到實體地址上的頁楨號。頁楨號實際上是實際的實體地址右移PAGA_SIZE得到的

(3)       整個對映的過程是有方向性的,同用戶空間(程序地址)到物理空間,而且使用者空間需要多少,物理空間才提供多少。提供的頁楨號從vm_pgoff開始。

(4)       對映必須以PAGE_SIZE為單位進行。即vma->vm_end - vma->vm_start=N*PAGE_SIZE

(5)       remap_pfn_range()函式原型

int remap_pfn_range(struct vm_area_struct *vma,unsigned long addr,

unsigned long pfn,unsigned long size,pgprot_t prot);

建立頁表。即對映。

還有一個作用相似的函式

int remap_page_range(unsigned long start,unsigned long phy_addr,

unsigned long size, pgprot_t prot);

它和 remap_pfn_range的區別就是前者用實際的實體地址(第二個引數)建立頁表,後者用頁楨號建立頁表。

        remap_pfn_range函式的一個限制是:它只能訪問保留頁和超出實體記憶體的實體地址。(Linux中,在記憶體對映時,實體地址頁被標記為reserved,表示記憶體管理對其不起作用。如在PC中,640KB-1MB之間的記憶體被標記為保留的,因為這個範圍位於核心自身程式碼的內部。)所以remap_pfn_range不允許重新對映常規地址。包括呼叫get_free_page函式所獲得的地址。相反,他能對映零記憶體頁。(引用來自網路)

       kmalloc()申請的記憶體若要被對映到使用者空間可以通過mem_map_reserve()設定為保留後進行。

       使用模板

————————————————————————————————————

       模組載入函式

              buffer = kmalloc(BUF_SIZE,GFP_KERNEL);

       for(page=virt_to_page(buffer);page<virt_to_page(buffer+BUF_SIZE);page++)

       mem_map_reserve(page);

       mmap()函式中

       while(size>0) {

              phyaddr=virt_to_phys((void *)buffer)

              if(remap_page_range(start,phyaddr,PAGE_SIZE,PAGE_SHARED))

                     return –EAGAIN;  

              start +=PAGE_SIZE;

              buffer +=PAGE_SIZE;

              size -= PAGE_SIZE;

              }return 0;

_______________________________________________________________________________

通常情況,I/O記憶體被對映時需要是noche的,這時,只需要在mmap()函式的使用模板中,對vma->vm_page_prot的標誌設為nocache即可。

即vma->vm_page_prot=pgprot_noncached(vma->vm_page_prot);

nopage()函式

當發生缺頁異常時,VMA操作集中的nopage()方法被呼叫。實現nopage()後,使用者通過mremap()系統呼叫重新對映區域所繫結的地址。

模板

struct page *xxx_vma_nopage(struct vm_area_struct *vma,unsigned long address,int *type)

{

       struct page *pageptr;

       unsigned long offset = vma->vm_pgoff<<PAGE_SHIFT;

       unsigned long physaddr = address – vma->vm_start+offset;

       unsigned long pageframe = physaddr >> PAGE_SHIFT;

       if(!pfn_valid(pageframe))

              return NOPAGE_SIGBUS;

       pageptr = pfn_to_page(pageframe);

       get_page(pageptr);

       if(type)

              *type = VM_FAULT_MINOR;

       return pageptr;

}

說明,pfn_valid()確保由使用者給定的虛擬地址轉化成實體地址頁楨號時是有效的。這樣,才能實現缺頁後,找到正確的物理頁面地址填充。

 nopage()中的address,可以是低端記憶體地址。對它的對映即是對RAM對映。而remap_pfn_range()一般對映裝置記憶體。