mmap那些事之android property实现之二

来源:互联网 发布:asp.net 字符串转json 编辑:程序博客网 时间:2024/05/16 16:01

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,但访问不成功。所以必须设置一个非零的文件长度,一般就设置为共享内存的大小。
  • 映射和拆除映射都是以物理页为单位的

[cpp] view plaincopy
  1. #include <sys/mman.h>  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. #include <sys/types.h>  
  5. #include <sys/stat.h>  
  6. #include <fcntl.h>  
  7.   
  8. #define SIZE_MAP 1024*4  
  9. #define FILE_NAME "/dev/__properties5__"  
  10.   
  11. int printf_buf(unsigned char * buf, int len)  
  12. {  
  13.     int i;  
  14.     for(i =0; i<len; i++)  
  15.     {  
  16.         printf("buf[%d]:0x%x ",i,buf[i]);  
  17.         if(!(i%4))  
  18.             printf("\n");  
  19.     }  
  20. }  
  21.   
  22. int main(int argc, char * argv[])  
  23. {  
  24.     int ret = 0;  
  25.     int fd = 0;  
  26.     unsigned int i =0 ;  
  27.     unsigned char *buf = NULL;  
  28.     if(!strcmp(argv[1],"a"))   //进程A  
  29.     {  
  30.         fd = open(FILE_NAME,O_RDWR | O_CREAT,0777);  
  31.         if(fd > 0)  
  32.             printf("open success fd:%d\n",fd);  
  33.         else  
  34.             printf("open failed\n");  
  35.         lseek(fd,10,SEEK_SET);  //注意必须要设置下文件的大小,如果文件大小是0,则不能映射   
  36.         write(fd,"",1);   
  37.         buf = (unsigned char *) mmap(NULL,SIZE_MAP,PROT_READ|PROT_WRITE, MAP_SHARED,fd,0);  
  38.         if (buf == MAP_FAILED) {  
  39.             printf("mmap failed for fd:%d\n",fd);  
  40.             close(fd);  
  41.             return -1;  
  42.         }  
  43.         printf("mmap success buf:%x \n",buf);  
  44.         printf_buf(buf, 50);    //打印映射后初始化前的buf的内容  
  45.         memset(buf,0x5a,SIZE_MAP);//设置映射后的buf的内容  
  46.         close(fd);              //关闭文件句柄,并不清楚映射,映射只有在munmap或进程被终止时才会被拆除  
  47.         for(i=0; i<20; i++)  //设置映射后的buf的内容  
  48.         {  
  49.             buf[i] = 'a'+i;  
  50.         }  
  51.         munmap(buf,SIZE_MAP);  //拆除进程A的映射  
  52.         printf("set share memory buf to 'a' for process A \n");  
  53.         // do not close fd  
  54.           
  55.     }else if(!strcmp(argv[1],"b"))  //进程B  
  56.     {  
  57.         fd = open(FILE_NAME,O_RDWR);  
  58.         if(fd > 0)  
  59.             printf("open success\n");  
  60.         else  
  61.             printf("open failed\n");  
  62.         buf = (unsigned char *) mmap(NULL,SIZE_MAP,PROT_READ|PROT_WRITE, MAP_SHARED,fd,0); //将内存映射到进程B  
  63.         if (buf == MAP_FAILED) {  
  64.             printf("mmap failed for fd:%d\n",fd);  
  65.             close(fd);  
  66.             return -1;  
  67.         }  
  68.         close(fd); //关闭文件句柄,并不清除映射,映射只有在munmap或进程被终止时才会被拆除  
  69.         printf("mmap for process B success,buf:%x\n",buf);  
  70.         printf("printf share memory for process B\n");  
  71.         printf_buf(buf, 50);//打印共享内存的内容,在进程B中  
  72.         munmap(buf,SIZE_MAP);//拆除进程B的映射  
  73.         printf("completed\n");    
  74.     }else  
  75.     {  
  76.         printf("please inpu the correct parameter:\n");  
  77.         printf("shmem a \n");  
  78.         printf("or \n");  
  79.         printf("shmem b \n");  
  80.     }  
  81.     return 0;  
  82. }  

以上测试程序在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进程分配的物理内存页映射到自己的进程的地址空间,从而实现物理内存在多个进程间的共享。
0 0