zoukankan      html  css  js  c++  java
  • 内存映射函数remap_pfn_range学习——示例分析(2)

    作者

    彭东林
    QQ 405728433
     

    平台

    Linux-4.10.17
    Qemu-2.8 + vexpress-a9
    DDR:1GB
     

    概述

    前面分析了用kzalloc分配内核缓冲区并通过remap_pfn_range的方式将其映射到用户空间的示例,能否用其他方式分配内核缓冲区并映射到用户空间呢?
    当然可以,下面分别用alloc_pages和vmalloc来实现。
     
    对应的驱动以及测试程序可以到下面的地址下载:
     

    正文

     

    一、用alloc_pages来实现

    alloc_pages的函数原型如下:
     1 #define alloc_pages(gfp_mask, order) 
     2         alloc_pages_node(numa_node_id(), gfp_mask, order)
     3 
     4 static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
     5                         unsigned int order)
     6 {
     7     if (nid == NUMA_NO_NODE)
     8         nid = numa_mem_id();
     9 
    10     return __alloc_pages_node(nid, gfp_mask, order);
    11 }
    它返回值的类型是struct page *,要获取对应的物理页帧或者虚拟地址的话,需要用专门的函数。这个函数可以保证分配到的物理内存是连续的。需要注意的是,如果是从低端内存分配出来的内存,在内核空间可以利用page_address()很容易的获取其对应的虚拟地址,但是如果是从高端内存区分配的内存,如果要在内核空间访问的话,需要先用kmap这样的函数将其映射到kmap区,然后才能访问。
    但是对于remap_pfn_range来说就不用担心,只要保证要映射的size大小的空间对应物理地址是连续的就可以,alloc_pages可以满足。为了简便,在调用alloc_pages的时候可以将gfp_mask设置为GFP_KERNEL,这样可以保证从低端内存区分配连续的物理页帧。
     
    下面是驱动的实现:
    首先在驱动init的是否分配32个page:
    static struct page *start_page = alloc_pages(GFP_KERNEL, get_order(BUF_SIZE));

    这里的BUF_SIZE是32*(PAGE_SIZE),也就是128KB,函数get_order计算可以存放下BUF_SIZE的最小阶数。

     
    然后用下面的方式将其映射到用户空间:
     1 static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
     2 {
     3     unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
     4     unsigned long pfn_start = page_to_pfn(start_page) + vma->vm_pgoff;
     5     unsigned long virt_start = (unsigned long)page_address(start_page);
     6     unsigned long size = vma->vm_end - vma->vm_start;
     7     int ret = 0;
     8 
     9     printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx
    ", pfn_start << PAGE_SHIFT, offset, size);
    10 
    11     ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);
    12     if (ret)
    13         printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]
    ",
    14             __func__, vma->vm_start, vma->vm_end);
    15     else
    16         printk("%s: map 0x%lx to 0x%lx, size: 0x%lx
    ", __func__, virt_start,
    17             vma->vm_start, size);
    18 
    19     return ret;
    20 }
    第4行使用了page_to_pfn将start_page指向的struct page结构体转换为对应的物理页帧号,当然不要忘记加上用户期望的offset
    第5行, page_address利用start_page指向的struct page结构体得到其在内核空间的虚拟地址,因为是从低端内存分配的,所以可以返回正确的虚拟地址。如果使用高端内存分配的,并且没有用kmap这样的函数映射到内核空间的话,page_address返回NULL
    第6行,获得vma表示的虚拟内存区域的尺寸
    第11行,调用remap_pfn_range将物理内存映射到用户空间
     

    二、用vmalloc实现

    vmalloc比较特殊,使用它分配的内存虚拟地址是连续的,但是不保证物理页帧也连续,这里不保证的意思是也可能是连续的。什么原因呢? 因为vmalloc在分配内存时是调用alloc_page一页一页的分配,就是每次从伙伴系统只分配一页,然后将分配得到的单页物理页帧映射到内核的vmalloc区连续的虚拟地址上。
    比如:我想用vmalloc分配128KB的内存,vmalloc计算发现需要分配32个page,然后会调用32次alloc_page(),每次从伙伴系统分配1个page,每分配一个page就将该page映射到准备好的连续的虚拟地址上,当然也就无法保证这些page之间对应的物理页帧的连续性。
     
    知道了vmalloc分配内存的特点,那么在调用remap_pfn_range的时候就需要注意,必须一页一页地映射。
     
    下面看驱动。
     
    同样,在驱动的init函数中分配128KB的空间:
    static void *kbuff = vmalloc(BUF_SIZE);
    然后使用下面的方式映射:
     1 static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
     2 {
     3     unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
     4     unsigned long virt_start = (unsigned long)kbuff + (unsigned long)(vma->vm_pgoff << PAGE_SHIFT);
     5     unsigned long pfn_start = (unsigned long)vmalloc_to_pfn((void *)virt_start);
     6     unsigned long size = vma->vm_end - vma->vm_start;
     7     int ret = 0;
     8     unsigned long vmstart = vma->vm_start;
     9     int i = 0;
    10 
    11     printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx
    ", pfn_start << PAGE_SHIFT, offset, size);
    12 
    13     while (size > 0) {
    14         ret = remap_pfn_range(vma, vmstart, pfn_start, PAGE_SIZE, vma->vm_page_prot);
    15         if (ret) {
    16             printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]
    ",
    17                 __func__, vmstart, vmstart + PAGE_SIZE);
    18             ret = -ENOMEM;
    19             goto err;
    20         } else
    21             printk("%s: map 0x%lx (0x%lx) to 0x%lx , size: 0x%lx, number: %d
    ", __func__, virt_start,
    22                 pfn_start << PAGE_SHIFT, vmstart, PAGE_SIZE, ++i);
    23 
    24         if (size <= PAGE_SIZE)
    25             size = 0;
    26         else {
    27             size -= PAGE_SIZE;
    28             vmstart += PAGE_SIZE;
    29             virt_start += PAGE_SIZE;
    30             pfn_start = vmalloc_to_pfn((void *)virt_start);
    31         }
    32     }
    33 
    34     return 0;
    35 err:
    36     return ret;
    37 }
    第4行,计算内核缓冲区中将要被映射到用户空间的位置的虚拟起始地址virt_start
    第5行,调用vmalloc_to_pfn将由vmalloc分配的虚拟地址转换为对应的物理页帧号
    第13行到32行的while循环调用remap_pfn_range,每次映射PAGE_SIZE,即4KB,每映射完一页,都要计算下一个虚拟地址对应的物理页帧号。
     
    下面用user_5测试一下使用vmalloc分配内核缓冲区的驱动。
     
    运行user_5,可以得到如下的log:
     1 [11712.435630] client: user_5 (874)
     2 [11712.435741] code  section: [0x8000   0x8828]
     3 [11712.435839] data  section: [0x10828   0x10964]
     4 [11712.435936] brk   section: s: 0x11000, c: 0x11000
     5 [11712.436042] mmap  section: s: 0xb6f1b000
     6 [11712.436131] stack section: s: 0xbefc6e20
     7 [11712.436256] arg   section: [0xbefc6f23   0xbefc6f2c]
     8 [11712.436378] env   section: [0xbefc6f2c   0xbefc6ff3]
     9 [11712.436603] phy: 0x9fdf8000, offset: 0x0, size: 0x20000
    10 [11712.436767] remap_pfn_mmap: map 0xf1443000 (0x9fdf8000) to 0xb6d69000 , size: 0x1000, number: 1
    11 [11712.436991] remap_pfn_mmap: map 0xf1444000 (0x9fdf7000) to 0xb6d6a000 , size: 0x1000, number: 2
    12 [11712.437210] remap_pfn_mmap: map 0xf1445000 (0x9fdf6000) to 0xb6d6b000 , size: 0x1000, number: 3
    13 [11712.437429] remap_pfn_mmap: map 0xf1446000 (0x9fdf5000) to 0xb6d6c000 , size: 0x1000, number: 4
    14 [11712.437647] remap_pfn_mmap: map 0xf1447000 (0x9fdf4000) to 0xb6d6d000 , size: 0x1000, number: 5
    15 [11712.437862] remap_pfn_mmap: map 0xf1448000 (0x9fdf3000) to 0xb6d6e000 , size: 0x1000, number: 6
    16 [11712.438086] remap_pfn_mmap: map 0xf1449000 (0x9fdf2000) to 0xb6d6f000 , size: 0x1000, number: 7
    17 [11712.438305] remap_pfn_mmap: map 0xf144a000 (0x9fdf1000) to 0xb6d70000 , size: 0x1000, number: 8
    18 [11712.438535] remap_pfn_mmap: map 0xf144b000 (0x9fdf0000) to 0xb6d71000 , size: 0x1000, number: 9
    19 [11712.438752] remap_pfn_mmap: map 0xf144c000 (0x9fdef000) to 0xb6d72000 , size: 0x1000, number: 10
    20 [11712.438966] remap_pfn_mmap: map 0xf144d000 (0x9fdee000) to 0xb6d73000 , size: 0x1000, number: 11
    21 [11712.439198] remap_pfn_mmap: map 0xf144e000 (0x9fded000) to 0xb6d74000 , size: 0x1000, number: 12
    22 [11712.439404] remap_pfn_mmap: map 0xf144f000 (0x9fdec000) to 0xb6d75000 , size: 0x1000, number: 13
    23 [11712.440003] remap_pfn_mmap: map 0xf1450000 (0x9fdeb000) to 0xb6d76000 , size: 0x1000, number: 14
    24 [11712.440145] remap_pfn_mmap: map 0xf1451000 (0x9fdea000) to 0xb6d77000 , size: 0x1000, number: 15
    25 [11712.440319] remap_pfn_mmap: map 0xf1452000 (0x9fde9000) to 0xb6d78000 , size: 0x1000, number: 16
    26 [11712.440499] remap_pfn_mmap: map 0xf1453000 (0x9fde8000) to 0xb6d79000 , size: 0x1000, number: 17
    27 [11712.440680] remap_pfn_mmap: map 0xf1454000 (0x9fde7000) to 0xb6d7a000 , size: 0x1000, number: 18
    28 [11712.440862] remap_pfn_mmap: map 0xf1455000 (0x9fde6000) to 0xb6d7b000 , size: 0x1000, number: 19
    29 [11712.441065] remap_pfn_mmap: map 0xf1456000 (0x9fde5000) to 0xb6d7c000 , size: 0x1000, number: 20
    30 [11712.441520] remap_pfn_mmap: map 0xf1457000 (0x9fde4000) to 0xb6d7d000 , size: 0x1000, number: 21
    31 [11712.441819] remap_pfn_mmap: map 0xf1458000 (0x9fde3000) to 0xb6d7e000 , size: 0x1000, number: 22
    32 [11712.442001] remap_pfn_mmap: map 0xf1459000 (0x9fde2000) to 0xb6d7f000 , size: 0x1000, number: 23
    33 [11712.442182] remap_pfn_mmap: map 0xf145a000 (0x9fde1000) to 0xb6d80000 , size: 0x1000, number: 24
    34 [11712.442370] remap_pfn_mmap: map 0xf145b000 (0x9fde0000) to 0xb6d81000 , size: 0x1000, number: 25
    35 [11712.442558] remap_pfn_mmap: map 0xf145c000 (0x9fc0c000) to 0xb6d82000 , size: 0x1000, number: 26
    36 [11712.442749] remap_pfn_mmap: map 0xf145d000 (0x9fc0d000) to 0xb6d83000 , size: 0x1000, number: 27
    37 [11712.442944] remap_pfn_mmap: map 0xf145e000 (0x9fdc5000) to 0xb6d84000 , size: 0x1000, number: 28
    38 [11712.443171] remap_pfn_mmap: map 0xf145f000 (0x9fdf9000) to 0xb6d85000 , size: 0x1000, number: 29
    39 [11712.443355] remap_pfn_mmap: map 0xf1460000 (0x9fdfa000) to 0xb6d86000 , size: 0x1000, number: 30
    40 [11712.443534] remap_pfn_mmap: map 0xf1461000 (0x9fdfb000) to 0xb6d87000 , size: 0x1000, number: 31
    41 [11712.443711] remap_pfn_mmap: map 0xf1462000 (0x9fdfc000) to 0xb6d88000 , size: 0x1000, number: 32
    可以看到,remap_pfn_mma被循环调用了32次,每次映射4KB,同时也可以看到每次映射的物理页帧之间有可能是连续的,也有可能不是连续的,具体跟当前系统中内存的碎片化程度有关,碎片化程度越高,上面的物理页帧之间的连续性也就越差。
    此外,可以看到,vmalloc分配的内存的地址都落在了高端内存区的vmalloc区,而且虚拟地址都是连续的,用户的vma的虚拟内存区域地址也是连续的,只有物理内存不一定连续。比如下面几行:
    1 [11712.442182] remap_pfn_mmap: map 0xf145a000 (0x9fde1000) to 0xb6d80000 , size: 0x1000, number: 24
    2 [11712.442370] remap_pfn_mmap: map 0xf145b000 (0x9fde0000) to 0xb6d81000 , size: 0x1000, number: 25
    3 [11712.442558] remap_pfn_mmap: map 0xf145c000 (0x9fc0c000) to 0xb6d82000 , size: 0x1000, number: 26
    4 [11712.442749] remap_pfn_mmap: map 0xf145d000 (0x9fc0d000) to 0xb6d83000 , size: 0x1000, number: 27
    5 [11712.442944] remap_pfn_mmap: map 0xf145e000 (0x9fdc5000) to 0xb6d84000 , size: 0x1000, number: 28
    6 [11712.443171] remap_pfn_mmap: map 0xf145f000 (0x9fdf9000) to 0xb6d85000 , size: 0x1000, number: 29
    7 [11712.443355] remap_pfn_mmap: map 0xf1460000 (0x9fdfa000) to 0xb6d86000 , size: 0x1000, number: 30
    8 [11712.443534] remap_pfn_mmap: map 0xf1461000 (0x9fdfb000) to 0xb6d87000 , size: 0x1000, number: 31
     
    未完待续....
  • 相关阅读:
    colorDialog颜色拾取
    ContextMenuStrip菜单
    C#根据当前时间确定日期范围(本周、本月、本季度、本年度及常见日期方法荟萃
    重设切片上下文
    DomainUpDown 控件
    SQL中使用WITH AS提高性能
    在Reporting Service中使用下拉框提供参数查询
    c# 发射机制
    Silverlight 浏览器外运行及更新判断
    自动化持续集成编译 配置 CruiseControl.Net SVN
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/8150462.html
Copyright © 2011-2022 走看看