内存管理

实验:获取系统的物理内存信息

Linux 内核中使用 page 结构体来描述物理页面。用于描述的意思是,你可以把 page 当成每个物理页的“说明书”。其中包含了物理页的各种信息,以便快速查找和管理页面。

现在我们来学习一下怎么查看“这张说明书”:这节实验主要使用 pageflags 成员。

代码清单 1 page 统计
  1. #include <linux/version.h>
  2. #include <linux/module.h>
  3. #include <linux/init.h>
  4. #include <linux/mm.h>
  5.  
  6. #define PRT(a ,b) pr_info("%-15s=%10d %10ld %8ld\n", \
  7.                             a, b, (PAGE_SIZE*b)/1024, (PAGE_SIZE*b)/1024/1024)
  8.  
  9. static int __init my_init(void)
  10. {
  11.     struct page* p;
  12.     unsigned long i, pfn, valid = 0;
  13.     int free = 0, locked = 0, reserved = 0, swapcache = 0,
  14.         referenced = 0, slab = 0, private = 0, uptodate = 0,
  15.         dirty = 0, active = 0, writeback = 0, mappedtodisk = 0;
  16.  
  17.     unsigned long num_physpages;
  18.  
  19.     num_physpages = get_num_physpages();
  20.     for (i = 0; i < num_physpages; i++)
  21.     {
  22.         /* Most of ARM system have ARCH_PFN_OFFSET */
  23.         pfn = i + ARCH_PFN_OFFSET;
  24.         /* may be holes due to remapping */
  25.         if (!pfn_valid(pfn))
  26.             continue;
  27.  
  28.         valid++;
  29.         p = pfn_to_page(pfn);
  30.         if (!p)
  31.             continue;
  32.         /* page_count(page) == 0 is a free page */
  33.         if (!page_count(p))
  34.         {
  35.             free++;
  36.             continue;
  37.         }
  38.         if (PageLocked(p))
  39.             locked++;
  40.         if (PageReserved(p))
  41.             reserved++;
  42.         if (PageSwapCache(p))
  43.             swapcache++;
  44.         if (PageReferenced(p))
  45.             referenced++;
  46.         if (PageSlab(p))
  47.             slab++;
  48.         if (PagePrivate(p))
  49.             private++;
  50.         if (PageUptodate(p))
  51.             uptodate++;
  52.         if (PageDirty(p))
  53.             dirty++;
  54.         if (PageActive(p))
  55.             active++;
  56.         if (PageWriteback(p))
  57.             writeback++;
  58.         if (PageMappedToDisk(p))
  59.             mappedtodisk++;
  60.     }
  61.  
  62.     pr_info("\nExamining %ld pages (num_phys_pages) = %ld MB\n",
  63.         num_physpages, num_physpages * PAGE_SIZE / 1024 / 1024);
  64.     pr_info("Page with valid PFN's=%ld, =%ld MB\n",
  65.         valid, valid * PAGE_SIZE / 1024 /1024);
  66.     pr_info("\n      Pages       KB      MB\n\n");
  67.  
  68.     PRT("free", free);
  69.     PRT("locked", locked);
  70.     PRT("reserved", reserved);
  71.     PRT("swapcache", swapcache);
  72.     PRT("referenced", referenced);
  73.     PRT("slab", slab);
  74.     PRT("private", private);
  75.     PRT("uptodate", uptodate);
  76.     PRT("dirty", dirty);
  77.     PRT("active", active);
  78.     PRT("writeback", writeback);
  79.     PRT("mappedtodisk", mappedtodisk);
  80.  
  81.     return 0;
  82. }
  83.  
  84. static void __exit my_exit(void)
  85. {
  86.     pr_info("Module exit\n");
  87. }
  88. module_init(my_init);
  89. module_exit(my_exit);
  90.  
  91. MODULE_AUTHOR("rlk");
  92. MODULE_LICENSE("GPL v2");

如代码清单 1 所示,我们首先使用 get_num_physpages 获取系统的物理页数。以次作为页帧号(pfn,Page Frame Number)的编码。

不是所有 ARM 系统都从物理地址 0 开始使用内存。针对大多数情况,这边加上 ARCH_PFN_OFFSET 偏移。

pfn_valid 函数检查指定的页帧号是否有映射到实际的物理内存页。有实际映射才进行下一步处理。

遗留一个问题:这边直接使用 get_num_physpages() 作为页帧号范围。不知道对于多内存条或者多处理器系统,编码是否就是按顺序排下来的。

最后可以通过 pfn_to_page 函数获取指定页帧号对应的 page 结构体。获取到 page 结构体,我们就可以检索其中的信息了。

这边我们使用到 page 结构的 flags 成员,以获取当前物理页所处的各种状态:

PageLocked:表示页面被锁定,不能被换出。

PageReserved:表示页面被保留,不参与常规的交换操作。

PageSwapCache:表示页面被换出到交换设备上了。

PageReferenced:表示页面最近被访问过。

PageSlab:表示页面是 Slab 分配器的一部分,用于管理小的内核对象。

PagePrivate:表示页面有关联的私有数据。

PageUptodate:表示页面的内容是最新的,与磁盘的数据一致。

PageDirty:表示页面的内容已被修改,但尚未写回到磁盘。

PageActive:表示页面是活动的,经常被访问。

PageWriteback:表示页面正在被写回磁盘。

PageMappedToDisk:表示页面有与之对应的磁盘位置。

PageLocked 这些函数,在 include/linux/page-flags.h 头文件中。

  • #define TESTPAGEFLAG(uname, lname, policy)              \
  • static __always_inline int Page##uname(struct page *page)    \
  •     { return test_bit(PG_##lname, &policy(page, 0)->flags); }

TESTPAGEFLAG 宏展开会获得以上 Page 打头的查询函数,可以看到就是检查 page->flags 中的各个位。

这些函数比较难找,是通过宏定义的。

实验:分配内存

这节实验我们了解 linux 内核中常用的内存分配接口,我们先直接看到代码清单 2。

如代码清单 2 所示,里面使用了 3 个内存分配接口:__get_free_pageskmallocvmalloc

代码清单 2 内存分配
  1. #include <linux/module.h>
  2. #include <linux/slab.h>
  3. #include <linux/init.h>
  4. #include <linux/vmalloc.h>
  5.  
  6. static int mem = 1024;
  7.  
  8. #define MB (1024*1024)
  9.  
  10. static int __init my_init(void)
  11. {
  12.     char* kbuf;
  13.     unsigned long order;
  14.     unsigned long size;
  15.     char* vm_buff;
  16.  
  17.     /* try __get_free_pages__ */
  18.     for (size = PAGE_SIZE, order = 0; order < MAX_ORDER; order++, size *= 2)
  19.     {
  20.         pr_info(" order=%2lu, pages=%5lu, size=%8lu ", order, size / PAGE_SIZE, size);
  21.         kbuf = (char*)__get_free_pages(GFP_ATOMIC, order);
  22.         if (!kbuf)
  23.         {
  24.             pr_err("... __get_free_pages failed\n");
  25.             break;
  26.         }
  27.         pr_info("... __get_free_pages OK\n");
  28.         free_pages((unsigned long)kbuf, order);
  29.     }
  30.  
  31.     /* try kmalloc */
  32.     for (size = PAGE_SIZE, order = 0; order < MAX_ORDER; order++, size *= 2)
  33.     {
  34.         pr_info(" order=%2lu, pages=%5lu, size=%8lu ", order, size / PAGE_SIZE, size);
  35.         kbuf = kmalloc((size_t) size, GFP_ATOMIC);
  36.         if (!kbuf)
  37.         {
  38.             pr_err("... kmalloc failed\n");
  39.             break;
  40.         }
  41.         pr_info("... kmalloc OK\n");
  42.         kfree(kbuf);
  43.     }
  44.  
  45.     /* try vmalloc */
  46.     for (size = 20 * MB; size <= mem * MB; size += 20 * MB)
  47.     {
  48.         pr_info(" order=%2lu, pages=%5lu, size=%8lu ", order, size / PAGE_SIZE, size);
  49.         vm_buff = vmalloc(size);
  50.         if (!vm_buff)
  51.         {
  52.             pr_err("... vmalloc failed\n");
  53.             break;
  54.         }
  55.         pr_info("... vmalloc OK\n");
  56.         vfree(vm_buff);
  57.     }
  58.  
  59.     return 0;
  60. }
  61.  
  62. static void __exit my_exit(void)
  63. {
  64.     pr_info("Module exit\n");
  65. }
  66.  
  67. module_init(my_init);
  68. module_exit(my_exit);
  69.  
  70. MODULE_AUTHOR("rlk");
  71. MODULE_LICENSE("GPL v2");

__get_free_pages 用于从内核的页分配器请求一个或多个连续的物理内存页。与其对应的释放函数是 free_pages。__get_free_pages 的原型为:

  • unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

其中,gfp_mask 是内存分配标志的位掩码。这边的 GFP_ATOMIC 表示用于原子上下文,即在分配内存的过程中不会执行页面回收或睡眠动作。order 表示要分配的页数是 2 的 order 次方。

这边的原子上下文可以理解成是“非阻塞”的。如果分配失败则直接返回,不会被挂起并放入等待队列,直到资源可用为止。

从日志中可以看到,本地环境 __get_free_pages 可以最多分配 1024 个连续的物理页。

  • [  796.021287]  order= 0, pages=    1, size=    4096
  • [  796.021291] ... __get_free_pages OK
  • [  796.021291]  order= 1, pages=    2, size=    8192
  • [  796.021293] ... __get_free_pages OK
  • [  796.021293]  order= 2, pages=    4, size=   16384
  • [  796.021296] ... __get_free_pages OK
  • [  796.021296]  order= 3, pages=    8, size=   32768
  • [  796.021299] ... __get_free_pages OK
  • [  796.021299]  order= 4, pages=   16, size=   65536
  • [  796.021304] ... __get_free_pages OK
  • [  796.021305]  order= 5, pages=   32, size=  131072
  • [  796.021313] ... __get_free_pages OK
  • [  796.021313]  order= 6, pages=   64, size=  262144
  • [  796.021330] ... __get_free_pages OK
  • [  796.021330]  order= 7, pages=  128, size=  524288
  • [  796.021364] ... __get_free_pages OK
  • [  796.021365]  order= 8, pages=  256, size= 1048576
  • [  796.021482] ... __get_free_pages OK
  • [  796.021484]  order= 9, pages=  512, size= 2097152
  • [  796.021620] ... __get_free_pages OK
  • [  796.021624]  order=10, pages= 1024, size= 4194304
  • [  796.021920] ... __get_free_pages OK

kmalloc 用于分配连续的物理内存。与其对应的释放函数为 kfree。kmalloc 的函数原型为:

  • void* kmalloc(size_t size, gfp_t flags);

从日志中可以看到,本地环境 kmalloc 最多可以分配到 2MB 的连续物理内存。

  • [  796.021926]  order= 0, pages=    1, size=    4096
  • [  796.021927] ... kmalloc OK
  • [  796.021927]  order= 1, pages=    2, size=    8192
  • [  796.021929] ... kmalloc OK
  • [  796.021929]  order= 2, pages=    4, size=   16384
  • [  796.021931] ... kmalloc OK
  • [  796.021933]  order= 3, pages=    8, size=   32768
  • [  796.021935] ... kmalloc OK
  • [  796.021936]  order= 4, pages=   16, size=   65536
  • [  796.021940] ... kmalloc OK
  • [  796.021941]  order= 5, pages=   32, size=  131072
  • [  796.021949] ... kmalloc OK
  • [  796.021949]  order= 6, pages=   64, size=  262144
  • [  796.021965] ... kmalloc OK
  • [  796.021966]  order= 7, pages=  128, size=  524288
  • [  796.021998] ... kmalloc OK
  • [  796.021999]  order= 8, pages=  256, size= 1048576
  • [  796.022060] ... kmalloc OK
  • [  796.022061]  order= 9, pages=  512, size= 2097152
  • [  796.022181] ... kmalloc OK

vmalloc 和 kmalloc 的区别在于,vmalloc 分配的是连续的虚拟地址空间,在物理地址上不一定连续。与之对应的释放函数为 vfree。vmalloc 的函数原型为:

  • void* vmalloc(unsigned long size);

从日志中可以看到,本地环境 vmalloc 最多可以分配到约 1G 的内存。

  • [  801.104194]  order=11, pages=230400, size=943718400
  • [  801.254238] ... vmalloc OK
  • [  801.265481]  order=11, pages=235520, size=964689920
  • [  801.418673] ... vmalloc OK
  • [  801.432217]  order=11, pages=240640, size=985661440
  • [  801.587991] ... vmalloc OK
  • [  801.599674]  order=11, pages=245760, size=1006632960
  • [  801.763869] ... vmalloc OK
  • [  801.775801]  order=11, pages=250880, size=1027604480
  • [  801.934132] ... vmalloc OK
  • [  801.946595]  order=11, pages=256000, size=1048576000
  • [  802.106948] ... vmalloc OK
  • [  802.119573]  order=11, pages=261120, size=1069547520
  • [  802.283889] ... vmalloc OK

分配过程和原理不做深究,这边先感性认识下这些函数:

__get_free_pages:分配连续物理页面。

kmalloc:分配小块的连续物理内存。

vmalloc:分配大块的虚拟地址空间中连续、但物理内存不一定连续的内存。

实验:slab

slab 分配器是 linux 内核中的一个内存管理机制,主要用于内核层的内存分配。它是为了满足内核中频繁分配和释放小块内存的需要而设计的,特别是为了提高这种小块内存分配的效率并减少碎片化。

我们直接看到代码清单 3,学习如何使用 slab。

代码清单 3 slab
  1. #include <linux/module.h>
  2. #include <linux/mm.h>
  3. #include <linux/slab.h>
  4. #include <linux/init.h>
  5.  
  6. static char* kbuf;
  7. static int size = 20;
  8. static struct kmem_cache* my_cache;
  9. module_param(size, int, 0644);
  10.  
  11. static int __init my_init(void)
  12. {
  13.     /* create a memory cache */
  14.     if (size > KMALLOC_MAX_SIZE)
  15.     {
  16.         pr_err("size=%d is too large; you can't have more than %lu!\n", size, KMALLOC_MAX_SIZE);
  17.         return -1;
  18.     }
  19.  
  20.     my_cache = kmem_cache_create("mycache", size, 0, SLAB_HWCACHE_ALIGN, NULL);
  21.     if (!my_cache)
  22.     {
  23.         pr_err("kmem_cache_create failed.\n");
  24.         return -ENOMEM;
  25.     }
  26.     pr_info("create mycache correctly\n");
  27.  
  28.     /* allocate a memory cache object */
  29.     kbuf = kmem_cache_alloc(my_cache, GFP_ATOMIC);
  30.     if (!kbuf)
  31.     {
  32.         pr_err("failed to create a cache object\n");
  33.         kmem_cache_destroy(my_cache);
  34.         return -1;
  35.     }
  36.     pr_info("successfully created a object, kbuf_addr_0x%x\n", (unsigned long)kbuf);
  37.  
  38.     return 0;
  39. }
  40.  
  41. static void __exit my_exit(void)
  42. {
  43.     /* destroy a memory cache object */
  44.     kmem_cache_free(my_cache, kbuf);
  45.     pr_info("destroyed a cache object\n");
  46.  
  47.     /* destroy the memory cache */
  48.     kmem_cache_destroy(my_cache);
  49.     pr_info("destroyed mycache\n");
  50. }
  51.  
  52. module_init(my_init);
  53. module_exit(my_exit);
  54.  
  55. MODULE_LICENSE("GPL v2");
  56. MODULE_AUTHOR("rlk");

kmem_cache_create 函数用于创建一个 slab 缓存,与之对应的销毁函数是 kmem_cache_destroy。其原型如下:

  • struct kmem_cache*
  •     kmem_cache_create(const char* name, size_t size, size_t align,
  •         unsigned long flags, void (*ctor)(void*));

其中,name 是要创建的 slab 缓存的名称;size 定义 slab 缓存中分配的每一个对象应该占多少字节;align 指定对齐;flags 用于定义 slab 标志,此处使用 SLAB_HWCACHE_ALIGN 指定按硬件缓存行对齐;ctor 指定一个构造函数,此处设置为 NULL。

如果创建成功,函数返回一个 kmem_cache 的结构指针,用于标识此 slab 缓存。

kmem_cache_alloc 用于从一个指定的 slab 缓存中分配一个对象,与之对应的释放函数是 kmem_cache_free。其原型如下:

  • void* kmem_cache_alloc(struct kmem_cache* cache, gfp_t flags);

其中,cache 就是 kmem_cache_create 创建的 slab 缓存指针;flags 指定分配标志。此处使用了 GFP_ATOMIC,代表分配用于原子上下文。

如果分配成功,则会返回一个新分配的对象指针。

这边虽说返回的是对象指针,但是使用起来和内存指针是一样的。

slab 上下文中特意使用“对象”术语。我感觉是要强调它是一个整体的概念,它是一个预先定义的有着特定大小和属性的内存块。

我的简单对比理解:kmem_cache_create 可以理解为创建一个特定的“内存池”,然后使用 kmem_cache_alloc 往其中分配内存。

加载上述内核模块后,我们可以在 /proc/slabinfo 中查看创建的 slab 的缓存信息。

  • tim@tim:~$ sudo cat /proc/slabinfo | grep -iE my
  • mycache 6 6 5056 6 8 : tunables 0 0 0 : slabdata 1 1 0

当创建的 slab 缓存和现有存在的 slab 缓存,大小接近且具有相同的属性时,slab 会进行合并。这时候在 /proc/slabinfo 中会搜索不到我们创建的 slab 名字。

当发生 slab 合并时,我们可以在 /sys/kernel/slab/ 目录下查看。在这个目录下,每一个 slab 都会有一个子目录,包含其属性和统计信息。

  • tim@tim:/sys/kernel/slab$ ls -l | grep -iE my
  • lrwxrwxrwx 1 root root 0 10月 5 16:02 mycache -> :0005056

实验:VMA

VMA(Virtual Memory Area)是 linux 内存管理中的核心概念,它为程序提供了一个连续、受保护的虚拟地址空间,并通过页表机制将这些地址映射到物理内存或硬盘上。

这节实验,我们编写一个内核模块,遍历一个用户进程的所有 VMA,并打印其信息。

代码清单 4 VMA
  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/mm.h>
  4. #include <linux/sched.h>
  5.  
  6. static int pid;
  7. module_param(pid, int, 0644);
  8.  
  9. static void printit(struct task_struct* tsk)
  10. {
  11.     struct mm_struct* mm;
  12.     struct vm_area_struct* vma;
  13.     int j = 0;
  14.     unsigned long start, end, length;
  15.  
  16.     mm = tsk->mm;
  17.     pr_info("mm_struct addr = 0x%lx\n", (unsigned long)mm);
  18.     vma = mm->mmap;
  19.  
  20.     /* protect from simultaneous modification */
  21.     down_read(&mm->mmap_lock);
  22.     pr_info("vmas:    vma    start    end    length\n");
  23.  
  24.     while (vma)
  25.     {
  26.         j++;
  27.         start = vma->vm_start;
  28.         end = vma->vm_end;
  29.         length = end - start;
  30.         pr_info("%6d: %16lx %16lx %16lx %8ld\n",
  31.             j, (unsigned long)vma, start, end, length);
  32.         vma = vma->vm_next;
  33.     }
  34.     up_read(&mm->mmap_lock);
  35. }
  36.  
  37. static int __init my_init(void)
  38. {
  39.     struct task_struct* tsk;
  40.     /* if don't pass the pid over insmod, the use the current process */
  41.     if (pid == 0)
  42.     {
  43.         tsk = current;
  44.         pid = current->pid;
  45.         pr_info("using current process\n");
  46.     }
  47.     else
  48.     {
  49.         tsk = pid_task(find_vpid(pid), PIDTYPE_PID);
  50.     }
  51.  
  52.     if (!tsk)
  53.         return -1;
  54.     pr_info("Examining vma's for pid=%d, command=%s\n", pid, tsk->comm);
  55.     printit(tsk);
  56.     return 0;
  57. }
  58.  
  59. static void __exit my_exit(void)
  60. {
  61.     pr_info("Module exit\n");
  62. }
  63.  
  64. module_init(my_init);
  65. module_exit(my_exit);
  66.  
  67. MODULE_LICENSE("GPL v2");

书上使用的是 mm_struct.mmap_sem,新内核版本中这个成员改为 mmap_lock。

如代码清单 4 所示,它针对当前进程或者指定进程 ID 的进程,来打印它所有的 VMA。

核心函数在 printit。task_structmm 成员描述了整个虚拟内存空间。mm 的类型是 mm_struct,其中 mmap 成员是一个链表头,记录了每一个 VMA。我们可以依次遍历。

VMA 使用 vm_area_struct 结构体描述,如代码所示,我们可以得到 VMA 的起始虚拟地址、结束虚拟地址和长度。

因为 mmap 链表在运行时可能被其他内核线程修改,我们使用读写锁确保安全地读取它。

我们可以使用 cat /proc/[进程ID]/smaps 来验证所写代码的正确性。

实验:mmap

mmap 可以让用户空间应用程序,将一个文件或设备映射到其虚拟地址空间。一旦映射,应用程序可以直接通过内存访问映射的内容,就不需要调用 read 和 write 这些系统调用,从而提高效率。

我们看到实验代码,如代码清单 5.1 所示,我们在之前学习的简单字符设备驱动上增加 mmap 接口。

读写接口中的 simple_read_from_buffersimple_write_to_buffer 第一次见。它们是 linux 内核提供的辅助函数,就是增加了逻辑的 copy_to_usercopy_from_user 封装。它们的函数原型参数类似,这边介绍一下 simple_read_from_buffer 的定义:

  • ssize_t simple_read_from_buffer(void __user* to, size_t count, loff_t* ppos,
  •     const void* from, size_t available);

simple_read_from_buffer 从一个内核空间的缓冲区复制数据到用户空间的缓冲区。to 用户空间的目标缓冲区;count 要读取的字节数;ppos 读取的位置偏移量的指针;from 内核空间的源缓冲区;available 源缓冲区中的可用字节数。

代码清单 5.1 mmap
  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/uaccess.h>
  4. #include <linux/init.h>
  5. #include <linux/miscdevice.h>
  6. #include <linux/device.h>
  7. #include <linux/slab.h>
  8. #include <linux/kfifo.h>
  9.  
  10. #define DEMO_NAME "mydemo_mmap_dev"
  11. static struct device* mydemodrv_device;
  12.  
  13. /* virtual FIFO device's buffer */
  14. static char* device_buffer;
  15. #define MAX_DEVICE_BUFFER_SIZE (10 * PAGE_SIZE)
  16.  
  17. #define MYDEV_CMD_GET_BUFSIZE 1 /* defines our IOCTL cmd */
  18.  
  19. static int demodrv_open(struct inode* inode, struct file* file)
  20. {
  21.     int major = MAJOR(inode->i_rdev);
  22.     int minor = MINOR(inode->i_rdev);
  23.  
  24.     printk("%s: major=%d, minor=%d\n", __func__, major, minor);
  25.  
  26.     return 0;
  27. }
  28.  
  29. static int demodrv_release(struct inode* inode, struct file* file)
  30. {
  31.     return 0;
  32. }
  33.  
  34. static ssize_t demodrv_read(struct file* file, char __user* buf, size_t count, loff_t* ppos)
  35. {
  36.     int nbytes = simple_read_from_buffer(buf, count, ppos, device_buffer, MAX_DEVICE_BUFFER_SIZE);
  37.  
  38.     printk("%s: read nbytes=%d done at pos=%d\n", __func__, nbytes, (int)*ppos);
  39.  
  40.     return nbytes;
  41. }
  42.  
  43. static ssize_t demodrv_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  44. {
  45.     int nbytes = simple_write_to_buffer(device_buffer, MAX_DEVICE_BUFFER_SIZE, ppos, buf, count);
  46.  
  47.     printk("%s: write nbytes=%d done at pos=%d\n", __func__, nbytes, (int)*ppos);
  48.  
  49.     return nbytes;
  50. }
  51.  
  52. static int demodrv_mmap(struct file* filp, struct vm_area_struct* vma)
  53. {
  54.     unsigned long pfn;
  55.     unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
  56.     unsigned long len = vma->vm_end - vma->vm_start;
  57.  
  58.     if (offset >= MAX_DEVICE_BUFFER_SIZE)
  59.         return -EINVAL;
  60.     if (len > (MAX_DEVICE_BUFFER_SIZE - offset))
  61.         return -EINVAL;
  62.  
  63.     printk("%s: mapping %ld bytes of device buffer at offset %ld\n", __func__, len, offset);
  64.  
  65.     /* pfn = page_to_pfn (virt_to_page (ramdisk + offset)); */
  66.     pfn = virt_to_phys(device_buffer + offset) >> PAGE_SHIFT;
  67.  
  68.     vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
  69.  
  70.     if (remap_pfn_range(vma, vma->vm_start, pfn, len, vma->vm_page_prot))
  71.         return -EAGAIN;
  72.  
  73.     return 0;
  74. }
  75.  
  76. static long demodrv_unlocked_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
  77. {
  78.     unsigned long tbs = MAX_DEVICE_BUFFER_SIZE;
  79.     void __user* ioargp = (void __user*)arg;
  80.  
  81.     switch (cmd)
  82.     {
  83.     default:
  84.         return -EINVAL;
  85.  
  86.     case MYDEV_CMD_GET_BUFSIZE:
  87.         if (copy_to_user(ioargp, &tbs, sizeof(tbs)))
  88.             return -EFAULT;
  89.         return 0;
  90.     }
  91. }
  92.  
  93. static const struct file_operations demodrv_fops = {
  94.     .owner = THIS_MODULE,
  95.     .open = demodrv_open,
  96.     .release = demodrv_release,
  97.     .read = demodrv_read,
  98.     .write = demodrv_write,
  99.     .mmap = demodrv_mmap,
  100.     .unlocked_ioctl = demodrv_unlocked_ioctl,
  101. };
  102.  
  103. static struct miscdevice mydemodrv_misc_device = {
  104.     .minor = MISC_DYNAMIC_MINOR,
  105.     .name = DEMO_NAME,
  106.     .fops = &demodrv_fops,
  107. };
  108.  
  109. static int __init simple_char_init(void)
  110. {
  111.     int ret;
  112.  
  113.     device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
  114.     if (!device_buffer)
  115.         return -ENOMEM;
  116.  
  117.     ret = misc_register(&mydemodrv_misc_device);
  118.     if (ret)
  119.     {
  120.         printk("failed register misc device\n");
  121.         kfree(device_buffer);
  122.         return ret;
  123.     }
  124.  
  125.     mydemodrv_device = mydemodrv_misc_device.this_device;
  126.     printk("succeeded register char device: %s\n", DEMO_NAME);
  127.  
  128.     return 0;
  129. }
  130.  
  131. static void __exit simple_char_exit(void)
  132. {
  133.     printk("removing device\n");
  134.  
  135.     kfree(device_buffer);
  136.     misc_deregister(&mydemodrv_misc_device);
  137. }
  138.  
  139. module_init(simple_char_init);
  140. module_exit(simple_char_exit);
  141.  
  142. MODULE_AUTHOR("rlk");
  143. MODULE_LICENSE("GPL v2");

现在我们开始看本节实验的重点—— demodrv_mmap 函数。我们可以通过参数中的 vm_area_struct 来获取请求映射的虚拟内存空间情况。

vm_area_struct 的确定是由内核的内存管理子系统完成填充的。

原理留作后续研究。

如代码所示,我们通过 vm_pgoff 成员获取映射的偏移信息。注意 vm_pgoff 的单位是页面大小,需要将其转换成字节。通过 vm_startvm_end 成员,可以获取映射范围。

接着我们通过 virt_to_phys 函数,来获取内核模块中即将被映射的缓冲的物理地址。并右移 PAGE_SHIFT 位来获取物理页帧号。

kmalloc 返回的也是虚拟地址。

vm_page_prot 描述了页面的保护属性。此处我们使用 pgprot_noncached 函数将其修改为非缓存。防止读写使用到 CPU 缓存,引发同步问题。

最后,我们使用 remap_pfn_range 函数,建立用户空间虚拟地址区域与物理内存之间的映射。以上过程即,我们映射了 kmalloc 出来的内核空间区域到用户层区域。

内核模块完成后,我们再写一个用户层的测试程序,验证我们写的 mmap 步骤是否正确。

代码清单 5.2 测试程序
  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. #include <unistd.h>
  4. #include <sys/mman.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include <sys/ioctl.h>
  8. #include <malloc.h>
  9.  
  10. #define DEMO_DEV_NAME "/dev/mydemo_mmap_dev"
  11.  
  12. #define MYDEV_CMD_GET_BUFSIZE 1 /* defines our IOCTL cmd */
  13.  
  14. int main()
  15. {
  16.     int fd;
  17.     int i;
  18.     size_t len;
  19.     char message[] = "Testing the virtual FIFO device";
  20.     char* read_buffer, *mmap_buffer;
  21.  
  22.     len = sizeof(message);
  23.  
  24.     fd = open(DEMO_DEV_NAME, O_RDWR);
  25.     if (fd < 0)
  26.     {
  27.         printf("open device %s fail\n", DEMO_DEV_NAME);
  28.         return -1;
  29.     }
  30.  
  31.     if (ioctl(fd, MYDEV_CMD_GET_BUFSIZE, &len) < 0)
  32.     {
  33.         printf("ioctl fail\n");
  34.         goto open_fail;
  35.     }
  36.  
  37.     printf("driver max buffer size=%ld\n", len);
  38.  
  39.     read_buffer = malloc(len);
  40.     if (!read_buffer)
  41.         goto open_fail;
  42.  
  43.     mmap_buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  44.     if (mmap_buffer == (char*)MAP_FAILED)
  45.     {
  46.         printf("mmap driver buffer fail\n");
  47.         goto map_fail;
  48.     }
  49.  
  50.     printf("mmap driver buffer succeeded: %p\n", mmap_buffer);
  51.  
  52.     /* modify the mmaped buffer */
  53.     for (i = 0; i < len; i++)
  54.         *(mmap_buffer + i) = (char)random();
  55.  
  56.     /* read the buffer back and compare with the mmap buffer */
  57.     if (read(fd, read_buffer, len) != len)
  58.     {
  59.         printf("read fail\n");
  60.         goto read_fail;
  61.     }
  62.  
  63.     if (memcmp(read_buffer, mmap_buffer, len))
  64.     {
  65.         printf("buffer compare fail\n");
  66.         goto read_fail;
  67.     }
  68.  
  69.     printf("data modify and compare succussful\n");
  70.  
  71.     munmap(mmap_buffer, len);
  72.     free(read_buffer);
  73.     close(fd);
  74.  
  75.     return 0;
  76.  
  77. read_fail:
  78.     munmap(mmap_buffer, len);
  79. map_fail:
  80.     free(read_buffer);
  81. open_fail:
  82.     close(fd);
  83.     return -1;
  84. }

用户层这边的 mmap 系统调用的原型为:

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

其中,start 为映射的建议开始地址,通常设置为 NULL,让内核选择地址;len 是要映射的字节数;prot 指定保护内存区域的方式;flags 描述映射类型和其他属性,比如此处设置的 MAP_SHARED 表示对映射内容的更改会写回到文件;fd 为打开的文件描述符,即将被映射的文件;offset 指定映射的偏移。

如代码清单 5.2 所示,它首先映射设备的内存区域到用户空间(第 43 行)。然后通过这个映射的地址往里面写数据(第 53 至 54 行)。最后我们通过 read 带复制机制的接口读取设备内存(第 57 行)。以此我们可以验证我们写入的内容是否生效。

实验:映射用户内存

上一节我们介绍了,如何将一个文件或设备(例子中是内核中分配的内存)映射到用户可访问的虚拟空间中,从而减少用户层操作内核中数据的开销。同样,反过来,我们也有机制在内核中映射用户空间页面的机制,减少内核层操作用户层中数据的开销。

我们直接看到代码清单 6.1 来学习,fops 接口没有增加,还是调用基本的 read 和 write 接口。核心函数封装在 demodrv_read_write 中,参数 buf 就是应用层传下来的应用层地址。

代码清单 6.1 get_user_pages
  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/uaccess.h>
  4. #include <linux/init.h>
  5. #include <linux/miscdevice.h>
  6. #include <linux/device.h>
  7. #include <linux/slab.h>
  8. #include <linux/kfifo.h>
  9. #include <linux/highmem.h>
  10.  
  11. #define DEMO_NAME "my_demo_dev"
  12. static struct device* mydemodrv_device;
  13.  
  14. #define MYDEMO_READ 0
  15. #define MYDEMO_WRITE 1
  16.  
  17. /* virtual FIFO device's buffer */
  18. static char* device_buffer;
  19. #define MAX_DEVICE_BUFFER_SIZE (1 * PAGE_SIZE)
  20.  
  21. #define MYDEV_CMD_GET_BUFSIZE 1 /* defines our IOCTL cmd */
  22.  
  23. static size_t demodrv_read_write(void* buf, size_t len, int rw)
  24. {
  25.     int ret, npages, i;
  26.     struct page** pages;
  27.     struct mm_struct* mm = current->mm;
  28.     char* kmap_addr, *dev_buf;
  29.     size_t size = 0;
  30.     size_t count = 0;
  31.  
  32.     dev_buf = device_buffer;
  33.  
  34.     /* how many pages? */
  35.     npages = DIV_ROUND_UP(len, PAGE_SIZE);
  36.  
  37.     printk("%s: len=%ld, npage=%d\n", __func__, len, npages);
  38.  
  39.     pages = kmalloc(npages * sizeof(pages), GFP_KERNEL);
  40.     if (!pages)
  41.     {
  42.         printk("alloc pages fail\n");
  43.         return -ENOMEM;
  44.     }
  45.  
  46.     down_read(&mm->mmap_sem);
  47.  
  48.     ret = get_user_pages_fast((unsigned long)buf, npages, 1, pages);
  49.     if (ret < npages)
  50.     {
  51.         printk("pin page fail\n");
  52.         goto fail_pin_pages;
  53.     }
  54.  
  55.     up_read(&mm->mmap_sem);
  56.  
  57.     printk("pin %d pages from user done\n", npages);
  58.  
  59.     for (i = 0; i < npages; i++)
  60.     {
  61.         kmap_addr = kmap(pages[i]);
  62.         size = min_t(size_t, PAGE_SIZE, len);
  63.         switch (rw)
  64.         {
  65.         case MYDEMO_READ:
  66.             memcpy(kmap_addr, dev_buf + PAGE_SIZE * i, size);
  67.             break;
  68.         case MYDEMO_WRITE:
  69.             memcpy(dev_buf + PAGE_SIZE * i, kmap_addr, size);
  70.             break;
  71.         default:
  72.             break;
  73.         }
  74.        
  75.         put_page(pages[i]);
  76.         kunmap(pages[i]);
  77.         len -= size;
  78.         count += size;
  79.     }
  80.  
  81.     kfree(pages);
  82.  
  83.     printk("%s: %s user buffer %ld bytes done\n", __func__, rw ? "write" : "read", count);
  84.  
  85.     return count;
  86.  
  87. fail_pin_pages:
  88.     upread(&mm->mmap_sem);
  89.     for (i = 0; i < ret; i++)
  90.         put_page(pages[i]);
  91.     kfree(pages);
  92.  
  93.     return -EFAULT;
  94. }
  95.  
  96. static int demodrv_open(struct inode* inode, struct file* file)
  97. {
  98.     int major = MAJOR(inode->i_rdev);
  99.     int minor = MINOR(inode->i_rdev);
  100.  
  101.     printk("%s: major=%d, minor=%d\n", __func__, major, minor);
  102.  
  103.     return 0;
  104. }
  105.  
  106. static int demodrv_release(struct inode* inode, struct file* file)
  107. {
  108.     return 0;
  109. }
  110.  
  111. static ssize_t
  112. demodrv_read(struct file* file, char __user* buf, size_t count, loff_t* ppos)
  113. {
  114.     size_t nbytes =
  115.         demodrv_read_write(buf, count, MYDEMO_READ);
  116.  
  117.     printk("%s: read nbytes=%ld done at pos=%d\n",
  118.         __func__, nbytes, (int)*ppos);
  119.  
  120.     return nbytes;
  121. }
  122.  
  123. static ssize_t
  124. demodrv_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  125. {
  126.  
  127.     size_t nbytes =
  128.         demodrv_read_write((void*)buf, count, MYDEMO_WRITE);
  129.  
  130.     printk("%s: write nbytes=%ld done at pos=%d\n",
  131.         __func__, nbytes, (int)*ppos);
  132.  
  133.     return nbytes;
  134. }
  135.  
  136. static int
  137. demodrv_mmap(struct file* filp, struct vm_area_struct* vma)
  138. {
  139.     unsigned long pfn;
  140.     unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
  141.     unsigned long len = vma->vm_end - vma->vm_start;
  142.  
  143.     if (offset >= MAX_DEVICE_BUFFER_SIZE)
  144.         return -EINVAL;
  145.     if (len > (MAX_DEVICE_BUFFER_SIZE - offset))
  146.         return -EINVAL;
  147.  
  148.     printk("%s: mapping %ld bytes of device buffer at offset %ld\n",
  149.         __func__, len, offset);
  150.  
  151.     /*    pfn = page_to_pfn (virt_to_page (ramdisk + offset)); */
  152.     pfn = virt_to_phys(device_buffer + offset) >> PAGE_SHIFT;
  153.  
  154.     if (remap_pfn_range(vma, vma->vm_start, pfn, len, vma->vm_page_prot))
  155.         return -EAGAIN;
  156.  
  157.     return 0;
  158. }
  159.  
  160. static long
  161. demodrv_unlocked_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
  162. {
  163.     unsigned long tbs = MAX_DEVICE_BUFFER_SIZE;
  164.     void __user* ioargp = (void __user*)arg;
  165.  
  166.     switch (cmd) {
  167.     default:
  168.         return -EINVAL;
  169.  
  170.     case MYDEV_CMD_GET_BUFSIZE:
  171.         if (copy_to_user(ioargp, &tbs, sizeof(tbs)))
  172.             return -EFAULT;
  173.         return 0;
  174.     }
  175. }
  176.  
  177. static const struct file_operations demodrv_fops = {
  178.     .owner = THIS_MODULE,
  179.     .open = demodrv_open,
  180.     .release = demodrv_release,
  181.     .read = demodrv_read,
  182.     .write = demodrv_write,
  183.     .mmap = demodrv_mmap,
  184.     .unlocked_ioctl = demodrv_unlocked_ioctl,
  185. };
  186.  
  187. static struct miscdevice mydemodrv_misc_device = {
  188.     .minor = MISC_DYNAMIC_MINOR,
  189.     .name = DEMO_NAME,
  190.     .fops = &demodrv_fops,
  191. };
  192.  
  193. static int __init simple_char_init(void)
  194. {
  195.     int ret;
  196.  
  197.     device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
  198.     if (!device_buffer)
  199.         return -ENOMEM;
  200.  
  201.     ret = misc_register(&mydemodrv_misc_device);
  202.     if (ret) {
  203.         printk("failed register misc device\n");
  204.         kfree(device_buffer);
  205.         return ret;
  206.     }
  207.  
  208.     mydemodrv_device = mydemodrv_misc_device.this_device;
  209.  
  210.     printk("succeeded register char device: %s\n", DEMO_NAME);
  211.  
  212.     return 0;
  213. }
  214.  
  215. static void __exit simple_char_exit(void)
  216. {
  217.     printk("removing device\n");
  218.  
  219.     kfree(device_buffer);
  220.     misc_deregister(&mydemodrv_misc_device);
  221. }
  222.  
  223. module_init(simple_char_init);
  224. module_exit(simple_char_exit);
  225.  
  226. MODULE_AUTHOR("rlk");
  227. MODULE_LICENSE("GPL v2");

get_user_pages_fast 用于锁定用户空间的页面,以便它们在操作期间不会被交换出内存。它的函数原型为

  • int get_user_pages_fast(unsigned long start,
  •     int nr_pages, int write, struct page** pages);

其中,start 是开始的用户地址;nr_pages 是要锁定的页数;write 写访问标志,如果设置,锁定的页面将为写访问,否则为读访问;pages 返回锁定页的数组。

如果 get_user_pages_fast 调用成功,它返回实际锁定的页面数;错误的话,返回一个负的错误码。

get_user_pages_fast 锁定的页面会增加其引用计数。所以,注意,在对页面的处理完成后,需要使用 put_page 函数减少页面的引用计数,确保后续页面交换和释放的正确进行。其函数定义为

  • void put_page(struct page* page);

get/put。put 为“放回”的意思。

当得到用户空间地址所对应的页面后,为了使内核地址空间可以访问其内容,需要使用 kmap 函数。其函数定义为

  • void* kmap(struct page* page);

kmap 函数的作用是将高端内存区域(这边就是用户空间区域)的页面映射到内核的地址空间中,从而允许内核直接访问它。注意,使用完之后,调用 kunmap 函数释放。

在 kmap 得到映射的地址后,我们就可以在内核空间中,自由的调用像 memcpy 这种内存操作函数,对内存进行操作。本处代码中,就是将用户层中的 buf,按一页面一页面进行复制操作。

写完内核模块之后,我们写一个用户层的测试程序进行测试。如代码清单 6.2 所示,它先在用户空间申请两个读写缓冲区,然后调用 write 接口,把写缓冲区内容写入内核模块;接着调用 read 接口,把内核模块中的内容写回用户层的读缓冲区。测试结果是读、写缓存区中的内容是要一样的。

代码清单 6.2 测试程序
  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. #include <unistd.h>
  4. #include <sys/mman.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include <fcntl.h>
  8. #include <sys/ioctl.h>
  9. #include <malloc.h>
  10.  
  11. #define DEMO_DEV_NAME "/dev/my_demo_dev"
  12.  
  13. #define MYDEV_CMD_GET_BUFSIZE 1    /* defines our IOCTL cmd */
  14.  
  15. int main()
  16. {
  17.     int fd;
  18.     int i;
  19.     size_t len;
  20.     char *read_buffer, *write_buffer;
  21.  
  22.     fd = open(DEMO_DEV_NAME, O_RDWR);
  23.     if (fd < 0) {
  24.         printf("open device %s failded\n", DEMO_DEV_NAME);
  25.         return -1;
  26.     }
  27.  
  28.     if (ioctl(fd, MYDEV_CMD_GET_BUFSIZE, &len) < 0) {
  29.         printf("ioctl fail\n");
  30.         goto open_fail;
  31.     }
  32.  
  33.     printf("driver max buffer size=%ld\n", len);
  34.  
  35.     read_buffer = malloc(len);
  36.     if (!read_buffer)
  37.         goto open_fail;
  38.  
  39.     write_buffer = malloc(len);
  40.     if (!write_buffer)
  41.         goto buffer_fail;
  42.  
  43.     /* modify the write buffer */
  44.     for (i = 0; i < len; i++)
  45.         *(write_buffer + i) = 0x55;
  46.  
  47.     if (write(fd, write_buffer, len) != len) {
  48.         printf("write fail\n");
  49.         goto rw_fail;
  50.     }
  51.  
  52.     /* read the buffer back and compare with the mmap buffer*/
  53.     if (read(fd, read_buffer, len) != len) {
  54.         printf("read fail\n");
  55.         goto rw_fail;
  56.     }
  57.  
  58.     if (memcmp(write_buffer, read_buffer, len)) {
  59.         printf("buffer compare fail\n");
  60.         goto rw_fail;
  61.     }
  62.  
  63.     printf("data modify and compare succussful\n");
  64.  
  65.     free(write_buffer);
  66.     free(read_buffer);
  67.     close(fd);
  68.  
  69.     return 0;
  70.  
  71. rw_fail:
  72.     if (write_buffer)
  73.         free(write_buffer);
  74. buffer_fail:
  75.     if (read_buffer)
  76.         free(read_buffer);
  77. open_fail:
  78.     close(fd);
  79.     return 0;
  80. }

但是,测试下来,不仅读、写两个缓存区的内容不一致,而且 free 时产生 crash。

为了调查问题产生的原因,如代码清单 6.2 所示,我们添加内存内容的打印,以此核对读写哪里发生了问题。

从打印的结果看,写入的内容和用户空间写入的内容不一致。有一个偏移开始,才是正确的内容,写入了很多“随机”数据。

进一步了解各个函数的作用,可以得知,get_user_pages_fast 是获取地址所在的页面,然后 kmap 映射的是整个页面的内容。举一个极端的例子,buf 即使只有两个字节,但是它们正好是跨页,get_user_pages_fast 也是返回的两页。

目前内核模块中的流程,读写都是按整页整页进行的,所以会把整页中的“未定义”数据也进行写入了。自然测试程序中的比较步骤不会通过。

同时内核模块中往用户空间写数据的时候,按页写的话就动了它不该动的地方。malloc 的数据内容前后,也包括 malloc 本身维护记录的内容。把它们也改写了的话,在 free 的时候就会发生错误。

代码清单 6.2 测试程序
  1. void custom_print_hex_dump_bytes(const char *prefix_str, const void *buf, size_t len)
  2. {
  3.     int i;
  4.     const unsigned char *data = buf;
  5.     char linebuf[3 * 64 + 2];  // 为了适应最多64个字节的情况
  6.  
  7.     if (!prefix_str) prefix_str = "";
  8.  
  9.     for (i = 0; i < len; i++) {
  10.         if (i % 64 == 0 && i) {  // 这里改为每64字节换一行
  11.             printk("%s%s\n", prefix_str, linebuf);
  12.             memset(linebuf, 0, sizeof(linebuf)); // 清空buffer
  13.         }
  14.         snprintf(linebuf + 3 * (i % 64), sizeof(linebuf) - 3 * (i % 64), "%02x ", data[i]);
  15.     }
  16.  
  17.     if (i % 64 || i == len) {
  18.         linebuf[3 * (i % 64)] = 0;  // 结束字符串
  19.         printk("%s%s\n", prefix_str, linebuf);
  20.     }
  21. }
  22.  
  23. static size_t demodrv_read_write(void* buf, size_t len, int rw)
  24. {
  25.     int ret, npages, i;
  26.     struct page** pages;
  27.     struct mm_struct* mm = current->mm;
  28.     char* kmap_addr, *dev_buf;
  29.     size_t size = 0;
  30.     size_t count = 0;
  31.     unsigned long user_offset = offset_in_page(buf);
  32.  
  33.     dev_buf = device_buffer;
  34.  
  35.     /* how many pages? */
  36.     npages = DIV_ROUND_UP(len + user_offset, PAGE_SIZE);
  37.  
  38.     printk("%s: len=%ld, npage=%d\n", __func__, len, npages);
  39.  
  40.     pages = kmalloc(npages * sizeof(pages), GFP_KERNEL);
  41.     if (!pages)
  42.     {
  43.         printk("alloc pages fail\n");
  44.         return -ENOMEM;
  45.     }
  46.  
  47.     down_read(&mm->mmap_lock);
  48.  
  49.     printk("buf=%px,user_offset=%lu,npages=%d\n", buf, user_offset, npages);
  50.     ret = get_user_pages_fast((unsigned long)buf, npages, 1, pages);
  51.     if (ret < npages)
  52.     {
  53.         printk("pin page fail\n");
  54.         goto fail_pin_pages;
  55.     }
  56.  
  57.     up_read(&mm->mmap_lock);
  58.  
  59.     printk("pin %d pages from user done\n", npages);
  60.  
  61.     for (i = 0; i < npages; i++)
  62.     {
  63.         kmap_addr = kmap(pages[i]);
  64.         custom_print_hex_dump_bytes("page: ", kmap_addr, PAGE_SIZE);
  65.         size = min_t(size_t, PAGE_SIZE - user_offset, len);
  66.         printk("PAGE_SIZE=%lu,user_offset=%lu,size=%lu\n", PAGE_SIZE, user_offset, size);
  67.         switch (rw)
  68.         {
  69.         case MYDEMO_READ:
  70.             memcpy(kmap_addr + user_offset, dev_buf + count, size);
  71.             custom_print_hex_dump_bytes("read page: ", kmap_addr, size);
  72.             break;
  73.         case MYDEMO_WRITE:
  74.             memcpy(dev_buf + count, kmap_addr + user_offset, size);
  75.             custom_print_hex_dump_bytes("write device: ", kmap_addr, size);
  76.             break;
  77.         default:
  78.             break;
  79.         }
  80.        
  81.         put_page(pages[i]);
  82.         kunmap(pages[i]);
  83.  
  84.         len -= size;
  85.         count += size;
  86.         user_offset = 0;
  87.     }
  88.  
  89.     kfree(pages);
  90.  
  91.     printk("%s: %s user buffer %ld bytes done\n", __func__, rw ? "write" : "read", count);
  92.  
  93.     return count;
  94.  
  95. fail_pin_pages:
  96.     up_read(&mm->mmap_lock);
  97.     for (i = 0; i < ret; i++)
  98.         put_page(pages[i]);
  99.     kfree(pages);
  100.  
  101.     return -EFAULT;
  102. }

代码清单 6.2 中的修复做法是,使用 offset_in_page 函数获取地址在页中的偏移量,然后在第一页操作的时候加上对偏移的考虑,确保读写的位置正确。

书中的修复做法如代码清单 6.3 所示,它改变应用层,使用匿名映射(MAP_ANON)来申请一块内存。因为 mmap 返回的地址是天然页对齐的。匿名映射不关联任何文件,只是简单的分配内存。

代码清单 6.3 匿名映射
  1.     read_buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
  2.     if (read_buffer == MAP_FAILED)
  3.         goto open_fail;
  4.  
  5.     write_buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
  6.     if (write_buffer == MAP_FAILED)
  7.         goto buffer_fail;

mmap 返回的地址是天然页对齐的。

改变应用层的思路是,只要传入的地址是页对齐的,那么数据肯定在页面开头,原先内核模块的逻辑就能跑通。所以如代码清单 6.4,用 posix_memalign 手动按页对齐也是可以的。

代码清单 6.4 posix_memalign
  1. size_t alignment = sysconf(_SC_PAGESIZE);
  2.  
  3. if (posix_memalign(&read_buffer, alignment, len) != 0)
  4. {
  5.     goto buffer_fail;
  6. }
  7.  
  8. if (posix_memalign(&write_buffer, alignment, len) != 0)
  9. {
  10.     goto buffer_fail;
  11. }