使用mmap实现一个轻量级的跨进程通信组件

背景

使用mmap实现的跨进程通信组件,也可用于写入数据较多的场景。

说到跨进程通信,你可能对Binder有着莫名的崇拜之情,因为它几乎贯穿了整个Android系统的跨进程通信任务,如果你探究到Binder是通过mmap实现的时候,你是否跟我一样,想用mmap实现一个跨进程组件,这就是我创建该项目的目的,对于你来说,这是一个不错的学习mmap的开源项目。

对比Binder

优势:

  • 通信数据大小突破binder限制
  • 轻量级,易用

缺点:

  • 安全,目前未考虑安全性,后续可以加进来,比如对数据加密,对外部进程访问加入验证规则

mmap原理

mmap 的函数

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • addr:指定映射的虚拟内存地址
  • length:映射的长度
  • prot:映射内存的保护模式
  • flags:指定映射的类型
  • fd:进行映射的文件句柄
  • offset:文件偏移量

mmap使用了虚拟内存,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址

mmap1.png

图中展示的是mmap将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,并且用户进程内存是虚拟的,只是映射到内核的读缓冲区,节省一半的内存空间

再来看一张图

mmap2.png

读写文件都需要经过页缓存,所以 mmap 映射的正是文件的 页缓存,而非磁盘中的文件本身。由于 mmap 映射的是文件的 页缓存,所以就涉及到同步的问题,即 页缓存 会在什么时候把数据同步到磁盘

Linux 内核并不会主动把 mmap 映射的 页缓存 同步到磁盘,而是需要用户主动触发。同步 mmap 映射的内存到磁盘有 4 个时机:

  • 调用 msync 函数主动进行数据同步(主动)

  • 调用 munmap 函数对文件进行解除映射关系时(主动)

  • 进程退出时(被动)

  • 系统关机时(被动)

系统调用mmap()用于共享内存的两种方式:

(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

  fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 

(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,这种方式是不是binder实现的方式呢,可以自己去翻一下源码看看哦

总结下

mmap实现了用户缓存区到内核缓冲区的映射,节省了用户内存,而内核空间中页缓存对应了一块实际的物理地址,mmap缓存数据会放到页缓存中,并没有主动放到磁盘,用户可以选择主动或者被动写入,还有就是 它跨进程的本质是使用了内核空间,而内核空间就是各个进程所共享的内存,此共享非彼共享,内核空间使用的是虚拟内存,虚拟内存最终需要映射到物理内存的,所以还是共享的物理内存来实现的,还有就是当你使用mmap配置的磁盘文件时,他就多了一个写入磁盘的操作,因为物理内存不会持久化,只有磁盘可以,这样来实现永久存储的目的。

实现Android跨进程通信

用例

初始化


class App : Application() {

    companion object {
        var mmipc: MMIPC = MMIPC()
    }

    override fun onCreate() {
        super.onCreate()
        val path: String = this.externalCacheDir!!.absolutePath + File.separator + "mmipc.arsc"
        "init".print(getProcessName())
        mmipc.initMMAP(path)
    }
}

保存数据

   App.mmipc.setData("1", "2")
   App.mmipc.setData("3", "4")

获取数据,不分进程

    App.mmipc.getData()
    App.mmipc.getData("key")
    App.mmipc.getData("key","defaultValue")

部门源码分析

代码中加了注释,直接看代码吧
github.com/ibaozi-cn/m…

参考

github.com/Tencent/MMK…
mp.weixin.qq.com/s/YjwxeLCwv…
mp.weixin.qq.com/s/xTKQjb45g…

猜你喜欢

转载自juejin.im/post/7112790520367153188