Binder问答
概括Binder机制
1.整体业务层CS架构,BpBinder/BnBinder
2.通信层Binder驱动,writeStrongBinder/readStrongBinder完成路由
其他的代码都是业务逻辑,没必要剖析。
一次Binder请求的数据封装/传输流程
数据结构
Android Binder框架实现之Binder中的数据结构
binder_proc
binder_proc是内核中描述Binder进程上下文信息的结构体,其在用户空间相对应的是ProcessState
- threads,它是包含该进程内用于处理用户请求的所有线程的红黑树。
- nodes,它是包含该进程内的所有Binder实体所组成的红黑树。
- refs_by_desc,它是包行该进程内的所有Binder引用所组成的红黑树。通过一个引用的desc可以在这里找到对应的ref
- buffer,它是该进程内核虚拟内存的起始地址。而user_buffer_offset,则是该内核虚拟地址和进程虚拟地址之间的差值。在Binder驱动中,将进程的内核虚拟地址和进程虚拟地址映射到同一物理页面,该user_buffer_offset则是它们之间的差值;这样,已知其中一个,就可以根据差值算出另外一个。
- todo wait,todo,是该进程的待处理事务队列,而wait则是等待队列。 没看懂什么区别。
- struct list_head buffers; // 和binder_buffer->entry关联到同一链表,从而对Binder内存进行管理
binder_thread
binder_thread是内核层描述Binder线程的结构体,在用户层与其相对应的是IPCThreadState
binder_node
binder_node是内核层描述Binder实体的结构体,用户层与之相对应的是具体的Binder Server。
- binder_uintptr_t ptr; // Binder实体在用户空间的地址(为Binder实体对应的Server在用户空间的本地Binder的引用)
- binder_uintptr_t cookie; // Binder实体在用户空间的其他数据(为Binder实体对应的Server在用户空间的本地Binder自身)
- proc,它是binder_proc(进程上下文信息)结构体对象。目的是保存该Binder实体的进程。
binder_ref
binder_ref是内核层描述Binder引用的结构体,用户层与之相对应的是某个具体服务的远程代理BpBinder或者BinderProxy。
- struct rb_node rb_node_desc; //关联到所属进程binder_proc->refs_by_desc红黑树中
- struct rb_node rb_node_node; // 关联到所属进程binder_proc->refs_by_node红黑树中
- node是该Binder引用所引用的Binder实体。
- proc,它是binder_proc(进程上下文信息)结构体对象。
- desc是Binder引用的描述,实际上它就是Binder驱动为该Binder分配的一个唯一的int型整数。通过该desc,可以在binder_proc->refs_by_desc中找到该Binder引用,进而可以找到该Binder引用所引用的Binder实体等信息。
binder_buffer
Binder驱动将Binder进程的内存分成一段一段进行管理,而这每一段则是使用binder_buffer来描述的。
- struct list_head entry; // 和binder_proc->buffers关联到同一链表,从而使Binder进程对内存进行管理。
flat_binder_object
flat_binder_object是描述Binder对象信息的结构体。binder通过此结构体传输binder对象。
struct flat_binder_object {
/* 8 bytes for large_flat_header. */
__u32 type; // 可以为BINDER_TYPE_BINDER或BINDER_TYPE_HANDLE等类型
__u32 flags; // 标记
/* 8 bytes of data. */
union {
binder_uintptr_t binder; // 当type=BINDER_TYPE_BINDER时,它指向Binder对象位于C++层的本地Binder对象(即BBinder对象)的弱引用。
__u32 handle; // 当type=BINDER_TYPE_HANDLE时,它等于Binder对象在Binder驱动中对应的Binder实体的Binder引用的描述。就是binder_ref->desc对象,可以通过desc在内核中找到binder_node
};
/* extra data associated with local object */
binder_uintptr_t cookie; // 当type=BINDER_TYPE_BINDER时才有效,它指向Binder对象位于C++层的本地Binder对象(即BBinder对象)。
};
它在用户空间被使用时(例如,Server发送添加服务请求给ServiceManager),flat_binder_object就是记录的Server位于用户空间的Binder对象的信息的结构体;此时的type的值一般都是BINDER_TYPE_BINDER类型,对应的union中的binder的值是该Binder对象在用户空间的本地Binder(即BBinder对象)的引用;同时,cookie则是本地Binder自身。 而当flat_binder_object在Binder驱动中被使用(例如,当Binder驱动收到发送服务请求时),它会将type修改为BINDER_TYPE_HANDLE,然后将联合体中的handle修改为"该Server对应的Binder实体的Binder引用"的描述;根据Binder引用的描述就能找到该Server。
binder_write_read
binder_write_read是用来描述Binder在驱动层和用户层进行数据读写交互的的结构体。用户层和内核层均有此结构体。
- write_size,是写内容的总大小;write_consumed,是已写内容的大小;write_buffer,是写的内容的虚拟地址。
- read_size,是读内容的总大小;read_consumed,是已读内容的大小;read_buffer,是读的内容的虚拟地址。
binder_transaction_data
binder_transaction_data是描述Binder事务交互的数据格式的结构体。它也属于内核空间和用户空间的通信结构体。
struct binder_transaction_data {
union {
__u32 handle; // 当binder_transaction_data是由用户空间的进程发送给Binder驱动时,
// handle是该事务的发送目标在Binder驱动中的信息,即该事务会交给handle来处理;
// handle的值是目标在Binder驱动中的Binder引用,Binder的引用在代码中也叫句柄(handle)
binder_uintptr_t ptr; //当binder_transaction_data是由Binder驱动反馈给用户空间进程时
//ptr是该事务的发送目标在用户空间中的信息,即该事务会交给ptr对应的服务来处理;
// ptr是处理该事务的服务在用户空间的本地Binder对象,即指向Binder对象内存的指针
} target; //该事务的目标对象(即,该事务数据包是给该target来处理的,具体是由handle还是ptr来制定,根据实际情况来确定)
binder_uintptr_t cookie; //只有当事务是由Binder驱动传递给用户空间时,cookie才有意思,它的值是处理该事务的Server位于C++层的本地Binder对象
__u32 code; //该成员存放收发双方约定的命令码,驱动完全不关心该成员的内容.如果是请求,则以BC_开头;如果是回复,则以BR_开头。
__u32 flags;
pid_t sender_pid;
uid_t sender_euid;
binder_size_t data_size; //数据大小
binder_size_t offsets_size; //数据包中包含的对象的个数
union {
struct {
binder_uintptr_t buffer; //真正保存通信数据的缓冲区
binder_uintptr_t offsets; //记录Binder对象偏移的数组
} ptr;
__u8 buf[8];
} data; //数据
};
binder_write_read 和 binder_transaction_data分工不同
- binder_transaction_data 记录了具体的数据
- binder_write_read 记录了处理的状态 比如 write_consumed,是已写内容的大小
binder_transaction
binder_transaction用来描述进程间通信过程,这个过程又称为一个事务,进程通信事务。
struct binder_transaction {
int debug_id;
struct binder_work work; //设置事务类型
struct binder_thread *from; //发起事务的线程,称为源线程
struct binder_transaction *from_parent; //该事务所依赖的事务
struct binder_proc *to_proc; //处理该事务的进程
struct binder_thread *to_thread; //处理该事务的线程
struct binder_transaction *to_parent; //目标线程的下一个事务
unsigned need_reply:1; //用来区分一个事务是同步的还是异步的
/* unsigned is_dead:1; */ /* not used at the moment */
struct binder_buffer *buffer; //为该事务分配的一块内核缓冲区,该缓冲区保存了通信数据
unsigned int code; //进程间通信代码
unsigned int flags; //标志位,描述进程间通信行为的特征
long priority; //发起事务的线程优先级
long saved_priority; //保存处理该事务的线程原有优先级
kuid_t sender_euid; //发起事务的euid
};
ServiceManager#binder_state
struct binder_state
{
int fd; //文件节点"/dev/binder"的句柄
void *mapped; //映射内存的起始地址
size_t mapsize; //映射内存的大小
};
ServiceManager#binder_io
binder_io是与binder_transaction_data对应的轻量级结构体。
不清楚具体的使用方式
ServiceManager#svcinfo
svcinfo是保存"注册到ServiceManager中的服务"的相关信息的结构体。它是一个单链表,
struct svcinfo
{
struct svcinfo *next; //svcinfo中的next是指向下一个服务的节点
uint32_t handle; //服务在Binder驱动中Binder引用的描述
struct binder_death death;
int allow_isolated;
size_t len;
uint16_t name[0]; //name则是服务的名称。
};
Parcel
Parcel是描述Binder在用户层服务代理端和服务端进行通信的信息的结构体。
status_t mError;
uint8_t* mData; // 数据
size_t mDataSize; // 数据大小
size_t mDataCapacity; // 数据容量
mutable size_t mDataPos; // 数据指针的当前位置
size_t* mObjects; // 对象在mData中的偏移地址
size_t mObjectsSize; // 对象个数
size_t mObjectsCapacity; // 对象的容量
数据传输
Android Binder框架实现之Parcel详解之read/writeStrongBinder实现
Parcel负责Binder通信过程中的数据打包,在写入数据的时候 可以通过growData自动扩容。
简单的数据直接写入到mData里,IBinder打包成flat_binder_object。
上面的链接,详细讲述了IBinder打包成flat_binder_object的过程。
IPCThreadState会在 writeTransactionData 中将mOut(Parcel)打包到binder_transaction_data结构体中。然后在talkWithDriver中讲数据通过 ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr(binder_write_read)) 发送到驱动层
Parcel可以传递各种类型的数据对象:
基本数据类型:借助Parcel的方法writePrimitives将基本数据类型从用户空间(源进程)copy到kernel空间(Binder驱动中)再写回用户空间(目标进程,binder驱动负责寻找目标进程)
复杂对象将经过序列化的数据借助Parcel的方法writeParcelable()/writeSerializable()从用户空间(源进程)copy到kernel空间(Binder驱动中)再写回用户空间(目标进程,binder驱动负责寻找目标进程),然后再反序列化
大型数据(文件描述符):通过Parcel的方法writeFileDescriptor通过Binder传递匿名共享内存 (Ashmem)的FileDescriptor从而达到传递匿名共享内存的方式,即传递的是FileDescriptor而并不是真的是大数据,参见博客Android 匿名共享内存的使用
IBinder对象:通过Parcel的方法writeStrongBinder方法,然后经由Kernel binder驱动专门处理来完成IBinder的传递
APP的binder线程的如何启动的?
BootLoader-》Linux kernel—》Android init—》service_manager——》Zygote——》system_server
Android APP进程都是由Zygote进程孵化出来的。常见场景:点击桌面icon启动APP,或者startActivity启动一个新进程里面的Activity,最终都会由AMS去调用Process.start()方法去向Zygote进程发送请求,让Zygote去fork一个新进程,Zygote收到请求后会调用Zygote.forkAndSpecialize()来fork出新进程,之后会通过RuntimeInit.nativeZygoteInit来初始化Andriod APP运行需要的一些环境,而binder线程就是在这个时候新建启动的
RuntimeInit.nativeZygoteInit, 该方法经过 JNI 映射,最终会调用到 app_main.cpp 中的 onZygoteInit。
/base/cmds/app_process/app_main.cpp
virtual void onZygoteInit()
{
sp<ProcessState> proc = ProcessState::self();
//这里会创建Binder的主线程。
proc->startThreadPool();
}
ProcessState::self()函数会调用open()打开/dev/binder设备,这个时候Client就能通过Binder进行远程通信;其次,proc->startThreadPool()负责新建一个binder线程,监听Binder设备,这样进程就具备了作为Binder服务端的资格。每个APP的进程都会通过onZygoteInit打开Binder,既能作为Client,也能作为Server,这就是Android进程天然支持Binder通信的原因。
Binder 线程的创建流程图:
每次由 Zygote fork 出新进程的过程中,伴随着创建 Binder 线程池,调用 spawnPooledThread 来创建 Binder 主线程。 当线程执行 binderthreadread 的过程中,发现当前没有空闲线程,没有请求创建线程,且没有达到上限,则创建新的 Binder 线程。
Binder 系统中可分为 3 类 Binder 线程:
- Binder 主线程:进程创建过程会调用 startThreadPool() 过程中再进入 spawnPooledThread(true),来创建 Binder 主线程。 编号从 1 开始,也就是意味着 Binder 主线程名为 binder1,并且主线程是不会退出的。
- Binder 普通线程:是由 Binder Driver 来根据是否有空闲的 Binder 线程来决定是否创建 Binder 线程,回调spawnPooledThread(false) ,isMain=false, 该线程名格式为 binderx。
- Binder 其他线程:其他线程是指并没有调用 spawnPooledThread 方法,而是直接调用 IPC.joinThreadPool(),将当前线程直接加入 Binder 线程队列。 例如: mediaserver 和 servicemanager 的主线程都是 Binder 线程,但 system_server 的主线程并非 Binder 线程。
services_manager进程的启动
以下参考自:Android Binder框架实现之servicemanager守护进程
Kernel启动加载完驱动之后,会启动Android的init