Implementation of mmap driver under Linux

For detailed analysis, please see: Wei Dongshan: mmap, the cornerstone of Linux drivers

1. Introduction to mmap

The mmap function is used to map a file or other object into memory. By reading and modifying this memory, the file can be read and modified without calling read, write and other operations.

Header file: <sys/mman.h>
Function prototype:

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 system call interface parameter description

  • (1) Mapping function

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

    Parameter Description:

    • addr: The specified file should be mapped to the starting address of the process address space, usually set to NULL, specified by the system.
    • len: The number of bytes mapped to the address space of the calling process, starting from offset bytes from the beginning of the mapped file.
    • prot: Specifies the access permission of the shared memory, which can be:
      PROT_EXEC: The mapped area can be executed
      PROT_READ : The mapped area can be read
      PROT_WRITE: The mapped area can be written
      PROT_NONE : The mapped area cannot be accessed
    • flags: Characteristics of the mapped area, which can be:
      MAP_SHARED: Data written to the mapped area will be copied back to the file and allowed to be shared by other processes mapping the file. MAP_PRIVATE
      : The write operation to the mapping area will produce a copy of the mapping area (copy_on_write), and the modifications made to this area will not be written back to the original file.
    • fd: The file descriptor returned by open, representing the file to be mapped.
    • offset: The offset of the mapping position at the beginning of the file, which must be an integer multiple of the paging size. Usually 0, which means mapping starts from the file header.
  • (2) Unmap

    int munmap(void *start, size_t length); 
    

    Function: Cancel the mapped memory pointed to by the parameter start. The parameter length indicates the memory size to be canceled. ​Return
    value: 0 is returned if the release is successful, otherwise -1 is returned.

3. mmap interface of Linux kernel

  • 3.1 The kernel describes the structure of virtual memory.
    The Linux kernel uses structures vm_area_structto describe the virtual memory area. Several of its main members are as follows:
    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为单位)
    

This structure is defined in the <linux/mm_types.h> header file.

The flags assigned to the vm_flags member of this structure are: VM_IO and 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 operation interface
    There is an interface for the mmap function in the file operation collection (struct file_operations) of the character device. The prototype is as follows:
    int (*mmap) (struct file *filp, struct vm_area_struct *vma);
    

The second parameter struct vm_area_struct * is equivalent to the virtual memory interval found by the kernel and can be used. The creation of page tables can be completed internally in mmap.

  • 3.3 Implement mmap mapping

    Mapping a device means associating an address in user space with device memory. When a program reads or writes this address in user space, it is actually accessing the device. Two operations need to be done here:
    1. Find the virtual address range that can be used for association. 2.
    Implement associated operations.
    Examples of mmap device operations are as follows:

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

    The buf is a space applied for in the kernel. Implemented using kmalloc function.
    code show as below:

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

  • 3.4.1 The remap_pfn_range function is used to create all page tables at once. The function prototype is as follows:

    int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot); 
    

    in:

    • vma: is the virtual address space that the kernel finds for us;
    • addr: The virtual address to be associated;
    • pfn: is the physical address to be associated;
    • size: is the length of the association;
    • prot: access permission (attribute) of vma.
  • 3.4.2 The difference between ioremap and phys_to_virt, virt_to_phys:​

    • ioremap: is used to create a mapping for IO memory. It allocates a virtual address to the IO memory so that the driver can access this memory.
    • phys_to_virt: Calculate the virtual address corresponding to a known physical address.
    • virt_to_phys: Convert virtual address to physical address
  • 3.5 Sample code: driver layer

    #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 Sample code: Application layer

    #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.Reference:

Guess you like

Origin blog.csdn.net/weixin_40837318/article/details/130080775