一、mmap系统调用
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
函数功能:内存映射,负责将文件内容映射到进程的虚拟内存空间,通过对这段内存空间的读写来实现对文件的读写操作,而不需要再调用read和write函数
参数start:文件映射到进程虚拟内存空间的起始地址,一般设置为NULL,由内核为自动选定
参数length:文件映射的长度
参数prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
参数flags:指定映射对象的类型
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
参数fd: 被映射的文件的文件描述符
参数offset:被映射的文件的文件偏移值
返回值:文件映射到进程虚拟内存空间的起始地址,以后通过该值就可以对文件进行读写操作了。
系统调用munmap()
#include <sys/mman.h>
int munmap(void * addr, size_t len)
功能:解除某文件在进程虚拟内存空间的映射
参数addr:调用mmap()时返回的地址
参数len:映射区的大小
二、内核驱动实现
当应用程序调用mmap后将导致相应设备驱动的mmap方法被调用,该函数主要做的工作:建立虚拟地址(用户虚拟进程空间)到物理地址(设备或者文件)的页表。
int mmap(struct file *file, struct vm_area_struct *vma)
参数file:应用打开的设备对应的文件描述符
参数vma:内核找到的可用的虚拟内存区域
mmap函数体主要就是实现虚拟内存区域和物理内存的关联工作
2.1 虚拟内存区域描述结构体
struct vm_area_struct {
struct mm_struct * vm_mm; /* The address space we belong to. */
unsigned long vm_start; /* 虚拟内存区域起始地址*/
unsigned long vm_end; /* 虚拟内存区域结束地址*/
struct vm_area_struct *vm_next, *vm_prev;/* linked list of VM areas per task, sorted by address */
pgprot_t vm_page_prot; /* 虚拟内存区域保护标志*/
unsigned long vm_flags; /* 虚拟内存区域标志*/
..........
};
2.2 Linux建立页表的方法
(1) 使用remap_pfn_range一次建立所有页表
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
参数vma:用户进程空间的一个虚拟内存区域指针
参数virt_addr:虚拟内存区域起始地址
参数pfn:要映射的物理地址所在的物理帧号(设备内存)
参数size:要映射的虚拟内存区域的大小
参数prot:VMA保护属性,驱动可(并且应当)使用在vma->vm_page_prot 中找到的值
返回值:成功返回 0, 失败返回一个负的错误值
(2) 使用nopage VMA方法每次建立一个页表项
struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type)
参数vma:用户进程空间的一个虚拟内存区域指针
参数address:用户空间传过来的用户空间虚拟地址
参数type:保存错误的类型
返回值:成功则返回一个有效映射页,失败返回NULL.
使用方面的限制:
remap_pfn_range不能映射常规内存,只存取保留页和在物理内存顶之上的物理地址。因为保留页和在物理内存顶之上的物理地址内存管理系统的各个子模块管理不到。640 KB 和
1MB 是保留页可能映射,设备I/O内存也可以映射。如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。
2.3 驱动代码举例
static int s3c_fimc_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long size= vma->vm_end - vma->vm_start;//虚拟内存区域大小
unsigned long max_size;
unsigned long page_frame_no;
page_frame_no = __phys_to_pfn(fimc_data_base_addr);//设备物理内存的帧号
max_size = fimc_reserved_mem_size;
if (size > max_size)
return -EINVAL;
vma->vm_flags |= VM_RESERVED | VM_IO;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (remap_pfn_range(vma, vma->vm_start, page_frame_no, size, vma->vm_page_prot)) {//完成设备内存的映射
printk("fimc remap error");
return -EAGAIN;
}
return 0;
}
三、参考文章