Linux裝置驅動之記憶體映射__Linux

來源:互聯網
上載者:User

http://blog.csdn.net/chenjin_zhong/article/details/6314213


1. 記憶體映射

所謂的記憶體映射就是把實體記憶體映射到進程的地址空間之內,這些應用程式就可以直接使用輸入輸出的地址空間,從而提高讀寫的效率。Linux提供了mmap()函數,用來映射實體記憶體。

在驅動程式中,應用程式以裝置檔案為對象,調用mmap()函數,核心進行記憶體映射的準備工作,產生vm_area_struct結構體,然後調用裝置驅動程式中定義的mmap函數。

 

2. 映射的種類

把同一個物理地址映射為虛擬位址有兩種方法,第一種是mmap()函數將物理地址映射到進程的虛擬位址空間中去,第二種方法為ioremap()函數映射到核心虛擬位址上的方法。

 

應用程式中的mmap函數:

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);

start 映射到進程空間的虛擬位址

length 映射空間的大小

prot 映射到記憶體的讀寫權限

flags flags可取MAP_SHARED,MAP_PRIVATE,MAP_FIXED,如果是MAP_SHARED,此進程對映射空間的內容修改會影響到其它的進程,即對其它的進程可見,而MAP_PRIVATE,此進程修改的內容對其它的進程不可見

fd  要對應檔的檔案標識符

offset 對應檔的位置,一般從頭開始。而在裝置檔案中,表示映射物理地址的起始地址

 

裝置驅動程式的mmap函數:

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

首先調用應用程式的mmap函數,然後核心進行適當處理之後,進行相應的記憶體映射,即產生vm_area_struct結構體,然後傳遞給裝置驅動程式的mmap函數。

關於vma中的一些參數說明:

(1)unsigned long vm_start 映射到進程空間的起始地址

(2)unsigned long vm_end   映射到進程空間的結束位址

(3)unsigned long vm_flags  即包含在應用程式中mmap中的flags值,如VM_READ,VM_WRITE,VM_SHARED,VM_EXEC

(4)unsigned long vm_pgoff 映射到實體記憶體的位移量

 

mmap映射的方法:

有兩種方法建立頁表,一次性建立頁表,可以調用函數remap_pfn_range和每次建立一個頁的頁表,調用函數nopage。

 

remap_pfn_range:

這個函數的功能是一次性建立新的頁表去映射物理地址。

int remap_pfn_range(struct vma_area_struct* vma,unsigned long virt_addr,unsigned long pfn,unsigned long size,pgprot_t prot);

 

傳回值:映射成功時返回0,否則返回一個錯誤的負數代碼。

 

vma 物理地址被映射到的虛擬記憶體地區

virt_addr 被映射到進程空間的起始虛擬位址。頁表建立的範圍在virt_addr到virt_addr+size

pfn 對應物理地址的頁框號,一般是vma->vm_pgoff域。

size 被映射地區的位元組大小

prot  vma->vm_page_prot

 

 

nopage:

 

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

 

vm_area_struct:虛擬記憶體地區

address:發生page fault的進程空間的虛擬位址

type  page fault的處理類型

 

get_page(struct page* pageptr);

增加被映射頁的使用次數。

 

3. remap_pfn_range與nopage的區別

(1)remap_pfn_range一次性建立頁表,而nopage通過缺頁中斷找到核心虛擬位址,然後通過核心虛擬位址找到對應的物理頁

(2)remap_pfn_range函數只對保留頁和實體記憶體之外的物理地址映射,而對常規RAM,remap_pfn_range函數不能映射,而nopage函數可以映射常規的RAM。

 

4. 例子

 

下面的例子分別採用remap_pfn_range與nopage建立記憶體映射

 驅動程式memap.c:

 #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <asm/page.h>
#include <asm/io.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/init.h>
#define SHARE_MEM_COUNT 4
#define SHARE_MEM_SIZE (PAGE_SIZE*SHARE_MEM_COUNT)

 

MODULE_LICENSE("GPL");
static char* reserve_virt_addr;
static int major;
char* share_memory=NULL;
int mmapdrv_open(struct inode*,struct file* filp); //驅動程式的open函數
int mmapdrv_release(struct inode*,struct file* filp);//驅動程式的release函數
int mmapdrv_mmap(struct file* file,struct vm_area_struct*vma);//驅動程式中的mmap函數
void simple_vma_open(struct vm_area_struct* vma );//vm_operations_struct對vma(虛擬記憶體地區)開啟函數
void simple_vma_close(struct vm_area_struct *vma);//vma的關閉函數
struct page* simple_vma_nopage(struct vm_area_struct* vma,unsigned long address,int *type);//nopage映射
struct page* simple_vma_nopage1(struct vm_area_struct* vma,unsigned long address,int *type);
static int simple_nopage_mmap(struct file* filp,struct vm_area_struct *vma);//驅動程式中的mmap與上面的mmap可選擇其一
static struct file_operations mmapdrv_fops={
 owner:THIS_MODULE,
 mmap:simple_nopage_mmap,
 open: mmapdrv_open,
 release:mmapdrv_release,

};
static struct vm_operations_struct simple_remap_vm_ops={
 .open=simple_vma_open,
 .close=simple_vma_close,
 .nopage=simple_vma_nopage1,

};

static int __init memc_init(void){

if((major=(register_chrdev(0,"mapdrv",&mmapdrv_fops)))<0){
  printk("register mapdrv failure/n");
  return -EIO;
}
printk("register success,major=%d/n",major);

share_memory=vmalloc(SHARE_MEM_SIZE);//通過vmalloc分配記憶體,返回的是核心虛擬位址,然後將實體記憶體映射到進程的虛擬位址空間上去
int lp;
for(lp=0;lp<SHARE_MEM_COUNT;lp++){
sprintf(share_memory+PAGE_SIZE*lp,"Test %d",lp);
}

 return 0;
}

static void __exit memc_exit(void){
  if(reserve_virt_addr){
  iounmap(reserve_virt_addr);
}
  unregister_chrdev(major,"mapdrv");
  return;

 


}

int mmapdrv_open(struct inode* inode,struct file* filp){

 //MOD_INC_USE_COUNT;
 return 0;

}

int mmapdrv_release(struct inode* inode,struct file* filp){
 //MOD_DEC_USE_COUNT;
 return(0);

}

 

//remap_pfn_range一次性建立頁表進行映射

int mmapdrv_mmap(struct file* filp,struct vm_area_struct *vma){

 

printk("vm_pgoff=0x%lx/n",vma->vm_pgoff<<PAGE_SHIFT); //返回的是物理地址位移
printk("vm_start=0x%lx/n",vma->vm_start);//進程地址空間的起始地址
printk("vm_end=0x%lx/n",vma->vm_end);//進程地址空間的結束位址
printk("vm_flags=0x%lx/n",vma->vm_flags);
unsigned long physical=vma->vm_pgoff<<PAGE_SHIFT;
unsigned long size=vma->vm_end-vma->vm_start; //映射的空間長度
vma->vm_flags|=VM_RESERVED;//remap只能對VM_RESERVED和實體記憶體之外的記憶體進行映射
vma->vm_flags|=VM_IO;
if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,size,PAGE_SHARED)){
  printk("remap page range failed/n");
return -ENXIO;
}

printk("remap page range success/n");
return 0;

 

 

}

 

//nopage映射
static int simple_nopage_mmap(struct file* filp,struct vm_area_struct *vma){
unsigned long offset=vma->vm_pgoff<<PAGE_SHIFT;
if(offset>=__pa(high_memory)||(filp->f_flags&O_SYNC)){
 vma->vm_flags|=VM_IO;
 }
vma->vm_flags|=VM_RESERVED;
vma->vm_ops=&simple_remap_vm_ops;//當發生page fault時會調用nopage函數進行缺頁處理
simple_vma_open(vma);
return 0;
}

void simple_vma_open(struct vm_area_struct *vma){
printk(KERN_NOTICE "simple VMA open virt %lx,phys %lx/n",vma->vm_start,vma->vm_pgoff<<PAGE_SHIFT);


}
void simple_vma_close(struct vm_area_struct *vma){
printk(KERN_NOTICE "Simple VMA close./n");


}

 

//simple_vma_nopage是通過物理地址找到page,而simple_vma_nopage1通過核心虛擬位址找到page, 即vmalloc返回的核心虛擬位址
struct page* simple_vma_nopage(struct vm_area_struct* vma,unsigned long address,int *type){

printk("call nopage method/n");
struct page* pageptr;
unsigned long offset=vma->vm_pgoff<<PAGE_SHIFT;
unsigned long physaddr=address-vma->vm_start+offset; //address是缺頁進程地址空間的虛擬位址,vm_start是進程地址空間的起始映射地址,address-vma->vm_start+offset要映射的物理地址
unsigned long pageframe=physaddr>>PAGE_SHIFT;
printk("offset is %lx, physaddr is %lx,pageframe is %lx/n",offset,physaddr,pageframe);
if(!pfn_valid(pageframe))
 return NOPAGE_SIGBUS;
pageptr=pfn_to_page(pageframe);//根據頁框號,得到page
if(type)
*type=VM_FAULT_MINOR;
printk("pageptr is %lx/n",(unsigned long)pageptr);
return pageptr;
}

 

//首先根據page fault的進程地址空間的address找到核心虛擬位址,然後根據核心虛擬位址,即vmalloc返回的地址找到相對應的page

//對於address-vma->vm_start位址範圍的內容是通過vmalloc()+address-vma->vm_start找到相應的頁取得的,所以對於使用者空間address-vma->vm_start儲存的內容就是vmalloc()+address-vma->vm_start對應頁的內容
struct page* simple_vma_nopage1(struct vm_area_struct* vma,unsigned long address,int *type){
struct page *page;
unsigned long offset1;
void *page_ptr;
unsigned long offset=vma->vm_pgoff<<PAGE_SHIFT;
unsigned long physaddr=address-vma->vm_start+offset;
unsigned long pageframe=physaddr>>PAGE_SHIFT;
printk("vm_pgoff is %lx, PAGE_SHIFT is %lx,PAGE_SIZE is %lx,offset is %lx, physaddr is %lx,pageframe is %lx/n",vma->vm_pgoff,PAGE_SHIFT,(unsigned long)PAGE_SIZE,offset,physaddr,pageframe);
offset1=address-vma->vm_start; //映射的進程地址空間的位移
if(offset1>=SHARE_MEM_SIZE)return NOPAGE_SIGBUS;
page_ptr=share_memory+offset1;//對應的缺頁的核心虛擬位址
printk("address is %lx,vma->vm_start is %lx,offset1 is %lx,share_memory is %lx,page_ptr is %lx/n",address,vma->vm_start,offset1,(unsigned long )share_memory,(unsigned long)page_ptr);
page=vmalloc_to_page(page_ptr);
get_page(page);//增加該頁的使用計數
if(type) *type=VM_FAULT_MINOR;
return page;

}


module_init(memc_init);
module_exit(memc_exit);

 

 

 

測試程式:

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define SHARE_MEM_COUNT 4
#define SHARE_MEM_SIZE (4096*SHARE_MEM_COUNT)
int main(){
int fd;
char *data;
int loop;
fd=open("/dev/mapdrv",O_RDWR|O_NDELAY);
if(fd>=0){

data=(char*)mmap(0,SHARE_MEM_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
printf("data is %lx/n",(unsigned long)data);

printf("[%s]/n",data+4096*loop);
}
munmap(data,SHARE_MEM_SIZE);
close(fd);
}
return 0;
}

 

運行:

(1)將memap.c與test.c檔案放到/usr/src/kernels/linux-2.6.20/drivers/char目錄下。

並在Makefile檔案中添加obj-m  +=memap.o

(2)返回到/usr/src/kernels/linux-2.6.20下make.

(3)插入模組insmod memap.ko,然後 mknod /dev/globalvar c 252 0

252是動態產生的major值。

(4)編譯test gcc -o test test.c

(5)./test,可以列印出寫入的值。

 [Test 0]

 [Test 1]

 [Test 2]

 [Test 3]

 

 

總結:

1.對於mmap的記憶體映射,是將實體記憶體映射到進程的虛擬位址空間中去,那麼進程對檔案的訪問就相當於直接對記憶體的訪問,從而加快了讀寫操作的效率。在這裡,remap_pfn_range函數是一次性的建立頁表,而nopage函數是根據page fault產生的進程虛擬位址去找到核心相對應的邏輯地址,再通過這個邏輯地址去找到page。完成映射過程。remap_pfn_range不能對常規記憶體映射,只能對保留的記憶體與實體記憶體之外的進行映射。

 

2.在這裡,要分清幾個地址,一個是物理地址,這個很簡單,就是實體記憶體的實際地址。第二個是核心虛擬位址,即核心可以直接存取的地址,如kmalloc,vmalloc等核心功能返回的地址,kmalloc返回的地址也稱為核心邏輯地址。核心虛擬位址與實際的物理地址只有一個位移量。第三個是進程虛擬位址,這個地址處於使用者空間。而對於mmap函數映射的是物理地址到進程虛擬位址,而不是把物理地址映射到核心虛擬位址。而ioremap函數是將物理地址映射為核心虛擬位址。

 

3.使用者空間的進程調用mmap函數,首先進行必要的處理,產生vma結構體,然後調用remap_pfn_range函數建立頁表。而使用者空間的mmap函數返回的是映射到進程地址空間的首地址。所以mmap函數與remap_pfn_range函數是不同的,前者只是產生mmap,而建立頁表通過remap_pfn_range函數來完成。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.