一. 概述
內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功后,用戶對這段內存區域的修改可以直接反映到內核空間,相反,內核空間對這段區域的修改也直接反映用戶空間。那么對於內核空間<---->用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。
首先,驅動程序先分配好一段內存,接着用戶進程通過庫函數mmap()來告訴內核要將多大的內存映射到內核空間,內核經過一系列函數調用后調用對應的驅動程序的file_operation中的mmap函數,在該函數中調用remap_pfn_range()來建立映射關系。直白一點就是:驅動程序在mmap()中利用remap_pfn_range()函數將內核空間的一段內存與用戶空間的一段內存建立映射關系。
用戶空間mmap()函數:
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset)
start:用戶進程中要映射的某段內存區域的起始地址,通常為NULL(由內核來指定)
length:要映射的內存區域的大小
prot:期望的內存保護標志
flags:指定映射對象的類型
fd:文件描述符(由open函數返回)
offset:要映射的用戶空間的內存區域在內核空間中已經分配好的的內存區域中的偏移。大小為PAGE_SIZE的整數倍
二. 實現
首先在驅動程序分配一頁大小的內存,然后用戶進程通過mmap()將用戶空間中大小也為一頁的內存映射到內核空間這頁內存上。映射完成后,驅動程序往這段內存寫10個字節數據,用戶進程將這些數據顯示出來。
驅動程序:
1 #include <linux/miscdevice.h> 2 #include <linux/delay.h> 3 #include <linux/kernel.h> 4 #include <linux/module.h> 5 #include <linux/init.h> 6 #include <linux/mm.h> 7 #include <linux/fs.h> 8 #include <linux/types.h> 9 #include <linux/delay.h> 10 #include <linux/moduleparam.h> 11 #include <linux/slab.h> 12 #include <linux/errno.h> 13 #include <linux/ioctl.h> 14 #include <linux/cdev.h> 15 #include <linux/string.h> 16 #include <linux/list.h> 17 #include <linux/pci.h> 18 #include <linux/gpio.h> 19 20 21 #define DEVICE_NAME "mymap" 22 23 24 static unsigned char array[10]={0,1,2,3,4,5,6,7,8,9}; 25 static unsigned char *buffer; 26 27 28 static int my_open(struct inode *inode, struct file *file) 29 { 30 return 0; 31 } 32 33 34 static int my_map(struct file *filp, struct vm_area_struct *vma) 35 { 36 unsigned long page; 37 unsigned char i; 38 unsigned long start = (unsigned long)vma->vm_start; 39 //unsigned long end = (unsigned long)vma->vm_end; 40 unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start); 41 42 //得到物理地址 43 page = virt_to_phys(buffer); 44 //將用戶空間的一個vma虛擬內存區映射到以page開始的一段連續物理頁面上 45 if(remap_pfn_range(vma,start,page>>PAGE_SHIFT,size,PAGE_SHARED))//第三個參數是頁幀號,由物理地址右移PAGE_SHIFT得到 46 return -1; 47 48 //往該內存寫10字節數據 49 for(i=0;i<10;i++) 50 buffer[i] = array[i]; 51 52 return 0; 53 } 54 55 56 static struct file_operations dev_fops = { 57 .owner = THIS_MODULE, 58 .open = my_open, 59 .mmap = my_map, 60 }; 61 62 static struct miscdevice misc = { 63 .minor = MISC_DYNAMIC_MINOR, 64 .name = DEVICE_NAME, 65 .fops = &dev_fops, 66 }; 67 68 69 static int __init dev_init(void) 70 { 71 int ret; 72 73 //注冊混雜設備 74 ret = misc_register(&misc); 75 //內存分配 76 buffer = (unsigned char *)kmalloc(PAGE_SIZE,GFP_KERNEL); 77 //將該段內存設置為保留 78 SetPageReserved(virt_to_page(buffer)); 79 80 return ret; 81 } 82 83 84 static void __exit dev_exit(void) 85 { 86 //注銷設備 87 misc_deregister(&misc); 88 //清除保留 89 ClearPageReserved(virt_to_page(buffer)); 90 //釋放內存 91 kfree(buffer); 92 } 93 94 95 module_init(dev_init); 96 module_exit(dev_exit); 97 MODULE_LICENSE("GPL"); 98 MODULE_AUTHOR("LKN@SCUT");
應用程序:
1 #include <unistd.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <fcntl.h> 6 #include <linux/fb.h> 7 #include <sys/mman.h> 8 #include <sys/ioctl.h> 9 10 #define PAGE_SIZE 4096 11 12 13 int main(int argc , char *argv[]) 14 { 15 int fd; 16 int i; 17 unsigned char *p_map; 18 19 //打開設備 20 fd = open("/dev/mymap",O_RDWR); 21 if(fd < 0) 22 { 23 printf("open fail\n"); 24 exit(1); 25 } 26 27 //內存映射 28 p_map = (unsigned char *)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0); 29 if(p_map == MAP_FAILED) 30 { 31 printf("mmap fail\n"); 32 goto here; 33 } 34 35 //打印映射后的內存中的前10個字節內容 36 for(i=0;i<10;i++) 37 printf("%d\n",p_map[i]); 38 39 40 here: 41 munmap(p_map, PAGE_SIZE); 42 return 0; 43 }
先加載驅動后執行應用程序,用戶空間打印如下: