本文主要内容
- Binder驱动接口
- ServiceManager的发动
- Binder规划理念
- 总结
Binder驱动接口
Binder驱动一共有3个重要的接口:
- binder_open,翻开binder驱动
- binder_mmap,内存映射
- binder_ioctl,与binder驱动通讯
binder_mmap是整套机制的基础,经过内存映射,不同进程间有一块同享内存,得以实现IPC。
binder_ioctl,类似于文件读写办法,它承当了Binder驱动的大部分事务,与Binder驱动通讯。
看上图,只需求把进程A的内存空间仿制到内核空间中(copy from user),内核空间中有了进程A的东西,然后经过内存映射给进程B读取,所以说Binder通讯只需求一次内存仿制
关于以上3个办法,后续再补充具体阐明。
ServiceManager的发动
ServiceManager是十分重要的体系服务,它类似于网络世界中的DNS(请拜见进程能通讯——智能指针)。 它是在init.rc中发动的,代码位于/framework/native/cmds/servicemanager 目录中,sm发动代码如下:
//service_manager.c
int main(int argc, char** argv)
{
struct binder_state *bs;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = "/dev/binder";
}
//翻开驱动
bs = binder_open(driver, 128*1024);
//将自己设置成context manager人物,ServiceManager体系中肯定只有一个
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
//进入循环
binder_loop(bs, svcmgr_handler);
}
代码较类似于socket通讯server端代码,新建ServerSocket,发动死循环,等待连接。查看binder_open代码:
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
struct binder_state *bs;
bs = malloc(sizeof(*bs));
//真实地翻开binder驱动
bs->fd = open(driver, O_RDWR | O_CLOEXEC);
//内存映射
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
return bs;
}
上一章中说到的binder驱动三大办法中,现在现已呈现俩了,binder_open办法中翻开binder驱动,并且完成内存映射。
接下来看看binder_loop办法:
void binder_loop(struct binder_state *bs, binder_handler func)
{
for (;;) {
//调用ioctl,与binder驱动通讯,从驱动中读取数据,并存放在bwr指针中
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
//处理读取的指令
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
}
}
binder_loop中存在一个死循环,循环中不断从驱动中读取指令,并处理指令。接着看binder_parse办法:
int binder_parse(struct binder_state *bs, struct binder_io *bio,
uintptr_t ptr, size_t size, binder_handler func)
{
while (ptr < end) {
switch(cmd) {
case BR_TRANSACTION:
struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
if (func) {
//func是一个函数指针,在此调用函数并回来,此函数类似于onTransact
res = func(bs, txn, &msg, &reply);
if (txn->flags & TF_ONE_WAY) {
binder_free_buffer(bs, txn->data.ptr.buffer);
} else {
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}
}
ptr += sizeof(*txn);
break;
}
}
}
binder_parse办法中,依据cmd的值履行对应的办法,当客户端调用transact办法时,ServiceManager会回调func函数。func是一个函数指针,它实质上是:
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
//调用do_find_service,查找对应的service binder
handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
case SVC_MGR_ADD_SERVICE:
//增加service
do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid)
case SVC_MGR_LIST_SERVICES: {
//链表数据结构,回来节点
si = svclist;
while ((n-- > 0) && si)
si = si->next;
}
return 0;
}
所以,当service manager从binder驱动中读取指令,并依据不同的指令履行不同的办法,最终将成果回来给binder驱动。
service manager与binder交互的逻辑,十分类似于socket通讯,先翻开binder驱动,再调用mmap办法映射内存,最终发动死循环,调用ioctl办法从binder驱动中获取指令,依据指令调用对应办法,回来成果给binder驱动。
Binder规划理念
如果是咱们自己来规划binder,那么咱们该怎么做?
很明显,参照service manager,在server端,用户需求翻开binder驱动,履行内存映射,建议死循环,与binder驱动通讯等等。client端使用仍是需求履行以上进程。进程适当繁锁,必须要进行相应封装,以使用用户开发。
并且以上代码均是c或c++代码,android上层用户更多地使用java,需求给java用户供给更友爱的使用方式。
binder主要在两个方面进行封装:
- 封装与binder驱动交互的部分
- 使用代理,proxy
1、ProcessState和IPCThreadState
ProcessState,专门管理每个使用进程中的Binder操作,包含与翻开驱动、内存映射等等,每个进程中只需求翻开一次驱动,履行一次内存映射即可。
使用进程中肯定会有多个线程,每个线程都可能需求进行进程间通讯,负责这个作业的便是IPCThreadState。实际上ProcessState仅仅负责翻开驱动、内存映射等,而与binder驱动实质进行通讯的是IPCThreadState。
2、proxy
binder中proxy无处不在,用户不便直接与service manager打交道,尤其是在java端,所以规划出proxy,然后更便利客户调用。
总结
关于binder机制,其实以前也曾写过一篇文章,Android binder机制(native服务篇),关于客户端部分如何与server端交互,请参照上文,今天再次阅读《深化理解android内核规划思维》,有了新的感悟,果然是常看常新。
有时往往代码特别多,不要害怕,宏大的代码,无数的文件往往仅仅作者在掩盖一些具体细节,在封装,便利用户调用而已,而繁琐的背面仅仅那一点点特别简单的道理,比方binder机制,中心原理和socket通讯几乎没有差别。只要咱们捉住主线,不要怕麻烦,全部都能尽在掌握当中
请允许我装逼一下,全部有为法,皆梦幻泡影,如雾亦如电,应作如是观。