Linux下mmap驱动实现​

详细分析请看:韦东山:Linux驱动程序基石之mmap

1.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);

2.mmap系统调用接口参数说明

  • (1)映射函数

    void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); 
    

    参数说明:

    • addr:指定文件应被映射到进程地址空间的起始地址,通常设为NULL,由系统指定。​
    • len:映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
    • prot:指定共享内存的访问权限,可以是:
      PROT_EXEC:映射区可被执行​
      PROT_READ:映射区可被读取​
      PROT_WRITE:映射区可被写入​
      PROT_NONE:映射区不能存取​
    • flags:映射区的特性,可以是:
      ​MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。​
      MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy_on_write),对此区域所做的修改不会写回原文件。​
    • fd:由open返回的文件描述符,代表要映射的文件。​
    • offset:映射位置在文件开始处的偏移量,必须是分页大小的整数倍。通常为0,表示从文件头开始映射。
  • (2)解除映射

    int munmap(void *start, size_t length); 
    

    功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。​
    返回值:解除成功返回0,否则返回-1

3.Linux内核的mmap接口

  • 3.1 内核描述虚拟内存的结构体
    Linux内核中使用结构体vm_area_struct来描述虚拟内存的区域,其中几个主要成员如下:​
    unsigned long vm_start 虚拟内存区域起始地址​
    unsigned long vm_end 虚拟内存区域结束地址​
    unsigned long vm_flags 该区域的标志​
    unsigned long vm_page_prot 此vma的访问保护属性,在内核arch\powerpc\include\asm\book3s\32\pgtable.h文件中有以pgprot_开头的函数来配置相关的属性。
    unsigned long vm_pgoff 基于映射的文件头的偏移(以PAGE_SIZE为单位)
    

该结构体定义在<linux/mm_types.h>头文件中。​

该结构体的vm_flags成员赋值的标志为:VM_IO和VM_RESERVED。​

VM_IO表示对设备IO空间的映射;
M_RESERVED表示该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出(取消)。
从linux 3.7.0开始内核不再支持struct vm_area_struct结构体中flag标志使用值 VM_RESERVED;而是需要在类似的驱动开发中需要将VM_RESERVED改成(VM_DONTEXPAND | VM_DONTDUMP);
  • 3.2 mmap操作接口
    在字符设备的文件操作集合(struct file_operations)中有mmap函数的接口。原型如下:
    int (*mmap) (struct file *filp, struct vm_area_struct *vma);
    

其中第二个参数struct vm_area_struct *相当于内核找到的,可以拿来用的虚拟内存区间。mmap内部可以完成页表的建立。

  • 3.3 实现mmap映射

    映射一个设备是指把用户空间的一段地址关联到设备内存上,当程序读写这段用户空间的地址时,它实际上是在访问设备。这里需要做的两个操作:
    1.找到可以用来关联的虚拟地址区间。​
    2.实现关联操作。
    mmap设备操作实例如下:

    static int tiny4412_mmap(struct file *filp, struct vm_area_struct *vma){​
    	vma->vm_flags |= VM_IO;//表示对设备IO空间的映射 ​
    	vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出 ​
    	
    	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);	//修改访问保护属性
    	
    	if(remap_pfn_range(
    		vma,	//虚拟内存区域,即设备地址将要映射到这里
    		vma->vma_start,	//虚拟空间的起始地址
    		(phys + offset) >> PAGE_SHIFT,	//与物理内存对应的页帧号,物理地址右移12位
    		vma->vma_end - vma->vma_start,	//映射区域大小,一般是页大小的整数倍 ​
    		vma->vm_page_prot	//保护属性, ​
    	) {
    		return -EAGAIN;}printk("tiny4412_mmap\n");return 0;}
    

    其中的buf就是在内核中申请的一段空间。使用kmalloc函数实现。
    代码如下:

    buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备 
    
  • 3.4 remap_pfn_range函数

  • 3.4.1 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:是关联的长度是多少;
    • prot:vma的访问权限(属性)。​
  • 3.4.2 ioremap与phys_to_virt、virt_to_phys的区别:​

    • ioremap:是用来为IO内存建立映射的, 它为IO内存分配了虚拟地址,这样驱动程序才可以访问这块内存。 ​
    • phys_to_virt:计算出某个已知物理地址所对应的虚拟地址。​
    • virt_to_phys :虚拟地址转换为物理地址
  • 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;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出  
    
    	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);	//修改访问保护属性
    
       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;
    }
    

4.参考:

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux下,mmap函数可以用来将文件或设备的一部分物理内存映射到进程的虚拟地址空间中,从而实现进程和文件或设备的直接交互。使用mmap函数可以提高文件或设备的读写效率,避免了频繁的系统调用和缓冲区的拷贝。 mmap函数的原型为: ```c void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ``` 各个参数的含义如下: - addr:映射区域的首地址,一般设为NULL,由系统自动分配。 - length:映射区域的长度,单位是字节。 - prot:映射区域的保护模式,可以是PROT_READ、PROT_WRITE或PROT_EXEC的组合。 - flags:映射区域的标志,可以是MAP_SHARED、MAP_PRIVATE、MAP_FIXED等的组合。 - fd:需要映射的文件描述符。 - offset:文件偏移量,表示从文件的哪个位置开始映射。 mmap函数返回映射区域的首地址或者MAP_FAILED,表示映射失败。 使用mmap函数时,需要先打开文件或设备,并获取相应的文件描述符。然后,调用mmap函数将文件或设备的一部分物理内存映射到进程的虚拟地址空间中。最后,使用指针来访问映射区域的数据,进行读写操作。使用完映射区域后,需要调用munmap函数解除映射关系。 需要注意的是,使用mmap函数进行读写操作时,需要考虑到内存对齐和边界问题,否则可能会出现读写错误。同时,对于设备文件的映射,还需要考虑到设备驱动程序的特殊要求,比如缓冲区的大小和对齐方式等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值