文件描述符传递
Binder 传递文件描述符时,接收方得到的是新的 fd number,但它引用同一个内核 open file description,因此文件位置和底层流状态可能共享。Java 层应优先用 ParcelFileDescriptor 表达 ownership;直接写 FileDescriptor 不会自动关闭发送方原 fd,错误的 adopt/dup 选择会造成 double close 或泄漏。
本文承接 Java-Parcel序列化、Bundle在Binder中的传输 和 Parcel序列化详解。本文追踪 Java fd 包装、Parcel JNI、dup/adopt、回收与测试,不展开驱动 binder_translate_fd 的内核实现。
1. Ownership入口
1.1 dup
源码文件:frameworks/base/core/java/android/os/ParcelFileDescriptor.java
public static ParcelFileDescriptor dup(
FileDescriptor orig) throws IOException {
FileDescriptor fd = new FileDescriptor();
int intfd = Os.fcntlInt(orig, F_DUPFD_CLOEXEC, 0);
fd.setInt$(intfd);
return new ParcelFileDescriptor(fd);
}dup 创建新的 fd number;原 fd 和新 fd 都要分别关闭。它们共享底层文件位置等 POSIX 状态。
1.2 fromFd
public static ParcelFileDescriptor fromFd(int fd)
throws IOException {
FileDescriptor original = new FileDescriptor();
original.setInt$(fd);
return dup(original);
}fromFd 不接管传入 raw fd,而是复制;调用者仍负责关闭原 fd。
1.3 adoptFd
public static ParcelFileDescriptor adoptFd(int fd) {
FileDescriptor fdesc = new FileDescriptor();
fdesc.setInt$(fd);
return new ParcelFileDescriptor(fdesc);
}adoptFd 转移 ownership;调用后调用者不得再次关闭 raw fd。选择错误会形成双重 owner 或资源泄漏。
2. Parcel写入
2.1 原始FD
源码文件:frameworks/base/core/java/android/os/Parcel.java
public final void writeFileDescriptor(
FileDescriptor val) {
nativeWriteFileDescriptor(mNativePtr, val);
}该 API 不关闭原 fd。Parcel 文档明确警告:Binder 返回对象时使用它可能泄漏,包含 ownership 语义时应使用 ParcelFileDescriptor.writeToParcel。
2.2 JNI写入
源码文件:frameworks/base/core/jni/android_os_Parcel.cpp
status_t err = parcel->writeDupFileDescriptor(
jniGetFDFromFileDescriptor(env, object));JNI 调用 native writeDupFileDescriptor,因此 Parcel 持有的是 dup;发送方 Java FileDescriptor 仍由原 owner 管理。驱动传输时再为目标进程安装新的 fd。
2.3 Parcelable flags
ParcelFileDescriptor.writeToParcel 接收 flags;当包含 PARCELABLE_WRITE_RETURN_VALUE 时,可在写出后关闭发送方 wrapper,实现“返回值 ownership 交给接收方”。普通参数传递通常不应自动关闭。
3. 接收路径
3.1 读取FD
源码文件:frameworks/base/core/java/android/os/Parcel.java
相关函数:readFileDescriptor()
public final ParcelFileDescriptor readFileDescriptor() {
FileDescriptor fd =
nativeReadFileDescriptor(mNativePtr);
return fd != null
? new ParcelFileDescriptor(fd) : null;
}读取结果立即由新的 ParcelFileDescriptor owner 管理;调用者用完必须 close。readRawFileDescriptor 返回裸 FileDescriptor,ownership 更容易出错。
3.2 关闭语义
ParcelFileDescriptor 构造时通过 IoUtils.setFdOwner 标记 owner,并用 CloseGuard 跟踪未关闭资源。close 最终关闭 mFd;wrapped PFD 避免 double close。
3.3 Reliable通道
createReliablePipe/socketPair 额外创建 comm fd,用于 closeWithError、checkError 和远端崩溃检测。数据 fd 与状态通道 fd 是两组资源,必须分别清理。
4. FD检测
4.1 Parcel flags
public boolean hasFileDescriptors() {
return nativeHasFileDescriptors(mNativePtr);
}Parcel 从对象表判断是否含 fd;range overload 检查指定数据范围。参数越界抛 IllegalArgumentException。
4.2 容器检测
Parcel.hasFileDescriptors(Object) 递归检查 Parcelable.describeContents、Map、List、数组、SparseArray 和 lazy value。Parcelable 实现变化可能改变结果,调用者不应把它当稳定业务 schema。
4.3 Bundle消费
Bundle 使用 FD flags 在未完全 unparcel 时报告内容是否含 fd。这是对象 metadata 的传播,不要求先实例化 ParcelFileDescriptor。
5. 失败路径
fd 无效、进程 fd 表耗尽、目标安装失败或 Parcel 禁止 fd 时,写入/事务返回异常。发送成功后,发送方提前关闭自己的 dup 不影响接收方已安装的 fd;但关闭共享 open file description 的最后一个引用会释放底层资源。
Binder 只能在允许传 fd 的事务中使用;安全边界还包括接收方权限和对共享文件内容的访问约束,fd 本身就是能力。
6. 测试边界
源码文件:frameworks/base/core/tests/coretests/src/android/os/ParcelTest.java
测试写入 FileDescriptor、读取并检查有效性,验证 hasFileDescriptors 和范围检测;BundleTest 使用 ParcelFileDescriptor pipe 验证 Bundle FD flags。Native Parcel 测试还验证多个 fd、部分 append 和 object count,但不覆盖设备 fd exhaustion 和 SELinux/文件权限。
rg -n "writeFileDescriptor|readFileDescriptor|hasFileDescriptors" frameworks/base/core/java/android/os/Parcel.java
rg -n "dup\(|fromFd|adoptFd|writeToParcel|closeInternal" frameworks/base/core/java/android/os/ParcelFileDescriptor.java
rg -n "FileDescriptor|hasFileDescriptors" frameworks/base/core/tests/coretests/src/android/os/ParcelTest.java frameworks/base/core/tests/coretests/src/android/os/BundleTest.java排查 fd 传输问题时,先确认 ownership 是 dup 还是 adopt、发送 Parcel 是否允许 fd、接收方是否 close、是否共享文件位置,以及 failure 发生在 Java 包装、native Parcel、驱动安装还是底层文件权限。
