背景:
近期有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 机制实现零拷贝。
文章参考来源:https://mp.weixin.qq.com/s/PabuIBgrX1EPcY0FLWSgGg
更多framework实战开发,关注下面“千里马学框架”