Android 系统架构 —— ServiceManager 进程的启动

 

前言

我们之前在学习 Binder 驱动的过程中就提及过 ServiceManager 是 Binder 驱动的上下文管理者

从 init 进程启动过程中我们知道 ServiceManager 服务管理进程是通过 init 进程 fork 出来的, 这里我们就分析一下 ServiceManager 的启动流程

ServiceManager 的作用

  • Service Manager 是 Binder 进程间通信的核心组件之一
  • 它扮演着 Binder 进程间通信机制的上下文管理者的角色
  • 负责管理系统中的 Service 组件, 并且向 Client 端提供获取 Service 代理对象的服务

一. 启动流程

该程序的入口函数 main 实现在 service_manager.c 中

// frameworks/base/cmds/servicemanager/service_manager.c
int main(int argc, char **argv) {
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
    // 1. 打开设备文件
    bs = binder_open(128*1024);
    // 2. 将自己注册为 Binder 驱动的上下文管理者
    if (binder_become_context_manager(bs)) {
        return -1;
    }
    svcmgr_handle = svcmgr;
    // 3. 循环等待和处理 Client 进程的通信请求
    binder_loop(bs, svcmgr_handler);
    return 0;
}

可见 service_manager 的主函数中主要做了三件事情

  • 调用 binder_open 打开 binder 设备文件 “/dev/binder”, 返回一个 binder_state 结构体
  • 调用 binder_become_context_manager 将自己注册成为一个 Binder 进程间通信的上下文管理者
  • 调用函数 binder_loop 来循环等待和处理 Client 进程的通信请求

接下来就这三个函数, 一一分析, 先看看 binder_open 打开 binder 驱动设备文件做了什么

二. 打开 Binder 驱动设备文件

// frameworks/base/cmds/servicemanager/binder.c
struct binder_state *binder_open(size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;
    // 在堆内存中创建了 binder_state 的实例
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }
    // 1. 通过 open 系统调用, 打开 Binder 设备文件
    bs->fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        goto fail_open;
    }
    
    // 2. 通过 ioctl 系统调用, 获取 binder 版本号, 验证是否打开成功
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }
    
    // 将给进程分配的内核缓冲区大小记录到 binder_state 结构体对象中
    bs->mapsize = mapsize;
    
    // 3. 通过 mmap 系统调用, 获取内核缓冲区用户虚拟地址空间首地址
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    ......
    
    // 返回这个 binder_state 这个结构体对象
    return bs;
    ......
}

可见 ServiceManager 的打开设备文件的操作非常简单

  • 通过 open 系统调用打开 Binder 设备文件
  • 通过 ioctl 系统调用, 获取 binder 版本号, 验证是否打开成功
  • 调用 mmap 系统调用, 初始化当前进程的 binder 内核缓冲区
    • 由传参可知, 其大小为 128 * 1024, 即 128 kb, 也就是说 ServiceManager 进程最高支持传输 128 kb 的数据
  • 将给进程分配的内核缓冲区大小记录到 binder_state 结构体对象中

关于 Binder 驱动设备文件的系统调用的实现, 我们在上一篇文章中已经仔细分析过了, 这里我们就不赘述了

下面我们看看 ServiceManager 是如何通过 binder_become_context_manager 将自己注册成上下文管理者的

三. 注册为 Binder 驱动的上下文管理者

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

可以看到注册上下文管理者的函数中, 调用了 ioctl 系统调用, 其 IO 指令码为 BINDER_SET_CONTEXT_MGR

关于 BINDER_SET_CONTEXT_MGR 我们在上一篇文章分析 ioctl 的时候就已经提到过, 这里我们就具体的分析一下

// Binder 通信上下文管理者的在 Binder 内核驱动中的 Binder 实体对象
static struct binder_node *binder_context_mgr_node;
// 描述了注册了 Binder 通信上下文管理者的有效用户 ID
static struct binder_context_mgr_uid = -1;

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    
    int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	
	......
	
	// 1. 获取当前线程的 binder_thread 描述
	thread = binder_get_thread(proc);
	
	// 2.  处理 IOCTL 指令
    switch(cmd) {
        ......
        case BINDER_SET_CONTEXT_MGR:
            // 说明 Binder 上下文管理者已经注册过了
            if (binder_context_mgr_node != NULL) {
                goto error;
            }
            // 说明 Binder 上下文管理者已经注册过了
            if (binder_context_mgr_uid != -1) {
                goto error;
            } else {
                // 3. 经过一系列验证之后, 给当前进程创建其对应的 binder 实体对象保存在全局的 binder_context_mgr_node 变量中
                binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
            }
            ......
            break;
    } 
}

可以看到 binder_ioctl 中首先通过 binder_get_thread 获取了当前线程的 binder_thread 的描述

对于 BINDER_SET_CONTEXT_MGR 指令码主要是通过 binder_new_node 创建一个 binder_node 对象, 并且保存在静态变量 binder_context_mgr_node 中

  • 并且通过一系列 if 判断, 保证了 binder_context_mgr_node 的唯一性, 足以看出 ServiceManager 的特殊

接下来我们分别看看 binder_get_thread 和 binder_new_node 的具体实现

一) binder_get_thread 获取 binder_thread 结构体

// kernel/goldfish/drivers/staging/android/binder.c
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
	struct binder_thread *thread = NULL;
	struct rb_node *parent = NULL;
	struct rb_node **p = &proc->threads.rb_node;
	// 1. 以当前线程的 pid 为标示, 获取红黑树上缓存的结构体
	while (*p) {
		parent = *p;
		thread = rb_entry(parent, struct binder_thread, rb_node);
		// 以当前线程的 pid 为标示, 获取红黑树上缓存的结构体
		if (current->pid < thread->pid)
			p = &(*p)->rb_left;
		else if (current->pid > thread->pid)
			p = &(*p)->rb_right;
		else
			break;
	}
	// 2. 若无对应的 binder_thread 描述, 则创建一个新 binder_thread 结构体
	if (*p == NULL) {
		thread = kzalloc(sizeof(*thread), GFP_KERNEL);
	    ......
	    // 2.1 注入数据
		thread->proc = proc;
		thread->pid = current->pid;
		// 2.2 初始化当前线程的任务队列
		init_waitqueue_head(&thread->wait);
		INIT_LIST_HEAD(&thread->todo);
		// 2.3 插入到 binder_proc 中的缓存
		rb_link_node(&thread->rb_node, parent, p);
		rb_insert_color(&thread->rb_node, &proc->threads);
		// 2.4 状态为不需要 looper
		thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
	}
	return thread;
}

binder_get_thread 操作以当前线程 pid 为 key 尝试 binder_proc 的红黑树中找寻对应的结构体描述 binder_thread 对象

  • 若不存在则创建一个 binder_thread 并且插入 binder_proc 的红黑树中缓存
  • 刚创建完毕时的 looper 状态为 BINDER_LOOPER_STATE_NEED_RETURN

关于获取 binder_thread 结构体就看到这里, 下面我们看看 binder_new_node 创建 binder_node 的流程

二) binder_new_node 创建 binder_node

下面我们看看 binder_new_node 中做了哪些处理

// kernel/goldfish/drivers/staging/android/binder.c
static struct binder_node *binder_new_node(
                       struct binder_proc *proc,
					   binder_uintptr_t ptr,
					   binder_uintptr_t cookie)
{
    // 获取 binder_proc 中缓存的红黑树
	struct rb_node **p = &proc->nodes.rb_node;
	struct rb_node *parent = NULL;
	struct binder_node *node;

    // 1. 找寻合适的插入位置
	while (*p) {
		parent = *p;
		node = rb_entry(parent, struct binder_node, rb_node);
		if (ptr < node->ptr)
			p = &(*p)->rb_left;
		else if (ptr > node->ptr)
			p = &(*p)->rb_right;
		else
			return NULL;
	}
	
	// 2. 创建 binder_node 结构体
	node = kzalloc(sizeof(*node), GFP_KERNEL);
	......
	binder_stats_created(BINDER_STAT_NODE);
	// 3. 插入到红黑树中
	rb_link_node(&node->rb_node, parent, p);
	rb_insert_color(&node->rb_node, &proc->nodes);
	// 4. 初始化相关变量
	node->proc = proc;
	node->ptr = ptr;
	node->cookie = cookie;
	node->work.type = BINDER_WORK_NODE;
	INIT_LIST_HEAD(&node->work.entry);
	INIT_LIST_HEAD(&node->async_todo);
	return node;
}

binder_new_node 函数中的操作, 还是非常清晰, 即创建 binder_node 对象, 并且将其插入到 binder_proc 的红黑树中缓存

接下来我们看看 ServiceManager 的 binder_loop 是如何注册为 binder 驱动的 looper 线程的

四. 注册 binder 驱动 looper 线程

// frameworks/base/cmds/servicemanager/binder.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    // 描述对 binder 驱动读写的结构体
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    // 描述一个 binder_write_read 的控制码 BC_ENTER_LOOPER
    readbuf[0] = BC_ENTER_LOOPER;
    
    // 1. 调用 binder_write 该函数, 通知 binder 驱动 binder_write_read 的控制码 BC_ENTER_LOOPER
    binder_write(bs, readbuf, sizeof(uint32_t));
    
    // 2. 死循环从 binder 驱动中获取需要处理的进程间通信数据并处理
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        
        // 2.1 获取 binder 驱动返回的数据
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        ......
        // 2.2 处理进程间的请求
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}

binder_loop 主要做了如下两件事件

  • 调用 binder_write, 通知 binder 驱动处理 binder_write_read 的读写控制码 BC_ENTER_LOOPER
  • 通过一个死循环, 不断的通过 ioctl 系统调用, 获取 binder 驱动写入到 binder_write_read 的数据
    • 通过 ioctl 的指令码 BINDER_WRITE_READ 获取其他进程发送过来的数据
    • 通过 binder_parse 处理请求数据

好的, 这里我们看到了 binder_write_read 结构体, 它的定义如下

// bionic/libc/kernel/uapi/linux/android/binder.h
struct binder_write_read {
  binder_size_t write_size;
  binder_size_t write_consumed;
  binder_uintptr_t write_buffer;
  binder_size_t read_size;
  binder_size_t read_consumed;
  binder_uintptr_t read_buffer;
};

binder_write_read 的作用是配合 ioctl 系统调用, 向 binder 驱动写入数据, 和从 binder 驱动读取数据

接下来我们便看看 binder 驱动是如何处理 binder_write_read 的读写控制码 BC_ENTER_LOOPER 的

一) 处理 BC_ENTER_LOOPER 读写指令

// frameworks/base/cmds/servicemanager/binder.c
int binder_write(struct binder_state *bs, void *data, size_t len)
{
    struct binder_write_read bwr;
    int res;
    bwr.write_size = len;
    bwr.write_consumed = 0;
    // 1. 将读写指令码 BC_ENTER_LOOPER 保存在 write_buffer 中
    bwr.write_buffer = (uintptr_t) data;
    bwr.read_size = 0;
    bwr.read_consumed = 0;
    bwr.read_buffer = 0;
    // 2. 调用 ioctl 与 binder 驱动通信, IO 指令码为 BINDER_WRITE_READ
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    ......
    return res;
}

下面我们看看 ioctl 是如何处理 BC_ENTER_LOOPER 的

// kernel/goldfish/drivers/staging/android/binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {

    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);

    // 获取当前线程的 binder_thread 描述
    thread = binder_get_thread(proc);
    
    // 处理 BINDER_WRITE_READ 指令码
    switch(cmd) {
        ......
        case BINDER_WRITE_READ:
            ......
            if (bwr.write_size > 0) {
                // 通过 binder_thread_write 处理 BC_ENTER_LOOPER
                ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.read_consumed);
            }
            ......
            break;
    }
}

binder_ioctl 的 binder_get_thread 我们在上面已经分析过了, 再次获取便不会重新创建 binder_thread 对象了

这里我们主要看看 binder_thread_write 是如何处理 BC_ENTER_LOOPER 指令的

// kernel/goldfish/drivers/staging/android/binder.c
int binder_thread_write(......) {
    while(...) {
        switch(cmd) {
            case BC_ENTER_LOOPER:
                // 这里将这个线程注册成为了 looper 线程, 至此 Binder 进行间的通信请求便会交由这个线程分发处理
                thread->looper |= BINDER_LOOPER_STATE_ENTERED;
            break;
        }
    }
}

binder_thread_write 中对读写指令码 BC_ENTER_LOOPER 的操作也非常简单, 即将当前线程的 binder_thread 的描述中的 looper flag 置为 BINDER_LOOPER_STATE_ENTERED, 表示当前线程即将进入 looper 死循环了

如此一来, 当其他进程向当前进程发起跨进程通信请求时, binder 驱动便会通过向 looper 的 thread 中写入工作项的方式来唤醒 looper 线程来处理这次跨进程通信了

二) 死循环处理 Binder 驱动返回数据

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    // 描述对 binder 驱动读写的结构体
    struct binder_write_read bwr;
    .....
    // 2. 死循环从 binder 驱动中获取需要处理的进程间通信数据并处理
    for (;;) {
        ......
        // 2.1 获取 binder 驱动返回的数据
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        ......
        // 2.2 处理进程间的请求
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        ......
    }
}

我们先看看 ioctl 系统调用如何通过 BINDER_WRITE_READ 获取其他进程的数据的

1. IO 指令码 BINDER_WRITE_READ

// kernel/goldfish/drivers/staging/android/binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	
	......
	
    // 获取一个当前线程的 binder_thread 结构体描述
	thread = binder_get_thread(proc);
	......
	case BINDER_WRITE_READ:
	    // 调用 binder_ioctl_write_read 处理 BINDER_WRITE_READ 指令
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
	......
}

对于 BINDER_WRITE_READ 指令, ioctl 系统调用分发给了 binder_ioctl_write_read 函数去处理, 下面我们看看它的实现

// kernel/goldfish/drivers/staging/android/binder.c
static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	int ret = 0;
	// 获取当前进程的 binder 驱动上下文
	struct binder_proc *proc = filp->private_data;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	struct binder_write_read bwr;
	......
	// 1. 拷贝用户空间传递来的数据到 bwr 结构体中
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
	// 2. 处理用户空间写入了数据的情况
	if (bwr.write_size > 0) {
	    // 调用 binder_thread_write 处理数据写入
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		......
	}
	// 3. 处理用户空间读取数据的情况
	if (bwr.read_size > 0) {
	    // 调用 binder_thread_read 函数, 读取数据
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		......
		// 若 proc 中仍然存在等待唤醒的线程, 则通知其唤醒
		if (!list_empty(&proc->todo))
			wake_up_interruptible(&proc->wait);
	    ......
	}
	// 4. 将处理好 bwr 结构体拷贝回用户空间
	if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
out:
	return ret;
}

binder_ioctl_write_read 中主要操作入下

  • 拷贝用户空间传递的数据到 binder_write_read 结构体中
  • binder_thread_write: 向其他进程发送数据
  • binder_thread_read: 读取其他进程发送的数据
  • 将处理后的 binder_write_read 重新拷贝回用户空间

binder_ioctl_write_read 中的信息量非常丰富了, 内核中同样定义了 binder_write_read 结构体, 通过这个结构体就可以和用户空间进行相互拷贝了, 返回的数据也通过这个结构体拷贝回用户空间

思考
这里我们可能会有疑问了, 网上不都说 Binder 驱动是一次拷贝吗? 为什么但是这里就已经出现两次拷贝了呢?

  • 因为用户空间使用系统调用时, 会陷入内核态, 系统调用的中的参数必须通过拷贝传递

网上所述的一次拷贝指的是跨进程数据的拷贝, 在后面的章节在会着重分析

在 ServiceManager 的 binder_looper 中可知, bwr 的 read_size 为 32, 因此这里调用 binder_thread_read 进行读操作, 下面我们看看它是如何读取数据的

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      binder_uintptr_t binder_buffer, size_t size,
			      binder_size_t *consumed, int non_block)
{
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;

	int ret = 0;
	int wait_for_proc_work;

	if (*consumed == 0) {
		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
	}

retry:
    // 1. 根据当前线程的 todo 队列是否为空, 判断是否需要执行等待操作
	wait_for_proc_work = thread->transaction_stack == NULL &&
				list_empty(&thread->todo);
    
    ......// 尝试从当前进程的 todo 队列中取任务执行
    
    // 2. 将当前线程状态标记为等待
    thread->looper |= BINDER_LOOPER_STATE_WAITING;
	......
	
	if (wait_for_proc_work) {
		if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
					BINDER_LOOPER_STATE_ENTERED))) {
		    
			wait_event_interruptible(binder_user_error_wait,
						 binder_stop_on_user_error < 2);
		}
		binder_set_nice(proc->default_priority);
		if (non_block) {
			if (!binder_has_proc_work(proc, thread))
				ret = -EAGAIN;
		} else {
		    // 3. 通过 wait_event_interruptible_exclusive 函数进入休眠状态, 等待请求到来再唤醒了
		    ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
		}
	}

	......// 4. 真正执行读取数据的操作
	
}

从这里可以看到 binder_thread_read 会根据当前进程是否有任务, 来判断是否需要进入睡眠等待, 若没有任务则睡眠在 wait_event_freezable_exclusive 函数上, 等待任务到来了再唤醒

关于 binder_thread_read 获取其他进程发送的数据我们到跨进程通信实例中再分析, 接下来我们看看用户空间的 binder_parse 如何处理从 Binder 驱动读到的数据

2. binder_parse 处理进程间通信请求

// frameworks/base/cmds/servicemanager/binder.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    // 描述对 binder 驱动读写的结构体
    struct binder_write_read bwr;
    .....
    // 2. 死循环从 binder 驱动中获取需要处理的进程间通信数据并处理
    for (;;) {
        ......
        // 2.1 获取 binder 驱动返回的数据
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        ......
        // 2.2 处理进程间的请求
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        ......
    }
}

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) {
        // 获取 binder 驱动的返回值指令码
        uint32_t cmd = *(uint32_t *) ptr;
        ptr += sizeof(uint32_t);
        ......
        switch(cmd) {
        ......
        case BR_TRANSACTION: {
            .......
            break;
        }
        case BR_REPLY: {
            ......
            break;
        }
        .......
    }
    return r;
}

这里通过解析 ptr 中 Binder 驱动返回的指令码, 进行相应的处理, 其中比较重要的有 BR_TRANSACTIONBR_REPLY

关于他们的具体实现, 我们到跨进程通信实例中再进行具体的分析

总结

ServiceManager 为服务管理进程, 它是 Binder 驱动的上下文管理者, 其启动流程主要分为如下几步

  • 打开 Binder 驱动
    • open: 打开 Binder 设备文件
    • mmap: 初始化一个 128 kb 的缓冲区
  • 注册为当前进程为 Binder 上下文的管理者
    • binder_node 对象通过静态变量 binder_context_mgr_node 保存
  • 调用 binder_loop 维护一个死循环, 处理其他进程的通信请求
    • 当其他进程对当前进程发起 Binder 通信请求时, looper 线程用于接收通信数据分发处理
    • 死循环通过 ioctl 从 Binder 驱动中读取数据
      • 没有任务, 则阻塞在 wait_event_freezable_exclusive 函数上, 等待任务到来了再唤醒
    • 读到任务之后, 通过 binder_parse 处理任务数据

ServiceManager 进程的启动到这里就结束了, 通过 binder_loop 中的死循环, 就可以监听其他进程的 Binder 通信请求了

  • 如此一来 Service Manager 就在 Android 系统的进程间通信机制 Binder 担负起守护进程的职责了

后面的文章我们会通过一次 Binder 跨进程通信, 来将之前学习的知识点串联起来