天天看點

Linux下MMAP驅動實作​

mmap簡介​

mmap函數用于将一個檔案或者其它對象映射進記憶體,通過對這段記憶體的讀取和修改,來實作對檔案的讀取和修改,而不需要再調用read,write等操作。​

頭檔案:<sys/mman.h>​

函數原型:​

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

int munmap(void* start,size_t length);​

mmap系統調用接口參數說明​

  • 映射函數
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); ​
  1. addr: 指定映射的起始位址,通常設為NULL,由系統指定。​
  2. length:映射到記憶體的檔案長度。​
  3. prot:映射的保護方式,可以是:​PROT_EXEC:映射區可被執行​

    PROT_READ:映射區可被讀取​

    PROT_WRITE:映射區可被寫入​

    PROT_NONE:映射區不能存取​

  4. Flags:映射區的特性,可以是:​MAP_SHARED:寫入映射區的資料會複制回檔案,且允許其他映射該檔案的程序共享。​

    MAP_PRIVATE:對映射區的寫入操作會産生一個映射區的複制(copy_on_write),對此區域所做的修改不會寫回原檔案。​

  5. fd:由open傳回的檔案描述符,代表要映射的檔案。​
  6. offset:以檔案開始處的偏移量,必須是分頁大小的整數倍,通常為0,表示從檔案頭開始映射。​
  • 解除映射
int munmap(void *start, size_t length); ​

功能:取消參數start所指向的映射記憶體,參數length表示欲取消的記憶體大小。​

傳回值:解除成功傳回0,否則傳回-1​

Linux核心的mmap接口​

3.1 核心描述虛拟記憶體的結構體​

Linux核心中使用結構體vm_area_struct來描述虛拟記憶體的區域,其中幾個主要成員如下:​

unsigned long vm_start 虛拟記憶體區域起始位址​

unsigned long vm_end 虛拟記憶體區域結束位址​

unsigned long vm_flags 該區域的标志​

該結構體定義在<linux/mm_types.h>頭檔案中。​

該結構體的vm_flags成員指派的标志為:VM_IO和VM_RESERVED。​

其中:VM_IO表示對裝置IO空間的映射,M_RESERVED表示該記憶體區不能被換出,在裝置驅動中虛拟頁和實體頁的關系應該是長期的,應該保留起來,不能随便被别的虛拟頁換出(取消)。​

mmap操作接口​

在字元裝置的檔案操作集合(struct file_operations)中有mmap函數的接口。原型如下:​

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

其中第二個參數struct vm_area_struct *相當于核心找到的,可以拿來用的虛拟記憶體區間。mmap内部可以完成頁表的建立。​

3.3 實作mmap映射​

映射一個裝置是指把使用者空間的一段位址關聯到裝置記憶體上,當程式讀寫這段使用者空間的位址時,它實際上是在通路裝置。這裡需要做的兩個操作:​

1.找到可以用來關聯的虛拟位址區間。​

2.實作關聯操作。​

mmap裝置操作執行個體如下:​

static int tiny4412_mmap(struct file *myfile, struct vm_area_struct *vma)​

{​

vma->vm_flags |= VM_IO;//表示對裝置IO空間的映射 ​

vma->vm_flags |= VM_RESERVED;//标志該記憶體區不能被換出,在裝置驅動中虛拟頁和實體頁的關系應該是長期的,應該保留起來,不能随便被别的虛拟頁換出 ​

if(remap_pfn_range(vma,//虛拟記憶體區域,即裝置位址将要映射到這裡 ​

虛拟空間的起始位址 ​

與實體記憶體對應的頁幀号,實體位址右移12位 ​

映射區域大小,一般是頁大小的整數倍 ​

保護屬性, ​

{ ​

return -EAGAIN; ​

} ​

printk("tiny4412_mmap\n");​

return 0;​

}​

其中的buf就是在核心中申請的一段空間。使用kmalloc函數實作。​

代碼如下:​

buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);​

//核心申請記憶體隻能按頁申請,申請該記憶體以便後面把它當作虛拟裝置 ​

3.4 remap_pfn_range函數​

remap_pfn_range函數用于一次建立所有頁表。函數原型如下:​

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot); ​

其中vma是核心為我們找到的虛拟位址空間,addr要關聯的是虛拟位址,pfn是要關聯的實體位址,size是關聯的長度是多少。​

  • ioremap與phys_to_virt、virt_to_phys的差別:​

ioremap是用來為IO記憶體建立映射的, 它為IO記憶體配置設定了虛拟位址,這樣驅動程式才可以通路這塊記憶體。 ​

3.5 示例代碼: 驅動層

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/slab.h> //定義kmalloc接口  
#include <asm/io.h>     //定義virt_to_phys接口  
#include <linux/mm.h>   //remap_pfn_range
#include <linux/vmalloc.h>
#include <linux/delay.h>
#define MM_SIZE 4096  
static char *buf= NULL;  
static int tiny4412_open(struct inode *my_indoe, struct file *my_file)
{
   buf=(char *)kmalloc(MM_SIZE,GFP_KERNEL);//核心申請記憶體隻能按頁申請,申請該記憶體以便後面把它當作虛拟裝置  
   if(buf==NULL)
   {
      printk("error!\n");
      return 0;
   }
   strcpy(buf,"1234567890");
   printk("open ok\n");
   return 0;
}

static int tiny4412_release(struct inode *my_indoe, struct file *my_file)
{
   printk("驅動層列印=%s\n",buf);
   kfree(buf); /*釋放空間*/
   printk("open release\n");
   return 0;
}

static int tiny4412_mmap(struct file *myfile, struct vm_area_struct *vma)
{
   vma->vm_flags |= VM_IO;//表示對裝置IO空間的映射  
   vma->vm_flags |= VM_RESERVED;//标志該記憶體區不能被換出,在裝置驅動中虛拟頁和實體頁的關系應該是長期的,應該保留起來,不能随便被别的虛拟頁換出  
   if(remap_pfn_range(vma,//虛拟記憶體區域,即裝置位址将要映射到這裡  
                  vma->vm_start,//虛拟空間的起始位址  
                  virt_to_phys(buf)>>PAGE_SHIFT,//與實體記憶體對應的頁幀号,實體位址右移12位  
                  vma->vm_end - vma->vm_start,//映射區域大小,一般是頁大小的整數倍  
                  vma->vm_page_prot))//保護屬性,  
   {  
      return -EAGAIN;  
   }  

   printk("tiny4412_mmap ok\n");
   return 0;
}

static struct file_operations tiny4412_fops=
{
   .open=tiny4412_open,
   .release=tiny4412_release,
   .mmap=tiny4412_mmap
};

static struct miscdevice misc={
   .minor=255,
   .name="tiny4412_mmap",  // /dev/下的名稱
   .fops=&tiny4412_fops,
};

static int __init hello_init(void)
{
   /*1. 注冊雜項字元裝置*/
   misc_register(&misc);
   printk("hello_init 驅動安裝成功!\n");
   return 0;
}

static void __exit hello_exit(void)
{
    /*2. 登出*/
    misc_deregister(&misc);
   printk("hello_exit驅動解除安裝成功!\n");
}

module_init(hello_init); 
module_exit(hello_exit); 

MODULE_AUTHOR("www.wanbangee.com");      //聲明驅動的作者
MODULE_DESCRIPTION("hello 子產品測試"); //描述目前驅動功能
MODULE_LICENSE("GPL");  //驅動許可證。支援的協定GPL。      

3.6 示例代碼: 應用層

#include <stdio.h>
#include <sys/mman.h>

unsigned char *fbmem=NULL;
unsigned char buff[10];
int main(int argc,char**argv)
{
   int fd;
   fd=open("/dev/tiny4412_mmap",2);
   if(fd<0)
    {
      printf("驅動打開失敗!\n");
      return -1;  
    }
   fbmem =(unsigned char *)mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
   if(fbmem==NULL)
   {
     printf("映射錯誤!\n");
   }
   printf("應用層列印1=%s\n",fbmem); //列印出123456789
   memcpy(fbmem,"123456789",10);   //向映射空間拷貝資料
   memcpy(buff,fbmem,10);          //将映射空間的資料拷貝出來
   printf("應用層列印2=%s\n",buff); //列印出123456789
   close(fd);
   return 0;
}
      

繼續閱讀