mmap内存映射操作

 我们在驱动的代码当中,经常需要把硬件地址映射到虚拟内存上面去,这就是我们需要学习mmap的原因所在了!正所谓磨刀不误砍柴工,让我们静下心来学习一下这个十分有用的工具吧。

 

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

parameter : 
start  : 映射区的开始地址。(一般建议为null,让内核帮我们自动寻找一个合适的地址)
length : 映射区的长度。
prot   :期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起。
         PROT_EXEC  //页内容可以被执行
         PROT_READ  //页内容可以被读取
         PROT_WRITE //页可以被写入
         PROT_NONE  //页不可访问
flags  :指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。
         MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。
                    //直到msync()或者munmap()被调用,文件实际上不会被更新。
         MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,
                     //只能使用其中一个。(还有更多可选参数,具体网上易得)
fd     :有效的文件描述词。
offset :被映射对象内容的起点。

return : 
返回所映射的虚拟内存首地址。

例程如下:
   
#include 
  
  
   
     
#include
   
   
    
      
#include
    
    
     
       
#include
     
     
      
        
#include
      
      
        #include 
       
         int main() { int fd; char *start; char buf[100]; /*打开文件*/ fd = open("testfile",O_RDWR); start=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); /* 读出数据 */ strcpy(buf,start); printf("buf = %s/n",buf); /* 写入数据 */ strcpy(start,"Buf Is Not Null!"); munmap(start,100); /*解除映射*/ close(fd); return 0; } 
        
      
     
     
    
    
   
   
  
  
1、 测试例程先打开测试文件testfile,取得文件描述符fd。并通过调用mmap函数把测试文件映射到虚拟内存当中去,
    其首地址返回到指针start。
2、 读出数据时,直接strcpy,从虚拟内存的首地址开始复制字符串到buf,其实质就是把测试文件testfile的内容复制到buf
    (事先已经做了映射)。
3、 写入数据时,同样通过strcpy即可,但是需要注意的事,写入数据的动作,不会改变测试文件的长度。亦即测试文件原来内容
    的长度为10个字节,如果我们写入多于10个字节的内容进去,文件只会保存前10个。如果写入少于10个字节长度的内容,那后
    面没有实际内容的空余字节会出现乱码。
4、munmap函数的作用是解除映射,一般与mmap配对使用,该函数相对简单,在这里不做详细介绍。

    通过以上分析,我们采用了内存映射的方法来代替了传统的read、write等系统调用,可能相对于文件来说其便捷性还不能充分
得以展示,倘若运用在硬件物理地址映射到虚拟内存地址上,其效果就能很好地体现出来了。
这次我们来讲述mmap较为具体一点的实现细节。
mmap设备方法是file_operations结构的成员,在Mmap系统调用发出时被调用。在此之前,内核已经完成了很多工作。mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表。

prototype : 
int (*mmap)(struct file *, struct vm_area_struct *);

parameter: 
struct file * : 需要操作的文件
struct vm_area_struct * : 内核自动帮我们找到的一个虚拟内存区域。

return : 
虚拟内存区域起始地址


Linux内核使用结构vm_area_struct 来描述虚拟虚拟内存区域,其中几个主要成员如下:
unsigned long vm_start : 虚拟内存区域起始地址
unsinged long vm_end  : 虚拟内存区域结束地址
unsigned long vm_flags : 该区域的标记(能否直接把信息通过虚拟地址存入物理地址等)

通过上面的介绍,其实mmap是如何完成页表的建立呢?方法有两种:
1.使用remap_pfn_range一次建立所有页表
2.使用nopage VMA方法每次建立一个页表

我们这里详细介绍方法一。

prototype : 
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
                    unsigned long pfn, unsigned long size, pgprot_t prot)
parameter: 
vma  : 虚拟内存区域指针
addr : 虚拟地址的起始值
pfn  : 要映射的物理地址的页帧号,即将物理地址右移PAGE_SHIFT(12位),至于为什
       么是12位,敬请查看《深入理解LINUX内核》内存管理部分
size : 要映射的区域的大小。
prot : VMA的保护属性。
return : 返回虚拟内存起始地址。

操作实例:
    
int memdev_mmap(struct file * filp, struct vm_area_struct * vma)  
{  
    vma -> vm_flags |= VM_IO;  
    vma -> vm_flags |= VM_RESERVED;  //设置保护属性  
      
    if (remap_pfn_range(vma, vma -> vm_start, virt_to_phys(dev -> data) >> PAGE_SHIFT, size, vma -> vm_page_prot))  
    {  
         return -EAGAIN;  
    }  
 
    return 0;  
}  
注意这里的remap_pfn_range函数里的virt_to_phys(dev -> data),因为例子采用的“设备”其实就是内存,所以需要先转化成物理地址再移位。如果以后我们操作实际的硬件,这里就不用转化了,直接填入物理地址即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值