Android系统中的Binder机制,差不多是许多程序员眼前一座大山。也许,有人会说,我们根本就不需要弄懂Binder具体实现的细节,一样可以理解Android系统的跨进程调用关系。但这样会让我们思路变得狭隘,让我们离“真相”越来越远;同样也失去征服崎途的快感,和体验美好风景的机会。
Android系统中的Binder机制,是移植自OpenBinder(http://www.open-binder.org/)开源项目,这是linux下一种进程间通信机制。对于Android里面的Binder机制,核心功能的实现是在Binder驱动(drivers/staging/android)里面。但是Binder驱动里面,许多数据结构体,数据传输并发性与异步性,以及操作系统里面的内存管理与进程管理等等相关知识,导致Binder驱动又是整个Binder机制较难理解的部分。
我反反复复的阅读代码,整理分类, 编写实例,机器调试。想从不一样的角度来解构整个Binder驱动,试图让读者能够愉快地授受知识。我计划花3-4个篇幅来分析。第一篇:Binder驱动的调试接口和MISCDEV;第二篇:Binder驱动的相关结构体;第三篇:Binder IOCTL分析。整个思考是从易到难,循序渐进。剩下的就是读者你花点时间,把这 3600余行代码吃下去,再消化掉。 :)
转载请保留出处:可夫小子(http://www.koffuxu.com)
1. Kernel Binder Debug 的接口1.1,debugFs
在驱动在binder_init的时候,会创建debugfs的dentry节点,那么就会在/sys/kernel/debug/下面创建一个binder目录
root@U4:/sys/kernel/debug/binder# ls -al -r--r--r--root root0 1970-01-01 08:00failed_transaction_log drwxr-xr-x root root2016-12-20 16:32proc -r--r--r--root root0 1970-01-01 08:00state -r--r--r--root root0 1970-01-01 08:00stats -r--r--r--root root0 1970-01-01 08:00transaction_log -r--r--r--root root0 1970-01-01 08:00transactions除了proc是文件夹之外,其它几个都实现了show函数,即都可以cat。
... //创建binder目录作为根 binder_debugfs_dir_entry_root=debugfs_create_dir("binder",NULL); if (binder_debugfs_dir_entry_root) //创建proc目录(文件夹) binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",binder_debugfs_dir_entry_root); ... //分别创建其它5个节点(文件) debugfs_create_file("state",S_IRUGO,binder_debugfs_dir_entry_root, NULL, &binder_state_fops); debugfs_create_file("stats",S_IRUGO,binder_debugfs_dir_entry_root, NULL,&binder_stats_fops); debugfs_create_file("transactions",S_IRUGO,binder_debugfs_dir_entry_root, NULL, &binder_transactions_fops); debugfs_create_file("transaction_log",S_IRUGO,binder_debugfs_dir_entry_root, &binder_transaction_log, &binder_transaction_log_fops); debugfs_create_file("failed_transaction_log",S_IRUGO,binder_debugfs_dir_entry_root, &binder_transaction_log_failed, &binder_transaction_log_fops);诸如binder_state_fops这样的fops定义,在binder.c上面,是通过宏实现的
#defineBINDER_DEBUG_ENTRY(name) static intbinder_##name##_open(struct inode *inode, struct file *file) { returnsingle_open(file,binder_##name##_show, inode->i_private); } static const structfile_operations binder_##name##_fops = { .owner=THIS_MODULE, .open=binder_##name##_open, .read=seq_read, .llseek=seq_lseek, .release=single_release, }cat某个节点的执行,就是binder_##name##_show这些函数。即binder_state_show这些函数,就可以对照各个打印的意思了。
除了这5个节点(failed_transaction_log,state,stats,transaction_log,transactions)之外,proc目录里面也有一些可以cat的节点,
root@U4:/sys/kernel/debug/binder/proc# ls 10064 31161 3286 ...这些是进程号,在open函数里面创建的
if (binder_debugfs_dir_entry_proc) { charstrbuf[11]; snprintf(strbuf, sizeof(strbuf), "%u",proc->pid); proc->debugfs_entry=debugfs_create_file(strbuf,S_IRUGO, binder_debugfs_dir_entry_proc,proc, &binder_proc_fops); }cat这里面的节点,通过binder_proc_fops可以知道,调用的是binder_proc_show函数,随便cat一个看一下。
node66955:u00000000f43110c0 c00000000f433e004 hs1hw1ls0lw0is5iw5proc8738 8531 7292 6925 7059也就是说系统只要open_binder的进程,都会记录在里面。当然包括Server端和Client端,我主要看这些一些ref和node,thread相关。
1.2,debug parameter
除了debugfs,在binder.c中创建了一个Module Parameter的节点节,用来定义打印级别,总共定义了16个开关,需要把哪个打开就把哪位置1.
static uint32_tbinder_debug_mask=BINDER_DEBUG_USER_ERROR| BINDER_DEBUG_FAILED_TRANSACTION|BINDER_DEBUG_DEAD_TRANSACTION; module_param_named(debug_mask,binder_debug_mask, uint,S_IWUSR|S_IRUGO);默认是7,如果要使能哪个级别打印,就在相应位上面置1。
root@U4:/sys/module/binder/parameters# cat debug_mask 7比如说,我要看如下打印
binder_debug(BINDER_DEBUG_READ_WRITE, "%d:%d write%lld at%016llx, read%lld at%016llxn",proc->pid,thread->pid, (u64)bwr.write_size, (u64)bwr.write_buffer, (u64)bwr.read_size, (u64)bwr.read_buffer);而BINDER_DEBUG_READ_WRITE = 1U << 6即把第6bit置1即二进制100,0000再加上默认的二进制111。
操作如下:
echo 0x47 > debug_mask
先玩玩这些log,把里面的一些数据结构有个大概的轮廓。3600行,先吃下这一小块。
可以说,如果你能很清楚各个调试信息的意义,也算是把这个Binder驱动弄得差不多了。
2. Kernel Binder的Misc设备相关的函数总的来讲,binder驱动是一个misc设备,在init里面注册的。所以一定有这个驱动的fops簇函数。
//这里面注册到系统misc dev中 ret=misc_register(&binder_miscdev); ... //fops函数簇 static const structfile_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, }; static structmiscdevice binder_miscdev= { .minor=MISC_DYNAMIC_MINOR, .name= "binder", .fops= &binder_fops };fops函数簇总共的6个函数,先看一下除binder_ioctl之外的其它5个,把最难啃的骨头留在最后。
2.1,binder_open和binder_mmap
binder_open在上层,一个service的入口都是先调用ProcessState:self()函数,这里面就一次执行了binder_open和binder_mmap。
binder_open如下
static intbinder_open(structinode*nodp, structfile*filp) { //构建binder_proc结构体,并填充相关数据 structbinder_proc*proc; proc=kzalloc(sizeof(*proc),GFP_KERNEL); get_task_struct(current); proc->tsk=current; //初始化一个名为wait的waitqueue,并以链表的方式组织 INIT_LIST_HEAD(&proc->todo); init_waitqueue_head(&proc->wait); proc->default_priority=task_nice(current); binder_stats_created(BINDER_STAT_PROC); //把这个proc以proc_node作为节点,回到Kernel的binder_procs链表中 hlist_add_head(&proc->proc_node, &binder_procs); proc->pid=current->group_leader->pid; //初始化delivered_death链表 INIT_LIST_HEAD(&proc->delivered_death); //把这个准备好的proc结构体放到打开文件指针的private数据段,给以后的ioctl,mmap其它函数用,这也是Kernel里面惯用的套路 filp->private_data=proc; //下面的debugfs创建已经说过了 ...binder_open函数主要是初始化binder_proc,并填充一些数据,设置一些链表。最后把这个proc结构体放到filp->private_data里面。
binder_mmap分析如下:第1步,通过创建一个Kernel空间的虚拟内存空间,以vm_struct来表示。大小跟进程mmap的大小一致。
注意这个函数里面的binder_proc仍然是主角。
先说说map系统调用
随便读一个服务进程的/proc/$PID/maps,可以看到对/dev/binder的映射
f6c42000-f6d40000 r--p00000000 00:0f 9206 /dev/binder解释一下参数:
startaddr-endaddr PemFlag Offset Major:Minor Inode File
开始地址-结束地址__权限(只读)标志(Private)__文件偏移量__主设备号:次设备号__INODE值__文件
先看上层是如何调用mmap的,这个函数调用在ProcessState这个类的构造函数里面。
mVMStart=mmap(0,BINDER_VM_SIZE,PROT_READ,MAP_PRIVATE|MAP_NORESERVE,mDriverFD, 0);该系统调用的原型如下:
mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )

第1个参数:addr,表示映射到用户进程空间(虚拟空间)的位置;这里是0表示有系统指定。我们cat /proc/$pid/maps知道开始地址是随机的
第2个参数:len,表示需要映射的大小;在binder驱动中定义的空间大小是BINDER_VM_SIZE,f6c42000-f6d40000 的空间即为(1M-8K)
第3个参数:prot,保护标志;这里面PROT_READ表示映射区可被读取,查看maps属性就是这个r p
第4个参数:flags,表示映射的标志;这里面是MAP_PRIVATE | MAP_NORESERVE。
第5个参数:fd,打开文件,即/dev/binder的文件描述符
第6个参数:offset,从文件偏移量开始映射,这里是0,表示从头开始映射。
有了些基础知识,再来看binder驱动的操作。这个函数的大致任务是要建立虚拟内存地址到物理内存地址的页表
static int binder_mmap(struct file *filp, struct vm_area_struct *vma) { structvm_struct*area; structbinder_proc*proc=filp->private_data; const char *failure_string; structbinder_buffer*buffer; //一些参数出错处理 ... //在Kernel内存空间申请一段大小跟进程mmap的大小一致的连续的虚拟Kernel内存,此时并没有对应实际的物理内存 。 area=get_vm_area(vma->vm_end-vma->vm_start,VM_IOREMAP);第2步,接着,对proc进行赋值填充,主要是buffer相关的值
proc->buffer=area->addr; //这个user_buffer_offset是mmap映射区域,用户空间与内核空间的偏移 proc->user_buffer_offset=vm