詳細な分析については、以下を参照してください: Wei Dongshan: mmap、Linux ドライバーの基礎
1. mmap の概要
mmap 関数は、ファイルまたはその他のオブジェクトをメモリにマップするために使用され、このメモリを読み取って変更することで、読み取り、書き込み、その他の操作を呼び出さずにファイルの読み取りと変更を行うことができます。
ヘッダー ファイル: <sys/mman.h>
関数プロトタイプ:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
2. mmap システムコールインターフェースパラメータの説明
-
(1) マッピング機能
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
パラメータの説明:
- addr: 指定されたファイルは、システムによって指定されたプロセス アドレス空間の開始アドレス (通常は NULL に設定) にマップされる必要があります。
- len: マップされたファイルの先頭からのオフセット バイトから始まる、呼び出しプロセスのアドレス空間にマップされたバイト数。
- prot: 共有メモリのアクセス許可を指定します。
PROT_EXEC: マップされた領域は実行可能
PROT_READ : マップされた領域は読み取り
可能 PROT_WRITE: マップされた領域は書き込み可能
PROT_NONE : マップされた領域はアクセスできない - flags: マップされた領域の特性。次のものが考えられます。
MAP_SHARED: マップされた領域に書き込まれたデータはファイルにコピーされて戻され、ファイルをマッピングしている他のプロセスによって共有されることが許可されます。MAP_PRIVATE
: マッピング領域への書き込み操作により、マッピング領域のコピー (copy_on_write) が生成され、この領域に加えられた変更は元のファイルに書き戻されません。 - fd: open によって返されるファイル記述子。マップされるファイルを表します。
- offset: ファイルの先頭のマッピング位置のオフセット。ページング サイズの整数倍でなければなりません。通常は 0 で、マッピングがファイル ヘッダーから開始されることを意味します。
-
(2) アンマップ
int munmap(void *start, size_t length);
機能: パラメータ start が指すマップされたメモリをキャンセルします パラメータの長さは、キャンセルされるメモリ サイズを示します。戻り
値: リリースが成功した場合は 0 が返され、それ以外の場合は -1 が返されます。
3. Linuxカーネルのmmapインターフェース
- 3.1 カーネルは仮想メモリの構造を記述します。Linux
カーネルは構造を使用してvm_area_struct
仮想メモリ領域を記述します。その主なメンバーのいくつかは次のとおりです。unsigned long vm_start 虚拟内存区域起始地址 unsigned long vm_end 虚拟内存区域结束地址 unsigned long vm_flags 该区域的标志 unsigned long vm_page_prot 此vma的访问保护属性,在内核arch\powerpc\include\asm\book3s\32\pgtable.h文件中有以pgprot_开头的函数来配置相关的属性。 unsigned long vm_pgoff 基于映射的文件头的偏移(以PAGE_SIZE为单位)
この構造は、<linux/mm_types.h> ヘッダー ファイルで定義されます。
この構造体の vm_flags メンバーに割り当てられるフラグは、VM_IO および VM_RESERVED です。
VM_IO表示对设备IO空间的映射;
M_RESERVED表示该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出(取消)。
从linux 3.7.0开始内核不再支持struct vm_area_struct结构体中flag标志使用值 VM_RESERVED;而是需要在类似的驱动开发中需要将VM_RESERVED改成(VM_DONTEXPAND | VM_DONTDUMP);
- 3.2 mmap 操作インターフェイス
キャラクタデバイスのファイル操作コレクション (struct file_operations) に mmap 関数のインターフェイスがあります。プロトタイプは次のとおりです。int (*mmap) (struct file *filp, struct vm_area_struct *vma);
2 番目のパラメーター struct vm_area_struct * は、カーネルによって検出された仮想メモリ間隔に相当し、使用できます。ページ テーブルの作成は mmap の内部で完了できます。
-
3.3 mmap マッピングの実装
デバイスのマッピングとは、ユーザー空間のアドレスをデバイス メモリに関連付けることを意味し、プログラムがユーザー空間でこのアドレスを読み書きするとき、実際にはデバイスにアクセスしていることになります。ここでは 2 つの操作を行う必要があります。
1. 関連付けに使用できる仮想アドレス範囲を見つけます。2.
関連する操作を実装します。
mmap デバイスの操作の例は次のとおりです。static int tiny4412_mmap(struct file *filp, struct vm_area_struct *vma) { vma->vm_flags |= VM_IO;//表示对设备IO空间的映射 vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出 vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); //修改访问保护属性 if(remap_pfn_range( vma, //虚拟内存区域,即设备地址将要映射到这里 vma->vma_start, //虚拟空间的起始地址 (phys + offset) >> PAGE_SHIFT, //与物理内存对应的页帧号,物理地址右移12位 vma->vma_end - vma->vma_start, //映射区域大小,一般是页大小的整数倍 vma->vm_page_prot //保护属性, ) { return -EAGAIN; } printk("tiny4412_mmap\n"); return 0; }
buf はカーネル内で適用されるスペースです。kmalloc関数を利用して実装します。
コードは以下のように表示されます。buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL); //内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备
-
3.4 remap_pfn_range関数
-
3.4.1 remap_pfn_range 関数は、すべてのページ テーブルを一度に作成するために使用されます。関数のプロトタイプは次のとおりです。
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
で:
- vma: カーネルが見つけた仮想アドレス空間です。
- addr: 関連付けられる仮想アドレス。
- pfn: は関連付けられる物理アドレスです。
- size: 関連付けの長さです。
- prot:vmaのアクセス権限(属性)。
-
3.4.2 ioremap と phys_to_virt、virt_to_phys の違い:
- ioremap: IO メモリのマッピングを作成するために使用され、ドライバがこのメモリにアクセスできるように仮想アドレスを IO メモリに割り当てます。
- phys_to_virt: 既知の物理アドレスに対応する仮想アドレスを計算します。
- virt_to_phys: 仮想アドレスを物理アドレスに変換します
-
3.5 サンプルコード: ドライバー層
#include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/slab.h> //定义kmalloc接口 #include <asm/io.h> //定义virt_to_phys接口 #include <linux/mm.h> //remap_pfn_range #include <linux/vmalloc.h> #include <linux/delay.h> #define MM_SIZE 4096 static char *buf= NULL; static int tiny4412_open(struct inode *my_indoe, struct file *my_file) { buf=(char *)kmalloc(MM_SIZE,GFP_KERNEL);//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备 if(buf==NULL) { printk("error!\n"); return 0; } strcpy(buf,"1234567890"); printk("open ok\n"); return 0; } static int tiny4412_release(struct inode *my_indoe, struct file *my_file) { printk("驱动层打印=%s\n",buf); kfree(buf); /*释放空间*/ printk("open release\n"); return 0; } static int tiny4412_mmap(struct file *myfile, struct vm_area_struct *vma) { vma->vm_flags |= VM_IO;//表示对设备IO空间的映射 vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出 vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); //修改访问保护属性 if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里 vma->vm_start,//虚拟空间的起始地址 virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位 vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍 vma->vm_page_prot))//保护属性, { return -EAGAIN; } printk("tiny4412_mmap ok\n"); return 0; } static struct file_operations tiny4412_fops= { .open=tiny4412_open, .release=tiny4412_release, .mmap=tiny4412_mmap }; static struct miscdevice misc={ .minor=255, .name="tiny4412_mmap", // /dev/下的名称 .fops=&tiny4412_fops, }; static int __init hello_init(void) { /*1. 注册杂项字符设备*/ misc_register(&misc); printk("hello_init 驱动安装成功!\n"); return 0; } static void __exit hello_exit(void) { /*2. 注销*/ misc_deregister(&misc); printk("hello_exit驱动卸载成功!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("www.wanbangee.com"); //声明驱动的作者 MODULE_DESCRIPTION("hello 模块测试"); //描述当前驱动功能 MODULE_LICENSE("GPL"); //驱动许可证。支持的协议GPL。
-
3.6 サンプルコード: アプリケーション層
#include <stdio.h> #include <sys/mman.h> unsigned char *fbmem=NULL; unsigned char buff[10]; int main(int argc,char**argv) { int fd; fd=open("/dev/tiny4412_mmap",2); if(fd<0) { printf("驱动打开失败!\n"); return -1; } fbmem =(unsigned char *)mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(fbmem==NULL) { printf("映射错误!\n"); } printf("应用层打印1=%s\n",fbmem); //打印出123456789 memcpy(fbmem,"123456789",10); //向映射空间拷贝数据 memcpy(buff,fbmem,10); //将映射空间的数据拷贝出来 printf("应用层打印2=%s\n",buff); //打印出123456789 close(fd); return 0; }