内存映射函数remap_pfn_range学习——示例分析
Posted 摩斯电码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存映射函数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);
正文
一、驱动程序
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,比如将进程的内存布局信息输出
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 }
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 << 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 }
第3行的vma_pgoff表示的是该vma表示的区间在缓冲区中的偏移地址,单位是页。这个值是用户调用mmap时传入的最后一个参数,不过用户空间的offset的单位是字节(当然必须是页对齐),进入内核后,内核会将该值右移PAGE_SHIFT(12),也就是转换为以页为单位。因为要在第9行打印这个编译地址,所以这里将其再左移PAGE_SHIFT,然后赋值给offset。
二、用户测试程序
1 #define PAGE_SIZE (4*1024) 2 #define BUF_SIZE (16*PAGE_SIZE) 3 #define OFFSET (0) 4 5 int main(int argc, const char *argv[]) 6 { 7 int fd; 8 char *addr = NULL; 9 10 fd = open("/dev/remap_pfn", O_RDWR); 11 12 addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET); 13 14 sprintf(addr, "I am %s\\n", argv[0]); 15 16 while(1) 17 sleep(1); 18 return 0; 19 }
第10和第12行,打开设备节点,然后从内核空间映射64KB的内存到用户空间,首地址存放在addr中,由于后面既要写入也要共享,所以设置了对应的flags。这里指定的offset是0,即映射前64KB。
1 #define PAGE_SIZE (4*1024) 2 #define BUF_SIZE (16*PAGE_SIZE) 3 #define OFFSET (0) 4 5 int main(int argc, const char *argv[]) 6 { 7 int fd; 8 char *addr = NULL; 9 10 fd = open("/dev/remap_pfn", O_RDWR); 11 12 addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET); 13 14 printf("%s", addr); 15 16 while(1) 17 sleep(1); 18 19 return 0; 20 }
user_2跟user_1实现一般一样,不同之处是将addr指向的虚拟地址空间的内容打印出来。
1 #define PAGE_SIZE (4*1024) 2 #define BUF_SIZE (16*PAGE_SIZE) 3 #define OFFSET (16*PAGE_SIZE) 4 5 int main(int argc, const char *argv[]) 6 { 7 int fd; 8 char *addr = NULL; 9 10 fd = open("/dev/remap_pfn", O_RDWR); 11 12 addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET); 13 14 sprintf(addr, "I am %s\\n", argv[0]); 15 16 while(1) 17 sleep(1); 18 return 0; 19 }
第12行的OFFSET设置的是64KB,表示将内核缓冲区的后64KB映射到用户空间
1 #define PAGE_SIZE (4*1024) 2 #define BUF_SIZE (16*PAGE_SIZE) 3 #define OFFSET (16*PAGE_SIZE) 4 5 int main(int argc, const char *argv[]) 6 { 7 int fd; 8 char *addr = NULL; 9 10 fd = open("/dev/remap_pfn", O_RDWR); 11 12 addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET); 13 14 printf("%s", addr); 15 16 while(1) 17 sleep(1); 18 return 0; 19 }
1 #define PAGE_SIZE (4*1024) 2 #define BUF_SIZE (32*PAGE_SIZE) 3 #define OFFSET (0) 4 5 int main(int argc, const char *argv[]) 6 { 7 int fd; 8 char *addr = NULL; 9 int *brk; 10 11 fd = open("/dev/remap_pfn", O_RDWR); 12 13 addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, 0); 14 memset(addr, 0x0, BUF_SIZE); 15 16 printf("Clear Finished\\n"); 17 18 while(1) 19 sleep(1); 20 return 0; 21 }
三、测试
1、内核空间的虚拟内存布局
1 [ 0.000000] Virtual kernel memory layout: 2 [ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4 kB) 3 [ 0.000000] fixmap : 0xffc00000 - 0xfff00000 (3072 kB) 4 [ 0.000000] vmalloc : 0xf0800000 - 0xff800000 ( 240 MB) 5 [ 0.000000] lowmem : 0xc0000000 - 0xf0000000 ( 768 MB) 6 [ 0.000000] pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB) 7 [ 0.000000] modules : 0xbf000000 - 0xbfe00000 ( 14 MB) 8 [ 0.000000] .text : 0xc0008000 - 0xc0800000 (8160 kB) 9 [ 0.000000] .init : 0xc0b00000 - 0xc0c00000 (1024 kB) 10 [ 0.000000] .data : 0xc0c00000 - 0xc0c7696c ( 475 kB) 11 [ 0.000000] .bss : 0xc0c78000 - 0xc0cc9b8c ( 327 kB)
2、用户虚拟地址空间的布局
3、user_1和user_2
[root@vexpress mnt]# ./user_1
可以看到如下内核log:
1 [ 2494.835749] client: user_1 (870) 2 [ 2494.835918] code section: [0x8000 0x87f4] 3 [ 2494.836047] data section: [0x107f4 0x1092c] 4 [ 2494.836165] brk section: s: 0x11000, c: 0x11000 5 [ 2494.836307] mmap section: s: 0xb6f17000 6 [ 2494.836441] stack section: s: 0xbe909e20 7 [ 2494.836569] arg section: [0xbe909f23 0xbe909f2c] 8 [ 2494.836689] env section: [0xbe909f2c 0xbe909ff3] 9 [ 2494.836943] phy: 0x8eb60000, offset: 0x0, size: 0x10000 10 [ 2494.837176] remap_pfn_mmap: map 0xeeb60000 to 0xb6d75000, size: 0x10000
进程号是870,可以分别用下面的查看一下该进程的地址空间的map信息:
1 [root@vexpress mnt]# cat /proc/870/maps 2 00008000-00009000 r-xp 00000000 00:12 1179664 /mnt/user_1 3 00010000-00011000 rw-p 00000000 00:12 1179664 /mnt/user_1 4 b6d75000-b6d85000 rw-s 00000000 00:10 8765 /dev/remap_pfn 5 b6d85000-b6eb8000 r-xp 00000000 b3:01 143 /lib/libc-2.18.so 6 b6eb8000-b6ebf000 ---p 00133000 b3:01 143 /lib/libc-2.18.so 7 b6ebf000-b6ec1000 r--p 00132000 b3:01 143 /lib/libc-2.18.so 8 b6ec1000-b6ec2000 rw-p 00134000 b3:01 143 /lib/libc-2.18.so 9 b6ec2000-b6ec5000 rw-p 00000000 00:00 0 10 b6ec5000-b6ee6000 r-xp 00000000 b3:01 188 /lib/libgcc_s.so.1 11 b6ee6000-b6eed000 ---p 00021000 b3:01 188 /lib/libgcc_s.so.1 12 b6eed000-b6eee000 rw-p 00020000 b3:01 188 /lib/libgcc_s.so.1 13 b6eee000-b6f0e000 r-xp 00000000 b3:01 165 /lib/ld-2.18.so 14 b6f13000-b6f15000 rw-p 00000000 00:00 0 15 b6f15000-b6f16000 r--p 0001f000 b3:01 165 /lib/ld-2.18.so 16 b6f16000-b6f17000 rw-p 00020000 b3:01 165 /lib/ld-2.18.so 17 be8e9000-be90a000 rw-p 00000000 00:00 0 [stack] 18 bed1c000-bed1d000 r-xp 00000000 00:00 0 [sigpage] 19 bed1d000-bed1e000 r--p 00000000 00:00 0 [vvar] 20 bed1e000-bed1f000 r-xp 00000000 00:00 0 [vdso] 21 ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
上面的每一行都可以表示一个vma的映射信息,其中第4行是需要关心的:
1 b6d75000-b6d85000 rw-s 00000000 00:10 8765 /dev/remap_pfn
含义:
1 [root@vexpress mnt]# pmap -x 870 2 870: {no such process} ./user_1 3 Address Kbytes PSS Dirty Swap Mode Mapping 4 00008000 4 4 0 0 r-xp /mnt/user_1 5 00010000 4 4 4 0 rw-p /mnt/user_1 6 b6d75000 64 0 0 0 rw-s /dev/remap_pfn 7 b6d85000 1228 424 0 0 r-xp /lib/libc-2.18.so 8 b6eb8000 28 0 0 0 ---p /lib/libc-2.18.so 9 b6ebf000 8 8 8 0 r--p /lib/libc-2.18.so 10 b6ec1000 4 4 4 以上是关于内存映射函数remap_pfn_range学习——示例分析的主要内容,如果未能解决你的问题,请参考以下文章Android驱动中的remap_pfn_range()校验漏洞(CVE-2013-2596)
dma_mmap_coherent和remap_pfn_range有什么区别?