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); |
- addr: 指定映射的起始位址,通常設為NULL,由系統指定。
- length:映射到記憶體的檔案長度。
-
prot:映射的保護方式,可以是:PROT_EXEC:映射區可被執行
PROT_READ:映射區可被讀取
PROT_WRITE:映射區可被寫入
PROT_NONE:映射區不能存取
-
Flags:映射區的特性,可以是:MAP_SHARED:寫入映射區的資料會複制回檔案,且允許其他映射該檔案的程序共享。
MAP_PRIVATE:對映射區的寫入操作會産生一個映射區的複制(copy_on_write),對此區域所做的修改不會寫回原檔案。
- fd:由open傳回的檔案描述符,代表要映射的檔案。
- 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;
}