內存映射函數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\n", 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]\n",
14             __func__, vma->vm_start, vma->vm_end);
15     else
16         printk("%s: map 0x%lx to 0x%lx, size: 0x%lx\n", __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\n", 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]\n",
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\n", __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

 

 
未完待續....

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM