查看原文
其他

Android底层:通熟易懂的分析binder--2. binder进程通信协议及“记录链路”结构体

我te man 牛晓伟
2024-08-24

前言




Android底层:通熟易懂分析binder:1.binder准备工作主要介绍了binder进程通信之前需要做的准备工作有哪些,既然binder准备工作做好了,那下一步就是通信了,在讲解通信之前,我希望先把通信的基础知识讲清楚,比如进程与driver层之间的通信协议是啥?binder_node,binder_ref,binder_thread,binder_proc这些东西到底都是啥?在通信过程中起啥作用?Binder,BinderProxy,BpBinder,BBinder又是啥?只有把这些基础知识了解清楚,对于后面的内容理解才能胸有成竹。



本篇内容




1. 从“表面”看什么是binder服务?,什么是binder服务引用?

2. 协议

    2.1 进程与driver层的通信协议

    2.2  "driver层代理进程"之间的通信协议

    2.3  binder通信协议总结

3. "记录链路"结构体

    3.1 binder_node

    3.2 binder_ref

    3.3 binder_thread

    3.4 binder_proc

    3.5 总结

4. 总结


会用到的词语:

client进程:binder进程通信的client端(使用binder服务)

server进程:binder进程通信的server端(提供binder服务)

driver层代理进程:driver层中其实是有一个结构体(binder_proc)与上层进程有一个对应关系的,这个东东我就起了个名字叫   driver层代理进程




01


从“表面”看binder服务/引用是啥?




从“表面”看是指我们从app开发的角度来看,这个角度是我们能看的着,并且经常用的着。来看一段代码


public interface IXXX extends android.os.IInterface {
/** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements IXXX {
private static final java.lang.String DESCRIPTOR = "...IXXX";
/** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); }
public static IXXX asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof IXXX))) { return ((IXXX) iin); } return new IXXX.Stub.Proxy(obj); }
@Override public android.os.IBinder asBinder() { return this; }
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_methodXX: { data.enforceInterface(descriptor); this.methodXX(arg); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } }
// 对方进程使用代理类 private static class Proxy implements IXXX {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) { mRemote = remote; }
@Override public android.os.IBinder asBinder() { return mRemote; }
public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; }
@Override public void methodXX(param) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((cb != null)) ? (cb.asBinder()) : (null))); mRemote.transact(Stub.TRANSACTION_methodXX, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }

}
static final int TRANSACTION_methodXX = ( android.os.IBinder.FIRST_CALL_TRANSACTION + 0); }
public void methodXX(param) throws android.os.RemoteException; }


上面的代码是android 的aidl工具解析aidl文件生成的一个java文件,整个文件很清晰,定义了


    1.   IXXX(这个接口定义了进程之间通信的协议)
    2.  Stub(继承了Binder类)
    3.  Proxy(实现了IXXX)

Stub

Stub这个类大家肯定不陌生,在Service组件中的onBind方法中会返回这个类的子类的对象(这个对象就是服务的提供者),Stub是定义在server进程中的,因此在java层可以理解为只要是继承了Binder类的类它就可以作为binder服务。


Proxy

Proxy这个类,从类名上就能看出它是在client进程中用到的,里面有一个很重要的属性 android.os.IBinder mRemote , 这个属性从名字上更能看出它是指代server进程的Stub的子类对象的,mRemote它具体的类型是BinderProxy


binder服务

java层中只要继承了Binder类就可以作为binder服务了,native层Binder对应的类是BBinder。咱们通常的用法是把Binder通过Service组件的onBind方法返回,Binder类的对象还可以在Activity或者其他代码中通过Parcel传递到driver层,作为binder进程通信的binder服务


binder服务引用

binder服务引用在java层是BinderProxy,在native层是BpBinder。BinderProxy的对象是由jni层生成的,它会指向native层的BpBinder,BpBinder有一个很重要的属性mHandle,后面会着中说它的作用,以及生成规则。


Binder与BinderProxy是对应关系, BBinder与BpBinder是对应关系,binder进程通信从表面上看实际是Binder与BinderProxy 或 BBinder与BpBinder之间的通信。


Binder,BinderProxy,BBinder,BpBinder都可以被写入Parcel中,传递到driver层。



02


协议




不管是socket通信还是web通信,它们都需要定义自己的协议,比如socket通信中,client与server端需要定义好协议,如code值都有啥,数据以啥开头以啥结尾,数据长度是多少。web通信的协议就很多了比如http,tcp,udp等等。那同理binder进程之间通信也需要定义自己的协议,因为binder进程通信分为:


1. 进程与driver层通信
2. “driver层代理进程”之间通信


那我们从这两方面来介绍下。


driver层代理进程:driver层中其实是有一个结构体(binder_proc)与上层进程有一个对应关系的,这个东东我就起了个名字叫 driver层代理进程


2.1 进程与driver层通信的协议


Android底层:通熟易懂分析binder:1.binder准备工作提到进程与driver层通信中最为频繁的结构体是**binder_write_read**,binder_write_read中的**write_buffer**是进程发送给driver层的数据,**read_buffer**是driver层发送给进程的数据。我们通过socket通信时,客户端和服务端传输的数据是需要定义一些协议,比如下面的协议

    cmd + 数据长度 + 数据


这样根据cmd就需要调用哪些功能了,其次在把数据拿出来进行处理。

同理,通过binder_write_read进行进程与driver层通信时,也需要定义一些协议,协议如下:

    cmd + 数据(binder_transaction_data)



    1.  进程发送给driver层的数据时,cmd是格式是 BC_XX ,以BC开头,

    2.  driver层返回给进程的数据格式是 BR_XX, 以BR开头


cmd


cmd有BR_TRANSACTION,BR_TRANSACTION_COMPLETE,BR_REPLY,BR_OK

BC_TRANSACTION,BC_REPLY,BC_FREE_BUFFER等,

其中BR_TRANSACTION,BR_REPLY,BC_TRANSACTION,BC_REPLY使用最多主要用来进程之间通信的。


数据


数据是存放在binder_transaction_data,这里的数据其实主要是上层传递的binder服务(BBinder)或binder服务引用(BpBinder),方法code值,方法参数,看下它的定义

struct binder_transaction_data { union { __u32 handle; binder_uintptr_t ptr; } target; binder_uintptr_t cookie; // code 调用的服务的方法对应的code值 __u32 code; __u32 flags; // 发送者的进程id 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; } ptr; __u8 buf[8]; } data;};

target 存放了 binder服务或者binder服务引用,里面的handle,ptr 分别对应BpBinderBBinder,target的主要作用就是在告诉driver层我要调用哪个服务


code 对应的是方法的code值

sender_pid 发送者的进程id

data_sizeoffsets_size 参数长度信息

data 就是具体的参数信息了。


2.2 "driver层代理进程"之间的通信协议


"driver层代理进程"之间的通信是binder进程通信的基础,每个"driver层代理进程"可以理解为代表者上层的一个进程。它们之间通信协议也是

      cmd + 数据


协议是被放在binder_work结构体中的,


cmd


cmd有BINDER_WORK_TRANSACTION,BINDER_WORK_TRANSACTION_COMPLETE,BINDER_WORK_NODE,BINDER_WORK_DEAD_BINDER等,它们都是以BINDER_WORK开头的。


数据


数据是存放在binder_transaction结构体中的,先看下它的结构,后面会着中介绍它

struct binder_transaction { int debug_id; struct binder_work work;  // from信息是用来进行回复给对方信息用的,使用在同步通信环节 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 */  // 数据信息,主要是传递的c参数 struct binder_buffer *buffer;  // 调用的方法的code值 unsigned int code; unsigned int flags; long priority; long saved_priority; kuid_t sender_euid;};


fromfrom_parentto_proc等信息是用来在driver层帮助确认通信的来源和去向的。

code 还是方法的对应的code值

binder_buffer 存放binder服务和方法的参数等信息,它的定义在下面



binder_threadbinder_proc后面会介绍,binder_buffer这个是主要用来存放关键的通信数据的,它的定义在下面

struct binder_buffer { struct list_head entry; /* free and allocated entries by address */ struct rb_node rb_node; /* free entry by size or allocated entry */ /* by address */ unsigned free:1;  // 是否允许上层释放空间 unsigned allow_user_free:1;  // 异步/同步 unsigned async_transaction:1; unsigned debug_id:29;
struct binder_transaction *transaction; // 目标node,会对应上层的binder服务 struct binder_node *target_node; // 主要是参数信息 size_t data_size; size_t offsets_size; uint8_t data[0];};


allow_user_free 是否允许上层释放占有的空间。

async_transaction 代表是同步还是异步通信。

target_node 指向binder服务

data_sizeoffsets_sizedata[0]  参数相关信息


2.3 binder通信协议总结


1. client请求server 协议cmd+数据 封包拆包过程


  1. client进程把BC_TRANSACTION + binder_transaction_databinder服务引用 handle,方法code,方法参数) 传递到driver层

  2. "driver层代理进程 clent"收到数据后把binder_transaction_data的数据进行拆包,把binder服务引用 handle,方法code,方法参数以及driver层的其他数据数据封包到binder_transaction中,把cmd为 BINDER_WORK_TRANSACTIONbinder_transaction放入binder_work中。

  3. “driver层代理进程 server”收到binder_work后(具体是怎么收到这个数据的后面的 “记录链路”结构体 会讲),把它拆包,把code,方法参数,binder服务(这时候handle已经转换为binder服务了) 放入 binder_transaction_data中。

  4. “driver层代理进程 server” 把 BR_TRANSACTION + binder_transaction_data 传递给 server进程,server进程在它自己的biner线程中 把 code,方法参数,binder服务 解析出来后开始执行binder服务方法。


        cmd转换过程 BC_TRANSACTION--->BINDER_WORK_TRANSACTION-          -->BR_TRANSACTION


2. server返回结果给client 协议cmd+数据 封包拆包过程


  1.  server进程把  BC_REPLY + binder_transaction_data返回结果) 传递到driver层

  2. "driver层代理进程 server"收到数据后把binder_transaction_data的数据进行拆包,把返回结果和driver层的一些数据封包到binder_transaction中,把cmd为BINDER_WORK_TRANSACTIONbinder_transaction封包到binder_work中。

  3. “driver层代理进程 client”收到binder_work后(具体是怎么收到这个数据的后面的 “记录链路”结构体 会讲),把它拆包,把返回结果 放入 binder_transaction_data中。

  4. “driver层代理进程 client” 把BR_REPLY + binder_transaction_data 传递给 client进程,client进程把返回结果解析出来

    cmd转换过程 BC_REPLY--->BINDER_WORK_TRANSACTION--->BR_REPLY






03


"记录链路"结构体



为什么要叫作  "记录链路"结构体, 我个人理解driver层的binder_proc,binder_node等结构体主要有两个关键作用:


  1.  记录上层进程中的信息,从而在driver层能够有信息与上层进程的信息对 应起来。

  2.  “链路”是指这些结构体为binder进程通信搭建了一条链路,那通信协议等可以在链路上传输了。


 

3.1 binder_node


3.1.1 binder_node结构体


binder_node记录了 进程中binder服务(Binder,BBinder) 的关键信息 ,,这样就可以与Binder,BBinder形成了对应关系,它的定义如下:

struct binder_node {int debug_id;struct binder_work work;union {struct rb_node rb_node;struct hlist_node dead_node; };// 所属进程相关信息struct binder_proc *proc;struct hlist_head refs;int internal_strong_refs;int local_weak_refs;int local_strong_refs;// 指向上层的binder服务binder_uintptr_t ptr;binder_uintptr_t cookie;    // 引用相关信息unsigned has_strong_ref:1;unsigned pending_strong_ref:1;unsigned has_weak_ref:1;unsigned pending_weak_ref:1;unsigned has_async_transaction:1;unsigned accept_fds:1;unsigned min_priority:8;struct list_head async_todo;};


has_strong_refhas_weak_ref等主要用来记录binder_node引用方面的信息的。


binder_node只在当前的“driver层代理进程”中存在,它封装的是Binder(java层),BBinder(native层)的信息




3.1.2 binder_node生成时机


先来看段代码

static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){
省略代码...
for (; offp < off_end; offp++) { struct flat_binder_object *fp;
省略代码...
// fp 存储着上层进程的binder实例,这个binder是Binder,BBinder,BinderProxy,BpBinder类型中的某一个实例 fp = (struct flat_binder_object *)(t->buffer->data + *offp); off_min = *offp + sizeof(struct flat_binder_object); switch (fp->type) { // 若当前是BINDER_TYPE_BINDER或者BINDER_TYPE_WEAK_BINDER,则能说明这个binder实例是binder服务(Binder,BBinder) case BINDER_TYPE_BINDER: case BINDER_TYPE_WEAK_BINDER: { struct binder_ref *ref; struct binder_node *node = binder_get_node(proc, fp->binder); // 若从当前的binder_proc中没有取到binder_node,则创建一个binder_node来封装上层进程的binder实例信息,并把它存放于binder_proc的nodes红黑树中 if (node == NULL) { node = binder_new_node(proc, fp->binder, fp->cookie);
省略代码...
}
// 从target_proc(binder_proc)中获取binder_ref,没有则依据binder_node创建binder_ref,并且把创建的binder_ref放入target_proc的refs红黑树中 ref = binder_get_ref_for_node(target_proc, node);
省略代码...
// 因为binder_ref是传递给 target_proc的,因此它是一个引用,它传递到 上层进程后的binder实例是(BinderProxy,BpBinder) if (fp->type == BINDER_TYPE_BINDER) fp->type = BINDER_TYPE_HANDLE; else fp->type = BINDER_TYPE_WEAK_HANDLE; // 把desc赋值给handle fp->handle = ref->desc;
省略代码...
} break; // 若当前是BINDER_TYPE_HANDLE或者BINDER_TYPE_WEAK_HANDLE,则能说明这个binder实例是binder服务引用(BinderProxy,BpBinder) case BINDER_TYPE_HANDLE: case BINDER_TYPE_WEAK_HANDLE: { struct binder_ref *ref = binder_get_ref(proc, fp->handle);
省略代码...
// 若target_proc就是当前进程,则需要把binder实例是binder服务引用类型的转为 binder服务类型 if (ref->node->proc == target_proc) { if (fp->type == BINDER_TYPE_HANDLE) fp->type = BINDER_TYPE_BINDER; else fp->type = BINDER_TYPE_WEAK_BINDER; fp->binder = ref->node->ptr; fp->cookie = ref->node->cookie; binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
省略代码...
} else { struct binder_ref *new_ref; // 从target_proc(binder_proc)中获取binder_ref,没有则依据binder_node创建binder_ref,并且把创建的binder_ref放入target_proc的refs红黑树中 new_ref = binder_get_ref_for_node(target_proc, ref->node); if (new_ref == NULL) { return_error = BR_FAILED_REPLY; goto err_binder_get_ref_for_node_failed; } fp->handle = new_ref->desc;
省略代码... } } break;
省略代码...
}
}


binder_transaction这个方法后续还会介绍,上面是摘取了其中一段代码,这段代码有以下几个作用:


  1. 把当前proc(binder_proc)的数据拷贝到target_proc,这也是一直说的binder进程通信数据只拷贝一次的地方。

  2. 若proc中的数据是binder服务(Binder,BBinder),

    2.1 则创建binder_node(封装了binder实例),把创建的binder_node存放在proc的nodes红黑树中。

    2.2 在target_proc中创建binder_ref(指向刚刚创建的binder_node),创建成功binder_ref后会返回一个desc(这个值就是binder服务引用的handle),在把binder_ref放入target_proc的refs_by_desc红黑树中

    2.3 把binder服务转换为binder服务引用(fp-type变为BINDER_TYPE_HANDLE),fp->handle = ref->desc,这样binder服务引用就会返回给target_proc对应的进程

  3. 若proc中的数据是binder服务引用(BinderProxy,BpBinder)

    3.1 若proc与target_proc相等,则把binder服务引用转为binder服务。

    3.2 否则,重复2.2--2.3过程


从上面的流程可以分析得知,当binder通信过程中,传递的参数数据中包含binder服务时就会创建binder_node。

3.2 binder_ref



3.2.1 binder_ref结构体



binder_ref记录了 **进程中binder服务引用(BinderProxy, BpBinder)** 的关键信息(其实就是handle),从而与BinderProxy,BpBinder形成对应关系,看下它的定义

struct binder_ref { /* Lookups needed: */ /* node + proc => ref (transaction) */ /* desc + proc => ref (transaction, inc/dec ref) */ /* node => refs + procs (proc exit) */ int debug_id; struct rb_node rb_node_desc; struct rb_node rb_node_node; struct hlist_node node_entry; // ref所属的进程 struct binder_proc *proc; // 所指向的binder_node struct binder_node *node; // desc就是上层的handle uint32_t desc; int strong; int weak; struct binder_ref_death *death;};


binder_ref是指向binder_node的引用,它存在于 使用binder服务的 “driver层代理进程”中。


desc就是BpBinder中的handle,因此在进行binder进程通信时,上层进程传递下来的BpBinder中的handle传递到driver层后,通过handle找到binder_ref,进而找到binder_node。关于查找过程后面会有讲解。


3.2.2 binder_ref创建时机


从上面 2.4.1.2 binder_node生成时机 可以得知,当两个binder_proc之间进行通信时通信参数数据包含binder服务或binder引用并且通信这两个binder_proc不相等,则会在目标binder_proc中创建binder_ref。binder_ref,及binder服务引用它们的创建都是一个被动的过程,binder服务引用是driver层创建成功后,把创建成功实例传给上层进程,因此BinderProxy它是被jni层代码初始化的。


3.3 binder_thread


3.3.1 binder_thread结构体


binder_thread与 进程中的binder线程(IPCThreadState) 是一一对应关系,binder线程是由ProcessState类孵化出来的,它的定义如下:

struct binder_thread { // 所属进程信息 struct binder_proc *proc; struct rb_node rb_node; int pid; int looper; struct binder_transaction *transaction_stack; struct list_head todo; uint32_t return_error; /* Write failed, return error code in read buf */ uint32_t return_error2; /* Write failed, return error code in read */ /* buffer. Used when sending a reply to a dead process that */ /* we are also waiting on */ wait_queue_head_t wait; struct binder_stats stats;};


looper的值是

enum { // driver层通知 进程启动binder线程,启动成功后进程返回这个值 BINDER_LOOPER_STATE_REGISTERED = 0x01, // ProcessState启动binder主线程成功后发送给driver层这个值 BINDER_LOOPER_STATE_ENTERED = 0x02, // 进程中的binder线程离开时,发送此值给driver层 BINDER_LOOPER_STATE_EXITED = 0x04, BINDER_LOOPER_STATE_INVALID = 0x08, // 内核线程处于等待状态,没有任何事务时 是这个值 BINDER_LOOPER_STATE_WAITING = 0x10, // 需要返回结果的looper BINDER_LOOPER_STATE_NEED_RETURN = 0x20};


之一,它根据 收到上层binder线程的状态值 来改变自己的状态值。


transaction_stack 主要用于binder进程同步通信时,等待binder服务的结果时,它的值是发送给binder服务时的binder_transaction。


todo 这个有点意思,它其实就是一个队列,主要存放binder_work,就是一个生产者/消费者模式,其他“driver层代理进程”往todo里面放binder_work,这个内核线程消费binder_work。


3.3.2 binder_thread创建时机


上层进程ProcessState孵化一个binder线程后,binder线程通过IPCThreadState与driver层通信后,就会在driver层创建binder_thread。


3.4 binder_proc


3.4.1 binder_proc结构体


binder_proc与 进程(ProcessState) 是一一对应关系,ProcessState是单例的,先看下它的定义:


struct binder_proc { struct hlist_node proc_node;  // binder_thread 红黑树 struct rb_root threads; // binder_node 红黑树 struct rb_root nodes; // binder_ref 红黑树 struct rb_root refs_by_desc; struct rb_root refs_by_node; // 上层进程id int pid;
省略代码...
struct page **pages; size_t buffer_size; uint32_t buffer_free;  // 存放binder_work队列 struct list_head todo; wait_queue_head_t wait; struct binder_stats stats; struct list_head delivered_death;  // 能开启的最大binder线程数,这个值是 ProcessState发送的 int max_threads; // 正在请求 ProcessState开启binder线程的 请求数量 int requested_threads; // 已经启动的binder线程数 int requested_threads_started; // 当前空闲的处于等待事务的binder线程数 int ready_threads; long default_priority; struct dentry *debugfs_entry;};


threadsnodesrefs_by_descrefs_by_node分别是binder_threadbinder_nodebinder_ref的红黑树,也就是binder_proc中存储了binder_threadbinder_nodebinder_ref等信息,只不过为了效率是以红黑树的形式存储的。


todo同binder_thread中的队列一样,是用来存储binder_work的。


max_threadsrequested_threadsrequested_threads_startedready_threads都是与binder线程有关的信息。


3.4.2 binder_proc创建时机



  在open binder driver后就会创建binder_proc。


3.5 总结



3.5.1 "记录链路"结构体是怎么样链路的


binder_proc,binder_node,binder_ref这几个结构体一起在binder进程通信过程中起一个“链路”的作用。我们来简单描述下这个“链路”过程(下面的流程分析都是基于client进程已经拿到了binder服务引用BinderProxy,关于怎么拿到它后面还会介绍):


  1. 进程client最终通过 ioctl(fd,BINDER_WRITE_READ,binder_write_read)把数据传递到driver层

  2. 通过fd把对应的binder_proc找到

  3. 从binder_write_read中拿到handle值

  4. 根据handle从binder_proc的refs红黑树中取出binder_ref

  5. 根据binder_ref取到binder_node

  6. 根据binder_node取到target_proc(binder_proc)

  7. 把binder_node,通信数据等放入target_proc的todo队列中中或者target_proc的target_thread的todo队列中

  8. 唤醒target_proc中处于等待的内核线程,从binder_transaction中拿出binder服务传递给 上层进程,这样在binder线程中调用binder服务的相应方法。



这样只要这个 "链路"打通了,那在“链路”上就可以传递数据了(协议)



04


总结




最后我用一张图来总结本篇内容


继续滑动看下一个
牛晓伟
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存