文档章节

Binder服务-底层驱动

小米墨客
 小米墨客
发布于 2017/05/24 16:14
字数 3769
阅读 25
收藏 0

**注意:**基于Android 8.0源码,linux版本4.9版本(和Android版本非对应关系)

@(源码系列)

#摘要 Binder是一种通信方式,必有它的通信形式和通信消息,然后为什么Android要采用Binder作为IPC机制? 在https://my.oschina.net/feiyangxiaomi/blog/896694文章中已经介绍过。主要考量四个方面:

  • 从性能的角度:Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要两次;
  • 从稳定性的角度:良好的系统需要隔离系统环境和应用环境,而且共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;
  • 从安全的角度:使用Binder机制可以在机制内部提供UID/PID实现通信过程中,对方身份的鉴别。
  • 从语言层面的角度:Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。

那么Binder服务是什么样的一种服务,代码路径为:

/linux/drivers/android/binder.c
/linux/include/uapi/linux/android/binder.h

#Binder驱动 Binder是Android系统提供一种IPC机制,Binder驱动也是一个标准的Linux驱动。Binder Driver被注册成一个misc device,并向上层提供一个/dev/binder节点,Binder节点并不是对应真实的硬件设备。Binder驱动运行于内核态,可提供openioctlmmap等常用的文件操作。Android系统基本上可以看作是一个基于Binder通信的C/S架构,Binder主要实现以下功能:

  • 用驱动程序来推进进程间的通信
  • 通过共享内存来提高性能
  • 为进程请求分配每个进程的线程池
  • 针对系统中的对象引入引用计数和跨进程的对象引用映射
  • 进程间同步调用

##1.Binder设备结构 Binder驱动源码在Kernel工程中,以常规的misc设备来分析源码,先来看一下驱动注册入口和函数的对应关系:

// misc设备结构体
static struct miscdevice binder_miscdev = {
	// 动态分配次设备号
	.minor = MISC_DYNAMIC_MINOR,
	// 驱动名称
	.name = "binder",
	// Binder驱动支持的文件操作
	.fops = &binder_fops
};

static int __init binder_init(void)
{
	int ret;

	...
	ret = misc_register(&binder_miscdev);
	...
	return ret;
}

// 设备驱动接口,在linux misc设备中,使用module_init和module_exit是为同时兼容支持静态编译驱动和动态编译驱动,而Binder使用device_initcall的目的就是不让Binder驱动支持动态编译,而且需要在内核做镜像
device_initcall(binder_init);

上述代码中主要通过misc_register函数完成Binder设备的注册,binder_init函数通过device_initcall完成驱动初始化的时候调用,而且要求该驱动做内核镜像,且不支持动态编译。binder_miscdev结构中,定义了Binder设备驱动的名称和驱动支持的文件操作结构——binder_fops

static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};

其中使用最多的是binder_ioctlbinder_mmapbinder_open,而一般文件操作需要的read()和write()则没有出现,是因为它们的功能完全可以用ioctlmmap来替代,后者更加灵活。

###1.1 binder_open 上层进程在访问Binder驱动时,首先需要打开/dev/binder节点,这个操作是在binder_open()中完成的。binder_open()函数会在在_proc目录中创建只读文件/proc/binder/proc/$pid,作为当前binder_proc对象,然后将其加入到全局binder_procs的哈系表中,我们要知道任何进程都可以访问binder_procs的哈系表:

hlist_add_head(&proc->proc_node, &binder_procs);

binder_open函数就是初始化proc的过程,初始化完毕后,将初始化完毕的proc与传入参数filp关联,以后直接通过filp变量找到初始化的proc

binder_open(struct inode *nodp, struct file *filp)

传入参数filp->private_data = proc,在目录/proc/binder/proc/$pid中创建只读文件pid命名,但是该pid字段并不是当前进程、线程的id,而是线程组的pid,表示线程组中第一个线程的pid:

proc->pid = current->group_leader->pid;

下面来看第二个函数binder_mmap函数,mmap()函数可以把设备指定的内存块直接映射到应用程序的内存空间中,而Binder就是通过binder_mmap函数在进程间共享内存的。

###1.2 binder_mmap 代码如下:

/**
* 描述一块应用程序使用的虚拟内存,内核专门维护了vma的链表和树状结构,
* vma的功能是管理进程地址空间中不同区域的数据结构
*/
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	// area变量是Binder驱动中对虚拟内存的描述
	struct vm_struct *area;
	// proc是Binder驱动为应用进程分配的一个数据结构,用于存储和该进程有关的所有信息。这里取出进程对应的proc对象。
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;

	if (proc->tsk != current)
		return -EINVAL;

	// 当应用程序申请的内存超过4M则扩容
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;

	...
	// 加入写标识
	vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

	mutex_lock(&binder_mmap_lock);
	...

	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
	...
	
	// 将proc中的buffer指针指向虚拟内存的起始地址
	proc->buffer = area->addr;
	// 计算它和应用程序中相关联的虚拟内存地址(vma->vm_start)的偏移量
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
	mutex_unlock(&binder_mmap_lock);

	...
	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;

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	// binder_update_page_range真正开始申请物理页面
	// static int binder_update_page_range(struct binder_proc *proc, int allocate, void *start, void *end, struct vm_area_struct *vma)
	// proc: 申请内存的进程所持有的bind_proc对象
	// allocate:是申请还是释放。1申请0释放
	// start: Binder中虚拟内存起点(页表映射是从虚拟内存到物理内存)
	// end: 虚拟内存终点
	// vma:应用程序中虚拟内存段的描述
	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_insert_free_buffer(proc, buffer);
	...
}

###1.3 binder_ioctl 内存映射完成后,还有一个就是binder_ioctl函数,如下代码:

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);
	...

	binder_lock(__func__);
	// 查找或创建binder_thread结构体
	thread = binder_get_thread(proc);

	switch (cmd) {
	// 读写操作,可以用此向binder中读写数据
	case BINDER_WRITE_READ:
		// 【见小节1.3.1】
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
	// 设置支持的最大线程数。因为客户端可以并发向服务器端发送请求,
	// 如果Binder驱动发现当前的线程数量已经超过设置值,则会告知binder server停止启动新的线程
	case BINDER_SET_MAX_THREADS:
		if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
			ret = -EINVAL;
			goto err;
		}
		break;
	// ServiceManager专用,设置为binder管理
	case BINDER_SET_CONTEXT_MGR:
		ret = binder_ioctl_set_ctx_mgr(filp);
		if (ret)
			goto err;
		break;
	// 通知binder线程退出,每个线程在退出时都会告诉binder驱动释放相关资源,否则会有内存泄漏
	case BINDER_THREAD_EXIT:
		binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
			     proc->pid, thread->pid);
		binder_free_thread(proc, thread);
		thread = NULL;
		break;
	// 版本号
	case BINDER_VERSION: {
		struct binder_version __user *ver = ubuf;

		if (size != sizeof(struct binder_version)) {
			ret = -EINVAL;
			goto err;
		}
		if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
			     &ver->protocol_version)) {
			ret = -EINVAL;
			goto err;
		}
		break;
	}
	default:
		ret = -EINVAL;
		goto err;
	}
	ret = 0;
...
}

binder_ioctl函数提供了Binder的一些操作,其中有:

|处理命令 | 用途| |--| | BINDER_WRITE_READ|读写操作,可以用此向Binder中读写数据| | BINDER_SET_MAX_THREADS|设置最大线程数| | BINDER_SET_CONTEXT_MGR|ServiceManager专用| | BINDER_THREAD_EXIT|通知binder线程退出| |BINDER_VERSION|查询版本号|

####1.3.1 binder_ioctl_write_read static int binder_ioctl_write_read(struct file *filp, unsigned int cmd, unsigned long arg, struct binder_thread *thread) { int ret = 0; struct binder_proc *proc = filp->private_data; unsigned int size = _IOC_SIZE(cmd); void __user *ubuf = (void __user *)arg; struct binder_write_read bwr;

	if (size != sizeof(struct binder_write_read)) {
		ret = -EINVAL;
		goto out;
	}
	// 将用户空间bwr结构体拷贝到内核空间
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
	...

	if (bwr.write_size > 0) {
		// 将数据放入目标进程【见小节1.3.2】
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		trace_binder_write_done(ret);
		...
	}
	if (bwr.read_size > 0) {
		// 读取自己队列的数据 【见小节1.3.3】
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		trace_binder_read_done(ret);
		// 当进程的todo队列有数据,则唤醒在该队列等待的进程
		if (!list_empty(&proc->todo))
			wake_up_interruptible(&proc->wait);
		// 当执行失败,则直接将内核bwr结构体写回用户空间,并跳出该方法
		if (ret < 0) {
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
				ret = -EFAULT;
			goto out;
		}
	}
	...
}

binder_ioctl_write_read函数完成对数据对进程的读和写,读和写的两个函数前两个参数分为别procthread

  • proc文件操作柄,由上层传递过来,应该就是打开 /dev/binder 驱动的句柄。
  • thread结构为binder_thread

binder_thread数据结构为:

struct binder_thread {
	struct binder_proc *proc;
	struct rb_node rb_node;
	int pid;
	int looper;
	struct binder_transaction *transaction_stack;
	struct list_head todo;
	uint32_t return_error; 
	wait_queue_head_t wait;
	struct binder_stats stats;
};

####1.3.2 binder_thread_write

switch (cmd) {
case BC_TRANSACTION:
case BC_REPLY: {
	struct binder_transaction_data tr;
	// 将ptr从用户空间拷贝内核空间
	if (copy_from_user(&tr, ptr, sizeof(tr)))
		return -EFAULT;
	ptr += sizeof(tr);
    // 见小节【1.3.4】
	binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
	break;

这里只是列举了处理BC_TRANSACTION的过程,调用binder_transaction函数,将binder_transaction_data数据结构传递给当前thread对象。tr变量就是binder_transaction_data结构。

struct binder_transaction_data {
	union {
		/* target descriptor of command transaction */
		__u32	handle;
		/* target descriptor of return transaction */
		binder_uintptr_t ptr;
	} target;
	binder_uintptr_t	cookie;	/* target object cookie */
	__u32		code;		/* transaction command */

	/* General information about the transaction. */
	__u32	        flags;
	pid_t		sender_pid;
	uid_t		sender_euid;
	binder_size_t	data_size;	/* number of bytes of data */
	binder_size_t	offsets_size;	/* number of bytes of offsets */

	...
};

####1.3.3 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) { ... // 从线程todo队列获取事务数据 while (1) { uint32_t cmd; struct binder_transaction_data tr; struct binder_work *w; struct binder_transaction *t = NULL;

		...

		switch (w->type) {
		case BINDER_WORK_TRANSACTION: {
			// 获取transaction数据
			t = container_of(w, struct binder_transaction, work);
		} break;		
		}
		if (t->buffer->target_node) {
			// 获取目标node
			struct binder_node *target_node = t->buffer->target_node;
			tr.target.ptr = target_node->ptr;
			...
			cmd = BR_TRANSACTION;//设置命令为BR_TRANSACTION
		} else {
			tr.target.ptr = 0;
			tr.cookie = 0;
			cmd = BR_REPLY; //设置命令为BR_REPLY
		}
		tr.code = t->code;
		tr.flags = t->flags;
		tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);

		...
		// 将cmd和数据写回用户空间
		if (put_user(cmd, (uint32_t __user *)ptr))
			return -EFAULT;
			
		...
		if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
			t->to_parent = thread->transaction_stack;
			t->to_thread = thread;
			thread->transaction_stack = t;
		} else {
			t->buffer->transaction = NULL;
			// 通信完成,则运行释放
			kfree(t);
			binder_stats_deleted(BINDER_STAT_TRANSACTION);
		}
		break;
	}
	return 0;
}

####1.3.4 binder_transaction static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){ struct binder_transaction *t; struct binder_work *tcomplete; binder_size_t *offp, *off_end; binder_size_t off_min; struct binder_proc *target_proc; struct binder_thread *target_thread = NULL; struct binder_node *target_node = NULL; struct list_head *target_list; wait_queue_head_t *target_wait; struct binder_transaction *in_reply_to = NULL;

    if (reply) {
        ...
    }else {
        if (tr->target.handle) {
            struct binder_ref *ref;
            // 由handle 找到相应 binder_ref, 由binder_ref 找到相应 binder_node
            ref = binder_get_ref(proc, tr->target.handle);
            target_node = ref->node;
        } else {
            target_node = binder_context_mgr_node;
        }
        // 由binder_node 找到相应 binder_proc
        target_proc = target_node->proc;
    }


    if (target_thread) {
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;
        target_wait = &target_thread->wait;
    } else {
        //首次执行target_thread为空
        target_list = &target_proc->todo;
        target_wait = &target_proc->wait;
    }

    t = kzalloc(sizeof(*t), GFP_KERNEL);
    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);

    //非oneway的通信方式,把当前thread保存到transaction的from字段
    if (!reply && !(tr->flags & TF_ONE_WAY))
        t->from = thread;
    else
        t->from = NULL;

    t->sender_euid = task_euid(proc->tsk);
    t->to_proc = target_proc; //此次通信目标进程为system_server
    t->to_thread = target_thread;
    t->code = tr->code;  //此次通信code = START_SERVICE_TRANSACTION
    t->flags = tr->flags;  // 此次通信flags = 0
    t->priority = task_nice(current);

    //从目标进程target_proc中分配内存空间
    t->buffer = binder_alloc_buf(target_proc, tr->data_size,
        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));

    t->buffer->allow_user_free = 0;
    t->buffer->transaction = t;
    t->buffer->target_node = target_node;

    if (target_node)
        binder_inc_node(target_node, 1, 0, NULL); //引用计数加1
    offp = (binder_size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));

    //分别拷贝用户空间的binder_transaction_data中ptr.buffer和ptr.offsets到内核
    copy_from_user(t->buffer->data,
        (const void __user *)(uintptr_t)tr->data.ptr.buffer, tr->data_size);
    copy_from_user(offp,
        (const void __user *)(uintptr_t)tr->data.ptr.offsets, tr->offsets_size);

    off_end = (void *)offp + tr->offsets_size;

    for (; offp < off_end; offp++) {
        struct flat_binder_object *fp;
        fp = (struct flat_binder_object *)(t->buffer->data + *offp);
        off_min = *offp + sizeof(struct flat_binder_object);
        switch (fp->type) {
        ...
        case BINDER_TYPE_HANDLE:
        case BINDER_TYPE_WEAK_HANDLE: {
            //处理引用计数情况
            struct binder_ref *ref = binder_get_ref(proc, fp->handle);
            if (ref->node->proc == target_proc) {
                if (fp->type == BINDER_TYPE_HANDLE)
                    fp->type = BINDER_TYPE_BINDER;
                else
                    fp->type = BINDER_TYPE_WEAK_BINDER;
                fp->binder = ref->node->ptr;
                fp->cookie = ref->node->cookie;
                binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
            } else {    
                struct binder_ref *new_ref;
                new_ref = binder_get_ref_for_node(target_proc, ref->node);
                fp->handle = new_ref->desc;
                binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
            }
        } break;
        ...

        default:
            return_error = BR_FAILED_REPLY;
            goto err_bad_object_type;
        }
    }

    if (reply) {
        //BC_REPLY的过程
        binder_pop_transaction(target_thread, in_reply_to);
    } else if (!(t->flags & TF_ONE_WAY)) {
        //BC_TRANSACTION 且 非oneway,则设置事务栈信息
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;
        thread->transaction_stack = t;
    } else {
        //BC_TRANSACTION 且 oneway,则加入异步todo队列
        if (target_node->has_async_transaction) {
            target_list = &target_node->async_todo;
            target_wait = NULL;
        } else
            target_node->has_async_transaction = 1;
    }

    //将BINDER_WORK_TRANSACTION添加到目标队列,本次通信的目标队列为target_proc->todo
    t->work.type = BINDER_WORK_TRANSACTION;
    list_add_tail(&t->work.entry, target_list);

    //将BINDER_WORK_TRANSACTION_COMPLETE添加到当前线程的todo队列
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
    list_add_tail(&tcomplete->entry, &thread->todo);

    //唤醒等待队列,本次通信的目标队列为target_proc->wait
    if (target_wait)
        wake_up_interruptible(target_wait);
    return;
}

主要功能:

  1. 查询目标进程的过程: handle->binder_ref->binder_node->binder_proc
  2. BINDER_WORK_TRANSACTION添加到目标队列target_list:
  • call事务, 则目标队列target_list=target_proc->todo;
  • reply事务,则目标队列target_list=target_thread->todo;
  • async事务,则目标队列target_list=target_node->async_todo.
  1. 设置事务栈信息 BC_TRANSACTION且非oneway, 将当前事务添加到thread->transaction_stack 事务分发过程:
  • BINDER_WORK_TRANSACTION添加到目标队列(此时为target_proc->todo队列);
  • BINDER_WORK_TRANSACTION_COMPLETE添加到当前线程thread->todo队列;
  1. 唤醒目标进程target_proc开始执行事务。

该方法中proc/thread是指当前发起方的进程信息,而binder_proc是指目标接收端进程。 此时当前线程thread的todo队列已经有事务, 接下来便会进入binder_thread_read来处理相关的事务.

####1.3.5 下一步何去何从

  1. 执行完binder_thread_write方法后, 通过binder_transaction()首先写入BINDER_WORK_TRANSACTION_COMPLETE写入当前线程.
  2. 这时bwr.read_size > 0, 回到binder_ioctl_write_read方法, 便开始执行binder_thread_read();
  3. binder_thread_read()方法, 将获取cmd=BR_TRANSACTION_COMPLETE, 再将cmd和数据写回用户空间;
  4. 一次Binder_ioctl完成,接着回调用户空间方法talkWithDriver(),刚才的数据以写入mIn.
  5. 这时mIn有可读数据, 回到IPC.waitForResponse()方法,完成BR_TRANSACTION_COMPLETE过程. 如果本次transaction采用非oneway方式, 这次Binder通信便完成, 否则还是要等待Binder服务端的返回。

对于startService过程, 采用的便是非oneway方式,那么发起者进程还会继续停留在waitForResponse()方法,继续talkWithDriver(),然后休眠在binder_thread_read()wait_event_freezable()过程,等待当前线程的todo队列有数据的到来,即等待收到BR_REPLY消息.

由于在前面binder_transaction()除了向自己所在线程写入了BINDER_WORK_TRANSACTION_COMPLETE, 还向目标进程(此处为system_server)写入了BINDER_WORK_TRANSACTION命令。

#Binder知识 Binder机制源于OpenBinder ,OpenBinder由Be Inc. 开发,后来的Palm, Inc.基金会开发Binder机制,也是OpenBinder的定制版,在Android操作系统上得到广泛的应用,OpenBinder在Linux 3.19版本合入内核源码,发布时间是2015.2.8日。

OpenBinder运行当前进程接口被其它线程调用。每一个进程管理一个线程池用于处理服务请求。OpenBinder考虑引用计数,将创建线程递归合入原始线程,当然也包括进程内部交流的线程。在Linux上,OpenBinder使用ioctls函数,在给定描述符后与底层驱动打交道。

在源码篇,函数binder_ioctl函数对Binder驱动进行读写,标志为BINDER_WRITE_READ,这个上面都有提到,发送的原理图如下所示:

Binder通信过程

图1.1 Binder进程间通信过程

图中我们可以看到Process A通过封装命令BC_TRANSACTION,调用方法:

ioctl(fd, BINDER_WRITE_READ, &bwt);

将数据发送给Binder驱动,Process B接收到命令,回给BR_TRANSACTION应答,此时Process A将binder_transaction_data数据结构传递给Process B。

#参考

OpenBinder website

"Linux kernel 3.19, Section 1.4. Android binder moved to stable"

© 著作权归作者所有

小米墨客
粉丝 67
博文 16
码字总数 65581
作品 0
海淀
程序员
私信 提问
Android系统学习总结2--Binder 机制

Binder 学习http://blog.csdn.net/ylyuanlu/article/details/6629332 Binder 的应用模型 一个IPC通讯我们可以简单的理解成客户端-服务器模式,客户端请求服务,服务端接收到客户端请求后处理...

jince
2014/09/12
197
2
Android 的 Binder 机制概念介绍

结合了以下两篇文章的介绍,对Android 的 Binder 机制概念开始有了一定的理解。分享给大家。 -------------------------------------分割线--------------------------------- 摘要 Binder是...

Freewheel
2015/11/28
1K
0
Binder学习(二)Binder机制解析

概述 由于Android是基于Linux内核的操作系统,所以在了解Binder级之前需要先了解一些关于Linux的知识,进程隔离以及虚拟地址: 进程隔离 进程隔离是为保护操作系统中进程互不干扰而设计的一组...

wustor
2017/11/25
0
0
02.Android之IPC机制问题

目录介绍 2.0.0.1 什么是Binder?为什么要使用Binder?Binder中是如何进行线程管理的?总结binder讲的是什么? 2.0.0.2 Android中进程和线程的关系?什么是IPC?为何需要进行IPC?多进程通信...

潇湘剑雨
01/07
39
0
Android 通信篇 -- 深入剖析Binder原理

Binder 概述 Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是...

DeepCoder_Marco
2018/09/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Docker下使用disconf:细说demo开发

Docker下的disconf实战全文链接 《Docker搭建disconf环境,三部曲之一:极速搭建disconf》; 《Docker搭建disconf环境,三部曲之二:本地快速构建disconf镜像》; 《Docker搭建disconf环境,...

程序员欣宸
30分钟前
6
0
centos7配置nfs共享存储服务

nfs 是一种网络文件系统,需要依赖rpc进行过程调度 注意nfs只验证id,验证用户名,并且只能在类unix os上进行文件共享服务,由于它的脆弱的验证机制,所以不适宜在internet上工作,在内网使用...

老孟的Linux私房菜
34分钟前
8
0
【F5小常识】F5的 Web 应用防火墙 (WAF)有什么优势?

     现如今传统防火墙已无法满足企业安全需求,网络攻击大多发生在应用层和网络层故障,且呈上升趋势,传统的防火墙存在着很大的不足之处,包括无法检测加密的Web流量、无法扩展深度检测...

梅丽莎好
45分钟前
4
0
整合到 Mockito 2

为了能够持续改进 Mockito 和在未来提升测试体验,我们希望你能够升级到 Mockito 2.10!Mockito 按照语义化版本(semantic versioning)的方式对版本进行编排,并且只在主版本升级的时候包含...

honeymoose
45分钟前
4
0
spring boot actuator

actuator 是监控系统健康的工具,引入 spring-boot-starter-actuator会暴露一些endpoint. 可通过如下配置来配置这些endpoint的基本配置: 可通过http:${url}:28081/management/actuator/*来访...

ZH-JSON
51分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部