对于android开发者来说,相信binder都听说过,但是binder不像四大组件那些之类的,具体的我们可以很直观感受到的模块,好像总是有一种说不上但是时时刻刻存在的感觉。的确,binder对于我们平时开发虽然接触没有直接的感觉,但是他确存在于几乎每一个程序之间,甚至可以说是最重要的基石之一,如果理解了binder,不光对于平时的系统和应用开发,甚至对整个android的系统体系的认识都会有一个不小的提高,所以准备把对于bander的理解记录下来,虽然说还不是认识的很全面,但是对于提高我们平日工作开发的帮助,应该还是有一些的。
Binder是进程间通信的一种方式,虽然linux已经有些不少的进程间通信方法,但是android几乎大部分的设计进程通信的模块都是采用binder来处理的。对于我的理解,一方面binder虽然也是很复杂,但是相对来说还算是可控的。我们知道进程间通信最主要的就是涉及到数据交互的问题,说白了就是不同进程的数据传递需要在不同进程在不断的开辟新的缓冲区,然后在进程间进行数据的复制,这样的开销是很大的,android作为一个移动设备对性能的要求是很高的,一切性能的问题对用户立马回造成卡顿等的影响,这个相信大多数用户也都遇到过,所以android如果采取普通的比如管道通信之类的,可能性能上就不能满足。其次,如果开辟一个缓冲区,让两个进程都映射到这个缓冲区,这样进程直接可以直接读写,性能确实是很高,但是这样理论上说也就不需要通过内核了,双发直接都可以读取对方的数据,安全性来说就不能得到保证,另外如果内核想控制安全等方面的问题,由于双方进程对这片区域的控制权太大,势必造成复杂度提升的问题。介于种种方面的考虑,android采用的binder作为进程间通信的主要手段,一方面binder在数据传输方面,由于内核和server会共享同一片缓冲区,所以对于server这端的传输数据来说可以直接读取,另一方面由于进程间的通信还是通过内核控制的,所以对于安全的控制也是有保障。所以Binder最终会被选择作为android的进程间通信手段。
写文章之前一直在想以什么切入点作为binder的开始,由于几乎所有的系统模块都会涉及都binder,而所有这些模块有个最重要的根模块,那就是service manager。我们熟悉的什么AMS,PMS等等都是注册在servie manger上面了,然后上层的应用再通过比如AMS来启动相应的程序,所以就决定从service manager作为binder的一个切入点,包括他的启动,然后怎么添加一个服务给他,又怎么读取一个服务,期间代码中遇到binder具体的内容,就顺着代码讲,整个流程下来基本binder的基本框架都包含了,对于service manager也理解了。后序上层的AMS等通信都是基于这个流程的,所以决定切入点就从service manager开始,不过在说service manager之前,我说一下binder的初始化,binder是在linux的内核中初始化的,只有binder初始化好后,service manager才能开始工作,所以我们先说下binder初始化工作。
## Binder初始化
```c
binder.c
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "binder",
.fops = &binder_fops
};
static int __init binder_init(void)
{
int ret;
binder_proc_dir_entry_root = proc_mkdir("binder", NULL); // 创建proc/binder/目录
if (binder_proc_dir_entry_root)
binder_proc_dir_entry_proc = proc_mkdir("proc", binder_proc_dir_entry_root);// 创建proc/binder/proc目录
ret = misc_register(&binder_miscdev); // 注册binder的一些操作方法
if (binder_proc_dir_entry_root) {
create_proc_read_entry("state", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_state, NULL);
create_proc_read_entry("stats", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_stats, NULL);
create_proc_read_entry("transactions", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transactions, NULL);
create_proc_read_entry("transaction_log", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transaction_log, &binder_transaction_log);
create_proc_read_entry("failed_transaction_log", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transaction_log, &binder_transaction_log_failed);
}
return ret;
}
```
这段代码先创建一个proc/binder/proc,之后所有用到binder通信的服务都会创建一个文件,文件名是进程的id,后面我们会看到这个。misc_register这个方法是注册了binder驱动的一些操作方法,我们主要看binder_miscdev这个结构体,其中.fops = &binder_fops这个指定了具体的一些操作方法:
```c
static struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
```
这里的一些方法很重要,我们在后面分析binder通信的时候会进程看到,比如binder_ioctl就是binder读写数据时候调用的方法,binder_mmap内存映射时候调用的方法,binder_open这个是创建一个binder文件时候的方法,这里前面的函数名是对外调用时候用的,后面的函数名指向真实的函数地址。我们后面遇到的时候说。回到前面__init binder_init方法,接着继续在proc/binder目录下调用create_proc_read_entry方法创建了几个文件,用来记录log。好了binder的初始化就说到这里了,我们下面开始说service manager的启动。
## service manager启动
```c
#define BINDER_SERVICE_MANAGER ((void*) 0) // service manager对应的本地对象地址值是0
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER; // service manager对应的本地对象地址值是0
// 打开/dev/binder设备文件,并进行空间映射。建立service manager结构体,以及缓冲区映射到service manager进程
bs = binder_open(128*1024);
// 注册service manager到binder,其实就是建立binder实体对象,service manager没有本地对象
if (binder_become_context_manager(bs)) {
LOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr; // 把service manager对应的本地对象地址值保存在一个全局变量中
binder_loop(bs, svcmgr_handler); // 等待client的请求
return 0;
}
```
我们如果自己开发一个service是运行在用户态中的,每一个用户态的service都有一个对应的地址,而由于service比较特殊,他是管理着所有的服务的,所以binder中把他的地址看作0。这个想想也不难理解。接着马上调用了binder_open方法,这个方法是创建一个binder文件,我们看这个方法:
```c
struct binder_state // 描述service manager进程和binder设备文件映射的结构体
{
int fd; // 文件描述符
void *mapped; // 映射起始地址
unsigned mapsize; // 映射空间大小
};
## binder_open方法
struct binder_state *binder_open(unsigned mapsize)
{
struct binder_state *bs;
bs = malloc(sizeof(*bs));
if (!bs) {
errno = ENOMEM;
return 0;
}
bs->fd = open("/dev/binder", O_RDWR); // 调用binder.c的binder_open函数被调用
if (bs->fd < 0) {
fprintf(stderr,"binder: cannot open device (%s)\n",
strerror(errno));
goto fail_open;
}
bs->mapsize = mapsize;
// 从内核划出一块缓冲区让用户进程也可以操作,所以需要把内核缓冲区映射到用户进程中
// 把文件映射到内存中,返回内存地址. mmap参数: 第一个null表示内存线性区的首地址,让内核自己选所以null,mapsize需要映射文件的长度,PROT_READ是读访问,MAP_PRIVATE私有型,不允许回写设备
// fd文件描述符,是一个整型,表示的是打开的文件,这里打开了一个binder文件,fd即可以理解成这个文件有个标识,把这个标识返回给用户进程,之后用户进程就可以用来操作这个文件相关的了。0是文件映射开始的偏移位置
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); // 调用 内核mmap
if (bs->mapped == MAP_FAILED) {
fprintf(stderr,"binder: cannot map device (%s)\n",
strerror(errno));
goto fail_map;
}
/* TODO: check version */
return bs;
fail_map:
close(bs->fd);
fail_open:
free(bs);
return 0;
}
```
首先创建一个binder_state结构体,该结构体保存了service manger的文件描述符,在内核中的映射内容起始地址,以及映射内存的大小,从前面传进来的参数可知,大小是128KB。接着调用了open方法,还记得最开始介绍binder初始化里面有个open方法吗,这里就是调用那里的方法在proc/binder/proc下创建一个文件,我们看下这个方法:
```c
// 打开binder设备,获取文件描述符。其他需要使用binder的进程会通过系统调用或者门调用等方法进入内核进程才能打开binder设备文件,所以这里打开binder的方法是在内核进程的,下面数据也都是在内核进程的,所以后面还需要使用mmap来映射到用户进程
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc; // proc就是表示server进程
if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE) // debug下打印些信息
printk(KERN_INFO "binder_open: %d:%d\n", current->group_leader->pid, current->pid);
proc = kzalloc(sizeof(*proc), GFP_KERNEL); //创建结构体,描述使用binder的进程信息
if (proc == NULL)
return -ENOMEM;
get_task_struct(current); // 获取当前任务控制块
proc->tsk = current; // 初始化任务块变量
INIT_LIST_HEAD(&proc->todo); // 初始化带处理任务列表
init_waitqueue_head(&proc->wait); // 初始化线性带唤醒列表
proc->default_priority = task_nice(current); // 初始化当前进程优先级
mutex_lock(&binder_lock);
binder_stats.obj_created[BINDER_STAT_PROC]++;
hlist_add_head(&proc->proc_node, &binder_procs); // binder_procs是哈希队列,把当前进程加入队列,这个队列中有所有使用binder的进程。即这里有个全局的hash表,保存着所有server端的binder
proc->pid = current->group_leader->pid; // 初始化进程组ID
INIT_LIST_HEAD(&proc->delivered_death); // 初始化死亡通知的队列
// 这里调用open的比如是一个server,那么内核就会返回给server一个binder的文件描述符给server(注意,binder驱动可能是一个文件,如果被多个进程打开,会返回给多个不同的文件描述符),这里的filp就是代表了文件描述符对应的设备,即有可能都是binder驱动,但是有几个不同的文件描述符,就有几个不同的filp,所以里面的proc逻辑上也是不同的
filp->private_data = proc; // 这里filp可以理解成就代表当前打开的文件(即一个进程),里面包含了这个文件的所有信息,包括读写操作方法等。把现在创建的这个binder使用的结构体(即server进程)保存到binder设备文件中
mutex_unlock(&binder_lock);
if (binder_proc_dir_entry_proc) { // binder_proc_dir_entry_proc是前面创建的/proc/binder/proc
char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); // 以本进程ID为名字的字符串
remove_proc_entry(strbuf, binder_proc_dir_entry_proc);
create_proc_read_entry(strbuf, S_IRUGO, binder_proc_dir_entry_proc, binder_read_proc_proc, proc); // 创建这个进程名的文件,数据是前面创建的proc,读取方法是binder_read_proc_proc
}
return 0;
}
```
首先创建一个binder_proc进程结构体,这个结构体描述了正在使用binder的进程,结构体具体描述如下:
```cstruct binder_proc { // 描述一个正在使用binder的进程
struct hlist_node proc_node; // 全局使用binder进程列表中的一个节点
struct rb_root threads; // Binder线程的红黑树根节点,以ID为关键字
struct rb_root nodes; // binder实体对象,以ptr为关键字,binder_node类
struct rb_root refs_by_desc; // binder引用对象,以desc为关键字 , binder_ref类
struct rb_root refs_by_node; // binder引用对象,以node为关键字 , binder_ref类
int pid; // 进程组ID
struct vm_area_struct *vma; // 缓冲区在用户空间的虚拟地址
struct task_struct *tsk; // 任务控制块
struct files_struct *files; // 打开的文件结构体数组
struct hlist_node deferred_work_node; // 可以延迟执行的工作项
int deferred_work; // 延迟工作项的类型
void *buffer; // 内核缓冲区的起始地址(虚拟地址)
ptrdiff_t user_buffer_offset; // 缓冲区内核空间和用户空间地址的差值
struct list_head buffers; // 缓冲区在内核空间的虚拟地址,这里是一个链表,缓冲区被划分成好多块,每块是一个binder_buffer
struct rb_root free_buffers; // 空闲的缓冲区块,一颗红黑树
struct rb_root allocated_buffers; // 已经分配的缓冲区块,是一个红黑树节点
size_t free_async_space; // 可以用来保存异步事务数据的内核缓冲区大小
struct page **pages; // 缓冲区物理页面地址的数组,一个页面大小是4KB
size_t buffer_size; // 内核缓冲区大小,即上面*buffer的大小
uint32_t buffer_free; // 空闲缓冲区块大小
struct list_head todo; // 待处理工作队列
wait_queue_head_t wait; // 空闲睡眠线程队列
struct binder_stats stats; // 统计进程数据,比如接受到进程间通信的次数
struct list_head delivered_death; // 正在发送死亡通知的工作项,BINDER_WORK_DEAD_BINDER或者BINDER_WORK_DEAD_BINDER_AND_CLEAR
int max_threads; // binder最多可以请求驱动注册多少个线程,即下面requested_threads_started<=max_threads
int requested_threads; // binder驱动正在请求增加线程的数量,请求完成后会减1
int requested_threads_started;// binder驱动已经请求注册多少个线程了
int ready_threads; // 当前空闲线程数量
long default_priority; // 默认设置为宿主进程的优先级
};
```
这个结构体具体意思注释中已经写了,就不多说了,继续说binder_open方法,在创建了binder_proc结构体后,初始化一些变量后,我们看到一个方法
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); 这个方法想必大家都听说过,就是内存映射的方法,具体来说这里就是在内核中划出一块mapsize大小的内存和service manager共享,这样service manager和内核之间的数据交换就不需要复制了,双放直接在这片内存区域中操作就可以了,service manager做为一个服务端,在binder机制里面都是这样来处理的,比如我们自己写一个service也会这样处理,所以这句话是比较重要的。接下去看到最后面
```c
if (binder_proc_dir_entry_proc) { // binder_proc_dir_entry_proc是前面创建的/proc/binder/proc
char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); // 以本进程ID为名字的字符串
remove_proc_entry(strbuf, binder_proc_dir_entry_proc);
create_proc_read_entry(strbuf, S_IRUGO, binder_proc_dir_entry_proc, binder_read_proc_proc, proc); // 创建这个进程名的文件,数据是前面创建的proc,读取方法是binder_read_proc_proc
}
```
这段方法就是在/proc/binder/proc目录下创建service manager的pid为名字的文件。至此,service manager在binder的文件就创建好了。我们回到main方法后
会继续调用binder_become_context_manager这个方法:
## binder_become_context_manager方法
```c
// 注册service manager到binder,主要是建立service manager的实体对象
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
```
我们知道后续我们的系统服务都会注册到service manager中,但是servie manager他又会注册给谁呢,这个方法就是给service manager注册的。这个方法里面我们看到调用的ioctl,我们在回想在最开始binder初始化里面,是不是有个和读写相关的方法,这个方法会最终调用到那里的binder_ioctl方法,这个是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;
/*printk(KERN_INFO "binder_ioctl: %d:%d %x %lx\n", proc->pid, current->pid, cmd, arg);*/
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
return ret;
mutex_lock(&binder_lock);
thread = binder_get_thread(proc); // 为这个进程创建一个线程结构体
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
switch (cmd) {
............
case BINDER_SET_CONTEXT_MGR:
if (binder_context_mgr_node != NULL) { // service manager的实体对象是否已经有了,如果有了退出
printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
ret = -EBUSY;
goto err;
}
if (binder_context_mgr_uid != -1) { // 有效用户id非等于-1
if (binder_context_mgr_uid != current->cred->euid) { // 如果和当前进程有效用户id不一样退出,如果一样,同一个进程还可以重复注册,说不定之前这个进程注册的时候出错了。但是不同进程就不可以了
printk(KERN_ERR "binder: BINDER_SET_"
"CONTEXT_MGR bad uid %d != %d\n",
current->cred->euid,
binder_context_mgr_uid);
ret = -EPERM;
goto err;
}
} else // 到这里说明service manager没有注册过
binder_context_mgr_uid = current->cred->euid; // 复制有效用户id
// 新建service manager实体对象。参数2本地对象多引用地址,参数3本地对象地址。service manager都是0,所以这里都是null。这里的null应该和service_manager.c中的BINDER_SERVICE_MANAGER是一样的意思
binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
if (binder_context_mgr_node == NULL) { // null说明已经有这个实体对象了
ret = -ENOMEM;
goto err;
}
binder_context_mgr_node->local_weak_refs++; // 增加实体对象对本地对象的内部弱引用计数
binder_context_mgr_node->local_strong_refs++;// 增加实体对象对本地对象的内部强引用计数
binder_context_mgr_node->has_strong_ref = 1; // 本地对象标识有强引用
binder_context_mgr_node->has_weak_ref = 1; // 本地对象标识有弱引用
break;
............
}
ret = 0;
err:
if (thread)
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;
}
```
这个方法开始我们看到会调用binder_get_thread这个方法来创建一个线程,可见每个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; // 获取当前进程的binder线程
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;
}
if (*p == NULL) { // 如果是null,说明没有找到当前线程
thread = kzalloc(sizeof(*thread), GFP_KERNEL); // 创建一个线程结构体空间
if (thread == NULL)
return NULL;
binder_stats.obj_created[BINDER_STAT_THREAD]++;
thread->proc = proc; // 线程的宿主进程
thread->pid = current->pid;
init_waitqueue_head(&thread->wait); // 初始化线程等待队列
INIT_LIST_HEAD(&thread->todo); // 初始化线程todo队列
rb_link_node(&thread->rb_node, parent, p); // 把创建的线程结构体插入红黑树
rb_insert_color(&thread->rb_node, &proc->threads); // 染色调整红黑树
// 设置线程状态,该线程在binder_ioctl中还需要有初始化,所以现在设置BINDER_LOOPER_STATE_NEED_RETURN表示不可用,后面初始化完后会再设置。
thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
thread->return_error = BR_OK;
thread->return_error2 = BR_OK;
}
return thread;
}
```
没有进程都有一个binder的线程队列,队列中的线程就是帮助进程来处理binder任务的,他们以进程的pid为关键字组成一颗红黑树,这个方法就是寻找是否有binder线程如果有的话就返回,没有的话就创建一个。这里特别提醒一下,注意这句话thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN这个代表当前权限还没有初始化完毕,所以是不可用状态,后面还会提到这点。回到前面方法,接下去是一个switch,我们这里初始化service manager的case是BINDER_SET_CONTEXT_MGR:
```c
case BINDER_SET_CONTEXT_MGR:
if (binder_context_mgr_node != NULL) { // service manager的实体对象是否已经有了,如果有了退出
printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
ret = -EBUSY;
goto err;
}
if (binder_context_mgr_uid != -1) { // 有效用户id非等于-1
if (binder_context_mgr_uid != current->cred->euid) { // 如果和当前进程有效用户id不一样退出,如果一样,同一个进程还可以重复注册,说不定之前这个进程注册的时候出错了。但是不同进程就不可以了
printk(KERN_ERR "binder: BINDER_SET_"
"CONTEXT_MGR bad uid %d != %d\n",
current->cred->euid,
binder_context_mgr_uid);
ret = -EPERM;
goto err;
}
} else // 到这里说明service manager没有注册过
binder_context_mgr_uid = current->cred->euid; // 复制有效用户id
// 新建service manager实体对象。参数2本地对象多引用地址,参数3本地对象地址。service manager都是0,所以这里都是null。这里的null应该和service_manager.c中的BINDER_SERVICE_MANAGER是一样的意思
binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
if (binder_context_mgr_node == NULL) { // null说明已经有这个实体对象了
ret = -ENOMEM;
goto err;
}
binder_context_mgr_node->local_weak_refs++; // 增加实体对象对本地对象的内部弱引用计数
binder_context_mgr_node->local_strong_refs++;// 增加实体对象对本地对象的内部强引用计数
binder_context_mgr_node->has_strong_ref = 1; // 本地对象标识有强引用
binder_context_mgr_node->has_weak_ref = 1; // 本地对象标识有弱引用
break;
```
在内核binder中会对应有每一个server进程的实体对象,用结构体binder_node来描述,上面方法就是创建servie manager的实体对象,如果没有service manager的实体对象,就binder_context_mgr_uid = current->cred->euid先复制当前进程的uid,然后调用binder_new_node方法创建service manager的实体对象
```c
struct binder_node { // 用来描述一个binder实体对象,每个service的binder本地对象,在binder驱动中都对应一个binder实体对象
int debug_id; // 用来调试的一个身份标志
struct binder_work work; // 一个工作项
union {
struct rb_node rb_node; // 宿主进程中会维护所有他的binder实体对象,这个节点就是维护红黑树中的结点
struct hlist_node dead_node; // 如果binder实体对象死亡,这个对象的dead_node会被保存在一个全局hash表中
};
struct binder_proc *proc; // 指向binder实体的宿主进程,即一个用户空间的service进场
struct hlist_head refs; // 所有引用这个binder实体对象的引用对象(反正就是client)都会保存在这里
int internal_strong_refs; // 外部强引用计数
int local_weak_refs; // 对本地对象弱引用计数,有1加1
int local_strong_refs; // 对本地对象强引用计数,有1加1
void __user *ptr; // service的引用计数对象,weakref_impl
void __user *cookie; // service地址
unsigned has_strong_ref : 1; // 对本地对象是否有强引用,只有0或1,计数器>=1,这里才会变1,表示是否还对本地对应有引用
unsigned pending_strong_ref : 1; // 增加强引用过程中会置1,结束后置0
unsigned has_weak_ref : 1; // 对本地对象是否有弱引用,只有0或1,表示是否还对本地对应有引用
unsigned pending_weak_ref : 1; // 增加弱引用过程中会置1,结束后置0
unsigned has_async_transaction : 1;// 是否正在处理一个异步事务,是的话1,不是0
unsigned accept_fds : 1; // 是否允许结构包含文件描述符的数据, 1表示接受
int min_priority : 8; // service具备的最小优先级
struct list_head async_todo; // binder异步事务保存队列,同一时刻,只有1个binder异步对象会得到处理
};
binder_new_node(struct binder_proc *proc, void __user *ptr, void __user *cookie) // 创建一个进程的实体对象返回,参数2是本地对象弱引用对象地址,参数3是本地对象地址
{
struct rb_node **p = &proc->nodes.rb_node; // 进程的实体对象
struct rb_node *parent = NULL;
struct binder_node *node;
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; // 如果找到就返回一个null给调用者,因为前面调用的地方接受到null就退出提示已经创建过了
}
node = kzalloc(sizeof(*node), GFP_KERNEL); // 到这里,就说明没有找到实体对象,创建之
if (node == NULL)
return NULL;
binder_stats.obj_created[BINDER_STAT_NODE]++;
rb_link_node(&node->rb_node, parent, p); // 插入进程实体对象红黑树
rb_insert_color(&node->rb_node, &proc->nodes); // 染色调整
node->debug_id = ++binder_last_id; // 调试用的标志
node->proc = proc; // 实体对象的宿主进程,即service进程
node->ptr = ptr; // 实体对象的本地对象弱引用计数地址
node->cookie = cookie; // 实体对象的本地对象的地址
node->work.type = BINDER_WORK_NODE; //实体对象的工作项
INIT_LIST_HEAD(&node->work.entry); // 初始化工作项队列
INIT_LIST_HEAD(&node->async_todo); // 初始化异步任务队列
if (binder_debug_mask & BINDER_DEBUG_INTERNAL_REFS)
printk(KERN_INFO "binder: %d:%d node %d u%p c%p created\n",
proc->pid, current->pid, node->debug_id,
node->ptr, node->cookie);
return node; // 返回实体对象
}
```
可以看到,和前面创建线程一样,每个进程下面有一颗红黑树,上面保留了所有这个进程上的实体对象,所谓实体对象他的结构体中的字段很多,其中最重要的是ptr和cookie,他们指向了一个真实的server的地址,所以只要找到了实体对象就可以找到运行的程序,这样理解就可以了。这里service manager的实体对象创建好了,其实就是service manager的准备工作基本也就完成了,我们回到最前面的main方法,最后会调用binder_loop(bs, svcmgr_handler)方法。
## binder_loop方法
svcmgr_handler是service manager等待任务后回调执行的方法,我们后面执行到这步再说,bs即前面创建的service manager的结构体,里面有service manager的内存映射地址和文件描述符。我们继续看binder_loop这个方法:
```c
struct binder_write_read {
signed long write_size; /* bytes to write */ // 缓冲区大小
signed long write_consumed; /* bytes consumed by driver */ // binder驱动已经处理了多少
unsigned long write_buffer; // 传给binder的缓冲区,在用户空间
signed long read_size; /* bytes to read */ // 缓冲区大小
signed long read_consumed; /* bytes consumed by driver */ // 已经读取了多少
unsigned long read_buffer; // binder返回给用户的数据缓冲区,在用户空间
};
void binder_loop(struct binder_state *bs, binder_handler func) // service循环等待client请求
{
int res;
struct binder_write_read bwr;
unsigned readbuf[32]; // 这个缓冲区最后要是否
bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;
readbuf[0] = BC_ENTER_LOOPER; // 把线程变成binder线程的命令
binder_write(bs, readbuf, sizeof(unsigned)); // 把命令和参数传入binder的ioctl方法进行执行
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (unsigned) readbuf;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); // 把缓冲区传入ioctl中,通过BINDER_WRITE_READ命令检查是否有新的进程
if (res < 0) {
LOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
break;
}
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func); // 解析binder驱动返回的数据,添加service等操作
if (res == 0) {
LOGE("binder_loop: unexpected reply?!\n");
break;
}
if (res < 0) {
LOGE("binder_loop: io error %d %s\n", res, strerror(errno));
break;
}
}
}
```
从这个方法名字可以看出,这个方法是需要循环等待了。方法开头的结构体binder_write_read是用来为即将准备的读写做准备的,不论是进行读还是写,都会把交互的数据写入write缓冲器,然后这个结构体里面的字段就会指向write缓冲区,传入binder执行后,如果有返回数据,会写入read缓冲区,之后就从read缓冲区中读出数据。这里我们看到,会创建一个缓冲区readbuf,然后写入了一个命令BC_ENTER_LOOPER,调用方法binder_write:
```c
int binder_write(struct binder_state *bs, void *data, unsigned len) // 调用ioctl进行读写
{
struct binder_write_read bwr;
int res;
bwr.write_size = len; // 这里3个是输入缓冲区
bwr.write_consumed = 0;
bwr.write_buffer = (unsigned) data; // 设置输入缓冲区
bwr.read_size = 0; // 这里3个是输出缓冲区,这里读缓冲区设置为0,这样写写入命令后,就不会在binder驱动中等待了,会回到用户空间
bwr.read_consumed = 0;
bwr.read_buffer = 0;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
fprintf(stderr,"binder_write: ioctl failed (%s)\n",
strerror(errno));
}
return res;
}
```
这个方法我们看到3个read相关参数的值都是0,说明不需要返回的数据,wirte相关的主要就是执行BC_ENTER_LOOPER命令相关的方法,最后调用ioctl方法。
ioctl方法前面说过了,会调用到binder中binder_ioctl方法,这里我们看到调用ioctl的时候传入的命令是BINDER_WRITE_READ,而缓冲区里有保存着BC_ENTER_LOOPER命令,所以现在我们先看BINDER_WRITE_READ执行了些什么。这个方法前面在初始化service manager的时候我们也看过了,现在主要看下BINDER_WRITE_READ执行了些什么:
```c
..........
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto err;
}
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { // 从用户空间复制bwr数据到这里,这里初始化service manager的时候,对应的是service manager中的binder_write中调用的ioctl
ret = -EFAULT;
goto err;
}
if (binder_debug_mask & BINDER_DEBUG_READ_WRITE)
printk(KERN_INFO "binder: %d:%d write %ld at %08lx, read %ld at %08lx\n",
proc->pid, thread->pid, bwr.write_size, bwr.write_buffer, bwr.read_size, bwr.read_buffer);
if (bwr.write_size > 0) { // 如果写入的数据大小大于0,则开始处理BINDER_WRITE_READ命令,即service manager中把自己主线程注册为binder线程
ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
if (ret < 0) {
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto err;
}
}
if (bwr.read_size > 0) { // 如果read缓冲区大小大于0,即用户向binder驱动请求数据
ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait);
if (ret < 0) {
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto err;
}
}
if (binder_debug_mask & BINDER_DEBUG_READ_WRITE)
printk(KERN_INFO "binder: %d:%d wrote %ld of %ld, read return %ld of %ld\n",
proc->pid, thread->pid, bwr.write_consumed, bwr.write_size, bwr.read_consumed, bwr.read_size);
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}
break;
}
.........
```
我们看到方法开始有个copy_from_user方法,我们执行目前binder我们已经进入到了内核,而service manager和内核不是一个进程,所以我们向binder发请求,需要把数据从service manager的进程写入内核,所以这里copy_from_user就是把service manager数据地址ubuf指向的数据复制到内核的bwr处,然后调用binder_thread_write方法继续处理发过来的命令,binder_thread_write这个方法也有很多的处理case,我们现在只看相关的:
```c
// 进程给binder发送命令,比如用户空间的代理对象给binder引用对象。而read那个方法是用户态(client或server)项binder获取数据和命令
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
void __user *buffer, int size, signed long *consumed)
{
uint32_t cmd;
void __user *ptr = buffer + *consumed; // 用户缓冲区开始位置,这里是write方法,里面应该保存着上面传过来的参数
void __user *end = buffer + size; // 用户缓冲区结束位置
while (ptr < end && thread->return_error == BR_OK) {
if (get_user(cmd, (uint32_t __user *)ptr)) // 读取一个命令
return -EFAULT;
ptr += sizeof(uint32_t); // 指针前移
if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
binder_stats.bc[_IOC_NR(cmd)]++;
proc->stats.bc[_IOC_NR(cmd)]++;
thread->stats.bc[_IOC_NR(cmd)]++;
}
switch (cmd) {
case BC_ENTER_LOOPER: // 这里是注册为一个binder线程
if (binder_debug_mask & BINDER_DEBUG_THREADS)
printk(KERN_INFO "binder: %d:%d BC_ENTER_LOOPER\n",
proc->pid, thread->pid);
// 该线程是一个非binder线程,这里判断下,这个状态不能注册为binder线程
if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
thread->looper |= BINDER_LOOPER_STATE_INVALID;
binder_user_error("binder: %d:%d ERROR:"
" BC_ENTER_LOOPER called after "
"BC_REGISTER_LOOPER\n",
proc->pid, thread->pid);
}
thread->looper |= BINDER_LOOPER_STATE_ENTERED; // 注册为binder线程
break;
}
// 这句话很重要,在处理完所有write数据后,这里的consumed是write处理完后,数据结构体binder_write_read中write_consumed字段。
// 因为在返回用户进程后,会一直等待着下一次的transact,transact分为write和read,如果这里consume处理完毕了,那句write缓冲区数
// 据就没有数据了。下次transact进binder后就会只执行read的方法等待 server返回数据了
*consumed = ptr - buffer;
```
开头void __user *ptr = buffer + *consumed这句就是缓冲区数据的起始地址,之后调用get_user(cmd, (uint32_t __user *)ptr)读取一个32位的值到cmd中,记得之前存入缓冲区的值吗,没错就是BC_ENTER_LOOPER,这里我们看到会把thread->looper的置为BINDER_LOOPER_STATE_ENTERED表示这个是一个binder线程可以进行binder的任务处理了。记得前面我们初始化的的时候,在初始化线程的时候是把thread->looper设置为BINDER_LOOPER_STATE_NEED_RETURN的吗,现在把他改为BINDER_LOOPER_STATE_ENTERED后就算真正可以处理任务了。
方法最后*consumed = ptr - buffer 由于ptr读取完数据后前移了,所以更新*consumed的值,这样下次缓冲区的起始位置也前移了。
好了,BC_ENTER_LOOPER任务处理完了,主要就是把当前service manager的主线程变为binder线程,接下去可以等待任务处理了。我们回到前面binder_loop这个方法,接下去的命令:
```c
............
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (unsigned) readbuf;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); // 把缓冲区传入ioctl中,通过BINDER_WRITE_READ命令检查是否有新的进程
..............
```
可以看到,这里是个for循环,接下去同样会调用ioctl方法,也同样是BINDER_WRITE_READ,但是加入的read缓冲区,所以这次最终会调用到binder_thread_read这个方法里面:
```c
// client或者希望从binder中收到数据,所以会不断调用这个方法检查是否自己的todo中(包括进程todo和线程todo)有新任务,如果有该方法会解析并且把用户需要的数据写入用户缓冲区,然后返回给用户
// 在这个方法前,binder应该已经把任务写入的实体对象的todo或者他线程池中的线程的todo中,server或client进场会不断遍历他们,调用下面方法,把用户进程缓冲区地址传入后陷入内核态,然后执行完后返回用户态,读取数据来执行
static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread,
void __user *buffer, int size, signed long *consumed, int non_block)
{
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:
wait_for_proc_work = thread->transaction_stack == NULL && list_empty(&thread->todo); // 没有当前线程需要处理的任务
if (thread->return_error != BR_OK && ptr < end) { //这里是做处理tracsation。death出错了,会先返回给用户
if (thread->return_error2 != BR_OK) {
if (put_user(thread->return_error2, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
if (ptr == end)
goto done;
thread->return_error2 = BR_OK;
}
if (put_user(thread->return_error, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
thread->return_error = BR_OK;
goto done;
}
thread->looper |= BINDER_LOOPER_STATE_WAITING; //线程马上要睡眠了,先把线程状态改为空闲
if (wait_for_proc_work) // 当前没有任务
proc->ready_threads++; // 就把空闲线程+1
mutex_unlock(&binder_lock);
if (wait_for_proc_work) { // 当前没有任务
if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))) {
binder_user_error("binder: %d:%d ERROR: Thread waiting "
"for process work before calling BC_REGISTER_"
"LOOPER or BC_ENTER_LOOPER (state %x)\n",
proc->pid, thread->pid, thread->looper);
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 // 阻塞的话,让进程在等待队列上
ret = wait_event_interruptible_exclusive(proc->wait, binder_has_proc_work(proc, thread));
} else { // 当前有任务
if (non_block) { // 非阻塞
if (!binder_has_thread_work(thread))
ret = -EAGAIN;
} else // 阻塞状态,放入线程等待队列
ret = wait_event_interruptible(thread->wait, binder_has_thread_work(thread));
}
mutex_lock(&binder_lock);
if (wait_for_proc_work) // 到这里说明可能有任务了(阻塞),但是也有可能EAGAIN(非阻塞)
proc->ready_threads--; // 之前如果是无任务的话,空闲ready_threads会++,所以现在阻塞返回了,需要把ready_threads--
thread->looper &= ~BINDER_LOOPER_STATE_WAITING; // 解除线程空闲状态
...........................
```
这个方法我截取了前面部分,因为最终binder线程的任务都会都在todo的队列中,如果todo队列有任务,service manager便会取出来执行,否则当前线程就切为阻塞状态,知道有任务唤醒它,这里注释基本把要点也写了,大概了解意思就可以了。
到这里的话,service manager的启动就结束了,如果没有任务的话,就一直保持阻塞状态。后面如果有任务了会继续执行,这里后面的代码暂时先不分析了,因为如果有新任务的话,肯定会是新任务来了后才会继续执行后面的代码,所以准备把流程切换到新任务这边,等新任务完毕,在继续走service manager这边处理的流程。
Binder相关的内容还是非常多了,难度也不低,所以准备分好几篇文章讲,这篇文章就从binder初始化开始,一直到service manager启动完毕,算是开了个头。下面把service的启动到等待任务这段用时序图表示下,看起来就比较清晰些。

Android进程间通信Binder(一)