本文主要分析ServiceManager
系统服务管理进程对 binder 的管理流程。
大纲:
- 1 打开 binder 驱动
- 2 成为系统唯一的上下文
- 3 进入 binder 循环
- 4 系统服务的注册和获取
本文约 3.7k 字,阅读大约 15 分钟。
>
Android源码基于 8.0。
揭开 Binder 面纱
Binder 跟键盘、显示器一样属于一种外设(没有实体的外设)。由于外设种类繁多,操作系统如 Linux 抽象出文件视图来方便用户使用外设。即对用户来说,通过读写外设文件,让*操作系统将指令发送给外设控制器*,来实现对外设的操作。
在 Linux 中,各种外设文件放在/dev
目录下:
不过这些文件并不是像 Windows 上的那些外设驱动程序,而是提供给用户去访问外设的一个端口(就跟文件访问一样),如:
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 个分别是binder
、hwbinder
、vndbinder
,我们只关注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_SIZE
即1MB-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_service
和do_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 个疑问继续探讨:
binder 句柄的远程转本地
one way 异步模式和他的串行调用(async_todo)、同步模式的并行调用
系列文章:
补充
这两个进程容易搞混,再贴出来巩固一下...
参考资料
更多性感文章,关注原创技术公众号:哈利迪ei
评论