基于tmpfs的mmap系统调用过程


前面一篇blog:mmap那些事之android property实现,讲述了android的属性系统是基于tmpfs的mmap来实现内存的共享,只是论述了应用层的使用,并未涉及到内核空间是怎么处理的。


包括如下几个问题:


mmap系统调用过程


tmpfs文件针对mmap做了哪些处理?这里包括tmpfs是怎样分配实际的物理内存到共享内存的,然后其他应用进程映射到这个tmpfs的文件时,又是怎么取得这个共享物理内存的,并且又是怎么建立到自己所属进程的地址空间映射页表的)


mmap的系统调用过程


首先简单说明下mmap的系统调用过程:




android mmap的使用 安卓手机如何打开.mmap文件_物理内存



do_mmap是应用空间mmap调用在内核空间入口,该函数前面只是做了些参数的合法性检查,在这里addr一般为0,如果不为0,则说明应用空间希望内核使用该地址作为虚拟地址的开始地址,但实际返回的地址是由当前进程的地址空间使用情况决定的,所以返回值并不一定是用户希望的addr的值,应用空间应用使用mmap系统嗲用返回的addr值。len参数指定映射的虚拟内存的长度。我们直接转到do_mmap_pgoff函数:   

android mmap的使用 安卓手机如何打开.mmap文件_android mmap的使用_02





android mmap的使用 安卓手机如何打开.mmap文件_物理内存_03



                               ............



android mmap的使用 安卓手机如何打开.mmap文件_文件系统_04



上面的line998 addr参数就是linux mm系统自动为我们分配的这段映射内存的开始虚拟地址


mmap_region函数主要做如下几件事情:


首先将[addr,addr+len]的这段虚拟地址空间的之前的映射拆除掉。



android mmap的使用 安卓手机如何打开.mmap文件_android mmap的使用_05



其次将[addr,addr+len]这段地址范围跟相邻的wma进行合并



android mmap的使用 安卓手机如何打开.mmap文件_物理内存_06



如果不能合并,则分配新的wma结构体来管理[addr,addr+len]这段地址范围



android mmap的使用 安卓手机如何打开.mmap文件_文件系统_07



最重要的地方出现了,如下图高亮部分,执行该映射文件句柄对应的mmap操作函数,该函数是需要支持mmap系统调用的驱动来实现的。file_operations中的mmap操作函数的实现方法有两种典型实现,ldd3的参考书籍上有详细的描述和实例,在这里,我们发现op->mmap的函数原型跟系统调用的mmap函数原型已经简化多了,因为linux的mm子系统已经为我们做了大部分事情,譬如已经为我们找到了一块合适的虚拟内存空间(vma数据结构体来表示)来为映射具体的物理内存空间做准备,并且在调用了驱动中的mmap函数后,将这个vma结构体连接到当前进程的mm结构体中。



android mmap的使用 安卓手机如何打开.mmap文件_系统调用_08



最后,将vma数据结构连接到所属进程的mm内存管理数据结构中, 



android mmap的使用 安卓手机如何打开.mmap文件_物理内存_09



tmpfs对mmap调用的支持


现在回到我们的主菜,即android上层在调用open创建一个文件:/dev/__properties__,接着针对该文件执行mmap系统调用,这个时候内存做了些什么事情?




他们为什么选择在/dev目录下,而不是其他目录,譬如/data目录下创建并映射这个文件可以吗?


带着这些问题,开始我们的内核之旅:




在linux控制台执行mount命令:



android mmap的使用 安卓手机如何打开.mmap文件_文件系统_10



如上图的黑色高亮部分显示的,/dev/目录下的文件对应的都是tmpfs文件。所以我们的讨论得从tmpfs开始。linux内核部分,tmpfs文件系统的实现时在trunk/mm/shmem.c文件中。在这里限于篇幅,我就不展开说,一个linux内核是如何实现并注册一个文件系统的。


tmpfs的open调用流程


android上层通过调用:fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);语句来创建一个tmpfs的文件:/dev/__properties__,该文件由于是首次打开,所以打开的时候就会创建它,见内核的open调用中的如下过程:



android mmap的使用 安卓手机如何打开.mmap文件_文件系统_11



line2259 判断该文件不存在,则在line2281处调用vfs_create来创建目录dir下的对应于dentry的文件。




android mmap的使用 安卓手机如何打开.mmap文件_系统调用_12






由于对应于tmpfs文件系统,所以line2074对应的i_op结构体就是:



android mmap的使用 安卓手机如何打开.mmap文件_系统调用_13



所以就调用static int shmem_create(struct inode *dir, struct dentry *dentry, umode_t mode,struct nameidata *nd)函数来创建对应于/dev/__properties__的文件,在执行这个函数时,参数dir对应的目录名称应该是dev,参数dentry目录项对应的文件名字应该就是__properties__。以上函数最终调用如下函数:



android mmap的使用 安卓手机如何打开.mmap文件_文件系统_14




继续展开shmem_get_inode函数




android mmap的使用 安卓手机如何打开.mmap文件_文件系统_15



至此,在应用层调用open函数,tmpfs主要是通过shmem_create函数来在/dev/目录下,创建一个__properties__文件,主要是生成该文件对应的inode节点,并且初始化该inode节点,并将该节点跟dentry关联起来,最终会将这个两个重要成员填充到 struct file结构体成员中,并返回对应的文件句柄。


tmpfs的mmap调用过程


至此android应用在获取到open返回的文件句柄后,调用如下函数来将共享内存映射到自己的进程地址空间:


data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);


该应用层的mmap系统调用在经历上述描述的mmap内核通用层的调用历程后,会掉到驱动中的mmap的实现,在这里的就是上面line1151种的shmem_file_operations结构体中的mmap成员函数:




android mmap的使用 安卓手机如何打开.mmap文件_物理内存_16



针对struct file_operations操作中的mmap的实现,ldd3中有详细的描述,概括的将分为两种实现方式:


一种是通过调用remap_pfn_range事先就将虚拟到物理地址的映射表建立好,此种方法在mmap调用完后,虚拟到物理映射的页表已经建立好了。


一种是通过nopage的方式来实现。这方式,其实在调用完mmap后,虚地到物理地址的映射还没建立,而是在应用具体到访问到这个虚地址时,会产生page fault错误,在缺页处理中,会调用nopage获得虚拟地址对应的物理地址,并将对应的虚拟到物理的映射表建立好。


而tmpfs的mmap的实现是使用的第二种方法:




android mmap的使用 安卓手机如何打开.mmap文件_文件系统_17


line1058 最终会调用shmem_getpage_gfp函数,展开如下:

android mmap的使用 安卓手机如何打开.mmap文件_文件系统_18



继续上面的函数,省略部分不相关的代码




android mmap的使用 安卓手机如何打开.mmap文件_物理内存_19




继续上面的函数,省略部分不相关的代码




android mmap的使用 安卓手机如何打开.mmap文件_系统调用_20

android mmap的使用 安卓手机如何打开.mmap文件_文件系统_21





结合上面代码中的注释,应该不难理解这个共享物理内存页的分配及管理。


最后回到我们开始提出的几个问题:


使用mmap实现内存共享的话,如果不想自己专门实现驱动层的mmap函数,则应该使用tmpfs提供的共享内存机制,所以必须要创建在基于tmpfs的文件系统中,至于文件叫什么名字都不重要


像之前举例的data分区,由于不是tmpfs文件系统,而是yaffs2文件系统,所以是不能用来实现内存共享的。


android属性系统对应的共享内存所对应的物理内存页都是由init进程分配的,并且挂在/dev/__properties__文件对应的file->f_path.dentry.d_inode->i_mapping中的平衡二叉树中。


所有其他以只读方式mmap这个/dev/__properties__文件的,则会去将init进程分配的物理内存页映射到自己的进程的地址空间,从而实现物理内存在多个进程间的共享。