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;
}