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()一般對映裝置記憶體。