聊一聊binder传递文件fd原理及新版本性能优化
聊一聊binder传递文件fd原理及新版本性能优化
背景:
近期有vip学员朋友,问道了一个问题,那就是关于binder跨进程传递fd具体是怎么做到的呢?这块其实binder跨进程传递文件fd,本质的原理的实现在binder驱动中,所以给这个同学找一下相关的binder驱动中的代码,但是发现这块代码还和我以前看过的跨进程传递fd代码有较大的差别了,这个差别还不是简单改变一下方法,而是设计思路上就有变化,所以这里整理一下给大家分享出来。
老版本binder驱动代码传递fd
安卓kernel内核版本:
VERSION = 4
PATCHLEVEL = 4
SUBLEVEL = 302
EXTRAVERSION =
一般在client端调用调用了Parcel.writeFileDescriptor(fd)进行fd传递,接下来跨进程通信会一路调用到binder内核的binder_transaction代码:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply,
binder_size_t extra_buffers_size)
{
//省略
hdr = (struct binder_object_header *)(t->buffer->data + *offp);
off_min = *offp + object_size;
switch (hdr->type) {
//省略
//针对binder传递的是fd类型
case BINDER_TYPE_FD: {
struct binder_fd_object *fp = to_binder_fd_object(hdr);
int target_fd = binder_translate_fd(fp->fd, t, thread,
in_reply_to);
fp->pad_binder = 0;
fp->fd = target_fd;
} break;
老版本内核处理fd转换:
static int binder_translate_fd(int fd,
struct binder_transaction *t,
struct binder_thread *thread,
struct binder_transaction *in_reply_to)
{
struct binder_proc *proc = thread->proc;
struct binder_proc *target_proc = t->to_proc;
int target_fd;
struct file *file;
int ret;
//这个fd的是源进程的,通过fd获取到对应的file结构
file = fget(fd);
//这里只是安全坚持,file是否可以传递过去
ret = security_binder_transfer_file(proc->cred, target_proc->cred, file);
//这里会从目标进程中获取没有使用的fd,赋值给target_fd
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
//直接把从目标进程获取的空闲target_fd与file进行绑定
task_fd_install(target_proc, target_fd, file);
return target_fd;
}
也看看fget和task_fd_install这两个核心方法:
fget其实是调用到__fget
static struct file *__fget(unsigned int fd, fmode_t mask, unsigned int refs)
{
struct files_struct *files = current->files;
struct file *file;
rcu_read_lock();
loop:
file = fcheck_files(files, fd);
//省略
rcu_read_unlock();
return file;
}
再看看task_fd_install方法:
void __fd_install(struct files_struct *files, unsigned int fd,
struct file *file)
{
struct fdtable fdt;
might_sleep();
rcu_read_lock_sched();
while (unlikely(files->resize_in_progress)) {
rcu_read_unlock_sched();
wait_event(files->resize_wait, !files->resize_in_progress);
rcu_read_lock_sched();
}
/ coupled with smp_wmb() in expand_fdtable() */
smp_rmb();
fdt = rcu_dereference_sched(files->fdt);
BUG_ON(fdt->fd[fd] != NULL);
rcu_assign_pointer(fdt->fd[fd], file);//这里就是源进程file与新进程fd进行了绑定
rcu_read_unlock_sched();
}
其实老版本的binder传递fd还是比较好理解的,主要就以下几个步骤:
1、根据源进程fd获取内核中对应的file
2、在目标进程中获取空闲没有使用的fd值
3、把第1步获取的file对象与第2步获取的fd值进行绑定既可以
老版本binder驱动的fd传递基本工作都直接在binder_translate_fd这个方法中完成,但是新版本binder驱动这块就有了较大的差异。
总结一个简单图给大家更容易理解:
新版本binder驱动代码传递fd
安卓kernel内核版本: VERSION = 6 PATCHLEVEL = 1 SUBLEVEL = 90 EXTRAVERSION = NAME = Curry Ramen 下面来看看这个6.1对应的源码是怎么传递fd的 static int binder_translate_fd(u32 fd, binder_size_t fd_offset, struct binder_transaction *t, struct binder_thread *thread, struct binder_transaction *in_reply_to) { struct binder_proc *proc = thread->proc; struct binder_proc *target_proc = t->to_proc; struct binder_txn_fd_fixup *fixup; struct file file; //这里也是通过源进程fd获取对应的file结构 file = fget(fd); //检验传递安全性 ret = security_binder_transfer_file(proc->cred, target_proc->cred, file); /
- Add fixup record for this transaction. The allocation
- of the fd in the target needs to be done from a
- target thread. */ //以下是差异部分 //构造出一个binder_txn_fd_fixup结构 fixup = kzalloc(sizeof(*fixup), GFP_KERNEL); //检验传递安全性 fixup->file = file;//获取file fixup->offset = fd_offset; fixup->target_fd = -1;//注意这里开始构造时候没有值 //该代码将 fixup 结构体(类型为 binder_txn_fd_fixup)的成员 fixup_entry //添加到事务 t的 fd_fixups 链表尾部,用于延迟处理文件描述符(fd)的跨进程映射。 list_add_tail(&fixup->fixup_entry, &t->fd_fixups); return ret; } 上面可以看出与老版本巨大差别在于,新版本根本没有直接在binder_translate_fd中获取target_fd和install target_fd到file,只是构造了binder_txn_fd_fixup对象,赋值file后,然后加入到事物t的fd_fixups列表中。 下面来看看binder_txn_fd_fixup 结构体 表示一个待处理的 fd 映射修正项,包含以下关键字段: struct binder_txn_fd_fixup { struct file *file; // 源进程的 file 对象 binder_size_t offset; // fd 在事务数据缓冲区中的偏移 int target_fd; // 目标进程的 fd(初始为 -1) struct list_head fixup_entry; // 链表节点(通过 list_add_tail 链接) }; 上面看完后最大疑问肯定是: 1、没有看到有target_fd在目标进行进行获取啊? 2、也没有看到file与target_fd进行映射? 那么新版本到底是如何通过binder_txn_fd_fixup就可以实现的fd的跨进程传递呢?又为什么要这样做呢? 按照上面源码分析可以看到最后的binder_txn_fd_fixup被放到了传递事物binder_transaction中去了,那么什么时候执行处理这个呢? 目标进程处理事务,这里处理事物肯定在目标进程的binder_thread_read时候: 在 binder_thread_read 或事务处理函数中,遍历 t->fd_fixups 链表: static int binder_thread_read(struct binder_proc *proc, struct binder_thread thread, binder_uintptr_t binder_buffer, size_t size, binder_size_t consumed, int non_block) { //省略 //使用binder_apply_fd_fixups方法来专门处理fd相关 ret = binder_apply_fd_fixups(proc, t); //省略 return 0; } 下面看看真正干活的binder_apply_fd_fixups /
- binder_apply_fd_fixups() - finish fd translation
- @proc: binder_proc associated @t->buffer
- @t: binder transaction with list of fd fixups
- Now that we are in the context of the transaction target
- process, we can allocate and install fds. Process the
- list of fds to translate and fixup the buffer with the
- new fds first and only then install the files.
- If we fail to allocate an fd, skip the install and release
- any fds that have already been allocated. */ static int binder_apply_fd_fixups(struct binder_proc *proc, struct binder_transaction *t) { struct binder_txn_fd_fixup *fixup, *tmp; int ret = 0; list_for_each_entry(fixup, &t->fd_fixups, fixup_entry) { //获取目标进程的空闲fd int fd = get_unused_fd_flags(O_CLOEXEC); fixup->target_fd = fd;//赋值给target_fd if (binder_alloc_copy_to_buffer(&proc->alloc, t->buffer, fixup->offset, &fd, sizeof(u32))) { } } list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) { //把源file与上面赋值的fixup->target_fd进行关联 fd_install(fixup->target_fd, fixup->file); list_del(&fixup->fixup_entry); kfree(fixup); } return ret; } 上面源码就很清楚看出来,fd的获取和关联file结构都是在目标进程进行read的时候才操作的,而不是和老版本那样在源进程进行binder_transaction时候做的,那么这样做是基于什么考虑呢? 个人认为有以下几个部分: 延迟处理设计 原因:目标进程的 fd 分配必须在其自身上下文中执行(涉及进程的 fd 表),避免竞态或权限问题。 流程: 收集阶段:在源进程的 Binder 线程中,通过 binder_translate_fd 收集所有待映射的 fd,形成 fd_fixups 链表。 处理阶段:当目标进程的线程处理该事务时,遍历 fd_fixups 链表,为每个 fixup 分配 target_fd,并修改事务数据缓冲区中的 fd 值。 有以下几个优势: 安全性 权限隔离:目标进程自行分配 fd,避免源进程直接操作目标资源。 策略检查:在 binder_translate_fd 阶段已通过 SELinux 检查(security_binder_transfer_file)。 原子性 所有 fd 修正项集中处理,确保事务数据的一致性。 性能优化 延迟分配减少上下文切换,结合 Binder 的 mmap 机制实现零拷贝。 文章参考来源: 更多framework实战开发,关注下面“千里马学框架”