通常、Linux ドライバーを作成してユーザー空間を操作するときは、常に copy_from_user を使用します。
ユーザー空間から転送されたデータをコピーします。なぜこれを行うのですか?
ユーザー空間はカーネル空間データに直接アクセスできないため、異なるアドレス空間をマップし、データは最初にコピーしてから操作することしかできません。
ユーザー空間が数 MB のデータをカーネルに転送する必要がある場合、元のコピー方法は明らかに非効率で非現実的です。では、どうすればよいでしょうか?
よく考えてみると、コピーする理由はユーザー空間からカーネル空間に直接アクセスできないからで、カーネル空間のバッファに直接アクセスできれば解決するのでしょうか?
簡単に言えば、物理メモリには 2 つのマッピングがあります。つまり、物理メモリには 2 つの仮想アドレスがあり、1 つはカーネル空間に、もう 1 つはユーザー空間にあります。 関係は次のとおりです:
は mmap
マッピングを通じて実現できます。
#mmap# を介して非常にシンプルです。 # #システム コール
Map を実行すると、返されたアドレスを操作できます。 char * buf;
/* 1. 打开文件 */
fd = open("/dev/hello", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}
/* 2. mmap
* MAP_SHARED : 多个APP都调用mmap映射同一块内存时, 对内存的修改大家都可以看到。
* 就是说多个APP、驱动程序实际上访问的都是同一块内存
* MAP_PRIVATE : 创建一个copy on write的私有映射。
* 当APP对该内存进行修改时,其他程序是看不到这些修改的。
* 就是当APP写内存时, 内核会先创建一个拷贝给这个APP,
* 这个拷贝是这个APP私有的, 其他APP、驱动无法访问。
*/
buf = mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
の最初のパラメータは、マップする開始アドレスです。通常は 2 番目のパラメータは、マップされる メモリ空間のサイズです。 PROT_READ | PROT_WRITE MAP_SHAREDNULL
に設定されます。 は、カーネルが開始アドレスを決定することを意味します
。 は、マップされたスペースが
読み取り可能および書き込み可能であることを示します。 または
MAP_PRIVATE:
に入力できます。MAP_SHARED
: 複数の APP
が mmap
を呼び出して同じメモリをマップすると、全員がメモリへの変更を確認できます。つまり、複数の APP
とドライバーが実際には 同じメモリ にアクセスします。 MAP_PRIVATE
: copy on write
プライベート マッピングを作成します。 APP
がこのメモリに変更を加えると、他のプログラムはこれらの変更を認識できなくなります。つまり、APP
がメモリに書き込むとき、カーネルは最初にこの APP
のコピーを作成します。このコピーはこの APP## に対してプライベートです。 #、その他のAPPおよびドライバーにアクセスできません。</section></li></ul><h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 1px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 2px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">驱动层</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">驱动层主要是实现<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">mmap
接口,而mmap
接口的实现,主要是调用了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
:该内存区域的访问权限
驱动主要步骤:
1、使用kmalloc
或者kzalloc
函数分配一块内存kernel_buf
,因为这样分配的内存物理地址是连续的,mmap
后应用层会对这一个基地址去访问这块内存。
2、实现mmap函数
static int hello_drv_mmap(struct file *file, struct vm_area_struct *vma) { /* 获得物理地址 */ unsigned long phy = virt_to_phys(kernel_buf);//kernel_buf是内核空间分配的一块虚拟地址空间 /* 设置属性:cache, buffer*/ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); /* map */ if(remap_pfn_range(vma, vma->vm_start, phy>>PAGE_SHFIT, vma->vm_end - vma->start, vma->vm_page_prot)){ printk("mmap remap_pfn_range failed\n"); return -ENOBUFS; } return 0; } static struct file_operations my_fops = { .mmap = hello_drv_mmap, };
1、通过virt_to_phys
将虚拟地址转为物理地址,这里的kernel_buf
是内核空间的一块虚拟地址空间
2、设置属性:不使用cache,使用buffer
3、映射:通过remap_pfn_range
函数映射,phy>>PAGE_SHIFT
其实就是按page
映射,除了这个参数,其他的起始地址、大小和权限都可以由用户在系统调用函数中指定。
当应用层调用mmap
后,就会调用到驱动层的mmap
函数,最终应用层的虚拟地址和驱动中的物理地址就建立了映射关系,应用层也就可以直接访问驱动的buffer了。
以上がLinux ドライバー IO の記事 - mmap 操作の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。