1 基于tmpfs的mmap系统调用过程

前面一篇blog:mmap那些事之android property实现,讲述了android的属性系统是基于tmpfs的mmap来实现内存的共享,只是论述了应用层的使用,并未涉及到内核空间是怎么处理的。 包括如下几个问题: mmap系统调用过程 tmpfs文件针对mmap做了哪些处理?这里包括tmpfs是怎样分配实际的物理内存到共享内存的,然后其他应用进程映射到这个tmpfs的文件时,又是怎么取得这个共享物理内存的,并且又是怎么建立到自己所属进程的地址空间映射页表的)

2 mmap的系统调用过程

首先简单说明下mmap的系统调用过程:
do_mmap是应用空间mmap调用在内核空间入口,该函数前面只是做了些参数的合法性检查,在这里addr一般为0,如果不为0,则说明应用空间希望内核使用该地址作为虚拟地址的开始地址,但实际返回的地址是由当前进程的地址空间使用情况决定的,所以返回值并不一定是用户希望的addr的值,应用空间应用使用mmap系统嗲用返回的addr值。len参数指定映射的虚拟内存的长度。我们直接转到do_mmap_pgoff函数: ............
............
上面的line998 addr参数就是linux mm系统自动为我们分配的这段映射内存的开始虚拟地址 mmap_region函数主要做如下几件事情:
首先将[addr,addr+len]的这段虚拟地址空间的之前的映射拆除掉。
其次将[addr,addr+len]这段地址范围跟相邻的wma进行合并
如果不能合并,则分配新的wma结构体来管理[addr,addr+len]这段地址范围
最重要的地方出现了,如下图高亮部分,执行该映射文件句柄对应的mmap操作函数,该函数是需要支持mmap系统调用的驱动来实现的。file_operations中的mmap操作函数的实现方法有两种典型实现,ldd3的参考书籍上有详细的描述和实例,在这里,我们发现op->mmap的函数原型跟系统调用的mmap函数原型已经简化多了,因为linux的mm子系统已经为我们做了大部分事情,譬如已经为我们找到了一块合适的虚拟内存空间(vma数据结构体来表示)来为映射具体的物理内存空间做准备,并且在调用了驱动中的mmap函数后,将这个vma结构体连接到当前进程的mm结构体中。
最后,将vma数据结构连接到所属进程的mm内存管理数据结构中,

3 tmpfs对mmap调用的支持

现在回到我们的主菜,即android上层在调用open创建一个文件:/dev/__properties__,接着针对该文件执行mmap系统调用,这个时候内存做了些什么事情?
他们为什么选择在/dev目录下,而不是其他目录,譬如/data目录下创建并映射这个文件可以吗? 带着这些问题,开始我们的内核之旅:
在linux控制台执行mount命令:
如上图的黑色高亮部分显示的,/dev/目录下的文件对应的都是tmpfs文件。所以我们的讨论得从tmpfs开始。linux内核部分,tmpfs文件系统的实现时在trunk/mm/shmem.c文件中。在这里限于篇幅,我就不展开说,一个linux内核是如何实现并注册一个文件系统的。

3.1 tmpfs的open调用流程

android上层通过调用:fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);语句来创建一个tmpfs的文件:/dev/__properties__,该文件由于是首次打开,所以打开的时候就会创建它,见内核的open调用中的如下过程:
line2259 判断该文件不存在,则在line2281处调用vfs_create来创建目录dir下的对应于dentry的文件。


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

继续展开shmem_get_inode函数

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

3.2 tmpfs的mmap调用过程

至此android应用在获取到open返回的文件句柄后,调用如下函数来将共享内存映射到自己的进程地址空间: data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
该应用层的mmap系统调用在经历上述描述的mmap内核通用层的调用历程后,会掉到驱动中的mmap的实现,在这里的就是上面line1151种的shmem_file_operations结构体中的mmap成员函数:

针对struct file_operations操作中的mmap的实现,ldd3中有详细的描述,概括的将分为两种实现方式: 一种是通过调用remap_pfn_range事先就将虚拟到物理地址的映射表建立好,此种方法在mmap调用完后,虚拟到物理映射的页表已经建立好了。 一种是通过nopage的方式来实现。这方式,其实在调用完mmap后,虚地到物理地址的映射还没建立,而是在应用具体到访问到这个虚地址时,会产生page fault错误,在缺页处理中,会调用nopage获得虚拟地址对应的物理地址,并将对应的虚拟到物理的映射表建立好。 而tmpfs的mmap的实现是使用的第二种方法:

line1058 最终会调用shmem_getpage_gfp函数,展开如下:
继续上面的函数,省略部分不相关的代码

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



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

4 应用层如何使用mmap建立共享内存

这里假设有两个进程:进程A和进程B,他们要共享一块物理内存:region_c(通过/dev/目录的tmpfs文件: ashmem_test),再具体点,譬如进程A就是camera进程,进程B就是H264的编码线程所在的进程。则一个camera的录像过程就是:进程A获取未压缩的yuv原始视频数据,并将该视频数据数据放置在共享内存region_c区域,然后通过内存共享,进程B可以直接从共享内存region_c区域取出未压缩的原始视频数据作为该编码进程的输入,进行编码。这样就避免了大量的数据在跨进程间的拷贝,从而结余了大量的cpu时间和物理内存空间。

下面是一个如何使用共享内存的一个简单列子,需要特别注意的有以下几点:

  • 关闭文件句柄,并不会清除映射
  • 只有在调用munmap或是进程被终止时,才会拆除进程对应的mmap映射
  • mmap系统调用后,并不会马上建立映射,而是会在随后对映射后的地址空间进行访问(读或写时)才会真正去分配物理内存,并建立相应的映射
  • 对零长度的文件,可以进行mmap,但访问不成功。所以必须设置一个非零的文件长度,一般就设置为共享内存的大小。
  • 映射和拆除映射都是以物理页为单位的

#include <sys/mman.h>#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#define SIZE_MAP 1024*4#define FILE_NAME "/dev/__properties5__"int printf_buf(unsigned char * buf, int len){int i;for(i =0; i<len; i++){printf("buf[%d]:0x%x ",i,buf[i]);if(!(i%4))printf("\n");}}int main(int argc, char * argv[]){int ret = 0;int fd = 0;unsigned int i =0 ;unsigned char *buf = NULL;if(!strcmp(argv[1],"a"))   //进程A{fd = open(FILE_NAME,O_RDWR | O_CREAT,0777);if(fd > 0)printf("open success fd:%d\n",fd);elseprintf("open failed\n");lseek(fd,10,SEEK_SET);  //注意必须要设置下文件的大小,如果文件大小是0,则不能映射 write(fd,"",1); buf = (unsigned char *) mmap(NULL,SIZE_MAP,PROT_READ|PROT_WRITE, MAP_SHARED,fd,0);if (buf == MAP_FAILED) {printf("mmap failed for fd:%d\n",fd);            close(fd);            return -1;        }printf("mmap success buf:%x \n",buf);printf_buf(buf, 50);//打印映射后初始化前的buf的内容memset(buf,0x5a,SIZE_MAP);//设置映射后的buf的内容close(fd);//关闭文件句柄,并不清楚映射,映射只有在munmap或进程被终止时才会被拆除for(i=0; i<20; i++)  //设置映射后的buf的内容{buf[i] = 'a'+i;}munmap(buf,SIZE_MAP);  //拆除进程A的映射printf("set share memory buf to 'a' for process A \n");// do not close fd}else if(!strcmp(argv[1],"b"))  //进程B{fd = open(FILE_NAME,O_RDWR);if(fd > 0)printf("open success\n");elseprintf("open failed\n");buf = (unsigned char *) mmap(NULL,SIZE_MAP,PROT_READ|PROT_WRITE, MAP_SHARED,fd,0); //将内存映射到进程Bif (buf == MAP_FAILED) {printf("mmap failed for fd:%d\n",fd);            close(fd);            return -1;        }close(fd); //关闭文件句柄,并不清除映射,映射只有在munmap或进程被终止时才会被拆除printf("mmap for process B success,buf:%x\n",buf);printf("printf share memory for process B\n");printf_buf(buf, 50);//打印共享内存的内容,在进程B中munmap(buf,SIZE_MAP);//拆除进程B的映射printf("completed\n");}else{printf("please inpu the correct parameter:\n");printf("shmem a \n");printf("or \n");printf("shmem b \n");}return 0;}

以上测试程序在X86 linux pc机上编译、测试结果如下:

rubbitxiao@szmce15:~/test/share_memory$ rm -rf shmem
rubbitxiao@szmce15:~/test/share_memory$
rubbitxiao@szmce15:~/test/share_memory$
rubbitxiao@szmce15:~/test/share_memory$ gcc -o shmem shmem.c
rubbitxiao@szmce15:~/test/share_memory$
rubbitxiao@szmce15:~/test/share_memory$
rubbitxiao@szmce15:~/test/share_memory$ ls -l
total 24
-rwxrwxr-x 1 rubbitxiao rubbitxiao 13070 Dec 8 20:51 shmem
-rwxr--r-- 1 rubbitxiao rubbitxiao 2447 Dec 8 20:50 shmem.c
-rw-rw-r-- 1 rubbitxiao rubbitxiao 3000 Dec 8 18:13 shmem.o
rubbitxiao@szmce15:~/test/share_memory$ sudo ./shmem a
open success fd:3
mmap success buf锛歝b52d000
buf[0]:0x0
buf[1]:0x0 buf[2]:0x0 buf[3]:0x0 buf[4]:0x0
buf[5]:0x0 buf[6]:0x0 buf[7]:0x0 buf[8]:0x0
buf[9]:0x0 buf[10]:0x0 buf[11]:0x0 buf[12]:0x0
buf[13]:0x0 buf[14]:0x0 buf[15]:0x0 buf[16]:0x0
buf[17]:0x0 buf[18]:0x0 buf[19]:0x0 buf[20]:0x0
buf[21]:0x0 buf[22]:0x0 buf[23]:0x0 buf[24]:0x0
buf[25]:0x0 buf[26]:0x0 buf[27]:0x0 buf[28]:0x0
buf[29]:0x0 buf[30]:0x0 buf[31]:0x0 buf[32]:0x0
buf[33]:0x0 buf[34]:0x0 buf[35]:0x0 buf[36]:0x0
buf[37]:0x0 buf[38]:0x0 buf[39]:0x0 buf[40]:0x0
buf[41]:0x0 buf[42]:0x0 buf[43]:0x0 buf[44]:0x0
buf[45]:0x0 buf[46]:0x0 buf[47]:0x0 buf[48]:0x0
buf[49]:0x0 set share memory buf to 'a' for process A
rubbitxiao@szmce15:~/test/share_memory$
rubbitxiao@szmce15:~/test/share_memory$
rubbitxiao@szmce15:~/test/share_memory$ sudo ./shmem b
open success
mmap for process B success,buf:25647000
printf share memory for process B
buf[0]:0x61
buf[1]:0x62 buf[2]:0x63 buf[3]:0x64 buf[4]:0x65
buf[5]:0x66 buf[6]:0x67 buf[7]:0x68 buf[8]:0x69
buf[9]:0x6a buf[10]:0x6b buf[11]:0x6c buf[12]:0x6d
buf[13]:0x6e buf[14]:0x6f buf[15]:0x70 buf[16]:0x71
buf[17]:0x72 buf[18]:0x73 buf[19]:0x74 buf[20]:0x5a
buf[21]:0x5a buf[22]:0x5a buf[23]:0x5a buf[24]:0x5a
buf[25]:0x5a buf[26]:0x5a buf[27]:0x5a buf[28]:0x5a
buf[29]:0x5a buf[30]:0x5a buf[31]:0x5a buf[32]:0x5a
buf[33]:0x5a buf[34]:0x5a buf[35]:0x5a buf[36]:0x5a
buf[37]:0x5a buf[38]:0x5a buf[39]:0x5a buf[40]:0x5a
buf[41]:0x5a buf[42]:0x5a buf[43]:0x5a buf[44]:0x5a
buf[45]:0x5a buf[46]:0x5a buf[47]:0x5a buf[48]:0x5a
buf[49]:0x5a completed
rubbitxiao@szmce15:~/test/share_memory$ ^C
rubbitxiao@szmce15:~/test/share_memory$

5 总结

最后回到我们开始提出的几个问题: 使用mmap实现内存共享的话,如果不想自己专门实现驱动层的mmap函数,则应该使用tmpfs提供的共享内存机制,所以必须要创建在基于tmpfs的文件系统中,至于文件叫什么名字都不重要 像之前举例的data分区,由于不是tmpfs文件系统,而是yaffs2文件系统,所以是不能用来实现内存共享的。 android属性系统对应的共享内存所对应的物理内存页都是由init进程分配的,并且挂在/dev/__properties__文件对应的file->f_path.dentry.d_inode->i_mapping中的平衡二叉树中。 所有其他以只读方式mmap这个/dev/__properties__文件的,则会去将init进程分配的物理内存页映射到自己的进程的地址空间,从而实现物理内存在多个进程间的共享。

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. 一款霸榜 GitHub 的开源 Linux 资源监视器!
  6. Android逆向之旅—Hook神器Cydia Substrate使用详解
  7. 《老罗的Android之旅》导读PPT
  8. 如何制作Ext4文件系统镜像
  9. Android(安卓)使用adb向虚拟机里安装APK

随机推荐

  1. android系统中自带的各种图标
  2. android 代码混淆之后 微信分享不起作用
  3. Force Localize an Application on Andro
  4. Android NDK 环境搭建(Native Developmen
  5. Android(安卓)SurfaceView
  6. Android(安卓)框架:快速开发中Util常用工
  7. Android抓包方法
  8. Android 在指定 LinearLayout 中动态添加
  9. SQLite一次性读取过多记录会造成内存溢出
  10. Android(安卓)Widget开发