背景:
近期有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_fdtarget_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;//获取filefixup->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) {//获取目标进程的空闲fdint fd = get_unused_fd_flags(O_CLOEXEC);fixup->target_fd = fd;//赋值给target_fdif (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 机制实现零拷贝。
文章参考来源:https://mp.weixin.qq.com/s/PabuIBgrX1EPcY0FLWSgGg
更多framework实战开发,关注下面“千里马学框架”