• mmap底层驱动实现(remap_pfn_range函数)


    mmap底层驱动实现

    myfb.c(申请了128K空间)
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define 	BUFF_SIZE 		(32 * 4 * 1024)
    
    static char *buff;
    static int major;
    static struct class * myfb_class;
    
    static int myfb_mmap (struct file *fp, struct vm_area_struct *vm)
    {
    	int res;
    	//表示该vma在虚拟地址空间中的偏移地址,单位是页(4K)
    	//unsigned long offset = vm->vm_pgoff << PAGE_SHIFT;   
    	
    	//计算被映射的物理内存的物理页帧号(物理地址+偏移),以页为单位, virt_to_phys将虚拟地址转成物理地址
    	//vm->pgoff表示的是用户空间映射时在VMA中的偏移(即mmap最后一个参数,单位是字节,但vm->pgoff自动转成页单位)
    	unsigned long pfn_start = (virt_to_phys(buff) >> PAGE_SHIFT);   
    
    	res = remap_pfn_range(vm, vm->vm_start, 
    				pfn_start + vm->vm_pgoff,   //在物理页帧号上加上偏移
    				vm->vm_end - vm->vm_start, 
    				vm->vm_page_prot);
    	if(res){
    		printk("remap_pfn_range failed\n");
    		return -1;
    	}
    
    	printk("[kernel] pfn_start = 0x%lx, vm->vm_pgoff = 0x%lx, \
    		\n[kernel] vm->vm_start = 0x%lx, vm->vm_end = 0x%lx, vir_ker_start = 0x%lx\n",  \
    		pfn_start, vm->vm_pgoff, vm->vm_start, vm->vm_end, (unsigned long)buff);
    	return 0;
    }
    
    static struct file_operations myfb_fops = {
    	.owner = THIS_MODULE,
    	.mmap  = myfb_mmap,
    };
    
    static int myfb_init(void)
    {
    	buff = kzalloc(BUFF_SIZE, GFP_KERNEL);
    	if (!buff){
    		printk("kzalloc failed!\n");
    		return -ENOMEM;
    	}
    	printk("kzalloc success!\n");
    
    	major = register_chrdev(0, "myfb", &myfb_fops);
    	myfb_class = class_create(THIS_MODULE, "myfb_class");
    	device_create(myfb_class, NULL, MKDEV(major, 0), NULL, "myfb");
    	
    	return 0;
    }
    
    static void myfb_exit(void)
    {
    	device_destroy(myfb_class, MKDEV(major, 0));
    	class_destroy(myfb_class);
    	unregister_chrdev(major, "myfb");
    	
    	kfree(buff);
    }
    
    
    module_init(myfb_init);
    module_exit(myfb_exit);
    MODULE_LICENSE("GPL");
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    mmap_read.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define   PAGE_SIZE    (4*1024)
    #define   BUFF_SIZE    (1 * PAGE_SIZE)
    #define   OFFSET       (2 * PAGE_SIZE)
    
    char *p;
    int fd;
    
    void ctrlc(int signum)
    {
    	munmap(p, BUFF_SIZE);
    	close(fd);
    }
    
    int main(void)
    {
    	signal(SIGINT, ctrlc);
    	fd = open("/dev/myfb", O_RDWR);
    
           	p = (char *)mmap(NULL, BUFF_SIZE, PROT_READ | PROT_WRITE , MAP_SHARED, fd, OFFSET);
    
    	if(p){
    		printf("mmap addr = 0x%x\n", p);
    		printf("data = %s\n", p);
    	}else{
    		printf("mmap failed\n");
    	}
    
    	while(1){
    		sleep(1);
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    mmap_write.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define   PAGE_SIZE    (4*1024)
    #define   BUFF_SIZE    (1 * PAGE_SIZE)
    #define   OFFSET       (0 * PAGE_SIZE)
    
    char *p;
    int fd;
    
    void ctrlc(int signum)
    {
    	munmap(p, BUFF_SIZE);
    	close(fd);
    }
    
    int main(void)
    {
    	signal(SIGINT, ctrlc);
    
    	fd = open("/dev/myfb", O_RDWR);
    
           	p = (char *)mmap(NULL, BUFF_SIZE, PROT_READ | PROT_WRITE , MAP_SHARED, fd, OFFSET);
    
    	if(p){
    		printf("mmap addr = 0x%x\n", p);
    		memcpy(p, "hello world", 20);
    	}else{
    		printf("mmap failed\n");
    	}
    
    	while(1){
    		sleep(1);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    Makefile

    KERNEL_DIR = /home/me/Kernel_Uboot/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
    
    all:
    	make -C $(KERNEL_DIR) M=`pwd` modules
    	$(CROSS_COMPILE)gcc mmap_read.c -o mmap_read
    	$(CROSS_COMPILE)gcc mmap_write.c -o mmap_write
    
    clean:
    	make -C $(KERNEL_DIR) M=`pwd` modules clean
    
    obj-m += myfb.o
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当读和写的进程内存映射地址的偏移都为0时,读进程能把写进程写入的数据读出
    在这里插入图片描述
    当写进程内存映射地址偏移为0,读进程内存映射地址**偏移为2(单位页)**时,读进程读出数据为空
    在这里插入图片描述

    PS:注意到读写进程的pfn_start相同,这个值是映射的物理内存地址,vm->vm_pgoff 是偏移(单位页,一页=4K(4096))

    但是两个进程映射的虚拟地址结果不一定相同,虚拟地址是进程自己独有的,这点很容易理解。


    1. 查看虚拟内存分布

    查看读写进程的pid
    在这里插入图片描述
    写进程的虚拟内存分布
    在这里插入图片描述

    读进程的虚拟内存分布
    在这里插入图片描述

    1.1 分析虚拟内存映射部分

    以读进程为例,/dev/myfb所在行即是内存映射的部分

    76ffa000: vm->vm_start 的值

    76ffb000: vm->vm_end 的值

    rw-s: 表示的是 vm->vm_flags,"rw"表示可读可写,"s"表示 share共享,"p"表示 private 私有

    00000000: 表示偏移量,即 vm->vm_pgoff(单位页,此处的偏移量单位是字节,需要做一下换算)

    00:06 : 表示主次设备号

    2564: 表示 inode 值

    /dev/myfb: 表示设备节点名

    1.2 关于偏移量

    将读进程中mmap函数最后一个参数改为2*4096(2页)后,进程的虚拟内存映射部分的地址分布如下。

    可以看到偏移量为 00002000,即 2*4096字节 = 2页 = 8K

    偏移量指的是mmap最后一个参数、同样也是vm->vm_pgoff(单位页),指的是映射时在文件的物理内存上的偏移,只映射了文件的部分内容,单位是页4K
    在这里插入图片描述


    2. remap_pfn_range函数
    2.1 remap_pfn_range函数原型
    /**
     * remap_pfn_range - remap kernel memory to userspace
     * @vma: user vma to map to
     * @addr: target user address to start at
     * @pfn: physical address of kernel memory
     * @size: size of map area
     * @prot: page protection flags for this mapping
     *
     *  Note: this is only safe if the mm semaphore is held when called.
     */
    int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
    		    unsigned long pfn, unsigned long size, pgprot_t prot)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    参数含义
    vm虚拟内存区域描述符,用于表示映射的虚拟内存区域
    addr映射的虚拟内存区域的首地址
    pfn物理页帧号
    size映射区域的大小
    prot物理页面的操作属性,例如读/写/执行权限
    2.2 remap_pfn_range函数使用

    这里主要搞懂 myfb.c 驱动代码中的 remap_pfn_range 中的如下代码

    unsigned long pfn_start = (virt_to_phys(buff) >> PAGE_SHIFT);   
    
    res = remap_pfn_range(vm, vm->vm_start, 
                          pfn_start + vm->vm_pgoff,   //在物理页帧号上加上偏移
                          vm->vm_end - vm->vm_start, 
                          vm->vm_page_prot);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • vm: 调用mmap时内核自动生成的VMA(虚拟内存描述符)
    • vm->vm_start: 该VMA的起始地址
    • vm->_end: 该VMA的结束地址
    • virt_to_phys: 将虚拟地址转成物理地址
    • PAGE_SHIFT: 宏,值为12,1<
    • vm->vm_pgoff: 指的是映射时在文件的物理内存上的偏移,只映射了文件的部分内容,单位是页(4K)
    • vm->vm_page_prot: 该虚拟内存的访问权限,由mmap的参数决定,例如可读可写,共享等

    参考文章:
    【内存映射函数remap_pfn_range学习——示例分析(1)】https://www.cnblogs.com/pengdonglin137/p/8149859.html

  • 相关阅读:
    第五课 算术运算
    国内什么牌子的ipad手写笔好用?开学性价比触控笔
    三次握手与四次挥的问题,怎么回答?
    hive介绍
    Java时间戳互转
    举个栗子~Tableau 技巧(245):用辅助标识快速查看标靶图
    ELK简介
    valueerror: Object arrays cannot be loaded when allow_pickle=False 报错解决方法
    DataExcel控件读取和保存excel xlsx 格式文件
    蓝桥(杂题3)
  • 原文地址:https://blog.csdn.net/HuangChen666/article/details/133633120