阅读 949

图解 | 不得错过的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的binder_context_mgr_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、添加系统服务的do_add_service

至此,可以看出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;
};
复制代码

这里用了HLIST_HEAD和hlist_node来串起链表,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