Ashmem共享内存
Ashmem/memfd 的 owner 在创建端、Binder fd translation、接收端 mmap 和 purge 之间变化;fd 传递成功不等于接收端映射成功。下图先固定这条生命周期。
本文承接 Binder one-copy 和 binder_alloc_buf分配。Binder transaction buffer 适合传递小型 Parcel;大块数据通常通过一个共享内存文件描述符传递。Android 17 的 libcutils API 仍叫 Ashmem,但实现已经可以按设备能力选择 memfd。本文追踪区域创建、映射和 pin/unpin,以及 fd 如何通过 Binder 到达另一个进程。
1. 创建入口
源码文件:system/core/libcutils/ashmem-dev.cpp
int ashmem_create_region(const char* name, size_t size) {
if (use_memfd())
return __memfd_create_region(name, size);
return __ashmem_create_region(name, size);
}use_memfd() 根据 sys.use_memfd、SELinux memfd_class capability、vendor API level 和应用 target SDK 决定后端。调用者得到的统一产物是 fd;不能仅凭函数名断言它一定是 /dev/ashmem 字符设备。
2. Ashmem后端
源码文件:system/core/libcutils/ashmem-dev.cpp
int __ashmem_create_region(const char* name, size_t size) {
unique_fd fd(__ashmem_open());
if (!fd.ok() || ioctl(fd, ASHMEM_SET_NAME, name) < 0 ||
ioctl(fd, ASHMEM_SET_SIZE, size) < 0) {
return -1;
}
return fd.release();
}传统路径打开带 boot id 的 /dev/ashmem 设备,为 fd 设置名字和大小。ashmem_valid 通过设备类型判断 fd 是否为 ashmem;设备路径变化不会影响上层 API。
3. Memfd后端
__memfd_create_region 调用 memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING),随后 ftruncate 设置大小,并添加 F_SEAL_GROW | F_SEAL_SHRINK,禁止尺寸变化以匹配旧 Ashmem 语义。memfd 是匿名文件,不是 Binder buffer;页由两个进程分别 mmap 同一个 fd 映射。
4. 映射关系
创建 fd 不会自动把数据映射到进程。每个进程使用 mmap(fd, ...) 得到自己的虚拟地址;页表指向同一文件 backing。发送方写入映射后,接收方从自己的映射读取,不需要把整块 payload 放进 Binder transaction buffer。
这种共享的 owner 是 fd/file mapping,而不是 Binder node 或 binder_alloc。关闭一个进程的 fd 不会立刻销毁另一进程仍持有的引用;所有映射和 fd 都释放后,内核才回收 backing pages。
5. Pin与回收
源码文件:system/core/libcutils/include/cutils/ashmem.h
int ashmem_pin_region(int fd, size_t offset, size_t len);
int ashmem_unpin_region(int fd, size_t offset, size_t len);pin/unpin 是 purgeable 语义:unpin 区域允许系统回收未使用页,pin 请求保留区域但不保证数据已存在。调用者必须能处理 ASHMEM_IS_PURGED 或重新填充数据;共享内存不是持久存储。
6. Binder传fd
源码文件:frameworks/native/libs/binder/Parcel.cpp
status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
if (!mAllowFds) return FDS_NOT_ALLOWED;
flat_binder_object obj;
obj.hdr.type = BINDER_TYPE_FD;
obj.handle = fd;
return writeObject(obj, false);
}Parcel 只写入 fd 对象描述;Binder 驱动在 binder_translate_fd 中为目标进程安装新的 fd,并在目标 buffer 中写入目标 fd 号。传递的是文件描述符引用,不是把 Ashmem 全部复制进 transaction。
7. AIDL封装
源码文件:hardware/interfaces/common/aidl/android/hardware/common/Ashmem.aidl
@VintfStability
parcelable Ashmem {
ParcelFileDescriptor fd;
long size;
}AIDL Ashmem 只携带 ParcelFileDescriptor 和逻辑 size;接收方仍需从 fd mmap,size 字段不是内核自动验证的映射长度。实现必须检查 size、offset 和访问权限,不能把 AIDL parcelable 当成已映射的 byte array。
8. 失败边界
memfd_class/SDK 条件不满足时回退 Ashmem,后端选择不是运行时数据格式协商。ftruncate、seal 或 Ashmem ioctl 失败时创建返回负 fd。mmap长度超过 fd size 会产生访问错误,AIDL size 不会替你扩容。Parcel禁止 fd 时返回FDS_NOT_ALLOWED。- 驱动 fd translation 失败会使事务返回失败并关闭已准备的目标 fd。
- unpin 后页面被 purge,重新 mmap 可能得到已清零/需重建的数据。
9. 内存对照
Binder one-copy:短生命周期、每次事务独占目标 buffer、由 BC_FREE_BUFFER 释放;Ashmem/memfd:长生命周期、fd 引用多个映射、数据页由文件 backing 管理。大对象应把 Binder 调用限制为 fd/元数据,把内容读写放在共享映射中。
10. 调试路径
先用 ashmem_valid//proc/self/fd 判断实际后端,再检查双方 mmap 地址和 size;若 Binder 调用失败,查看 Parcel fd 是否允许、驱动 fd translation 是否成功;若数据丢失,检查 unpin/purge 和 producer 是否仍持有有效 mapping。不要只检查 fd 数字相等,跨进程 fd 号通常不同但指向同一 file object。
11. 阅读检查
从 ashmem_create_region 选择后端,画到 Binder BINDER_TYPE_FD translation,再画到接收端 mmap。分别解释“fd 传递成功但 mmap 失败”和“mmap 成功但 unpin 后数据被 purge”两种现象对应的 owner、状态和恢复动作。
