1

最近就是遇到什么问题就整理一下文档,系统的写一个课题需要的时间较多,目前还是抽不出时间来。这个题目的起因就是有人问共享内存的问题,说道Binder就是基于共享内存实现的,之前在网上也有看到过。不知道这种说法的起因是什么,但这完全不对啊。好多人都这样说,我也有点迷糊,还是看看Ashmem的实现好。

共享内存

共享内存的实质就是让多个进程访问同一块内存空间。我们知道,每个进程都有独立的地址空间,彼此是不可以访问的。但进程真正分配使用的内存还是来自物理内存的,进程在Kernel空间是可以访问到全部可分配的物理内存,那么多个进程就可能使用同一块物理内存。最简单方法就是使用内存文件共享。在内存中创建一个内存文件,然后通过mmap()映射到进程空间,因为每个进程映射的都是同一内存文件,那么实质上使用的也是同一块内存。

Linux系统中存在多种共享内存机制,但其多数本质上与上述简单方法的原理相同,都是访问同一内存文件。常用的内存共享机制有,

  • System V共享内存。
  • POSIX 共享内存。
  • 通过memfd_create()创建共享文件。

System V共享内存

System V共享内存使用的接口包括:shmget(),shmat(),shmdt(),shmctl()等。shmget()用来打开或创建一个共享内存区,其定义如下。

int shmget(key_t key, size_t size, int shmflg);

其中key为标识共享内存的键值,size为要建立共享内存的大小,shmflg为IPC_CREAT,IPC_EXCL等。

  • key是IPC_PRIVATE时,将创建一块新的共享内存。
  • key不是IPC_PRIVATE,并且key对应的共享内存不存在,这时shmflg为IPC_CREAT或IPC_CREAT|IPC_EXCL将创建一块新的共享内存。
  • 如果key对应的共享内存存在,这时shmflg为IPC_CREAT将打开共享内存,shmflg为IPC_CREAT|IPC_EXC将返回错误。

shmget()创建的共享内存在tmpfs文件系统中,多进程使用相同的key就可以实现内存共享。一种常见的做法是使用ftok()获取文件的key,这样进程打开同一文件就可。System V的共享内存可以通过“/proc/sysvipc/shm”来查看。

POSIX 共享内存

POSIX 共享内存使用的接口包括shm_open(),shm_unlink(),mmap()等。这种共享内存的的操作方式更像似在操作普通的文件,进程使用shm_open()打开或创建文件,用ftruncate()调整文件大小(新建文件时),然后使用mmap()进行映射完成共享。shm_open()的定义如下。

int shm_open(const char *name, int oflag, mode_t mode);

shm_open()的定义都与open()类似,可以通oflag来指定创建文件或打开文件的属性。使用shm_open()创建一个共享内存文件时,如果name仅仅是一个名字而非全路径的话,就将在“/dev/shm/”下创建一个文件,/dev/shm也是建立在tmpfs文件系统上的。

通过memfd_create()创建

memfd_create()会创建一个匿名文件,并返回文件描述符。这个文件像普通文件一样,可以执行修改,截取,映射等操作。区别在于这个文件是存放在RAM中的,在tmpfs文件系统中创建的。使用在共享内存中时,存在一个问题就是如何让另一个进程获得这个文件?因为memfd_create()创建的是匿名文件,无法在文件系统中找到FD,这就让文件共享变得困难。不能像其他共享内存机制一样约定好文件名进行共享,一种可行的方案通过proc来传递文件。进程创建的匿名文件在proc系统中是有记录的,存在于”/proc/<pid>/fd/<fd>“。创建文件的进程需要将这个路径传递给需要共享的进程,打开该路径就实现了文件共享。

Ashmem

Ashmem特性

Android并没有使用Linux上现有的共享内存机制,而是编写了一个新的机制Ashmem。Ashmem不像上述的机制是通过系统调用实现的,而是呈现为一个字符设备,通过操作字符设备来获取共享内存的描述符。

  • Ashmem是基于Linux虚拟内存系统shmem的,共享内存最终表现为tmpfs文件系统中的一个匿名文件。
  • Ashmem增加了内存管理(pin, unpin, purge)和内存回收(shrink)的机制,减少了用户的复杂度。
  • Ashmem与Android MemoryFile紧密相连,上层应用使用MemoryFile类来完成内存共享。
  • MemoryFile通过Binder来传递Ashmem共享内存的描述符,描述符的转换在Binder驱动中完成。

偷张图,细节就不分析了。

https://my.oschina.net/youran...

ashmem_1.jpg

Binder与Ashmem

Binder与共享内存有啥关系?我觉得没多大关系。在Android系统中,如果想在多进程中共享使用Ashmem,就需要将共享内存的FD传递给其他进程,这个过程是用Binder实现的。另外就是使用共享内存时免不了需要在进程间传输控制信息,这个传输在Android中也只能通过Binder。除此之外,好像Binder与Ashmem并没有什么联系了。Binder传输FD的过程中将FD从一个进程转换到另一个进程,这段代码还是值得看一下的。

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
        ......
        case BINDER_TYPE_FD: {
            int target_fd;
            struct file *file;
            ......
            // 获取文件在内核中的描述
            file = fget(fp->handle);
            ......
            // 在目标进程中获取一个未使用的FD
            target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
            ......
            // 将文件与目标进程新获取的FD对应起来
            task_fd_install(target_proc, target_fd, file);
            ......
            fp->binder = 0;
            fp->handle = target_fd;
        } break;
        ......

FD的转换在内核中实现起来很方便,但在用户空间中就很麻烦。用户空间访问同一匿名文件可以通过”/proc/<pid>/fd/“下的文件实现,在memfd_create中有说明。

Ashmem是一种共享内存机制,也可以认为是IPC的一种方式。它与Binder并不冲突,应该根据不同的场合使用不同的方式。

  • Binder用来传输信息和小数据。
  • Ashmem用来共享大数据。
  • 小于1M的数据,如果不频繁使用,都可以使用Binder传输。因为即使使用Ashmem也避免不了用到Binder,还不如将数据一并发送。
  • Ashmem更倾向于共享纯数据,如果将控制信息也放入共享内存中会增加数据维护的复杂性,得不偿失。

戈壁老王
143 声望64 粉丝

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,