binder机制原理分析(一):ServiceManager 进程启动

binder机制原理分析一共分5个部分,其实省了一点,但是分析到后面都差不多了,以后再补充吧。

1、ServiceManager 进程启动

2、普通Service注册到ServiceManager

3、从ServiceManager中获取服务

4、java层service的注册过程

5、Java层service的获取过程

前言:

要讲解分析binder,最好是从SM的启动开始分析讲解,不然就会感觉总有黑洞没有探索,会总惦记着,感觉缺少点啥;

ServiceMangerd的启动主要分为3步骤:

  1. 通过binder_open方法打开binder设备驱动文件,然后通过mmap机制实现地址双映射。
  2. 将通过binder_become_context_manager指令将自己设置成为系统服务的大管家。
  3. 通过binder_looper方法,通过ioctl不断与binder驱动进行读写交互,并通过binder_parse处理数据。
int main(){
    struct binder_state *bs;
1、service_manager.c在初始化第一步就通过binder.c打开这个binder驱动设备文件。所以这个文件非常的重要。
    bs = binder_open(128*1024);
2、第二步是将自己注册成功所有服务的管家
if (binder_become_context_manager(bs)) {
    ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
............
3、这里通过binder_looper等待客户端的请求,第二个单数是servicemanager和handler句柄
    binder_loop(bs, svcmgr_handler);
    return 0;
}

打开驱动文件binder_open(128*1024)

binder_open(128*1024);通过binder_open方法打开binder设备驱动文件:

1、先初始化binder_proc结构体:包含了四颗红黑树、state、buffer、buffersize、user_buffer_offset、pages、todo队列、default_priorety等等。
作用:(待补充);将binder_proc结构体赋给file—>private_data中,方便获取。
2、通过mmap机制申请内核虚拟共享空间,配分物理页地址,并将地址映射到内核空间和用户空间。
作用就是当查询时发现物理页缺页的情况下,执行1拷贝操作。
struct binder_state *binder_open(size_t mapsize){
    struct binder_state *bs;
    struct binder_version vers;
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }
	初始化binder_proc结构体
	binder_proc里的4棵树需要了解一下(待补充):
	proc—>task(保存使用了binder机制的进程信息)
	proc—>buffer(内核地址)
	proc—>buffer_size(内核地址大小)
	proc—>user_buffer_offset(用户进程地址偏移量)
	proc—>pages(物理页)物理页地址的双映射是binder实现1拷贝的关键。
	proc—>todo队列(保存IPC传递过来的任务)
	proc—>default_priorety(保存进程的优先级);
	1、将binder_proc注册到binder_procs中,通过binder_procs就行可以查询到所有的进程信息。
	2、将binder_proc注册到file结构体的private_data中,这样bingder的其他操作就能直接获取到binder_proc结构体,就比如说下面mmap()的操作;
    bs->fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
    .................
    //通过mmap机制开辟虚拟内核共享空间。
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    .................
    return NULL;
}

mmap的原理:

  1. 申请内核虚拟地址空间
    根据进程的start和end,在内核虚拟空间申请一块指定大小的内存area,area不能超过4M(end-start),且没有禁用映射。将area赋给proc—>buffer,将偏移量(start-buffer)—>user_buffer_offset;
  2. 构建物理页数组
    通过buffer/PAGE_SIZE赋给proc—>pages物理数组。
  3. 实现物理页地址双映射
    通过binder_update_page_range遍历给buffer~buffer+pagesize之间的每个物理页并通过alloc_page分配地址,将该地址映射到虚拟内核空间(map_kernel_range_noflush)和虚拟进程空间(vm_insert_page)。
static int binder_mmap(struct file *filp, struct vm_area_struct *vma){
............
1、判断进程申请的虚拟空间大小是否超过4M、是否禁用了映射;但是在ProcessState中,也就是APP中已经设置了默认不超过(1*1024*1024)-(4096*2)也就是1M-8K的大小了,因此这里的判断对APP无效。
if ((vma->vm_end - vma->vm_start) > SZ_4M)
   	vma->vm_end = vma->vm_start + SZ_4M;
	if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
   	ret = -EPERM;
   	failure_string = "bad vm_flags";
   	goto err_bad_arg;
	}
2、在内核虚拟地址空间中申请分配指定大小的内存
   area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
   if (area == NULL) {
      ret = -ENOMEM;
      failure_string = "get_vm_area";
      goto err_get_vm_area_failed;
   }
3、保存内核虚拟地址空间的起始值和偏移量以及物理页,构建物理页数组,这个物理页必须以PAGE_SIZE为单位,这也是mmap的一个缺点
   proc->buffer = area->addr;
   proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
   proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE),GFP_KERNEL);
   if (proc->pages == NULL) {
      ret = -ENOMEM;
      failure_string = "alloc page array";
      goto err_alloc_pages_failed;
   }
   //计算分配的缓存区大小
   proc->buffer_size = vma->vm_end - vma->vm_start;
    //注册进程虚拟地址空间的操作函数
   vma->vm_ops = &binder_vm_ops;
   //将当前进程的binder_proc注册到虚拟地址空间描述符的vm_private_data中
   vma->vm_private_data = proc;
    // 分配实际的物理页面并同时映射到进程虚拟地址空间和内核虚拟地址空间中
4、为每个物理数组分配物理页地址,并且将物理页地址映射到虚拟内存空间和虚拟进程空间
   if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
      ret = -ENOMEM;
      failure_string = "alloc small buf";
      goto err_alloc_small_buf_failed;
   }
   //这段地址空间使用一个binder_buffer来描述,分别插入到proc->buffers链表和proc->free_buffers红黑树中去
   buffer = proc->buffer;
   INIT_LIST_HEAD(&proc->buffers);
   list_add(&buffer->entry, &proc->buffers);
   buffer->free = 1;
   binder_insert_free_buffer(proc, buffer);
   proc->free_async_space = proc->buffer_size / 2;
   barrier();
   proc->files = get_files_struct(current);
   proc->vma = vma;
   return 0;
。。。。。。。。。。。
   return ret;
}

mmap机制图解总结一下:

绑定SM:binder_become_context_manager()

这是ServiceManager启动的第二步,在binder驱动中为ServiceManager创建binder实体binger_node和binder_thread。整个binder通信机制是通过ioctl设备命令操作符来完成的。这里就是通过设备命令操作符,将ServiceManager设置为上下文。
实现步骤:

  1. 从proc—>threads红黑树中(一共有四棵树,这是其中一颗)查找获取是否有线程thread,没有的话就创建一个binder_thread,然后插入到红黑树中去。
  2. 通过ioctl传入BINDER_SET_CONTEXT_MGR指令,遍历proc—>nodes红黑树是否有node,没有创建一个binder_node并赋值给binder_context_mgr_node,同时设置binder_context_mgr_uid,此id唯一。
int binder_become_context_manager(struct binder_state *bs){
//ServiceManager通过BINDER_SET_CONTEXT_MGR指令在binder驱动中注册SMgr,
//系统中只能有一个MSgr存在,除非它调用close关闭。其他服务才能来注册。
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
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;
   ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
   if (ret)
      return ret;
   mutex_lock(&binder_lock);
1、从binder_proc中取出binder_thread线程描述符
   thread = binder_get_thread(proc);
   if (thread == NULL) {
      ret = -ENOMEM;
      goto err;
   }
2、处理不同的binder命令
   switch (cmd) {
   case BINDER_SET_CONTEXT_MGR:
	ret = binder_ioctl_set_ctx_mgr(filp);
      break;
      goto err;
   }
   ret = 0;
err:
   if (thread)
3、设置binder线程状态
      thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
   mutex_unlock(&binder_lock);
   wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
   if (ret && ret != -ERESTARTSYS)
      printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
   return ret;
}
主要目的就是设置context->binder_context_mgr_node和id的值,这个是唯一的
static int binder_ioctl_set_ctx_mgr(struct file *filp){
   int ret = 0;
   struct binder_proc *proc = filp->private_data;       通过filp->private_data获取proc
   struct binder_context *context = proc->context;
   kuid_t curr_euid = current_euid();
   if (context->binder_context_mgr_node) {
      pr_err("BINDER_SET_CONTEXT_MGR already set\n");  存在就报错,保证唯一性
      ret = -EBUSY;
      goto out;
   }
主要就是这两句话,设置UID和设置node,两个值唯一;
   context->binder_context_mgr_uid = curr_euid;
   context->binder_context_mgr_node = binder_new_node(proc, 0, 0); 
}

等待用户请求阶段

通过binder_loop(bs, svcmgr_handler);

  1. 首先写入write操作,设置cmd为BINDER_ENTRY_LOOPER,在binder_thread_write中设置了binder_thread的状态looper为BINDER_LOOPER_STATE_ENTERED,表示当前binder线程进入循环状态。
  2. 开起for循环,并设置read_size大小表示可读,通过ioctl执行binder_thread_read操作读取数据,读取时,如果thread->transaction_stack和thread->todo都为空,当前进程就通过wait_event_interruptible_exclusive函数进入休眠状态。
  3. 通过binder_parse方法解析读取到的消息。
void binder_loop(struct binder_state *bs, binder_handler func){
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));
开启for循环,readsize赋值,然后通过ioctl作用一个永远读取操作。
    for ( ;; ) {
开个读空间
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
读空间不为空,进行读写操作;
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
读取到数据后,进行解析
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
    }
}

ServiceManager进程启动总结:

  1. 通过open_binder()打开binder驱动设备文件。
  2. 通过mmap()在内核中开辟一块共享虚拟内存,同时将根据进程大小创建物理页,并将物理页地址同时映射到进程虚拟空间和内核虚拟空间。
  3. 通过ioctl的BINDER_SET_CONTEXT_MGR指令在内核中为ServiceManager创建binder_node实体,设置全局的binder_context_mgr_node和binder_context_mgr_uid两个全局变量。
  4. 设置ioctl的BINDER_WRITE_READ指令进行读写操作。分别调用copy_from_user —> binder_thread_write(binder_thread_read)—>copy_to_user三步走,并且别分设置binder_thread的looper线程状态为执行状态——BINDER_LOOPER_STATE_ENTERED以及睡眠状态BINDER_LOOPER_STATE_WAITING,这个状态也就是ServiceManager进程的状态。
  5. 如果thread的transaction_stack或者todo队列为空,那么就进入BINDER_LOOPER_STATE_WAITING状态,否正,唤醒进程并进入BINDER_LOOPER_STATE_ENTEYED状态。







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值