内存映射函数remap_pfn_range学习——示例分析(1)
作者
平台
参考
概述
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 0 rw-p /lib/libc-2.18.so 11 b6ec2000 12 8 8 0 rw-p [ anon ] 12 b6ec5000 132 64 0 0 r-xp /lib/libgcc_s.so.1 13 b6ee6000 28 0 0 0 ---p /lib/libgcc_s.so.1 14 b6eed000 4 4 4 0 rw-p /lib/libgcc_s.so.1 15 b6eee000 128 122 0 0 r-xp /lib/ld-2.18.so 16 b6f13000 8 8 8 0 rw-p [ anon ] 17 b6f15000 4 4 4 0 r--p /lib/ld-2.18.so 18 b6f16000 4 4 4 0 rw-p /lib/ld-2.18.so 19 be8e9000 132 4 4 0 rw-p [stack] 20 bed1c000 4 0 0 0 r-xp [sigpage] 21 bed1d000 4 0 0 0 r--p [vvar] 22 bed1e000 4 0 0 0 r-xp [vdso] 23 ffff0000 4 0 0 0 r-xp [vectors] 24 -------- ------ ------ ------ ------ 25 total 1808 662 48 0
1 [root@vexpress mnt]# ./user_2 2 I am ./user_1
可以看到user_1写入的信息,下面是内核log以及虚拟地址空间映射信息:
1 [ 2545.832903] client: user_2 (873) 2 [ 2545.833087] code section: [0x8000 0x87e0] 3 [ 2545.833178] data section: [0x107e0 0x10918] 4 [ 2545.833262] brk section: s: 0x11000, c: 0x11000 5 [ 2545.833346] mmap section: s: 0xb6fb5000 6 [ 2545.833423] stack section: s: 0xbea0ee20 7 [ 2545.833499] arg section: [0xbea0ef23 0xbea0ef2c] 8 [ 2545.833590] env section: [0xbea0ef2c 0xbea0eff3] 9 [ 2545.833761] phy: 0x8eb60000, offset: 0x0, size: 0x10000 10 [ 2545.833900] remap_pfn_mmap: map 0xeeb60000 to 0xb6e13000, size: 0x10000 11 12 [root@vexpress mnt]# cat /proc/873/maps 13 00008000-00009000 r-xp 00000000 00:12 1179665 /mnt/user_2 14 00010000-00011000 rw-p 00000000 00:12 1179665 /mnt/user_2 15 b6e13000-b6e23000 rw-s 00000000 00:10 8765 /dev/remap_pfn 16 b6e23000-b6f56000 r-xp 00000000 b3:01 143 /lib/libc-2.18.so 17 b6f56000-b6f5d000 ---p 00133000 b3:01 143 /lib/libc-2.18.so 18 b6f5d000-b6f5f000 r--p 00132000 b3:01 143 /lib/libc-2.18.so 19 b6f5f000-b6f60000 rw-p 00134000 b3:01 143 /lib/libc-2.18.so 20 b6f60000-b6f63000 rw-p 00000000 00:00 0 21 b6f63000-b6f84000 r-xp 00000000 b3:01 188 /lib/libgcc_s.so.1 22 b6f84000-b6f8b000 ---p 00021000 b3:01 188 /lib/libgcc_s.so.1 23 b6f8b000-b6f8c000 rw-p 00020000 b3:01 188 /lib/libgcc_s.so.1 24 b6f8c000-b6fac000 r-xp 00000000 b3:01 165 /lib/ld-2.18.so 25 b6fb0000-b6fb3000 rw-p 00000000 00:00 0 26 b6fb3000-b6fb4000 r--p 0001f000 b3:01 165 /lib/ld-2.18.so 27 b6fb4000-b6fb5000 rw-p 00020000 b3:01 165 /lib/ld-2.18.so 28 be9ee000-bea0f000 rw-p 00000000 00:00 0 [stack] 29 beedf000-beee0000 r-xp 00000000 00:00 0 [sigpage] 30 beee0000-beee1000 r--p 00000000 00:00 0 [vvar] 31 beee1000-beee2000 r-xp 00000000 00:00 0 [vdso] 32 ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
上面的的log信息可以查看: https://github.com/pengdonglin137/remap_pfn_demo/blob/master/log/user2
4、user_3和user_4
1 [ 4938.000918] client: user_3 (884) 2 [ 4938.001117] code section: [0x8000 0x87f4] 3 [ 4938.001205] data section: [0x107f4 0x1092c] 4 [ 4938.001281] brk section: s: 0x11000, c: 0x11000 5 [ 4938.001410] mmap section: s: 0xb6ff1000 6 [ 4938.001485] stack section: s: 0xbea10e20 7 [ 4938.001549] arg section: [0xbea10f23 0xbea10f2c] 8 [ 4938.001606] env section: [0xbea10f2c 0xbea10ff3] 9 [ 4938.001793] phy: 0x8eb70000, offset: 0x10000, size: 0x10000 10 [ 4938.001996] remap_pfn_mmap: map 0xeeb70000 to 0xb6e4f000, size: 0x10000 11 12 [root@vexpress mnt]# 13 [root@vexpress mnt]# cat /proc/884/maps 14 00008000-00009000 r-xp 00000000 00:12 1179666 /mnt/user_3 15 00010000-00011000 rw-p 00000000 00:12 1179666 /mnt/user_3 16 b6e4f000-b6e5f000 rw-s 00010000 00:10 8765 /dev/remap_pfn 17 b6e5f000-b6f92000 r-xp 00000000 b3:01 143 /lib/libc-2.18.so 18 b6f92000-b6f99000 ---p 00133000 b3:01 143 /lib/libc-2.18.so 19 b6f99000-b6f9b000 r--p 00132000 b3:01 143 /lib/libc-2.18.so 20 b6f9b000-b6f9c000 rw-p 00134000 b3:01 143 /lib/libc-2.18.so 21 b6f9c000-b6f9f000 rw-p 00000000 00:00 0 22 b6f9f000-b6fc0000 r-xp 00000000 b3:01 188 /lib/libgcc_s.so.1 23 b6fc0000-b6fc7000 ---p 00021000 b3:01 188 /lib/libgcc_s.so.1 24 b6fc7000-b6fc8000 rw-p 00020000 b3:01 188 /lib/libgcc_s.so.1 25 b6fc8000-b6fe8000 r-xp 00000000 b3:01 165 /lib/ld-2.18.so 26 b6fed000-b6fef000 rw-p 00000000 00:00 0 27 b6fef000-b6ff0000 r--p 0001f000 b3:01 165 /lib/ld-2.18.so 28 b6ff0000-b6ff1000 rw-p 00020000 b3:01 165 /lib/ld-2.18.so 29 be9f0000-bea11000 rw-p 00000000 00:00 0 [stack] 30 bebe9000-bebea000 r-xp 00000000 00:00 0 [sigpage] 31 bebea000-bebeb000 r--p 00000000 00:00 0 [vvar] 32 bebeb000-bebec000 r-xp 00000000 00:00 0 [vdso] 33 ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
需要关注的是第16行,其中的"00010000"表示offset,大小是64KB,也就是vma->vm_pgoff的值。
5、user_5
本文来自博客园,作者:dolinux,未经同意,禁止转载