内存映射函数remap_pfn_range学习——示例分析(1)

本文详细介绍了内存映射函数remap_pfn_range在驱动程序中的应用,通过示例分析了驱动如何注册misc设备,并在用户空间进行内存映射。文章还提供了用户测试程序的说明,展示了不同映射方式对内核和用户空间虚拟内存布局的影响,并给出了详细的测试步骤和结果。
摘要由CSDN通过智能技术生成

作者

彭东林
QQ 405728433
 

平台

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

参考

 

概述

Linux内核提供了remap_pfn_range函数来实现将内核空间的内存映射到用户空间:
 1 /**
 2  * remap_pfn_range - remap kernel memory to userspace
 3  * @vma: user vma to map to
 4  * @addr: target user address to start at
 5  * @pfn: physical address of kernel memory
 6  * @size: size of map area
 7  * @prot: page protection flags for this mapping
 8  *
 9  *  Note: this is only safe if the mm semaphore is held when called.
10  */
11 int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
12             unsigned long pfn, unsigned long size, pgprot_t prot);
上面的注释对参数进行了说明。当用户调用mmap时,驱动中的file_operations->mmap会被调用,可以在mmap中调用remap_pfn_range,它的大部分参数的值都由VMA提供。 具体可以参考LDD3的P420.
 

正文

下面结合一个简单的例子学习一下。 在驱动中申请一个32个Page的缓冲区,这里的PAGE_SIZE是4KB,所以内核中的缓冲区大小是128KB。user_1和user_2将前64KB映射到自己的用户空间,其中user_1向缓冲区中写入字符串,user_2去读取。user_3和user_4将后64KB映射到自己的用户空间,其中user_3向缓冲区中写入字符串,user_4读取字符串。user_5将整个128KB映射到自己的用户空间,然后将缓冲区清零。此外,在驱动中申请缓冲区的方式有多种,可以用kmalloc、也可以用alloc_pages,当然也可用vmalloc,下面会分别针对这三个接口实现驱动。
 
涉及到的测试程序和驱动程序可以到下面的链接下载:
 

一、驱动程序

下面先以kzalloc申请缓冲区的方式为例介绍,调用kmalloc申请32个页,我们知道kzalloc返回的虚拟地址的特点是对应的物理地址也是连续的,所以在调用remap_pfn_range的时候很方便。首先在驱动init的时候申请128KB的缓冲区:
 1 static int __init remap_pfn_init(void)
 2 {
 3     int ret = 0;
 4 
 5     kbuff = kzalloc(BUF_SIZE, GFP_KERNEL);  // 这里的BUF_SIZE是128KB
 6     if (!kbuff) {
 7         ret = -ENOMEM;
 8         goto err;
 9     }
10 
11     ret = misc_register(&remap_pfn_misc);   // 注册一个misc设备
12     if (unlikely(ret)) {
13         pr_err("failed to register misc device!\n");
14         goto err;
15     }
16 
17     return 0;
18 
19 err:
20     return ret;
21 }

第11行注册了一个misc设备,相关信息如下:

1 static struct miscdevice remap_pfn_misc = {
2     .minor = MISC_DYNAMIC_MINOR,
3     .name = "remap_pfn",
4     .fops = &remap_pfn_fops,
5 };

这样加载驱动后会在/dev下生成一个名为remap_pfn的节点,用户程序可以通过这个节点跟驱动通信。其中remap_pfn_fops的定义如下:

1 static const struct file_operations remap_pfn_fops = {
2     .owner = THIS_MODULE,
3     .open = remap_pfn_open,
4     .mmap = remap_pfn_mmap,
5 };

第3行的open函数这里没有做什么实际的工作,只是打印一些log,比如将进程的内存布局信息输出

第4行,负责处理用户的mmap请求,这是需要关心的。
先看一下open函数具体打印了那些内容:
 1 static int remap_pfn_open(struct inode *inode, struct file *file)
 2 {
 3     struct mm_struct *mm = current->mm;
 4 
 5     printk("client: %s (%d)\n", current->comm, current->pid);
 6     printk("code  section: [0x%lx   0x%lx]\n", mm->start_code, mm->end_code);
 7     printk("data  section: [0x%lx   0x%lx]\n", mm->start_data, mm->end_data);
 8     printk("brk   section: s: 0x%lx, c: 0x%lx\n", mm->start_brk, mm->brk);
 9     printk("mmap  section: s: 0x%lx\n", mm->mmap_base);
10     printk("stack section: s: 0x%lx\n", mm->start_stack);
11     printk("arg   section: [0x%lx   0x%lx]\n", mm->arg_start, mm->arg_end);
12     printk("env   section: [0x%lx   0x%lx]\n", mm->env_start, mm->env_end);
13 
14     return 0;
15 }
第5行将进程的名字以及pid打印出来
第6行打印进程的代码段的范围
第7行打印进程的data段的范围,其中存放的是已初始化全局变量。而bss段存放的是未初始化全局变量,存放位置紧跟在data段后面,堆区之前
第8行打印进程的堆区的起始地址和当前地址
第9行打印进程的mmap区的基地址,这里的mmap区是向下增长的。具体mmap区的基地址跟系统允许的当前进程的用户栈的大小有关,用户栈的最大size越大,mmap区的基地址就越小。修改用户栈的最大尺寸需要用到ulimit -s xxx命令,单位是KB,表示用户栈的最大尺寸,用户栈的尺寸可以上G,而内核栈却只有区区的2个页。
第10行打印进程的用户栈的起始地址,向下增长
第11行和第12行的暂不关心。
 
下面是remap_pfn_mmap的实现:
 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 = (virt_to_phys(kbuff) >> PAGE_SHIFT) + vma->vm_pgoff;
 5     unsigned long virt_start = (unsigned long)kbuff + offset;
 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 <<
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值