Android Binder原理从开始到放弃

Binder是Android系统IPC通信的一块基石,不管是AMS、WMS都可以看到Binder 的身影,如果搞不清楚的话,看Android源码的时候就会搞不清,比如为什么需要各种代理?怎么突然就跳跃了?

Binder内部细节太多了,然后个人只想搞清楚整体的机制而又不想深究内部的细节。不过一些东西又必须要弄清楚,比如IBinder、BBinder、BPBinder、Binder Proxy、ServiceManager等,所以尽可能以流程图的形式帮助理解,但是也包含一些关键数据结构。

同时,以下是个人学习binder的一个记录,有很多不足的地方,望谅解。

1. IPC机制

IPC(Inter-Process Communication), 进程之间相互通信的机制,目前 Android支持的一些IPC的手段有:Bundle(四大组件通信)、文件共享、AIDL、Messneger、ContentProvider、Socket。

2. Binder的优势

Client-Server 结构
安全性高,带有调用方的Pid、Uid
传输性能高,利用匿名共享内存,数据拷贝只要一次,

参考:https://www.zhihu.com/question/39440766/answer/89210950

3. Binder的使用:AIDL

在Android中,可以很方便的使用AIDL和Service来建立一个Binder跨进程通信,文章希望可以从AIDL入手,从Binder的应用,慢慢往内部实现层层剖析,加深对Binder的理解。

定义一个AIDL接口:

interface IPlusService {
    int add(int a, int b);
}

IPlusService有一个add方法,相加获取结果,我们需要在A进程调用B进程的add服务,并且获取得到结果。

在这里我们把服务的调用方称为客户端,就是A;服务的提供方称为服务端,就是B。

使用Android Studio的build,默认会帮助我们构造一个文件IPlusService.java,打开看一下:

public interface IPlusService extends IInterface {
	
	// Stub供服务端实现
	public static abstract class Stub extends Binder implements IPlusService {
		// binder的唯一标识
		private static final String DESCRIPTOR = "com.jscheng.sweather.IPlusService";

		public Stub() {
			this.attachInterface(this, DESCRIPTOR);
		}
		
		// 提供给客户端:将IBinder转化成IPlusService接口
		public static IPlusService asInterface(IBinder obj) {
			if ((obj==null)) {
				return null;
			}
			// 如果是客户端跟服务端同一个进程,直接返回server端的binder对象
			IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin!=null)&&(iin instanceof IPlusService))) {
				return ((IPlusService)iin);
			}
			// 若不同进程,则生成一个binder的代理
			return new IPlusService.Stub.Proxy(obj);
		}
	
		// 底层对应BBinder
		@Override 
		public IBinder asBinder() {
			return this;
		}
		
		// 运行在服务端:客户端调用transact(),会引发服务端onTransact()
		// code 表示客户端请求方法标志
		// data 表示参数
		// reply 表示写入返回值
		// 返回值:客户端请求结果,可以做权限控制
		@Override
		public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
			switch (code) {
				case INTERFACE_TRANSACTION: 
					reply.writeString(DESCRIPTOR);
					return true;
				case TRANSACTION_add:
					data.enforceInterface(DESCRIPTOR);
					// 从Parcel读取客户端传过来的数据
					int _arg0 = data.readInt();
					int _arg1 = _arg1 = data.readInt();
					// 调用服务端的add()
					int _result = this.add(_arg0, _arg1);
					reply.writeNoException();
					// 写入返回给客户端的数据
					reply.writeInt(_result);
					return true;
			}
			return super.onTransact(code, data, reply, flags);
		}
		
		// 客户端:服务的代理类
		private static class Proxy implements IPlusService {
			private IBinder mRemote;
			Proxy(IBinder remote) {
				mRemote = remote;
			}
			
			// 底层对应 BinderProxy
			@Override 
			public IBinder asBinder() {
				return mRemote;
			}
		
			public String getInterfaceDescriptor() {
				return DESCRIPTOR;
			}

			// 客户端:打包数据,调用代理binder的transact(),会引发服务端transact()
			@Override 
			public int add(int a, int b) throws RemoteException {
				Parcel _data = Parcel.obtain();
				Parcel _reply = Parcel.obtain();
				int _result;
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					_data.writeInt(a);
					_data.writeInt(b);
					// 代理类,调用transact()
					mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
					_reply.readException();
					_result = _reply.readInt();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}
		}
		static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0);
	}
	// 真正实现服务的地方
	public int add(int a, int b) throws RemoteException;
}

在Activity中,作为binder的客户端,我们这样使用:

private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binderProxy) {
            try {
                // 将Service传过来的IBinder转化成IPlusService
                IPlusService plusServiceProxy = IPlusService.Stub.asInterface(binderProxy);
                // 调用add()并同步得到结果
                int result = plusServiceProxy.add(1, 2);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
		...
    };

而在Service端,作为Binder的服务端实现起来也很方便,记得要在Manifest文件中,给MainService的声明加上 process=":remote",表示当前的Service启动在另一个进程。

public class MainService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return new PlusServiceImpl();
    }
    public class PlusServiceImpl extends IPlusService.Stub {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }
    }
}

AIDL的使用起来很简单,因为工具帮我们生成了代码框架,我们只需要重写一下服务端的接口和客户端的调用,中间的传输细节基本透明化了。

server-client

整个IPC的调用流程:

  1. 客户端获取IBinder接口,即Binder的代理对象,调用add服务。

  2. 客户端会将参数打包成parcel对象,Binder的代理对象会调用transact(),同时会阻塞当前线程,等待transact()结束,返回结果。

  3. 服务端会在onTransact()收到调用方的调用,通过code知道调用的是add服务,通过传过来的parcel解析出参数,最后返回服务结果给客户端。

从上面的Binder 的使用流程我们可以知道几个事情:

  1. 服务端和客户端是两个进程,客户端调用transact(),服务端实现onTransact()
  2. Binder的调用,在客户端看来是同步的,transact()从调用到获取结果的过程。
  3. Binder的被调用,在服务端看来是异步,支持多个客户端调用,服务端底层是线程池。所以可能需要注意同步的问题。
  4. 如果服务端和客户端在同一个进程,是不会发生Binder通信的,而是在asInterface返回服务端的对象。
  5. onTransact可以做权限验证,拒绝调用。
  6. 客户端在调用add的时候,内部是调用transact,在等待回应的时候将会被挂起,所以若是耗时操作的话需要开启子线程。
Parcelable 和 Parcel

Parcelable是一个接口、用来实现序列化,实现了Parcelable的类就可以在进程间通过binder传输,比如Intent;

Parcel用于在进程间传递数据,在内存上序列和反序列化,实现Parcelable的类可以调用writeToParcel()把Parcelable数据写入到Parcel中,调用createFromParcel读取数据。

参考:https://www.jianshu.com/p/f5e103674953

IInterface、IBinder、Binder、BinderProxy、BBinder、BPBinder

IBinder表示具备跨进程传输的能力,只要实现了IBinder接口可以进行跨进程传递这个对象,由驱动底层支持的。

IInterface对应的是AIDL声明的接口,表示提供怎么样的服务。

Binder类代表Binder本地对象,继承自IBinder,具有跨进程传输的能力。

BinderProxy类是Binder类的内部类,代表远程进程的Binder对象的代理,继承自IBinder,具有跨进程传输的能力。

跨进程通信中,Binder驱动会自动抓换Binder和BinderProxy。

public interface IInterface {
    public IBinder asBinder();
}

声明的binder service接口必须继承自IInterface,它提供了将服务或者服务代理类转为IBinder类型

public interface IBinder {
	public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
        throws RemoteException;
}

IBinder接口是提供远程调用的接口,描述了与远程对象进行交互的抽象协议。如果我们需要定制一个服务,建议继承Binder类,而不是直接实现这个接口。

public class Binder implements IBinder {
    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
        int flags) throws RemoteException {
        ...
        boolean r = onTransact(code, data, reply, flags);
        ...
    }

    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
        int flags) throws RemoteException {

    }
}

Binder 实现了IBinder接口,是真正的binder对象,内部有两个方法,transact()和onTransact(),其实transact()内部也只是转化调用了onTransact()

public class BinderProxy implements IBinder {
    public boolean transact(int code, Parcel data, Parcel reply, int flags) 
    	throws RemoteException {
    }
}

BinderProxy 是 Binder的内部类,同时也实现了IBinder接口,它表示远程Binder 的本地代理。
这里我们把client端持有的是Binder的本地代理BinderProxy,调用了BinderProxy.transact(),这时候远程server的Binder.transact()是响应了Client发出的远程调用,调用onTransact()处理任务。

也许你会很奇怪,这里怎么突然冒出了个BinderProxy?

因为Binder在客户端和服务端的表现是不一样的。

在server端的IPlusService.Stub的asBinder()返回的IBinder,底层jni对应的是BBinder对象。

而在client端的IPluService.Stub.Proxy的asBinder()返回的mRemote,其实是BinderProxy,它是由底层的jni BpBinder创建的一个Java服务对象,并保存在BpBinder中,一个BpBinder关联多个BinderProxy。

下面就更奇怪了,怎么突然又出现了BBinder和BpBinder了呢?

BBinder和BpBinder都是IBinder的实现类,在server端表现为BBinder,是真正的binder,而在client端是binder的引用对象BpBinder,为了提供给上层使用生成了代理类BinderProxy,这样client端通过引用对象就可以通知到远程的server端了。

在这里插入图片描述

小结:

下面问题来了BpBinder怎么跟BBinder通信的呢?

4. Binder的总体概览

cs
BInder采用了CS的结构,Client和Server是存在于用户空间,Client和Server通信实现是由Binder驱动在内核的实现的。

从Binder组件中,包含Client、Server、ServiceManager以及binder驱动。

1. ServiceManager

其中ServiceManager负责管理系统中的各种服务,比如Client需要某个服务,可以通过ServiceManager查询获取服务,Server也可以向ServiceManager注册服务。ServiceManager是Android进程间通信机制Binder的守护进程。

ServiceManager也是在一个独立的进程中,那么Client进程或者Server进程怎么跟ServiceManager通信呢?其实也是利用了binder机制。

ServiceManager进程在启动的时候,调用Binder驱动时,使用使BINDER_SET_CONTEXXT_MGR命令将自己注册成ServiceManager时,Binder驱动会自动创建Binder实体。

即使ServiceManager在驱动中注册了binder实体,那其他client怎么拿到他的binder的引用呢?因为从上面的流程来看,server端有binder实体BBinder,client端需要拿到BpBinder,就可以实现通信。

这里有一个机巧的设置,client端中handle值为0的binder引用(BpBinder)就是指向ServiceManager,这样一来client端就直接拿到BpBinder。

2.Server向ServiceManager注册服务

Server 利用handle值为0的引用向ServiceManager,注册Binder的引用。ServiceManager在表中增加Service的名字和Binder 的引用。

3.Client从ServiceManager中获得Service

Client也利用了handle值为0的引用向ServiceManager请求访问某个Service。ServiceManager在查找表中找到Binder的引用打包发送给Client。这样Clinet就拿到了对应服务的BpBinder。

在这里插入图片描述

小结

ServerManager做为守护进程,在启动的时候注册成为ServerManager。
Server通过ServerManager注册服务。
Client通过ServerManger获取得到服务。

5. 重要的数据结构

其实,以上还是没有讲述到BBinder和BpBinder怎么实现通信的。果然还是要从内核驱动的视角出发去理解。

binder_proc

struct binder_proc {
    //上述全局hash表中一个节点,用以标记该进程
    struct hlist_node proc_node;
    // 进程组ID
    int pid;
    // 任务控制模块 
    struct task_struct *tsk; 
    // 文件结构体数组
    struct files_struct *files;

    /**  Binder线程池每一个Binder进程都有一个线程池,由Binder驱动来维护,Binder线程池中所有线程由一个红黑树来组织,RB树以线程ID为关键字  */
    //上述红黑树的根节点
    struct rb_root threads;
        
    /** 一系列Binder实体对象(binder_node)和Binder引用对象(binder_ref) */
    /** 在用户控件:运行在Server端称为Binder本地对象,运行在Client端称为Binder代理对象*/
   /**  在内核空间:Binder实体对象用来描述Binder本地对象,Binder引用对象来描述Binder代理对象 */
    
    // Binder实体对象列表(RB树),关键字 ptr
    struct rb_root nodes;
    // Binder引用对象,关键字  desc
    struct rb_root refs_by_desc;
    // Binder引用对象,关键字  node
    struct rb_root refs_by_node;
    // 这里有两个引用对象,是为了方便快速查找 
         
     /**  进程可以调用ioctl注册线程到Binder驱动程序中,当线程池中没有足够空闲线程来处理事务时,Binder驱动可以主动要求进程注册更多的线程到Binder线程池中 */
    // Binder驱动程序最多可以请求进程注册线程的最大数量
    int max_threads;
    // Binder驱动每主动请求进程添加注册一个线程的时候,requested_threads+1
    int requested_threads;
    // 进程响应Binder要求后,requested_thread_started+1,request_threads-1,表示Binder已经主动请求注册的线程数目
    int requested_threads_started;

    // 进程当前空闲线程的数目
    int ready_threads;
    // 线程优先级,初始化为进程优先级
    long default_priority;
    //进程的整个虚拟地址空间
    struct mm_struct *vma_vm_mm;

    /** mmap 内核缓冲区*/
    // mmap——分配的内核缓冲区  用户控件地址(相较于buffer)
    struct vm_area_struct *vma; 
    // mmap——分配内核缓冲区,内核空间地址(相交于vma)  两者都是虚拟地址
    void *buffer;
    // mmap——buffer与vma之间的差值
    ptrdiff_t user_buffer_offset;

    /** buffer指向的内核缓冲区,被划分为很多小块进行性管理;这些小块保存在列表中,buffer就是列表的头部 */
    // 内核缓冲列表
    struct list_head buffers;
    // 空闲的内存缓冲区(红黑树)
    struct rb_root free_buffers;
    // 正在使用的内存缓冲区(红黑树)
    struct rb_root allocated_buffers;
    // 当前可用来保存异步事物数据的内核缓冲区大小
    size_t free_async_space;
    //  对应用于vma 、buffer虚拟机地址,这里是他们对应的物理页面
    struct page **pages;
    //  内核缓冲区大小
    size_t buffer_size;
    // 空闲内核缓冲区大小
    uint32_t buffer_free;

    /** 进程每接收到一个通信请求,Binder将其封装成一个工作项,保存在待处理队列to_do中  */
    //待处理队列
    struct list_head todo;
    // 等待队列,存放一些睡眠的空闲Binder线程
    wait_queue_head_t wait;
    // hash表,保存进程中可以延迟执行的工作项
    struct hlist_node deferred_work_node;
    // 延迟工作项的具体类型
    int deferred_work;
    
    //统计进程相关数据,具体参考binder_stats结构体
    struct binder_stats stats;
    // 表示 Binder驱动程序正在向进程发出死亡通知
    struct list_head delivered_death;
    // 用于debug
    struct dentry *debugfs_entry;
    // 连接 存储binder_node和binder_context_mgr_uid以及name
    struct binder_context *context;
};

binder_proc 表示进程信息,是内核驱动创建的,表示当前binder所在的进程。

这里我们需要关注的,binder_proc 包含了以下这些重要的信息:

  • Binder实体对象binder_node,表示当前进程提供了哪些binder服务;
  • Binder引用对象binder_ref,表示哪些进程持有当前进程的binder的引用;
  • Binder线程池,每一个进程都有一个Binder线程池,由Binder驱动来维护,线程接受驱动的命令;
  • todo工作项,驱动会将binder_transaction,加入todo列表,被唤醒之后的线程就可以取出处理;

binder_node

struct binder_node {
    // debug调试用的
    int debug_id;
    struct binder_work work;  //binder驱动中进程要处理的工作项 

    /** 每一个binder进程都由一个binder_proc来描述,binder进程内部所有Binder实体对象,
        由一个红黑树来进行组织(struct rb_root nodes)  ; rb_node 则对应nodes的一个节点 */
    union {
        //用于本节点连接红黑树
        struct rb_node rb_node;
        // 如果Binder 实体对象对应的进程死亡,销毁节点时需要将rb_node从红黑树中删除,
        //如果本节点还没有引用切断,则用dead_node将其隔离到另一个链表中,
        //直到通知所有进程切断与该节点的引用后,该节点才能销毁
        struct hlist_node dead_node;
    };

    // 指向该Binder实体对象 对应的进程,进程由binder_proc描述
    struct binder_proc *proc;
    // 该 Binder实体对象可能同时被多个Client组件引用,所有指向本实体对象的引用都
    //保存在这个hash队列中refs表示队列头部;这些引用可能隶属于不同进程,遍历该
    //hash表能够得到这些Client组件引用了这些对象
    struct hlist_head refs;
    //远程强引用计数
    int internal_strong_refs;  //实际上代表了一个binder_node与多少个binder_ref相关联
    //本地弱引用技数
    int local_weak_refs;
    //本地强引用计数
    int local_strong_refs;

    unsigned has_strong_ref:1;
    unsigned pending_strong_ref:1;
    unsigned has_weak_ref:1;
    unsigned pending_weak_ref:1;

     /** 用来描述用户控件中的一个Service组件 */
    // 描述用户控件的Service组件,对应Binder实体对应的Service在用户控件的(BBinder)的引用
    binder_uintptr_t ptr;
    // 描述用户空间的Service组件,Binder实体对应的Service在用户控件的本地Binder(BBinder)地址
    binder_uintptr_t cookie;

     // 异步事务处理,单独讲解
    unsigned has_async_transaction:1;
    struct list_head async_todo;
    // 表示该Binder实体对象能否接收含有该文件描述符的进程间通信数据。当一个进程向
    //另一个进程发送数据中包含文件描述符时,Binder会在目标进程中打开一个相同的文件
    //故设为accept_fds为0 可以防止源进程在目标进程中打开文件
    unsigned accept_fds:1;
     // 处理Binder请求的线程最低优先级
    unsigned min_priority:8;

};

binder_node 是binder实体,由驱动创建,对应着每个服务,内部包含进程信息binder_proc,还有binder的引用binder_ref。


binder_ref

struct binder_ref {
        //debug 调试用的
        int debug_id;
     
        /** binder_proc中使用红黑树(对应两个rb_root变量) 来存储器内部所有引用对象,
         *下面的rb_node则是红黑树中的节点
         */
        //Binder引用的宿主进程
        struct binder_proc *proc;
        //对应 refs_by_desc,以句柄desc索引  关联到binder_proc->refs_by_desc红黑树 
        struct rb_node rb_node_desc;
         //对应refs_by_node,以Binder实体对象地址作为关键字关联到binder_proc->refs_by_node红黑树
        struct rb_node rb_node_node;

        /** Client通过Binder访问Service时,仅需指定一个句柄,Binder通过该desc找到对应的binder_ref,
         *  再根据该binder_ref中的node变量得到binder_node(实体对象),进而找到对应的Service组件
         */
        // 对应Binder实体对象中(hlist_head) refs引用对象队列中的一个节点
        struct hlist_node node_entry;
        // 引用对象所指向的Binder实体对象
        struct binder_node *node;
        // Binder引用的句柄值,Binder驱动为binder驱动引用分配一个唯一的int型整数(进程范围内唯一)
        // ,通过该值可以在binder_proc->refs_by_desc中找到Binder引用,进而可以找到Binder引用对应的Binder实体
        uint32_t desc;

        // 强引用 计数
        int strong;
        // 弱引用 计数
        int weak;
      
        //  表示Service组件接受到死亡通知
        struct binder_ref_death *death;
};

Binder的引用对象,每一个Clinet在驱动中都有一个binder_ref和他对应,内部有对应的进程信息binder_proc和binder_node信息。

client端拿着BpBinder引用对象,在驱动层对应着binder_ref,就可以找到binder_proc和binder_node信息,找到指定的进程可以实现交互。


binder_buffer

struct binder_buffer {
        //entry对应内核缓冲区列表的buffers(内核缓冲区列表)
        struct list_head entry; /* free and allocated entries by address */
        //结合free,如果free=1,则rb_node对应free_buffers中一个节点(内核缓冲区)
        //如果free!=1,则对应allocated_buffers中的一个节点
        struct rb_node rb_node; /* free entry by size or allocated entry */
                /* by address */
        unsigned free:1;

         /**  Binder将事务数据保存到一个内核缓冲区(binder_transaction.buffer),然后交由Binder
          * 实体对象(target_node) 处理,而target_node会将缓冲区的内容交给对应的Service组件
          * (proc) 来处理,Service组件处理完事务后,若allow_user_free=1,则请求Binder释放该
          * 内核缓冲区
          */
        unsigned allow_user_free:1;
         // 描述一个内核缓冲区正在交给那个事务transaction,用以中转请求和返回结果
        struct binder_transaction *transaction;
         // 描述该缓冲区正在被那个Binder实体对象使用
        struct binder_node *target_node;

        //表示事务时异步的;异步事务的内核缓冲区大小是受限的,这样可以保证事务可以优先放到缓冲区
        unsigned async_transaction:1;
         //调试专用
        unsigned debug_id:29;

        /** 
         *  存储通信数据,通信数据中有两种类型数据:普通数据与Binder对象
         *  在数据缓冲区最后,有一个偏移数组,记录数据缓冲区中每一个Binder
         *  对象在缓冲区的偏移地址
         */
        //  数据缓冲区大小
        size_t data_size;
        // 偏移数组的大小(其实也是偏移位置)
        size_t offsets_size;
        // 用以保存通信数据,数据缓冲区,大小可变
        uint8_t data[0];
        // 额外缓冲区大小
        size_t extra_buffers_size;
};

进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
进程间,内核空间的数据可共享,所以内核空间 = 可共享空间

binder利用mmap()将用户空间映射到内核空间,使得数据的交互只需要一次。client在每次发起请求的时候会构造一个binder_buffer,放置在binder_transaction.buffer。


binder_thread

进程在启动时会创建一个binder线程池,并向其中注册一个Binder线程;驱动发现没有空闲binder线程时会向Server进程注册新的的binder线程。对于一个Server进程有一个最大Binder线程数限制,默认为16个binder线程。对于所有Client端进程的binder请求都是交由Server端进程的binder线程来处理的。


binder_work

一次进程请求的工作,会被封装在transaction里面,放置在对应进程的todo中。


binder_transaction

struct binder_transaction {
        //调试调用
        int debug_id;
        // 用来描述的处理的工作事项,这里会将type设置为BINDER_WORK_TRANSACTION,具体结构看binder_work
        struct binder_work work;

        /**   源线程 */
        // 源线程,即发起事务的线程
        struct binder_thread *from;
        // 源线程的优先级
        long    priority;
        //源 线程的用户 ID
        kuid_t  sender_euid;

        /**  目标线程*/
        //  目标进程:处理该事务的进程
        struct binder_proc *to_proc;
        // 目标线程:处理该事务的线程
        struct binder_thread *to_thread;
   
         // 表示另一个事务要依赖事务(不一定要在同一个线程中)
        struct binder_transaction *from_parent;
         // 目标线程下一个需要处理的事务
        struct binder_transaction *to_parent;

        // 标志事务是同步/异步;设为1表示同步事务,需要等待对方回复;设为0异步
        unsigned need_reply:1;
        /* unsigned is_dead:1; */   /* not used at the moment */
     
        /* 参考binder_buffer中解释,指向Binder为该事务分配内核缓冲区
         *  code与flag参见binder_transaction_data
         */
        struct binder_buffer *buffer;
        unsigned int    code;
        unsigned int    flags;

        /**  目标线程设置事务钱,Binder需要修改priority;修改前需要将线程原来的priority保存到
         *    saved_priority中,用以处理完事务回复到原来优先级
         *   优先级设置:目标现场处理事务时,优先级应不低于目标Serivce要求的线程优先级,也
         *   不低于源线程的优先级,故设为两者的较大值。
         */
        long    saved_priority;

};

transact() 调用在驱动里都会生产一个binder_transaction,放在对应server进程的todo队列里,然后唤醒server的binder线程来最终完成onTransact()调用。我们这里说的进程间通信其实就是发送端把binder_transaction,放到目标进程或其子线程的todo队列中,等目标进程或线程不断循环地从todo队列中取出数据并进行相应的操作。


binder_transaction_data

struct binder_transaction_data {
         union {
               /* target descriptor of command transaction */
               __u32    handle;
               /* target descriptor of return transaction */
               binder_uintptr_t ptr;
         } target;
      
         //Binder实体带有的附加数据
         binder_uintptr_t   cookie; /* target object cookie */
         // code是一个命令,描述了请求Binder对象执行的操作,表示要对目标对象请求的命令代码
         __u32      code;       /* transaction command */

         // 事务标志,详细看transaction_flag结构体
         __u32          flags;
         // 发起请求的进程PID
         pid_t      sender_pid;
         // 发起请求的进程UID
         uid_t      sender_euid;
         // data.buffer缓冲区的大小,命令的真正要传输的数据就保存data.buffer缓冲区
         binder_size_t  data_size;  /* number of bytes of data */
          // data.offsets缓冲区的大小
         binder_size_t  offsets_size;   /* number of bytes of offsets */
         union {
               struct {
                        /* transaction data */
                        binder_uintptr_t    buffer;
                        /* offsets from buffer to flat_binder_object structs */
                        binder_uintptr_t    offsets;
               } ptr;
               __u8 buf[8];
         } data;
};

binder_transaction_data 是进程一次传输的数据,data.buffer可能是普通数据或者是flat_binder_object(封装了binder引用或者binder对象)。binder驱动需要对此做处理。


flat_binder_object

struct flat_binder_object {
 unsigned long type;  
 unsigned long flags;
 union {
 void *binder;       //BBinder,可以找到对应的binder_node
 signed long handle; //BpBinder,可以找到对应的binder_ref
 };
 void *cookie;
};

在进程将传输过程中,binder对象会被封装到flat_binder_object进行传输。

驱动根据type来判断,如果是BINDER_TYPE_BINDER,表示传输的是binder实体,会在内核创建binder_node,并创建binder_ref的flat_binder_object给目标进程,因为给远程的只能是binder_ref;如果是BINDER_TYPE_HANDLE,表示传输的是binder引用。

总结

以上注释的资料来自:https://www.jianshu.com/p/5740a8447324

6. Binder的基本流程

这里不罗列Binder的大部分代码,都是以流程的形式讲述。

  1. binder驱动初始化

    1. 每个进程启动的时候,都会打开binder驱动
      在binder驱动中会创建对应的进程的binder_proc对象,并把当前进程等信息保存到binder_proc对象,并加入到全局链表
    2. 调用mmap()分配内存映射空间,驱动层创建binder_buffer,并放入当前binder_proc的buffers 链表
    3. 创建一个线程池(Binder主线程),负责处理Binder驱动发送上来的一些请求或返回值,线程的创建和销毁是在用户空间进行的,但是线程是受驱动层控制。可以说,这些线程就是真正负责干活的。
    4. 如果是ServerManager进程,驱动会创建binder_node,通过BINDER_SET_CONTEXT_MGR命令,成为ServerManager,ServiceManager进入循环,不断监听客户端的请求,没有请求时ioctl将阻塞,有请求到达是就分析请求类型,做出相应的处理
  2. Server怎么注册服务

    1. server 通过flat_binder_object(内部含有binder对象),经内核驱动时会创建一个binder_node,挂载到对应的进程的binder_proc;
    2. 内核会创建一个binder_ref,挂在binder_node下,同时提供给ServiceManager,注册完成;
    3. server 的线程会在binder_proc的wait队列上等待,等待binder_work。
  3. Client怎么调用服务

    1. 我们先假设client跟ServiceManager拿到了Server的BpBinder了,
    2. client调用transact()时候,在内核驱动会根据BpBinder指向的binder_ref,找到binder_node,创建了一个binder_transaction插入到对应server进程binder_prc的todo队列,唤醒server进程下的binder_thread。
    3. 同时将client的调用线程阻塞放在wait队列,等待结果
    4. server端的thread从binder_proc的todo列表取出,最终执行on_transact(),
    5. 处理完成以后,向内核发送命令,内核构造一个binder_transaction放在client调用线程的todo中,同时唤醒client调用线程。
    6. 被唤醒的client线程,处理todo的binder_transaction。

那么其实client同ServiceManager查询服务也是一样的道理,通过0号引用,默认拿到了ServiceManager的BpBinder了,后续的流程也是类似的。

总体架构

不足

本篇文章写得很凌乱,主要有两个原因:

  1. 个人对binder的了解不够深入;
  2. binder涉及的东西太多了,还有很多东西没有涉及到;

后续要再好好学习,重新整理。

参考

https://www.moyokoo.com/p/25/
http://gityuan.com/2015/11/28/binder-summary/
https://www.jianshu.com/p/3c71473e7305
https://www.cnblogs.com/everhad/p/6246551.html

  • 10
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值