Binder学习[1]: ServiceManger 如何成为所有Service的管理进程

本文主要解析两个关键点:

  1. ServiceManger 如何成为所有Service的管理进程
  2. ServiceManager 与 Binder Driver 共享 128K 内存

我们从ServiceManager的 main函数开始(service_manager.c):

int main()
{
    struct binder_state *bs;

    bs = binder_open(128*1024);
    ...
    binder_become_context_manager(bs);
    ...
    binder_loop(bs, svcmgr_handler);
    return  0;
}

从其main函数可以看到,ServiceManager进程主要通过这三个函数的执行,使得其成为 Service的管理进程,我们依次来说;

1.binder_open

我们看下binder_open函数(代码在 /frameworks/native/cmds/servicemanager/binder.c), 这里摘取了函数的主要逻辑:

struct binder_state *binder_open(size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;

    bs = malloc(sizeof(*bs));
    ...
    bs->fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
    ...
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }
    ...
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    ...
    return bs;
}

其中 bs 是一个struct:

struct binder_state
{
    int fd;
    void *mapped;
    size_t mapsize;
};

bs->fd : 保存着打开的 binder driver节点 "/dev/binder"对应的文件描述符, 待 ServiceManager 进程与 binder driver通信时,

会使用到这个fd;

bs->mmaped : 保存着 mmap 返回的用户态虚拟地址;

bs->mapsize :128 KB

 

函数首先调用 malloc 给 bs分配一块内存,这个没什么可说的;

接下来 open 函数会打开 binder driver对应的字符设备节点 "/dev/binder",对于用户态来说,目的就是返回了一个 fd;

而由于 binder driver 注册,而对于这个节点的 open操作,最终经过 open 系统调用后,会调用 binder driver的 binder_open函数,binder driver的 binder_open 函数 (kernel/drivers/android/binder.c):

static int binder_open(struct inode *nodp, struct file *filp)
{
	struct binder_proc *proc; //每个要进行binder通信的进程都有一个对应的 binder_proc
        ...
	proc = kzalloc(sizeof(*proc), GFP_KERNEL); //在kernel为调用open的进程分配binder_proc 
        ...
	get_task_struct(current);
	proc->tsk = current;  //设置该 binder_proc的 task指向调用 open的进程
	INIT_LIST_HEAD(&proc->todo); //初始化todo list,这个list用来保存将要进行的通信工作
	init_waitqueue_head(&proc->wait);//初始化 wait,即正在睡眠的 binder 线程list
	proc->default_priority = task_nice(current);//进程优先级
	hlist_add_head(&proc->proc_node, &binder_procs);//把 proc添加到binder_procs list中
	proc->pid = current->group_leader->pid; //记录当前进程 pid
	INIT_LIST_HEAD(&proc->delivered_death); //death list
	filp->private_data = proc;  //把 proc记录到 file的 private_data

        return 0;
}

这里面的操作主要是 创建一个 binder_proc,初始化一些数据,并把 binder_proc保存到service_manager进程打开 "/dev/binder"时,

创建的 filp->private_data中。binder_proc是每个要进行 binder通信都需要的一个数据结构,与进程关系是一对一 ,其保存进程binder通信时使用到的一些数据。

下面我们看一下 binder_proc:

struct binder_proc {
    // 进程打开设备文件/dev/binder时,Binder驱动会为它创建一个binder_proc结构体,并将它
    // 保存在全局hash列表中,proc_node是该hash列表的节点。
    struct hlist_node proc_node;

    // 每个使用了Binder机制的进程都有一个Binder线程池,用来处理进程间通信请求。threads以
    // 线程ID作为key来组织进程的Binder线程池。进程可以调用ioctl将线程注册到Binder驱动中
    // 当没有足够的空闲线程处理进程间通信请求时,驱动可以要求进程注册更多的线程到Binder线程
    // 池中
    struct rb_root threads; 

    struct rb_root nodes;           // 组织Binder实体对象,它以成员ptr作为key
    struct rb_root refs_by_desc;    // 组织Binder引用对象,它以成员desc作为key
    struct rb_root refs_by_node;    // 组织Binder引用对象,它以成员node作为key
    int pid;                        // 指向进程组ID
    struct vm_area_struct *vma;     // 内核缓冲区的用户空间地址,供应用程序使用
    struct mm_struct *vma_vm_mm;
    struct task_struct *tsk;        // 指向进程任务控制块
    struct files_struct *files;     // 指向进程打开文件结构体数组

    // 一个hash表,保存进程可以延迟执行的工作项,这些延迟工作有三种类型
    // BINDER_DEFERRED_PUT_FILES、BINDER_DEFERRED_FLUSH、BINDER_DEFERRED_RELEASE
    // 驱动为进程分配内核缓冲区时,会为该缓冲区创建一个文件描述符,进程可以通过该描述符将该内
    // 核缓冲区映射到自己的地址空间。当进程不再需要使用Binder机制时,就会通知驱动关闭该文件
    // 描述符并释放之前所分配的内核缓冲区。由于这不是一个马上就要完成的操作,因此驱动会创建一
    // 个BINDER_DEFERRED_PUT_FILES类型的工作来延迟执行;
    // Binder线程池中的空闲Binder线程是睡眠在一个等待队列中的,进程可以通过调用函数flush
    // 来唤醒这些线程,以便它们可以检查进程是否有新的工作项需要处理。此时驱动会创建一个
    // BINDER_DEFERRED_FLUSH类型的工作项,以便延迟执行唤醒空闲Binder线程的操作;
    // 当进程不再使用Binder机制时,会调用函数close关闭文件/dev/binder,此时驱动会释放之
    // 前为它分配的资源,由于资源释放是个比较耗时的操作,驱动会创建一个
    // BINDER_DEFERRED_RELEASE类型的事务来延迟执行
    struct hlist_node deferred_work_node;

    int deferred_work;              // 描述该延迟工作项的具体类型
    void *buffer;                   // 内核缓冲区的内核空间地址,供驱动程序使用
    ptrdiff_t user_buffer_offset;   // vma和buffer之间的差值

    // buffer指向一块大的内核缓冲区,驱动程序为方便管理,将它划分成若干小块,这些小块的内核缓
    // 冲区用binder_buffer描述保存在列表中,按地址从小到大排列。buffers指向该列表的头部。
    struct list_head buffers; 

    struct rb_root free_buffers;      // buffers中的小块有的正在使用,被保存在此红黑树
    struct rb_root allocated_buffers;   // buffers中的空闲小块被保存在此红黑树
    size_t free_async_space;            // 当前可用的保存异步事务数据的内核缓冲区的大小

    struct page **pages;    // buffer和vma都是虚拟地址,它们对应的物理页面保存在pages
                            // 中,这是一个数组,每个元素指向一个物理页面
    size_t buffer_size;     // 进程调用mmap将它映射到进程地址空间,实际上是请求驱动为它
                            // 分配一块内核缓冲区,缓冲区大小保存在该成员中
    uint32_t buffer_free;   // 空闲内核缓冲区的大小
    struct list_head todo;  // 当进程接收到一个进程间通信请求时,Binder驱动就将该请求封
                            // 装成一个工作项,并且加入到进程的待处理工作向队列中,该队列
                            // 使用成员变量todo来描述。
    wait_queue_head_t wait; // 线程池中空闲Binder线程会睡眠在由该成员所描述的等待队列中
                            // 当宿主进程的待处理工作项队列增加新工作项后,驱动会唤醒这
                            // 些线程,以便处理新的工作项
    struct binder_stats stats;  // 用来统计进程数据

    // 当进程所引用的Service组件死亡时,驱动会向该进程发送一个死亡通知。这个正在发出的通知被
    // 封装成一个类型为BINDER_WORK_DEAD_BINDER或BINDER_WORK_DEAD_BINDER_AND_CLEAR
    // 的工作项,并保存在由该成员描述的队列中删除
    struct list_head delivered_death;  

    int max_threads;        // 驱动程序最多可以主动请求进程注册的线程数
    int requested_threads;
    int requested_threads_started;
    int ready_threads;      // 进程当前的空闲Binder线程数
    long default_priority;  // 进程的优先级,当线程处理一个工作项时,线程优先级可能被
                            // 设置为宿主进程的优先级
    struct dentry *debugfs_entry;
};

总结:用户进程调用open("/dev/binder"),其实做了2个工作:

  1. 记录一个文件描述符 fd到用户进程,以便后面与 binder driver 通信
  2. 内核中执行内核的 binder_open 函数,为该用户进程创建一个对应的 binder_proc,以便通信使用

接下来,ServiceManager中的 binder_open函数会依次执行 version校验 和 mmap操作,分到下面两节来说。

2.version校验

对应代码是:

    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }

就是从获取一下binder driver中的binder的版本,比较是否与 service manger使用 binder 版本匹配;

主要工作在 binder version的获取,是通过 ioctl来获取的。

ioctl(bs->fd, BINDER_VERSION, &vers);

刚才记录的 bs->fd就是用来做 ioctl通信的,发的命令是 BINDER_VERSION,得到的version记录在 vers中。

ioctl经过系统调用后,会调用到 binder driver的 binder_ioctl 函数,这里只摘录跟本次调用 相关的代码:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;//这个filp就是servicemanager进程打开 "/dev/binder"节点是对应的进程,之前把 proc记录到了其 private_data中
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd); // cmd, BINDER_VERSION
	void __user *ubuf = (void __user *)arg;//对应上面的 vers
        ...
	thread = binder_get_thread(proc);//从proc->threads.rb_node中找,找不到则创建一个,并记录
	switch (cmd) {
        ...
	case BINDER_VERSION: {
		struct binder_version __user *ver = ubuf;

		if (size != sizeof(struct binder_version)) {
			ret = -EINVAL;
			goto err;
		}
		if (put_user_preempt_disabled(BINDER_CURRENT_PROTOCOL_VERSION,
			     &ver->protocol_version)) {//把 binder driver version写入到 vers中,这样返回后,Servicemanger中 binder_open函数的局部变量 vers中就有数据了
			ret = -EINVAL;
			goto err;
		}
		break;
	}
	}
	ret = 0;
	if (thread)
		thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
	return ret; //正常返回
}

完成binder dirver的vers获取,即可进行对比验证。

接下来是:     bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

 

3.binder_mmap

 

service manager 的 version校验办成后,接下来就是mmap,

bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

这个函数主要完成两件事情:

  1. 把 mmap 128KB 返回的service manager进程的用户空间地址记录到  bs->mmaped
  2. 在内核调用 binder driver的 binder_mmap函数,为进程在内核空间申请 128K虚拟内存,在用户空间也申请 128K虚拟内存;并先 alloc一个 PAGE的物理内存以便使用,后续需要的的时候,再真正分配物理页面

实际上,kernel空间的 128K 虚拟内存,和用户空间的 128K虚拟内存,在使用时,是会对应到相同的物理内存的。代码如下:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;

	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;
        ...
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;//每个进程的binder通信内存最大不能超过4M
        ...
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP); //分配内核虚拟内存属性
	proc->buffer = area->addr; //指向内核虚拟内存起始位置
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;//记录 offset
	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);//分配虚拟内存用以记录物理页面对应的指针
	proc->buffer_size = vma->vm_end - vma->vm_start;//buffer size

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;
	ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma); //分配一个 PAGE 的实际物理页面,并通过vma与用户空间虚拟地址绑定,通过 proc->pages与内核空间虚拟地址绑定起来
	buffer = proc->buffer;
	INIT_LIST_HEAD(&proc->buffers);
	list_add(&buffer->entry, &proc->buffers);
	buffer->free = 1;
	binder_insert_free_buffer(proc, buffer);
	proc->fre
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值