您的位置:首页 > 教育 > 培训 > 苏州市住房和城乡建设局网站首页_广东和深圳的关系_济南头条新闻热点_站长工具查询网站信息

苏州市住房和城乡建设局网站首页_广东和深圳的关系_济南头条新闻热点_站长工具查询网站信息

2025/3/17 17:59:38 来源:https://blog.csdn.net/learnframework/article/details/146224227  浏览:    关键词:苏州市住房和城乡建设局网站首页_广东和深圳的关系_济南头条新闻热点_站长工具查询网站信息
苏州市住房和城乡建设局网站首页_广东和深圳的关系_济南头条新闻热点_站长工具查询网站信息

背景:

近期有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实战开发,关注下面“千里马学框架”

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com