跟面试官讲Binder(一)

面试官太忙了,过了好久之后,才想起我来,于是一个电话又把我叫过来了。
他说,想了解多一点关于Binder的知识。
心中一怔,其实对于Binder所知的其实也不多,但想着人家都问了,不随便说一点,这脸该往哪放呢?
琢磨一番,想着上一次讲的时候,有提到Binder中的通信的双方,一个叫server,一个叫client。
也就是说, Binder使用Client-Server的通信模式。
其中一个进程作为Server,多个进程作为Client,跨进程向Server获取服务。
我们可以联想到Web开发中的服务器设置,怎么知道是向哪个服务器中的哪个应用程序发送请求呢?
一个是ip地址,一个是端口号,如果服务器中有多个应用,我们还需要知道具体的应用名称呢,对吧。
简而言之,作为服务器,必须有确定的访问点或者地址来接受请求,比如我们上次说过了ServiceManager的句柄永远是0,这就是一个确定的访问点。

既然知道了对方在哪,Good,我们可以来通信了,那么问题来了,怎么通信?
跟英国人,美国人讲话,我们知道我们可以用英语,跟国人讲话,我们可以用普通话,跟家乡人,我们可以讲方言。
在网络上,这就是协议,规定了通信的规范和方式,比如 HTTP协议,我知道你用get就是想获得信息,用post就是想推送信息等。
很显然,在Binder机制中, 肯定也存在这样的协议和规范,来进行数据的通信。

上一篇文章中,我们讲到,在ServiceManager进程还不叫ServiceManager的时候,是通过  BINDER_SET_CONTEXT_MGR 命令来让其成为一个人人都要依靠的ServiceManager的。
BINDER_SET_CONTEXT_MGR就是Binder机制中规范的的内容之一。
Binder制定Command-Reply协议,通过ioctl命令来跟驱动互动,告诉其什么样的命令去对数据进行怎么样的操作,如下:

#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)
#define BINDER_THREAD_EXIT _IOW('b', 8, int)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)

如我们讲过的  BINDER_SET_CONTEXT_MGR ,还有很重要的 BINDER_WRITE_READ 命令大家都知道,肯定是关于读写的方面的命令的。
BINDER_WRIT_READ:往驱动写数据或者从驱动读数据,先写后读。单独写或者单独读,都是异步请求,如果同时有写读,则会是同步请求。
BINDER_SET_MAX_THREADS:告知驱动,接受方线程池的最大容量
BINDER_SET_CONTEXT_MGR:将当前进程设置为ServiceManager。在系统中只能有一个ServiceManager进程。
BINDER_THREAD_EXIT:通知驱动当前进程已退出。
BINDER_VERSION:获取当前驱动版本。
BINDER_WRITE_READ协议的格式(命令+数据)写

而对应的ioctl命令,主要以BC和BR开头,分别表示命令和返回结果的意思。
BinderCommand - BC
      BC_TRANSACTION = _IOW_BAD('c', 0, struct binder_transaction_data),
      BC_REPLY = _IOW_BAD('c', 1, struct binder_transaction_data),
      BC_REGISTER_LOOPER = _IO('c', 11),
      BC_ENTER_LOOPER = _IO('c', 12),
      BC_EXIT_LOOPER = _IO('c', 13),
BinderReply        - BR
      BR_ERROR = _IOR_BAD('r', 0, int),
      BR_OK = _IO('r', 1),
      BR_SPAWN_LOOPER = _IO('r', 13),
      BR_TRANSACTION = _IOR_BAD('r', 2, struct binder_transaction_data),
      BR_REPLY = _IOR_BAD('r', 3, struct binder_transaction_data),
      BR_TRANSACTION_COMPLETE = _IO('r', 6),

上面几个命令大概的作用如下:
往驱动写数据:
BC_TRANSACTION/ BC_REPLY等应该就是跟驱动进行数据交互的
BC_REGISTER_LOOP由Server方通知驱动线程池中已创建了一个新的线。
BC_ENTER_LOOP通知驱动此线程已经进入LOOP循环,能够接收数据。
BC_EXIT_LOOP通知驱动此线程已经退出LOOP循环,不再接收数据。

从驱动中读的数据格式
BR_NOOP
BR_ERROR / BR_OK
BR_SPAWN_LOOPER:驱动告知接收方没线程了,需要再创建,或者不再接收数据。
BR_TRANSACTION / BR_REPLY :对应于 BC_TRANSACTION / BC_REPLY
BR_TRANSACTION_COMPLETE,无论成功与否,驱动都会告知发送方的结果。

当然,如果想再了解得具体点,深入点,就需要好好地再看一下源代码了。此时,我偷偷看了一下面试官的表情,真让人敬畏啊。

我们可以看到其参数有 binder_transaction_data ,对吧,这肯定就是我们从用户空间写到内核空间,由驱动找到相对应的Server,处理好之后,返回来的结果呀。
下面就是binder_transaction_data的结构。
struct binder_transaction_data {
union {
size_t handle;
void *ptr;
} target;
void *cookie;
unsigned int code;

unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size;
size_t offsets_size;

union {
struct {

const void *buffer;

const void *offsets;
} ptr;
uint8_t buf[8];
} data;
};

在上面,我们可以看到一个union结构,一个是handle句柄, 一个是 *ptr指针。
在上一次面试中,已经讲过了,不同的进程间的引用叫句柄,相同进程中的引用,则称之为指针,也就是对进程空间中虚拟地址的引用。
当Client向Server发送请求的时候,它只知道Server是在另一个进程中,肯定没办法知道其对应的地址引用,它知道的只是ServiceManager告诉它的一个句柄而已。
而当驱动接到这个请求的时候,因为驱动是知道这个句柄对应的在另外一个进程(Server)中的引用的。所以会直接将handle替换成*ptr,让Server进程可以直接拿此*ptr来使用。

面试官估计听得有点不是很明白,为什么驱动会知道handle跟*ptr指针是互相对应的呢?
我深深觉得,我又发散了。。。收回来!
面试官的智慧是无穷的,他知道我上面讲了这么多,其实无非就是讲了两件事:
1)Binder是CS架构的,有对应的协议, 其中定义了众多的命令和数据结构来在不同的进程间传递数据。
2)在不同的进程间传递数据,是通过驱动的ioctl命令来对数据进行读写操作,也就是说,很多数据是在驱动中交互的。
但是,问题又来了,那到底整个过程是怎么样的呢,能不能具体点呢?

面试官好像要发火了,嗯,仔细再想想,怎么讲好?
Server和Client,那么肯定是先要有Server,才会有所谓的Client来请求的。
不对,之前应该先有ServiceManager的。
对,但是ServiceManager也是一个Server。
事情应该是这样的:
1)Server启动了,他要创建一个Binder实体,它的句柄是0,比如BpBinder(0),当设置了 BINDER_SET_CONTEXT_MGR,驱动收到这个命令,它就知道是要将当前进程设置为ServiceManager,于是它就会当前这个Binder实体创建一个Binder节点(BinderNode),它在这里记录了 0 -> ServiceManager这样的mapping。
2)另一个Server启动了,它也会创建一个Binder实体,名字叫 XXXManagerService吧,但是它的句柄就不会是0了,是什么呢?不知道,当它这个Binder实体放到驱动中,驱动同时也会为其创建一个Binder节点,并且为其随机创建一个句柄,比如就叫 1 吧。
于是我们就知道在驱动中, 1 ->  XXXManagerService的引用。
它进入驱动是为了寻找句柄0,去注册自己,也就是addService。
当然,驱动就会找到句柄0,发现其对应的是ServiceManager,就会将对应的数据,让ServiceManager来进行处理,这当然利用的就是内核空间的内存了。
在ServiceManager中,有一个服务列表svclist,保存了不同的服务名称对应的句柄,比如ServiceManager就会在自己的空间中保存这样一个对应  1-  > “ XXXManagerService”。
OK,到这里,XXXManagerService就将自己注册到ServiceManager中了。
3)有一个Client也启动了,它要找XXXManagerService请求点资源,但是它只知道XXXManagerService的名字,而不知道其具体的句柄,不同进程之间,咫尺就是天涯啊。但是它知道有个叫ServiceManager的东西,它可能知道XXXManagerService在哪里,刚好,它知道它句柄就是0,于是它就去驱动中找一个句柄为0的,找到了,跟他通信,说它要找XXXManagerService, 也就是findService。
刚好XXXManagerService注册了,ServiceManager就将其句柄传给了Client,现在Client终于知道XXXManagerService的地址(句柄)了,于是它就又重新包装好数据,这次的句柄就不是0了,而是 1 了,又一头扎进驱动了,继续做该做的事情。

这样说得好像挺形象的,面试官好像也听明白了,表情挺精彩的。

好吧,就再给个图吧,希望能够给面试官点印象分。

跟面试官讲Binder(一)_第1张图片

面试官看完之后,依然面无表情,我好捉急,这到底是怎么样啊?结合上面讲的,这个图应该还是可以理解的吧,可能是画功太烂了。

你可能感兴趣的:(android,原理,Binder,ServiceManager)