写点什么

图解 | 不得错过的 Binder 浅析(二)

用户头像
哈利迪
关注
发布于: 2020 年 11 月 24 日
图解 | 不得错过的Binder浅析(二)

本文主要分析ServiceManager系统服务管理进程对 binder 的管理流程。


大纲:


  • 揭开 Binder 面纱

  • Binder 的管理

- 1 打开 binder 驱动

- 2 成为系统唯一的上下文

- 3 进入 binder 循环

- 4 系统服务的注册和获取

  • 总结

  • 参考资料


本文约 3.7k 字,阅读大约 15 分钟。

>

Android源码基于 8.0。


揭开 Binder 面纱


Binder 跟键盘、显示器一样属于一种外设(没有实体的外设)。由于外设种类繁多,操作系统如 Linux 抽象出文件视图来方便用户使用外设。即对用户来说,通过读写外设文件,让*操作系统将指令发送给外设控制器*,来实现对外设的操作。



在 Linux 中,各种外设文件放在/dev目录下:



不过这些文件并不是像 Windows 上的那些外设驱动程序,而是提供给用户去访问外设的一个端口(就跟文件访问一样),如:


  • /dev/console:系统控制台

  • /dev/mem:物理内存的全镜像。可以用来直接存取物理内存。

  • /dev/kmem:内核看到的虚拟内存的全镜像。可以用来访问内核中的内容。

  • /dev/tty0:虚拟终端

  • ...


Linux 抽象出文件视图,为用户提供统一接口,一段简单的操作外设的程序如下:


//打开 /dev 下的外设文件int fd = open(“/dev/xxx”);for (int i = 0; i < 10; i++) {    //进行读写操作    write(fd,i,sizeof(int));}//关闭文件close(fd);
复制代码


用户读写外设文件,Linux 会通过外设文件找到外设控制器的地址、内容格式等信息,向他发送合适的指令来操作外设。


现在我们通过adb shell进入 Android 设备,看下他的/dev目录长啥样:



可以看到有 binder,标黄部分的 3 个分别是binderhwbindervndbinder,我们只关注binder就行了。


从「一图摸清Android应用进程的启动」一文可知,在应用程序启动 binder 线程池时,ProcessState.cpp有这么一段代码,


//ProcessState.cpp
sp<ProcessState> ProcessState::self(){ //传入 binder 外设文件路径 gProcess = new ProcessState("/dev/binder"); return gProcess;}
//ProcessState构造函数ProcessState::ProcessState(const char *driver) //路径赋给 mDriverName : mDriverName(String8(driver)) //1. 打开 binder 驱动 , mDriverFD(open_driver(driver)) ,//...{ //2. 映射内存 mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);}
复制代码


我们看下打开 binder 驱动的open_driver函数,


//ProcessState.cpp
static int open_driver(const char *driver){ //打开外设文件 /dev/binder int fd = open(driver, O_RDWR | O_CLOEXEC); int vers = 0; //获取 binder 版本进行检查 status_t result = ioctl(fd, BINDER_VERSION, &vers); size_t maxThreads = DEFAULT_MAX_BINDER_THREADS; //设置 binder 最大线程数为 15 result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); //返回 int 类型的 fd 给 mDriverFD return fd;}
复制代码


看起来是不是跟 Linux 操作外设的那段程序很像,只不过这里的读写操作由write换成了ioctl


在计算机中,ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,该调用传入一个跟设备有关的请求码,系统调用的功能完全取决于请求码。举个例子,CD-ROM 驱动程序可以弹出光驱,它就提供了一个对应的 Ioctl 请求码。设备无关的请求码则提供了内核调用权限。ioctl 这名字第一次出现在 Unix 第七版中,他在很多类unix系统(比如Linux、Mac OSX 等)都有提供,不过不同系统的请求码对应的设备有所不同。

>

-- 引用自百科 ioctl


可见ioctl是一个可以控制设备 I/O 通道的系统调用,通过它用户空间可以跟设备驱动沟通。


至于为什么要有ioctl,主要是为非标准设备考虑的(如 binder 就是一种非标准外设),详见百科 ioctl 背景


ioctl函数如下:


int ioctl(int fd, ind cmd, …);
复制代码


第一个参数fd是文件描述符,如 binder 外设文件;


第二个参数cmd则是控制命令,如指令BINDER_SET_MAX_THREADS是“设置线程数”,最后的省略号则是各指令所需的参数,如maxThreads表示最大线程数为 15。


指令BINDER_SET_MAX_THREADS的定义如下:


#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)
复制代码


_IOW是一个宏,Linux 内核提供了一些宏来方便用户定义指令(传入各种参数进行包装):


// nr为序号,datatype 为数据类型,如 int_IO(type, nr ) //没有参数的命令_IOR(type, nr, datatype) //从驱动中读数据_IOW(type, nr, datatype) //写数据到驱动_IOWR(type,nr, datatype) //双向传送
复制代码


名字很好理解,就是 io read write 的缩写。


对 binder 的了解暂且到这,只需知道他是一个**外设,以文件形式通过ioctl来操作**就行了。


Binder 的管理


从「一图摸清Android系统服务」一文可知,init 进程会启动运行在独立进程的ServiceManager服务来统一管理系统服务的注册和获取。



ServiceManager的入口函数即service_manager.c的 main 函数中,


//frameworks/native/cmds/servicemanager/service_manager.c
int main(int argc, char** argv){ char *driver = "/dev/binder"; //1. 打开 binder 驱动 struct binder_state *bs = binder_open(driver, 128*1024); //2. 让自己成为整个系统唯一的上下文管理器, // 这样其他进程就能找到 ServiceManager 来注册服务了 binder_become_context_manager(bs); //3. 进入binder循环,等待系统服务的注册和查找请求 binder_loop(bs, svcmgr_handler);}
复制代码


下面分析这 3 个步骤。


1 打开 binder 驱动


128 * 1024 即 128kb 是 mapsize,表示把 binder 驱动文件的128kb映射到内存空间,而在「一图摸清Android应用进程的启动」一文可知应用进程使用的 mapsize 大小为BINDER_VM_SIZE1MB-8kb,可见两者的大小是不同的,


//ProcessState.cpp//一次Binder通信最大可以传输的大小是 1MB-4KB*2#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)//映射内存mmap(..., BINDER_VM_SIZE, ...);
复制代码


回到ServiceManager,看 binder_open()的内部实现binder.c


//frameworks/native/cmds/servicemanager/binder.c
struct binder_state *binder_open(const char* driver, size_t mapsize){ struct binder_state *bs; //分配空间 bs = malloc(sizeof(*bs)); //打开 binder 驱动,得到int类型的文件描述符 fd bs->fd = open(driver, O_RDWR | O_CLOEXEC); //记录传入的 128kb bs->mapsize = mapsize; //映射内存,记录内存映射区的指针 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); return bs;}
复制代码


mmap 可以将一个文件或者其它对象映射进内存,函数原型:


void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
复制代码


各参数如下:

  • start:映射区的开始地址,传 NULL 表示由系统决定映射区的起始地址

  • length:映射区的长度,传 128kb

  • prot:期望的内存保护标志,传 PROT_READ 只读

  • flags:指定映射对象的类型,映射选项和映射页是否可以共享。传 MAP_PRIVATE 建立一个写入时拷贝的私有映射,内存区域的写入不会影响到原文件

  • fd:有效的文件描述符,一般是由 open()函数返回

  • offset:被映射对象内容的起点,传 0

  • return:成功执行时,mmap()返回被映射区的指针


mmap 会根据入参将 binder 驱动文件的一部分映射到内存空间,然后返回该内存空间的指针。


最后 binder_open()返回的 bs 结构体如下:


//frameworks/native/cmds/servicemanager/binder.c
struct binder_state{ // binder 驱动文件描述符 int fd; //由 mmap 得到的内存映射区的指针 void *mapped; // 128kb size_t mapsize;};
复制代码


2 成为系统唯一的上下文


ServiceManager让自己成为整个系统唯一的上下文管理器,这样其他进程就能找到ServiceManager来注册服务了,


//frameworks/native/cmds/servicemanager/binder.c
int binder_become_context_manager(struct binder_state *bs){ return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);}
复制代码


可见就是前边提到的ioctl调用,向 binder 驱动发送一个指令“我ServiceManager已成为全局上下文管理器”。


binder 驱动层代码暂不跟进,我们只需知道:


一般情况下,应用层的每个 binder 实体都会在 binder 驱动层对应一个 binder_node 节点,然而ServiceManager的 bindercontextmgr_node 比较特殊,它没有对应的应用层 binder 实体。在整个系统里,它是如此特殊,以至于系统规定,任何应用都必须使用句柄0来跨进程地访问它。

>

-- 引用自 博客 - 红茶一杯话Binder


3 进入 binder 循环


进入 binder 循环,等待系统服务的注册和查找请求,


//frameworks/native/cmds/servicemanager/binder.c
void binder_loop(struct binder_state *bs, binder_handler func){ int res; struct binder_write_read bwr; //readbuf 用于跟 binder 驱动互传数据 uint32_t readbuf[32]; bwr.write_size = 0; bwr.write_consumed = 0; bwr.write_buffer = 0; //指令:binder 开始循环 readbuf[0] = BC_ENTER_LOOPER; //向 binder 发送该指令 //内部会执行 ioctl(bs->fd, BINDER_WRITE_READ, &bwr); binder_write(bs, readbuf, sizeof(uint32_t));
for (;;) { //进入循环 bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (uintptr_t) readbuf; //向 binder 发送 读写指令 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); //解析从 binder 读来的数据,交给传入的函数 svcmgr_handler 处理 res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func); }}
复制代码


其中binder_parse解析逻辑如下:


//frameworks/native/cmds/servicemanager/binder.c
int binder_parse(struct binder_state *bs, struct binder_io *bio, uintptr_t ptr, size_t size, binder_handler func){ int r = 1; //计算能读完最后一条指令的偏移 uintptr_t end = ptr + (uintptr_t) size; while (ptr < end) { //不断从 readbuf 读取 binder 回传的指令 uint32_t cmd = *(uint32_t *) ptr; //每读完一条,进行偏移 ptr += sizeof(uint32_t); switch(cmd) { //处理各种指令 case BR_TRANSACTION_COMPLETE: break; case BR_TRANSACTION: //转交给传入的 svcmgr_handler 函数处理 res = func(bs, txn, &msg, &reply); //... break; case BR_REPLY: //... break; //... } } return r;}
复制代码


然后看到传入的处理函数svcmgr_handler


//frameworks/native/cmds/servicemanager/service_manager.c
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: //查找系统服务 handle = do_find_service(...); bio_put_ref(reply, handle); case SVC_MGR_ADD_SERVICE: //添加系统服务 do_add_service(...); }}
复制代码


svcmgr_handler函数会根据不同的语义码 code 来执行相应逻辑,如查找系统服务的do_find_service、添加系统服务的doaddservice


至此,可以看出ServiceManager的 binder 启动流程:



4 系统服务的注册和获取


下面简要分析一下系统服务的注册和获取,在「一图摸清Android系统服务」一文已对上层逻辑进行介绍,这里直接看do_add_servicedo_find_service两个方法。


1.添加系统服务do_add_service


struct svcinfo *svclist 以链表的形式记录了所有的系统服务,其结构体如下:


//frameworks/native/cmds/servicemanager/service_manager.c
struct svcinfo{ //下一个服务 struct svcinfo *next; //服务的 binder 句柄值 uint32_t handle; struct binder_death death; int allow_isolated; size_t len; //服务注册时取的名字 uint16_t name[0];};
复制代码


然后看到do_add_service


//frameworks/native/cmds/servicemanager/service_manager.c
struct svcinfo *svclist; //链表
int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle, uid_t uid, int allow_isolated, pid_t spid){ if (!svc_can_register(s, len, spid, uid)) { //判断是否可以注册系统服务 //只有 root 进程、SystemServer 进程、有在 allowed[] 数组中声明的进程可以 return -1; } //分配空间给新的节点 struct svcinfo *si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t)); //记录服务的 binder 句柄值 si->handle = handle; si->len = len; //记录服务注册时取的名字 memcpy(si->name, s, (len + 1) * sizeof(uint16_t)); //追加一个结束符 si->name[len] = '\0'; //...还有各种赋值
//新节点的 next 指向链表 si->next = svclist; //链表的头插法 svclist = si;}
复制代码


如下,



至于系统服务 int 类型的 binder 句柄值 handle 怎么来的,是由 binder 驱动层为我们分配,然后包装成特定的数据结构回传给我们的。


可见ServiceManager链表svclist管理各系统服务的 binder 句柄,结构体是svcinfo


而对应到 binder 驱动层,则是用链表binder_procs管理的,结构体是binder_proc,在/drivers/android/binder.c中,


//drivers/android/binder.c
//链表头结点static HLIST_HEAD(binder_procs);//结构体struct binder_proc { //链表普通节点,由他的 next 和 pprev 串起链表 struct hlist_node proc_node;
//4棵红黑树,rb = red black //记录执行传输动作的线程信息 binder_thread struct rb_root threads; //记录 binder 实体 binder_node struct rb_root nodes; //记录 binder 代理 binder_ref struct rb_root refs_by_desc; struct rb_root refs_by_node;};
复制代码


这里用了 HLISTHEAD 和 hlistnode 来串起链表,binder 驱动层的代码暂不展开,感兴趣可以阅读「红茶一杯话Binder传输机制篇」。


2.查找系统服务do_find_service


//frameworks/native/cmds/servicemanager/service_manager.c
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid){ //遍历链表找到节点 struct svcinfo *si = find_svc(s, len); //返回 binder 句柄值 return si->handle;}
struct svcinfo *find_svc(const uint16_t *s16, size_t len){ struct svcinfo *si; //遍历链表找到节点 for (si = svclist; si; si = si->next) { if ((len == si->len) && !memcmp(s16, si->name, len * sizeof(uint16_t))) { return si; } } return NULL;}
复制代码


综上,查找系统服务的do_find_service和添加系统服务的do_add_service如下图:



总结


ServiceManager作为管理系统服务的进程,经过打开 binder 驱动、注册成为系统唯一的上下文、进入 binder 循环 3 个核心步骤,便开始支持系统服务的注册和获取。系统服务的注册和获取过程基于 binder 机制实现 IPC 通信,binder 的本质就是一个**外设,以文件形式通过ioctl系统调用来操作**。


留下 2 个疑问继续探讨:


  1. binder 句柄的远程转本地

  2. one way 异步模式和他的串行调用(async_todo)、同步模式的并行调用


系列文章:



补充


  • 系统服务由ServiceManager进程管理,但用户自定义的 Service 组件,bindService 时的onServiceConnected回调拿到的 IBinder 句柄,是由SystemServer进程的AMS管理的,后面再开篇分析了。


这两个进程容易搞混,再贴出来巩固一下...



参考资料





更多性感文章,关注原创技术公众号:哈利迪ei


发布于: 2020 年 11 月 24 日阅读数: 34
用户头像

哈利迪

关注

. 2019.02.13 加入

公众号:哈利迪ei

评论

发布
暂无评论
图解 | 不得错过的Binder浅析(二)