Skip to content

文件描述符传递

追踪 Binder 中 fd 的 dup、Parcel 对象传输、ParcelFileDescriptor ownership、关闭和检测边界。

基于android-17.0.0_r1
AndroidBinderFileDescriptorParcelFileDescriptorJava框架

文件描述符传递 ​

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

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 ​

java
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 ​

java
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

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

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()

java
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 ​

java
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/文件权限。

bash
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、驱动安装还是底层文件权限。