背景

最近在分析问题时,遇到了mmap fb设备失败问题,顺便看了下4.1版本内核中的mmap相关流程,以前看过老版本的,有些忘记了,这里权当记录,供后续参考。

mmap是什么?

相信做过Linux开发的兄弟都或多或少用过,或者听说过mmap,但可能并不完全了解mmap的作用。

mmap字面上是内存映射的意思,听起来比较抽象,其用法有很多,但总结起来,主要是如下两个用途:

  1. 将文件内容映射到进程用户态的虚拟地址空间中,如此,进程就可以通过读写相应的虚拟地址空间内容,而直接读写相应文件中的内容。如此映射最大的好处是,进程可以直接从用户态访问文件中的数据,而不需要用户态和内核态之间的内存拷贝操作(正常流程下,如果想要向文件中写数据,需要将数据从用户态拷贝到内核态,然后再从内核态写入文件),相当于少了一次内存拷贝操作,这也是人们常说的零拷贝技术之一。当然,这里的文件不限于普通文件,Unix环境中,一切皆文件嘛,这里的文件完全可能是特殊文件,比如设备文件,那相应的mmap操作就需要单独的驱动实现了。
  2. 分配内存。当mmap中传入的fd为空时,其作用就是分配内存,类似于malloc(其实malloc的glibc实现中就使用了mmap来分配内存),俗称“匿名映射”,匿名的意思就是fd为空,名字很抽象,本质不复杂:就是在进程的虚拟地址空间中分配一段虚拟内存(用vma表示),物理内存在缺页异常中分配,并修改相应页表。

mmap基本原理

如前面所述,mmap主要有两种用途,其中第一种用途分两种情况(普通文件和特殊文件),这里分别描述相关原理:

1.普通文件的mmap基本原理为:每个文件(file)都定义了相应的文件操作数据结构(file_operations),该结构中定义了mmap操作,比如ext3文件系统文件对应的文件操作为:ext3_file_operations,对应的mmap操作接口为:generic_file_mmapgeneric_file_mmap中就是创建(或查找利用现有的)vma,然后设置相应的成员,包括缺页异常对应的处理钩子,最后返回相应的虚拟地址。当进程访问(写)相应的虚拟地址时,硬件会触发缺页异常(因为相应的页表项还没有创建),此时会进入缺页异常流程(do_page_fault),然后会进入之前设置的缺页异常钩子,该钩子会触发文件系统的写入操作,最终会将数据写入到文件中。

2.特殊文件(以设备文件为例)的mmap的基本原理与上述普通文件类似,主要差别在于:其定义的文件操作不同,对应的接口不同;其实现取决于具体的驱动,流程与普通文件实现可以完全不同,这里不详述。

3.匿名映射的基本原理:由于没有具体的fd,没有对应的文件,匿名映射没有对应的文件操作,其流程比较直接,主要还是创建(或查找利用现有的)vma,然后设置相应的成员,返回相应的虚拟地址。当进程访问(写)相应的虚拟地址时,硬件会触发缺页异常(因为相应的页表项还没有创建),此时会进入缺页异常流程(do_page_fault),然后会进入匿名映射对应的流程,主要就是为虚拟地址范围创建相应的页表,本质上,就是分配了相应的物理内存。

代码流程

mmap流程

mmap有相应的系统调用接口,从系统调用开始的大致流程如下(代码不是很好找,需要仔细看看):

SYSCALL_DEFINE6(mmap_pgoff, ...
  sys_mmap_pgoff	
    SYSCALL_DEFINE6(mmap_pgoff, ...
	  vm_mmap_pgoff
	    do_mmap_pgoff
		  mmap_region
			file->f_op->mmap() //不同文件(驱动)自己定义的mmap钩子,比如ext3文件系          统对应为`generic_file_mmap`

文件缺页异常流程

do_page_fault
  __do_page_fault
	handle_pte_fault
  	  __handle_mm_fault
		handle_pte_fault
		  do_fault
			do_shared_fault
			  __do_fault
				vma->vm_ops->fault()  //文件系统或驱动注册的缺页异常钩子,如ext4文件对应为filemap_fault

匿名映射缺页异常流程

do_page_fault
  __do_page_fault
	handle_pte_fault
  	  __handle_mm_fault
		handle_pte_fault
		  do_anonymous_page
		    mk_pte //创建页表项
			set_pte_at