android 进程间通讯之mmap(转载补充)

一、序

说到内存映射函数mmap大家可能觉得陌生,其实Android中的Binder机制就是mmap来实现的。不仅如此,微信的MMKV key-value组件、美团的 Logan的日志组件 都是基于mmap来实现的。mmap强大的地方在于通过内存映射直接对文件进行读写,减少了对数据的拷贝次数,相较于传统shaferance的key value读写,大大的提高了IO读写的效率

二、Linux文件系统

由于Android是基于Linux系统,因此在介绍mmap之前,不得不先介绍下Linux的文件系统。

类似于网络的分层结构,下图显示了 Linux 系统中对于磁盘的一次读请求在核心空间中所要经历的层次模型:


  • 虚拟文件系统层:作用是屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。
  • 文件系统层 :具体的文件系统层,一个文件系统一般使用块设备上一个独立的逻辑分区。
  • Page Cache (层页高速缓存层):引入 Cache 层的目的是为了提高 Linux 操作系统对磁盘访问的性能。
  • 通用块层:作用是接收上层发出的磁盘请求,并最终发出 I/O 请求。
  • I/O 调度层:作用是管理块设备的请求队列。
  • 块设备驱动层 :利用驱动程序,驱动具体的物理块设备。
  • 物理块设备层:具体的物理磁盘块。

其他层暂不细讲,主要说说Page Cache层 (页高速缓存)这一层。引入 Cache 层的目的是为了提高 Linux 操作系统对磁盘访问的性能。Cache 层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在 Cache 中存在该数据且是的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。

Page Cache层实际上是内核中的物理内存,在磁盘和用户空间之间多了一层缓存层,由内核负责管理控制。由于物理内存的速度远远快于磁盘的速度,有了这一层的存在,数据放入Page Cache中可以更快的进行访问`。而且数据一旦被访问后,短时间内有极大会再一次被访问,短时间内集中访问同一数据的原理就叫做局部性原理。因此经常需要被访问的数据,如果将其放入缓存中,那就有可能再次被页高速缓存命中,这也是Page Cache所带来的性能提升!

当系统free内存不足时,这时如果有进程申请内存,操作系统会从page cache中回收内存页进行分配,如果page cache也已不足,那么系统会将当期驻留在内存中的数据置换到事先配置在磁盘上的swap空间中,然后空出来的这部分内存就可以用来分配了。这就是swap交换。swap交换往往会带来磁盘IO的大量消耗,严重影响到系统正常的磁盘io。

注:和虚拟内存的关系,参考什么是物理/虚拟/共享内存

三、用户空间与内核空间

Linux的进程是相互独立的,也叫做沙盒模式,一个进程是不能直接操作或者访问别一个进程空间的。进程空间分为用户空间内核空间,相当于把Kernel和上层的应用程序抽像的隔离开。

这里有两个隔离,一个进程间是相互隔离的,二是进程内有用户和内核的隔离。进程间的交互就叫进程间通信(IPC,或称跨进程通信),而进程内的用户和内核的交互就是系统调用。

  • 进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
  • 进程间,内核空间的数据可共享,所以内核空间 = 可共享空间
  • 所有进程共用1个内核空间
  • 用户空间访问内核空间的唯一方式就是系统调用;通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

进程内用户空间与 内核空间进行交互需通过系统调用两个函数。

copy_from_user():将用户空间的数据拷贝到内核空间
copy_to_user():将内核空间的数据拷贝到用户空间

四、Cache Page与Read/Write操作

由于有了Cache Page的存在,read/write系统调用会有以下的操作,我们那Read过程来进行说明:

  • 用户进程向内核发起读取文件的请求,这涉及到用户态到内核态的转换。
  • 内核读取磁盘文件中的对应数据,并把数据读取到Cache Page中。
  • 由于Page Cache处在内核空间,不能被用户进程直接寻址 ,所以需要从Page Cache中拷贝数据到用户进程的堆空间中。
  • 注意,这里涉及到了两次拷贝:第一次拷贝磁盘到Page Cache,第二次拷贝Page Cache到用户内存。最后物理内存的内容是这样的,同一个文件内容存在了两份拷贝,一份是页缓存,一份是用户进程的内存空间。

整个流程如下图所示:

简化图

可见我们平时所使用的read/write操作作对文件操作的过程中会涉及到两次拷贝的操作!还有个缺陷,当频繁的调用系统的比方写操作,会导致用户态和内核态的频繁切换(即cpu在内核程序和用户程序不断的切换装载),系统性能会受到限制
而我们本章要讲的mmap操作,它读写效率更高,而且只涉及一次拷贝操作,IO读写效率远远高于read/write!

五、mmap的使用

mmap的函数位于 <sys/mman.h> 头文件中,它的函数原型如下:

// 用户进程调用,    函数用于将文件映射到内存
void* mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);


// 函数用于取消映射,进程在映射空间对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap() 后才执行该操作。
int munmap(void *addr, size_t length);
// 函数用于实现磁盘文件内容与共享内存区中的内容一致,即同步操作。
// 除了调用munmap取消映射,我们也可以调用msync()实现磁盘上文件内容与内核内存的内容一致
int msync(void * addr, size_t len, int flags);
  • mmap 函数用于将文件映射到内存 。
1. 用户进程调用进程内存映射函数库mmap,当前进程在线程虚拟地址空间中寻找一段空闲的满足要求的虚拟地址

2. 内核同样收到请求后会调用内核的mmap函数,实现地址映射关系配对,`即进程虚拟地址空间<< >>文件磁盘地址关系映射`,
`该映射与内核内存没有任何关联`。
  • munmap 函数用于取消映射,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用 munmap() 后才执行该操作。

  • msync 函数用于实现磁盘文件内容与共享内存区中的内容一致,即同步操作,除了调用munmap取消映射,我们也可以调用msync()实现磁盘上文件内容与共享内存区的内容一致。

细节点一: mmap映射区域大小必须是物理页大小(page_size)的整倍数(在Linux中内存页通常是4k)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。

例如,有一个文件的大小是5K,mmap函数从文件的起始位置映射5K到虚拟内存中,由于内存物理页是4K,虽然映射的文件只有5K,但是实际上映射到内存区域的内存是8K,以便满足物理页大小的整数倍。映射后对5~8K的内存区域用零填充,对这部分的操作不会报错也不会写入到原文件中。

细节点二 : 映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。

六、案例:映射文件到内存

#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    
    const char * file = "/home/root/mmap.txt";
    //打开文件,fd文件句柄
    int fd = open(file, O_RDWR | O_CREAT |O_TRUNC,0644);

    if(fd < 0){
        printf("Can't open %s\n",file);
        exit(0);
    }

   //修改文件默认大小为20字节  
    ftruncate(fd,20);

    //使用mmap进行映射
    char* mapped = (char *)mmap(NULL, 20 , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(mapped== MAP_FAILED){
        printf("File mmap failed\n");
        exit(0);
    }
    
    //映射结束,关闭文件
    close(fd);

    //对映射内存进行修改,首地址写入Hello World
    strcpy(mapped,"Hello World!");
    
    //同步内存与文件
    //只要文件映射存在,就可以通过msync将映射空间的内容写入文件,实现空间和文件的同步。
    msync(mapped,20, MS_SYNC);
   
    //释放映射区,取消映射
    munmap(mapped, 20);
    
    printf("mmap success \n");   
    
    return 0;
}

创建mmap.cpp,在里面输入上述代码,然后

touch mmap.cpp

#拷贝代码进去,保存

gcc mmap.cpp -o mmap

./mmap

程序运行成功输出mmap success,然后我们再打开mmap.txt,Hello World写入成功:


六、mmap的应用场景

mmap在Linux、Android系统上有非常多的应用场景。

1、Linux进程的创建
Linux执行一个程序,这个程序在磁盘上,为了执行这个程序,需要把程序加载到内存中,这时也是采用的是mmap。你可以从/proc/pid/maps看到每个进程的mmap状态。

2、内存分配
我们使用c库的malloc申请内存,malloc的分配内存有两个系统调用,一个brk,另一个就是mmap。其实mmap不仅可以映射文件,也可以映射内存,当mmap使用的flag是MAP_ANONYMOUS,称为建立匿名映射,此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。匿名映射存储的数据就是在物理内存上,不属于任何文件。malloc分配内存底层就是用mmap的匿名映射来操作的。

3、Binder进程间通信
了解进程间通信的人都知道Android使用的是Binder进行进程间通信,它的效率高于Linux其他传统的进程间通信,因为它只要一次拷贝,而之所以只需要进行一次拷贝的原因就在于使用了mmap!

一次完整的 Binder IPC 通信过程通常是这样:

  • Server端在启动之后,调用对/dev/binder设备调用mmap。
  • 内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后在Server端的用户空间和内核空间同时进行映射。内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后在Server端的用户空间和内核空间同时进行映射
  • Client发送请求,这个请求将先到驱动中,同时需要将数据从Client进程的用户空间拷贝(Client发送请求,这个请求将先到驱动中,同时需要将数据从Client进程的用户空间拷贝(copy_from_user)到内核空间,一次拷贝
  • 驱动通过请求通知Server端有人发出请求,Server进行处理。由于内核空间和Server端进程的用户空间存在内存映射,因此Server进程的代码可以直接访问。这样便完成了一次进程间的通信。

mmap最主要的功能就是提高了IO读写的效率,微信的MMKV key-value组件、美团的 Logan的日志组件 都是基于mmap来实现的。在微信的 MMKV/Android/MMKV/mmkv/src/main/cpp/MMKV.cpp 和美团的 Meituan-Dianping/Logan/blob/master/Logan/Clogan/mmap_util.c 的这两个文件中你都可以看到对mmap函数的使用,有兴趣的小伙伴可以自行查阅。

链接:

  1. 【深入浅出Linux】关于mmap的解析,mmap测试

  2. 什么是物理/虚拟/共享内存——Linux内存管理小结

  3. Android-内存映射mmap

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容