前言
为什么要了解Binder的原理呢,会用不就可以了吗?深入了解Binder不仅仅是为了了解Binder的通信方式,而是从Binder设计中去学习一种优秀的架构思想和问题的解决思路。当我们遇到复杂的通信场景时,或者在一个新的平台上,需要一种新的通信方式提高安全或者效率性能等问题时,我们可以从Binder的设计中借鉴灵感。架构师的成长之路,就是不断的从优秀的设计中学习经验和解决方案。所以,作为一个有追求的程序员,开始对Binder的深入了解吧。网上介绍Binder的文章非常多,我为什么还要再写呢?主要是因为Binder很大部分都是在Linux内核中,如果想真正深入的掌握Binder,就需要掌握很多Linux的知识,网上大部分介绍Binder的文章都对Linux的讲解很少,所以这篇会介绍更多的Linux系统相关的知识,掌握了这些Linux系统相关的知识,才能真正的深入理解Binder的原理。由于篇幅的限制,我会分上下两篇来深入讲解Binder的通信原理。
为什么是Binder
在《深入理解Android进程间通信》这篇文章中我介绍了Android中所有的IPC通信的方式,包括Linux早期的FIFO,管道,信号,到后来的信号量,消息队列,共享内存,再到eventfd,mmap内存映射,Socket等等。Linux系统的IPC通信机制有很多,并且Linux系统上的程序应用也一直在使用这些方式进程IPC通信,那么为什么Android要设计Binder呢?直接用Linux的IPC通信机制不行吗?
我觉得,Android之所以设计一套新的IPC通信机制,一是因为Android系统相比于Linux,对安全性有更高的要求,Android中的每个应用都是一个沙盒模型,不能访问其他应用的数据,每个进程之间也不能直接访问其他进程的数据,相比于Android,Linux的限制就松了很多,所以PC的系统的病毒也比手机系统的病毒多很多,因为PC系统中一个进程的访问权限会大很多。二是因为Android的IPC场景也会比Linux多很多,一个程序可能需要频繁的获取系统中其他进程的信息,比如电量,通讯录等等,并且前面提到的安全性的限制,也会使Android的IPC场景比Linux中的IPC场景更加频繁。在这种更加频繁的IPC和安全的场景要求下,Linux原有的IPC机制在使用的便利性或者性能等方面不能很好的胜任。
既然原有的IPC机制不能胜任,需要重新设计IPC的方式,那么需要怎么设计呢?我们需要考虑下面几点,这几点不仅是Binder的设计需要考虑的,也是我们在程序架构上需要考虑的几点。
性能
安全
可扩展性和低耦合性
下面讲一下Binder是如何保证上面三点的。
性能
在Linux系统中,进程间传输数据性能最好的方式就是共享内存,或是以共享内存为原理衍生出来的技术,如mmap内存映射。共享内存或者mmap都只需要进行一次数据拷贝,即把想要传输的数据拷贝到共享或者映射的内存区域中,另一个共享或者映射了这段内存的进程就可以直接使用内存中的数据了,其他的IPC传输都需要两次拷贝,即将传输的数据拷贝的指定的内存区域(一般是内核空间),然后又将指定的内存区域的数据拷贝到需要通信的进程的内存中去。所以为了性能考虑,Binder在设计时,采用了mmap内存映射这种方式来进行数据的传输。关于mmap的知识在后面会详讲。
安全性
Binder设计中为了安全性的考虑, 天然支持携带进程ID,这样在进程间通信时,可以通过进程ID进程相应的权限控制。并且Binder是CS架构,Servcer更容易对Client的访问权限进行控制。
可扩展和低耦合
Binder的可扩展和低耦合体现在两个方面的架构上,一是它的C/S架构s合计,在CS架构上,Clinet和Server都容易扩展,想要扩展通信,只需要增加Client或者Server就可以了,而不用去管中间的通信流程。二是基于驱动的架构设计,在Android8.0之前,Binder只在Frameworkd之间使用,Binder挂载在dev/binder目录下,8.0开始硬件供应商部分,如相机,手电筒等硬件进程通信也开始使用binder,硬件部分的Binder挂载在/dev/vnbinder或/dev/hwbinder,可以看到,基于驱动的设计下,只需要新增一个虚拟设备,就可以很容易的实现扩展Binder通信的范围。
Linux基础知识
了解了Android为什么要设计Binder,接着会开始深入了解Binder,但在这之前,需要先熟悉一些Linux的基础知识,这是我们掌握Binder原理的基石。
用户空间与内核空间
Linux系统在32位机上为每个Linux进程分配了4G(2^32)的虚拟内存,其中有3GB的虚拟地址供该进程使用,称为用户空间,还有1GB留给其页表和其他内核数据,称为内核空间,内核空间是所有进程共享的。在用户态下运行时,内核的1GB是不可见的,但是当进程陷入到内核时是可以访问的。
mmap内存映射
mmap可以将一个文件或者其它对象映射进进程的用户空间,这种情况下,可以像使用自己进程的内存一样使用这段内存。Linux系统的mmap函数原型是这样的
void *mmap(void *addr,size_t length,int prot,int flags,int fd, off_t offset);
参数addr指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
参数length表示将文件中多大的部分映射到内存
参数prot指定映射区域的读写权限
参数flags指定映射时的特性,如是否允许其他进程映射这段内存
参数fd指定映射内存的文件描述符
参数offset指定映射位置的偏移量,一般为0
非mmap或者内存共享的Linux IPC机制常用的通信方式如下,数据发送进程的用户空间数据通过copy_from_user,复制到内核空间,由于内核空间是所有进程共享,所以内核通过调用copy_to_user将数据写入到接收基础,通过两次拷贝的方式,完成了IPC的通信。
通过mmap或者内存共享的Linux IPC机制,直接将同一段内存映射到数据发送进程和数据接收进程的用户空间,这样数据发送进程只需要将数据拷贝到共享的内存区域,数据接收进程就可以直接使用数据了。
Linux设备驱动
这里为什么要介绍Linux的设备驱动相关的知识呢?因为Binder的重要组成部分就是Binder驱动设备,为了更好的理解Binder,我们需要知道什么是Linux的设备驱动。
Linux的设备,主要包括字符设备(如键盘,鼠标,触摸屏等),块设备(硬盘,内存,U盘等)和网络接口(网卡,蓝牙等)等,都需要驱动程序才能和系统进行通信。这些驱动程序,都挂载在dev目录下,如硬盘的驱动挂载在dev/sda上,内存的驱动挂载在/dev/ram上。
块设备和字符设备的驱动程序一般都要实现open、close、read和write等系统调用函数,这些函数都属于系统I/O函数,直接我们就可以直接通过open或者read等I/O函数,读写设备的数据。而且dev目录下不仅仅是挂载真实的物理设备驱动,还可以挂载虚拟设备的驱动。虚拟设备的设计主要用来实现系统的功能,虽然虚拟设备没有具体的物理设备,但是我们依然需要在驱动程序中实现I/O函数,只不过虚拟设备驱动的I/O函数不是对物理设备的操作,而是功能逻辑操作。网桥就是Linux的一个虚拟设备,Binder也是一个挂载在dev/binder下的虚拟设备。
Binder的架构设计
前面已经知道了Android为什么要设计Binder,我们接着来看看Binder的架构设计。
Binder主要由这几部分组成
Binder设备驱动
Client端,数据发送端
Server端,数据接收端
ServiceManager
下面介绍以下这几个组成部分
Binder设备驱动
Binder驱动设备是真正分配内存空间用来存放通信数据的部分,在Binder的架构中,Clinet端发送的数据拷贝到Binder驱动设备分配的内存空间中,Server会通过mmap将Binder驱动设备中分配的内存映射到自己进程的用户空间中,映射完成后,Server在用户空间就可以直接读取Binder驱动中存放数据的这段内存了。
Clinet端
Client端是数据发送方,它会通过I/O函数,ioctl陷入内核,通知binder驱动将client端的数据通过copy_from_user函数拷贝过来,并存放在binder驱动的内存中。
Server端
Server是数据接收方,它接收数据方式的方式是映射Binder驱动中存放Clinet端数据的内存到自己的用户空间,这样就可以直接使用这段内存了。
ServiceManager
ServiceManager是专门用来管理Server端的,Client端想要和Server通信,必须知道Server的映射的内存地址,这样才能往这这段内存中拷贝数据,但是我们不可能知道所有Server端的地址,所以这个时候,我们只需要知道ServiceManager的地址,在ServiceManager中寻找其他Server的地址就可以了。所以ServiceManager有点类似DNS服务器。
Binder实现原理
了解了Binder的架构,我们开始分模块(即Binder驱动设备,ServiceManage,Client,Server这四个模块)深入解析Binder的具体实现。
Binder设备驱动
在前面已经大致介绍过了Linux的设备驱动,Binders设备驱动是Binder很重要的组成部分,之所以将Binder设计成驱动,我觉得是可以充分利用驱动热插拔,以及驱动程序天然支持I/O操作这两个特性。我们接着来深入了解一下Binder的设备驱动设计。
和所有其他的设备驱动一样,Binder驱动也是随着Linux的内核启动而一起启动的。在内核启动的过程中,只要位于deriver目录下的驱动程序在代码中按照规定的方式添加了初始化函数,这个驱动程序就会被内核自动加载,那么这个规定的方式是怎么样的呢?它的方式定义在/include/linux/init.h文件中。
/include/linux/init.h
……#define pure_initcall(fn) __define_initcall(fn, 0)#define core_initcall(fn) __define_initcall(fn, 1)#define core_initcall_sync(fn) __define_initcall(fn, 1s)#define postcore_initcall(fn) __define_initcall(fn, 2)#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)#define arch_initcall(fn) __define_initcall(fn, 3)#define arch_initcall_sync(fn) __define_initcall(fn, 3s)#define subsys_initcall(fn) __define_initcall(fn, 4)#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)#define fs_initcall(fn) __define_initcall(fn, 5)#define fs_initcall_sync(fn) __define_initcall(fn, 5s)#define rootfs_initcall(fn) __define_initcall(fn, rootfs)#define device_initcall(fn) __define_initcall(fn, 6)#define device_initcall_sync(fn) __define_initcall(fn, 6s)#define late_initcall(fn) __define_initcall(fn, 7)#define late_initcall_sync(fn) __define_initcall(fn, 7s)……
可以看到,里面有很多xxx_initcall的宏定义函数,如core_initcall,device_initcall等,这些宏定义都按照了优先级的顺序定义的,想要内核在启动的时候,能够启动驱动程序,只需要在驱动程序的代码里面加上xxx_initcall的宏定义方法,就能按照优先级被内核动态加载。
我们看看Binder驱动的源码,它位于/drivers/staging/目录下,我们知道Linux的drivers目录就是专门用来存放系统驱动程序的目录,它的源码里就可以看到device_initcall 这行代码,device_initcall是最常用的一个initcall函数,于是内核在启动的过程中,就会自动的去加载binder.c驱动程序中的binder_init初始化函数。
/drivers/staging/android/binder.c
……
device_initcall(binder_init);
……
我们再看一下binder的驱动程序的binder_init函数的实现
/drivers/staging/android/binder.c
static int __init binder_init(void){ int ret; binder_deferred_workqueue = create_singlethread_workqueue("binder"); if (!binder_deferred_workqueue) return -ENOMEM; binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL); if (binder_debugfs_dir_entry_root) binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", binder_debugfs_dir_entry_root); //注册binder为杂项设备驱动 ret = misc_register(&binder_miscdev); …… return ret;}
binder_init中主要只做了一件事,就是调用misc_register函数将当前驱动程序,也就是Binder驱动程序注册成杂项设备驱动,在前面讲Linux设备时,提到过Linux设备主要有字符设备,块设备等,杂项设备也属于Linux的一种设备类型,它是嵌入设系统用的比较多的一种设备。注册的设备的信息定义在binder_miscdev结构体里。
看一下binder_miscdev里有什么内容
/drivers/staging/android/binder.c
static struct miscdevice binder_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "binder", .fops = &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_miscdev结构体中定义了当前的驱动名为binder,并指定了open,mmap,compat_ioctl,compat_ioctl等I/O函数的实现函数。这些函数在后面说到的时候再详细讲。
ServerManager
从ServerManager开始分析,是因为他比较特殊,即是Client和Server通信的中间人,Client要先去ServerManager中寻找Server的Binder地址,同时也是一个特殊的Server端,作为一个特殊的Server,他的功能很单一,就是返回指定Server的Binder地址。
Android系统在启动过程中,首先会在用户空间启动init进程,然后启动zygote进程,zygote接着fork出ServerManager进程,ServerManager进程启动完成后,会接着启动system_server进程,system_server进程中会启动AMS,WMS等framework的服务,并将这些服务的binder句柄添加到到ServerManager中维护,最后启动Launcher这个应用进程,关于Android详细启动流程可以看看我的文章《Android底层启动解析》,了解了这一背景,我们看一下ServerManager进程启动时做了哪些事情。
/frameworks/native/cmds/servicemanager/service_manager.c
int main(int argc, char** argv){ struct binder_state *bs; union selinux_callback cb; char *driver; if (argc > 1) { driver = argv[1]; } else { driver = "/dev/binder"; } //打开Binder驱动 bs = binder_open(driver, 128*1024); if (!bs) { return -1; } //将ServerManager设置成BinderManager if (binder_become_context_manager(bs)) { ALOGE("cannot become context manager (%s)\n", strerror(errno)); return -1; } //调用循环,并不断的读取数据 binder_loop(bs, svcmgr_handler); return 0;}
ServerManager的main函数主要做的事情有这三件
调用binder_open函数,打开binder驱动,并在binder驱动中为ServiceManager分配128kb的空间
调用binder_become_context_manager,将ServiceManager的binder设置成BinderManager。
调用binder_loop,让ServiceManager不断循环,并在循环的过程中,去读缓冲区中是否有其他进程发送过来的数据
下面详细讲解一下这三件事情。
打开Binder驱动
先看第一件事情,binder_open函数,它的实现如下
/frameworks/native/cmds/servicemanager/binder.c
struct binder_state *binder_open(const char* driver, size_t mapsize){ struct binder_state *bs; struct binder_version vers; bs = malloc(sizeof(*bs)); if (!bs) { errno = ENOMEM; return NULL; } //打开binder驱动 bs->fd = open(driver, O_RDWR | O_CLOEXEC); if (bs->fd < 0) { goto fail_open; } //获取binder驱动版本号 if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) || goto fail_open; } bs->mapsize = mapsize; //将binder驱动的内存映射到Servermanager的用户空间 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); if (bs->mapped == MAP_FAILED) { goto fail_map; } return bs;fail_map: close(bs->fd);fail_open: free(bs); return NULL;}
可以看到这个binder_open函数做了三件事情
调用I/O函数open,打开binder驱动
调用I/O函数ioctl,获取binder驱动版本
调用I/O函数mmap,映射128kb大小的binder驱动内核空间的内存到ServerManager用户空间
我们先看open函数时如何打开binder驱动程序的
open到binder_open
在前面说过open方法最终会调用到binder驱动程序的open_binder函数。那么从open到open_binder,里面经历了哪些流程呢?里面的流程涉及到了Linux系统调用的相关机制,秉着对技术追求,这里我们一起看看Linux的系统调用是怎么实现的。
open函数的实现在Bionic库中,Bionic库是Android平台为了使用C/C++进行原生应用程序开发所有提供的POSIX标准C库。
/bionic/libc/bionic/open.cpp
int open(const char* pathname, int flags, ...) { mode_t mode = 0; if ((flags & O_CREAT) != 0) { va_list args; va_start(args, flags); mode = static_cast<mode_t>(va_arg(args, int)); va_end(args); } return __openat(AT_FDCWD, pathname, force_O_LARGEFILE(flags), mode);}
open函数调用了__openat函数。__openat函数的实现是一段字节码指令。
/bionic/libc/arch-arm/syscalls/__openat.S
ENTRY(__openat) mov ip, r7 .cfi_register r7, ip ldr r7, =__NR_openat //系统调用函数 swi #0 //软件中断 mov r7, ip .cfi_restore r7 cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b __set_errno_internalEND(__openat)
这段字节码指令实际上是通过swi指令,这个是arm平台中的软中断指令让当前的进程陷入内核调用,__NR_mmap2是调用的系统函数。我们去Linux的系统函数调用表,这个表记录了用户进程能调用的所有内核函数,查找NR_mmap2这个函数,他的定义如下。
/arch/arm/include/uapi/asm/unistd.h
#define __NR_openat (__NR_SYSCALL_BASE+322)
在表中可以查到__NR_openat 函数的索引为(__NR_SYSCALL_BASE+322),知道索引后,内核就会去calls.S文件中根据索引寻找对应的内核函数
/arch/arm/kernel/calls.S
/* 320 */ CALL(sys_get_mempolicy) CALL(sys_set_mempolicy) CALL(sys_openat)
在calls.S中,序号是322的系统函数是sys_openat函数。以sys_开头的函数说明进入了最终的系统调用函数,系统调用函数都会在syscalls文件中被重新定义
/include/linux/syscalls.h
……#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)……asmlinkage long sys_fork(void);asmlinkage long sys_vfork(void);asmlinkage long sys_clone(unsigned long, unsigned long, int, int __user *,int __user *, int);……asmlinkage long sys_openat(int dfd, const char __user *filename, int flags, umode_t mode);
这个文件中申明了所有的系统调用方法,比如我们熟悉frok函数的系统调用函sys_fork,clone的系统调用函数sys_clone等,并将所有的系统调用函数定义成宏函数,定义的规则是根据函数入参的数量定义成对应的SYSCALL_DEFINEx,这个x就表示入参的数量,比如sys_openat有四个入参,就被定义成了宏函数SYSCALL_DEFINE4。知道了sys_openat被定义成SYSCALL_DEFINE4,我们接着去这个函数最终实现的地方。
/fs/open.c
SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, umode_t, mode){ if (force_o_largefile()) flags |= O_LARGEFILE; return do_sys_open(dfd, filename, flags, mode);}
从SYSCALL_DEFINE4函数开始,我们在用户进程调用的open函数就已经真正的开始内核函数调用的流程中了,在内核函数的调用中,会根据我们open的文件名”dev/binder”,最终调用到位于”dev/binder”驱动程序的open函数,也就是binder_open方法。我们接着往下看这个流程是怎么走的。
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode){ struct open_flags op; struct filename *tmp; …… tmp = getname(filename); if (IS_ERR(tmp)) return PTR_ERR(tmp); //寻找一个空闲的文件描述符fd fd = get_unused_fd_flags(flags); if (fd >= 0) { struct file *f = do_filp_open(dfd, tmp, &op); …… } putname(tmp); return fd;}
do_sys_open主要是分配文件描述符,然后执行do_filp_open函数。
/fs/namei.c
struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op){ struct nameidata nd; int flags = op->lookup_flags; struct file *filp; filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU); if (unlikely(filp == ERR_PTR(-ECHILD))) filp = path_openat(dfd, pathname, &nd, op, flags); if (unlikely(filp == ERR_PTR(-ESTALE))) filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL); return filp;}
do_filp_open函数中执行了path_openat函数
/fs/namei.c
static struct file *path_openat(int dfd, struct filename *pathname, struct nameidata *nd, const struct open_flags *op, int flags){ struct file *base = NULL; struct file *file; struct path path; int opened = 0; int error; file = get_empty_filp(); if (IS_ERR(file)) return file; //初始化文件路径数据结构 error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base); if (unlikely(error)) goto out; //解析路径 error = link_path_walk(pathname->name, nd); if (unlikely(error)) goto out; …… //启动文件 error = do_last(nd, &path, file, op, &opened, pathname); …… return file;}
path_openat主要执行路径查找逻辑,这时的文件路径是“dev/binder”,查找到目标文件后,交给do_last函数处理
/fs/namei.c
static int do_last(struct nameidata *nd, struct path *path, struct file *file, const struct open_flags *op, int *opened, struct filename *name){ ……finish_open_created: //权限检查 error = may_open(&nd->path, acc_mode, open_flag); if (error) goto out; BUG_ON(*opened & FILE_OPENED); /* once it's opened, it's opened */ //打开目标文件 error = vfs_open(&nd->path, file, current_cred()); if (!error) { *opened |= FILE_OPENED; } else { if (error == -EOPENSTALE) goto stale_open; goto out; } ……}
do_last函数非常长,逻辑也很复杂,主要逻辑是对访问路径进行加锁和屏蔽处理,最后调用vfs_open来实现最终的文件打开操作。
/fs/open.c
int vfs_open(const struct path *path, struct file *filp, const struct cred *cred){ struct inode *inode = path->dentry->d_inode; if (inode->i_op->dentry_open) return inode->i_op->dentry_open(path->dentry, filp, cred); else { filp->f_path = *path; return do_dentry_open(filp, NULL, cred); }}
如果文件不是打开状态,则执行do_dentry_open函数。
/fs/open.c
static int do_dentry_open(struct file *f, int (*open)(struct inode *, struct file *), const struct cred *cred){ …… f->f_op = fops_get(inode->i_fop); if (unlikely(WARN_ON(!f->f_op))) { error = -ENODEV; goto cleanup_all; } …… if (!open) //执行目标文件的open函数 open = f->f_op->open; if (open) { error = open(inode, f); if (error) goto cleanup_all; } ……}
do_dentry_open函数中最关键的代码就是f->f_op->open,这个操作最终会调用目标文件,也就是/dev/binder的open方法,在前面我们已经知道,binder设备驱动在初始化时,binder_fops数据结构中将open方法定向到了binder_open函数。到这里,我们终于看到了用户空间执行open(‘’dev/binder””)方法后,是如何最终调用到了binder驱动中的binder_open方法。
binder_open
f->f_op->openh函数会执行Binder驱动的open函数,即binder_open函数,我们接着看这个函数的实现。
/drivers/staging/android/binder.c
static int binder_open(struct inode *nodp, struct file *filp){ struct binder_proc *proc; //为当前用户进程分配binder_proc proc = kzalloc(sizeof(*proc), GFP_KERNEL); if (proc == NULL) return -ENOMEM; //get_task_struct是Linux内核函数,用来获取当前进程的描述符 get_task_struct(current); proc->tsk = current; //初始化binder_proc结构体中的任务队列 INIT_LIST_HEAD(&proc->todo); //初始化binder_proc结构体中的等待队列 init_waitqueue_head(&proc->wait); //设置优先级 proc->default_priority = task_nice(current); binder_lock(__func__); binder_stats_created(BINDER_STAT_PROC); //将当前进程的binder_proc保存全局哈希表binder_procs中 hlist_add_head(&proc->proc_node, &binder_procs); //设置binder_proc结构体中的进程id proc->pid = current->group_leader->pid; INIT_LIST_HEAD(&proc->delivered_death); //将binder_proc结构体proc保存在参数filp成员变量的private_data中 filp->private_data = proc; binder_unlock(__func__); return 0;}
binder_open函数中初始是提调用open_binder的用户进程在驱动程序中创建binder_proc,每个使用了Binder的进程都有一个唯一的binder_proc,并对binder_proc进行初始化,初始化流程在代码中详细注释了,我们接着看binder_proc这个结构体是什么。
binder_proc结构体
/drivers/staging/android/binder.c
struct binder_proc { struct hlist_node proc_node; // 全局哈希表binder_procs中的node节点 struct rb_root threads; // 红黑树结构的Binder线程池 struct rb_root nodes; // 该进程内的binder实体组成的红黑树 struct rb_root refs_by_desc; // 进程内的binder引用组成的红黑树,该引用以句柄来排序 struct rb_root refs_by_node; // 进程内的binder引用组成的红黑树,该引用以它对应的binder实体的地址来排序 int pid; // 进程id struct vm_area_struct *vma; // 进程的内核虚拟内存 struct mm_struct *vma_vm_mm; struct task_struct *tsk; // 进程控制结构体(每一个进程都由task_struct 数据结构来定义)。 struct files_struct *files; // 保存了进程打开的所有文件表数据 struct hlist_node deferred_work_node; //可延迟执行的工作项 int deferred_work; void *buffer; // 该进程映射的物理内存在内核空间中的起始位置 ptrdiff_t user_buffer_offset; // 内核虚拟地址与进程虚拟地址之间的差值 // 内存管理的相关变量 struct list_head buffers; // 内核缓冲区列表的头部 struct rb_root free_buffers; // 空闲内存 struct rb_root allocated_buffers; // 已分配内存 size_t free_async_space; //异步事务数据的内核缓冲区的大小 struct page **pages; // 映射内存的page页面数组,page是描述物理内存的结构体 size_t buffer_size; // 映射内存的大小 uint32_t buffer_free; struct list_head todo; // 该进程的待处理事件队列。 wait_queue_head_t wait; // 等待队列。 struct binder_stats stats; struct list_head delivered_death; int max_threads; // 最大线程数。定义threads中可包含的最大进程数。 int requested_threads; int requested_threads_started; int ready_threads; long default_priority; // 默认优先级。 struct dentry *debugfs_entry;};
这里对binder_proc结构体有一个大概的认识,后面用到的时候再详讲。
mmap到binder_mmap
知道到了open函数是如何最终调用Binder驱动的biner_open函数的,我们接着了解mmap到binder_mmap的流程。mmap的实现还是在bionic库中
/bionic/libc/bionic/mmap.cpp
void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) { return mmap64(addr, size, prot, flags, fd, static_cast<off64_t>(offset));}void* mmap64(void* addr, size_t size, int prot, int flags, int fd, off64_t offset) { …… void* result = __mmap2(addr, size, prot, flags, fd, offset >> MMAP2_SHIFT); …… return result;}
mmap64调用了__mmap2函数,这个函数也是一段字节码函数
/bionic/libc/arch-arm/syscalls/__mmap2.S
ENTRY(__mmap2) mov ip, sp stmfd sp!, {r4, r5, r6, r7} .cfi_def_cfa_offset 16 .cfi_rel_offset r4, 0 .cfi_rel_offset r5, 4 .cfi_rel_offset r6, 8 .cfi_rel_offset r7, 12 ldmfd ip, {r4, r5, r6} ldr r7, =__NR_mmap2 //syscall地址 swi #0 //软中断指令 ldmfd sp!, {r4, r5, r6, r7} .cfi_def_cfa_offset 0 cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b __set_errno_internalEND(__mmap2)
可以看到,__mmap2同样通过swi软中断指令陷入内核,并调用内核函数__NR_mmap2,我们去Linux的系统函数调用表查找__NR_mmap2这个函数
/arch/arm/include/uapi/asm/unistd.h
#define __NR_mmap2 (__NR_SYSCALL_BASE+192)
系统函数调用表中,__NR_mmap2的索引是__NR_SYSCALL_BASE+192,我们接着根据索引去calls.S文件中寻找对应的内核函数
/arch/arm/kernel/calls.S
……/* 190 */ CALL(sys_vfork) CALL(sys_getrlimit) CALL(sys_mmap2) CALL(ABI(sys_truncate64, sys_oabi_truncate64))……
序号是192的系统函数就是sys_mmap2函数,我们在syscalls中找不到sys_mmap2的宏定义,是因为sys_mmap2其实是调用了sys_mmap_pgoff函数。
/arch/arm/kernel/entry-common.S
sys_mmap2:#if PAGE_SHIFT > 12 tst r5, #PGOFF_MASK moveq r5, r5, lsr #PAGE_SHIFT - 12 streq r5, [sp, #4] beq sys_mmap_pgoff mov r0, #-EINVAL ret lr#else str r5, [sp, #4] b sys_mmap_pgoff#endifENDPROC(sys_mmap2)
我们接着去syscalls文件中寻找sys_mmap_pgoff的宏定义
/include/linux/syscalls.h
……#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)……asmlinkage long sys_fork(void);asmlinkage long sys_vfork(void);……asmlinkage long sys_mmap_pgoff(unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, unsigned long pgoff);
sys_mmap_pgoff函数的入参有6个,所以它的宏定义SYSCALL_DEFINE6,知道了sys_mmap_pgoff的宏定义函数是SYSCALL_DEFINE6,我们接着去看它的最终实现流程。
/mm/mmap.c
SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len, unsigned long, prot, unsigned long, flags, unsigned long, fd, unsigned long, pgoff){ struct file *file = NULL; unsigned long retval = -EBADF; …… flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);out_fput: if (file) fput(file);out: return retval;}
SYSCALL_DEFINE6函数调用了vm_mmap_pgoff函数
/mm/util.c
unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long pgoff){ unsigned long ret; struct mm_struct *mm = current->mm; unsigned long populate; ret = security_mmap_file(file, prot, flag); if (!ret) { down_write(&mm->mmap_sem); ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff, &populate); up_write(&mm->mmap_sem); if (populate) mm_populate(ret, populate); } return ret;}
vm_mmap_pgoff中执行了do_mmap_pgoff函数
/mm/mmap.c
unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff, unsigned long *populate){ struct mm_struct *mm = current->mm; …… //获取用户进程有效的虚拟地址空间 addr = get_unmapped_area(file, addr, len, pgoff, flags); if (addr & ~PAGE_MASK) return addr; …… addr = mmap_region(file, addr, len, vm_flags, pgoff); if (!IS_ERR_VALUE(addr) && ((vm_flags & VM_LOCKED) || (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE)) *populate = len; return addr;}
do_mmap_pgoff函数主要做的事情就是获取调用进程,也就是ServerManager进程,有效的用户空间地址,只有获取了进程的用户空间地址,才能将内存映射上去。do_mmap_pgoff函数最后会调用mmap_region函数
/mm/mmap.c
unsigned long mmap_region(struct file *file, unsigned long addr, unsigned long len, vm_flags_t vm_flags, unsigned long pgoff){ struct mm_struct *mm = current->mm; struct vm_area_struct *vma, *prev; int error; struct rb_node **rb_link, *rb_parent; unsigned long charged = 0; …… /* Clear old maps */ error = -ENOMEM; //遍历当前进程的vma红黑树,寻找可用的vmamunmap_back: if (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) { if (do_munmap(mm, addr, len)) return -ENOMEM; goto munmap_back; } …… //没有找到可用的vma,则新建vma vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL); if (!vma) { error = -ENOMEM; goto unacct_error; } //初始化vma数据 vma->vm_mm = mm; vma->vm_start = addr; vma->vm_end = addr + len; vma->vm_flags = vm_flags; vma->vm_page_prot = vm_get_page_prot(vm_flags); vma->vm_pgoff = pgoff; INIT_LIST_HEAD(&vma->anon_vma_chain); if (file) { …… vma->vm_file = get_file(file); error = file->f_op->mmap(file, vma); …… } else if (vm_flags & VM_SHARED) { …… } ……}
mmap_region主要是寻找或者创建创建vma,vma是表示用户空间的一段连续的虚拟内存地址,最后执行file->f_op->mmap(file, vma),最终调用Binder驱动的mmap函数。
binder_mmap
前面已经知道了mmap如何调用Binder驱动mmap函数的流程,我们接着来看Binder的设备驱动中binder_mmap的实现。
/drivers/staging/android/binder.c
static int binder_mmap(struct file *filp, struct vm_area_struct *vma){ int ret; struct vm_struct *area; //获取当前进程的binder_proc结构体,在前面binder_open的操作中,已经将结构体保存在了filp的private_data变量中 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; //检查用户空间地址是否有可写权限 if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) { ret = -EPERM; failure_string = "bad vm_flags"; goto err_bad_arg; } vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE; mutex_lock(&binder_mmap_lock); //判断是否已经映射了空间,避免重复映射 if (proc->buffer) { ret = -EBUSY; failure_string = "already mapped"; goto err_already_mapped; } //获取空闲的和用户空间大小一致的内核空间虚拟地址 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; } //将获取的内核空间地址赋值给binder_proc结构体的buffer变量 proc->buffer = area->addr; proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; mutex_unlock(&binder_mmap_lock); //分配物理页的指针数组,数组大小为vma的等效page个数 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; } //设置binder_proc中的buffer_size proc->buffer_size = vma->vm_end - vma->vm_start; vma->vm_ops = &binder_vm_ops; vma->vm_private_data = proc; //分配真实的物理地址,并将物理页面和虚拟地址空间进行关联,这里为了节约内存,只会先分配PAGE_SIZE,也就是4kb真实的物理内存。 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; } 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; proc->vma_vm_mm = vma->vm_mm; ……}
我们知道Linux的用户进程的内存空间分为用户空间和内核空间两部分,用户空间的虚拟地址用vm_area_struct结构体描述,内核空间的虚拟地址用vm_struct结构体描述。binder_mmap函数中做的主要的事情就是在内核中分配指定大小的物理内存,然后同时映射到用户空间vm_area_struct和内核空间vm_struct上。这样ServerManager的用户空间和内核空间就可以同用一块内存了。
binder_update_page_range函数会真正分配物理地址,并且将vm_area_struct和vm_struct绑定同一块物理地址上,并且binder_update_page_range只会先分配4kb大小的真实物理内存,这样做是为了节约内存,真正需要更大的内存时再分配。我们看一下它的具体实现。
/drivers/staging/android/binder.c
static int binder_update_page_range(struct binder_proc *proc, int allocate, void *start, void *end, struct vm_area_struct *vma){ void *page_addr; unsigned long user_page_addr; struct vm_struct tmp_area; struct page **page; struct mm_struct *mm; if (end <= start) return 0; trace_binder_update_page_range(proc, allocate, start, end); if (vma) mm = NULL; else mm = get_task_mm(proc->tsk); if (mm) { down_write(&mm->mmap_sem); vma = proc->vma; if (vma && mm != proc->vma_vm_mm) { vma = NULL; } } if (allocate == 0) //如果allocate为0,则释放内存 goto free_range; if (vma == NULL) { goto err_no_vma; } //按照一个页面大小,即4KB,连续循环分配指定大小的真实内存 for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { int ret; page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; BUG_ON(*page); //分配一个page的物理内存 *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO); if (*page == NULL) { pr_err("%d: binder_alloc_buf failed for page at %p\n", proc->pid, page_addr); goto err_alloc_page_failed; } tmp_area.addr = page_addr; tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */; //物理空间映射到内核空间 ret = map_vm_area(&tmp_area, PAGE_KERNEL, page); if (ret) { pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n", proc->pid, page_addr); goto err_map_kernel_failed; } user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset; //物理空间映射到用户空间 ret = vm_insert_page(vma, user_page_addr, page[0]); /* vm_insert_page does not seem to increment the refcount */ } if (mm) { up_write(&mm->mmap_sem); mmput(mm); } return 0;free_range: //释放流程 …… return -ENOMEM;}
binder_update_page_range主要逻辑逻辑
调用alloc_page创建真正的物理内存
调用map_vm_area函数,将创建的内存绑定到内核空间
调用vm_insert_page函数,将创建的内存绑定到用户空间
到这里我们已经了解了binder_open和binder_mmap的所有的流程和细节了,这是进程想要使用Binder进行通信打开必须要经历的两个步骤。不管是ServerManager,还是其他的应用进程或者system_server进程,在使用Binder时,都需要先经历open和mmap这两个步骤。
注册BinderManager
我们接着看ServerMnaager独有的流程,即调用binder_become_context_manager函数,将service_manager的binder设置成BinderManager。它的实现如下
/frameworks/native/cmds/servicemanager/binder.c
int binder_become_context_manager(struct binder_state *bs){ return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);}
可以看到,这里同样调用了标准的I/O函数ioctl,通过ioctl陷入内核的Binder驱动程序中,最终调用Binder驱动的binder_ioctl函数,我们看一下中间的流程。
ioctl到binder_ioctl
ioctl函数也位于bionic库中,它的实现如下。
/bionic/libc/bionic/ioctl.cpp
int ioctl(int fd, int request, ...) { va_list ap; va_start(ap, request); void* arg = va_arg(ap, void*); va_end(ap); return __ioctl(fd, request, arg);}
ioctl函数中调用了__ioctl函数,它也是一段字节码指令
/bionic/libc/arch-arm/syscalls/__ioctl.S
ENTRY(__ioctl) mov ip, r7 .cfi_register r7, ip ldr r7, =__NR_ioctl swi #0 mov r7, ip .cfi_restore r7 cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b __set_errno_internalEND(__ioctl)
这里依然时通过swi软中断陷入内核函数__NR_ioctl的调用,我们继续去内核函数表中查找它的实现。
/arch/arm/include/uapi/asm/unistd.h
#define __NR_ioctl (__NR_SYSCALL_BASE+ 54)
/arch/arm/kernel/calls.S
/* 50 */ CALL(sys_getegid16) CALL(sys_acct) CALL(sys_umount) CALL(sys_ni_syscall) /* was sys_lock */ CALL(sys_ioctl)
__NR_ioctl的实现函数是sys_ioctl,它会被定义成SYSCALL_DEFINE3函数。
/include/linux/syscalls.h
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg);
SYSCALL_DEFINE3的最终实现也在kernal的fs目录下
/fs/ioctl.c
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg){ int error; struct fd f = fdget(fd); if (!f.file) return -EBADF; error = security_file_ioctl(f.file, cmd, arg); if (!error) error = do_vfs_ioctl(f.file, fd, cmd, arg); fdput(f); return error;}
SYSCALL_DEFINE3调用do_vfs_ioctl函数。
/fs/ioctl.c
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned long arg){ int error = 0; int __user *argp = (int __user *)arg; struct inode *inode = file_inode(filp); …… default: //判断是否是文件或者目录 if (S_ISREG(inode->i_mode)) error = file_ioctl(filp, cmd, arg); else error = vfs_ioctl(filp, cmd, arg); break; } return error;}
do_vfs_ioctl函数中会执行vfs_ioctl
/fs/ioctl.c
static long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ int error = -ENOTTY; if (!filp->f_op->unlocked_ioctl) goto out; error = filp->f_op->unlocked_ioctl(filp, cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; out: return error;}
可以看到,这里又到了关键的f_op操作,也就是filp->f_op->unlocked_ioctl函数的调用。在前面Binder驱动注册时,Binder驱动程序的unlocked_ioctl实现就是binder_ioctl函数。
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,};
所以filp->f_op->unlocked_ioctl最终会执行Binder驱动的binder_ioctl函数。我们接着看binder_ioctl函数的实现。
binder_ioctl
前面BinderManager调用ioctl时的cmd入参是BINDER_SET_CONTEXT_MGR,我们接着看看binder_ioctl是如何处理BINDER_SET_CONTEXT_MGR参数的。
/drivers/staging/android/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; /*pr_info("binder_ioctl: %d:%d %x %lx\n", proc->pid, current->pid, cmd, arg);*/ trace_binder_ioctl(cmd, arg); ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); if (ret) goto err_unlocked; binder_lock(__func__); thread = binder_get_thread(proc); if (thread == NULL) { ret = -ENOMEM; goto err; } switch (cmd) { case BINDER_WRITE_READ:…… case BINDER_SET_MAX_THREADS:…… case BINDER_SET_CONTEXT_MGR: ret = binder_ioctl_set_ctx_mgr(filp); if (ret) goto err; ret = security_binder_set_context_mgr(proc->tsk); if (ret < 0) goto err; break; case BINDER_THREAD_EXIT:…… case BINDER_VERSION: …… default: ret = -EINVAL; goto err; } ret = 0; …… return ret;}
从binder_ioctl函数中可以看到,它支持的协议主要有这几种
ioctl协议 | 作用 |
---|---|
BINDER_WRITE_READ | 读写数据 |
BINDER_SET_MAX_THREADS | 设备最大线程数 |
BINDER_SET_CONTEXT_MGR | 注册成Binder管理者 |
BINDER_THREAD_EXIT | 退出Binder线程 |
BINDER_VERSION | 获取Binder版本 |
我们这里先看BINDER_SET_CONTEXT_MGR的实现,其他的协议在后面遇到时在详说。它的处理逻辑如下
case BINDER_SET_CONTEXT_MGR: ret = binder_ioctl_set_ctx_mgr(filp); if (ret) goto err; ret = security_binder_set_context_mgr(proc->tsk); if (ret < 0) goto err; break;
我们看一下binder_ioctl_set_ctx_mgr函数的实现。
/drivers/staging/android/binder.c
static struct binder_node *binder_context_mgr_node;static int binder_ioctl_set_ctx_mgr(struct file *filp){ int ret = 0; struct binder_proc *proc = filp->private_data; kuid_t curr_euid = current_euid(); //binder_context_mgr_node是一个全局变量,首先判断该变量是否为空以验证是否已经为ServiceManager进程创建了binder实体对象 if (binder_context_mgr_node != NULL) { pr_err("BINDER_SET_CONTEXT_MGR already set\n"); ret = -EBUSY; goto out; } //binder_context_mgr_uid也是一个全局变量,判断是否与当前ServiceManager进程的uid相同 if (uid_valid(binder_context_mgr_uid)) { if (!uid_eq(binder_context_mgr_uid, curr_euid)) { pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n", from_kuid(&init_user_ns, curr_euid), from_kuid(&init_user_ns, binder_context_mgr_uid)); ret = -EPERM; goto out; } } else { //设置ServiceManager进程的uid到binder_context_mgr_uid全局变量中 binder_context_mgr_uid = curr_euid; } //为当前ServiceManager进程创建一个binder实体对象,并保存到binder_context_mgr_node全局变量中 binder_context_mgr_node = binder_new_node(proc, 0, 0); if (binder_context_mgr_node == NULL) { ret = -ENOMEM; goto out; } 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;out: return ret;}
可以看到,binder_ioctl_set_ctx_mgr函数实际是为ServerManager创建一个binder_node结构体,并且将binder_node结构体设置成Binder驱动的全局遍历。一个binder_node表示一个binder实体,我们看一下binder_node结构体实现。
binder_node结构体
/drivers/staging/android/binder.c
struct binder_node { int debug_id; struct binder_work work; union { struct rb_node rb_node; struct hlist_node dead_node; }; struct binder_proc *proc; // 该binder实体所属的binder_proc struct hlist_head refs; // 该Binder实体的所有Binder引用所组成的链表 int internal_strong_refs; int local_weak_refs; int local_strong_refs; binder_uintptr_t ptr; binder_uintptr_t cookie; unsigned has_strong_ref:1; unsigned pending_strong_ref:1; unsigned has_weak_ref:1; unsigned pending_weak_ref:1; unsigned has_async_transaction:1; unsigned accept_fds:1; unsigned min_priority:8; struct list_head async_todo;};
ServerManager陷入循环
我们接着看service_manager的main函数中做的最后一件事情,执行binder_loop函数,让ServerManager进程陷入循环。binder_loop的实现如下。
/frameworks/native/cmds/servicemanager/binder.c
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循环 for (;;) { bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (uintptr_t) readbuf; //读取数据 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); if (res < 0) { ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno)); break; } //解析数据 res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func); if (res == 0) { ALOGE("binder_loop: unexpected reply?!\n"); break; } if (res < 0) { ALOGE("binder_loop: io error %d %s\n", res, strerror(errno)); break; } }}
binder_loop做的事情就是进入for循环,然后不断的读取是否有其他进程发送过来的数据,然后解析数据。我们先看ioctl函数是如何去读数据的。
读取数据
ioctl最终会执行Binder驱动的binder_ioctl函数。这次ioctl传的参数是BINDER_WRITE_READ和一个binder_write_read
binder_write_read结构体定义如下,里面主要是两个Buffer:一个写数据buffer,一个读数据buffer。
struct binder_write_read { binder_size_t write_size; binder_size_t write_consumed; binder_uintptr_t write_buffer; binder_size_t read_size; binder_size_t read_consumed; binder_uintptr_t read_buffer;};
我们接着再看一下binder_ioctl是如何处理BINDER_WRITE_READ的。
/drivers/staging/android/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; trace_binder_ioctl(cmd, arg); ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); if (ret) goto err_unlocked; binder_lock(__func__); //从binder_proc获取或者创建一个thread thread = binder_get_thread(proc); if (thread == NULL) { ret = -ENOMEM; goto err; } switch (cmd) { case BINDER_WRITE_READ: ret = binder_ioctl_write_read(filp, cmd, arg, thread); if (ret) goto err; break; case BINDER_SET_MAX_THREADS:…… case BINDER_SET_CONTEXT_MGR:…… case BINDER_THREAD_EXIT:…… case BINDER_VERSION: …… default: ret = -EINVAL; goto err; } ret = 0; …… return ret;}
binder_ioctl会通过binder_get_thread函数获取或者创建一个binder_thread来处理事务,我们看一下binder_get_thread的实现
/drivers/staging/android/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; while (*p) { parent = *p; thread = rb_entry(parent, struct binder_thread, rb_node); if (current->pid < thread->pid) p = &(*p)->rb_left; else if (current->pid > thread->pid) p = &(*p)->rb_right; else break; } if (*p == NULL) { thread = kzalloc(sizeof(*thread), GFP_KERNEL); if (thread == NULL) return NULL; binder_stats_created(BINDER_STAT_THREAD); thread->proc = proc; thread->pid = current->pid; init_waitqueue_head(&thread->wait); INIT_LIST_HEAD(&thread->todo); rb_link_node(&thread->rb_node, parent, p); rb_insert_color(&thread->rb_node, &proc->threads); thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN; thread->return_error = BR_OK; thread->return_error2 = BR_OK; } return thread;}
binder_get_thread函数会从binder_proc的threads中寻找一个可用的binder_thread,如果找不到,则会创建一个新的binder_thread,我们再看一下binder_thread的结构体。
binder_thread结构体
/drivers/staging/android/binder.c
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; /* Write failed, return error code in read buf */ uint32_t return_error2; /* Write failed, return error code in read */ /* buffer. Used when sending a reply to a dead process that */ /* we are also waiting on */ wait_queue_head_t wait; struct binder_stats stats;};
里面有一个binder_transaction结构,数据发送进程会将数据写入binder_transaction中,并插入到目标进程,也就是ServerManager的todo列表中。
binder_transaction结构体
/drivers/staging/android/binder.c
struct binder_transaction { int debug_id; struct binder_work work; struct binder_thread *from; struct binder_transaction *from_parent; struct binder_proc *to_proc; struct binder_thread *to_thread; struct binder_transaction *to_parent; unsigned need_reply:1; /* unsigned is_dead:1; */ /* not used at the moment */ struct binder_buffer *buffer; unsigned int code; unsigned int flags; long priority; long saved_priority; kuid_t sender_euid;};
了解了binder_thread和binder_transaction两个结构体,我们接着回到BINDER_WRITE_READ处理逻辑,这里会调用binder_ioctl_write_read函数。
/drivers/staging/android/binder.c
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; } //将binder_write_read结构体拷贝到内核空间 if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { ret = -EFAULT; goto out; } //检查binder_write_read中写数据Buffer是否为空 if (bwr.write_size > 0) { ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed); trace_binder_write_done(ret); if (ret < 0) { bwr.read_consumed = 0; if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT; goto out; } } //检查binder_write_read中读数据Buffer是否为空 if (bwr.read_size > 0) { 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); if (!list_empty(&proc->todo)) wake_up_interruptible(&proc->wait); if (ret < 0) { if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT; goto out; } } ……out: return ret;}
我们直接看bwr.read_size > 0,也就是有其他进程往BinderManager的内存缓冲区写如数据时的处理逻辑。这个时候会调用binder_thread_read,我们看一下这个函数的实现。
/drivers/staging/android/binder.c
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){ void __user *buffer = (void __user *)(uintptr_t)binder_buffer; void __user *ptr = buffer + *consumed;//数据起始地址 void __user *end = buffer + size; //数据结束地址 int ret = 0; int wait_for_proc_work; if (*consumed == 0) { //当已使用字节数为0时,将BR_NOOP响应码写会buffer if (put_user(BR_NOOP, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); }retry: //判断thread->transaction_stack和thread->todo是否为空,来决定是否陷入休眠 wait_for_proc_work = thread->transaction_stack == NULL && list_empty(&thread->todo); …… thread->looper |= BINDER_LOOPER_STATE_WAITING; //如果陷入等待,则ready_threads计数加1 if (wait_for_proc_work) proc->ready_threads++; binder_unlock(__func__); //只有当前thread->todo队列为空,并且只有当前thread->transaction_stack也为空,才会开始处于当前进程的事务 if (wait_for_proc_work) { binder_set_nice(proc->default_priority); if (non_block) { if (!binder_has_proc_work(proc, thread)) ret = -EAGAIN; } else //当进程todo队列没有数据,则进入休眠等待状态 ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread)); } else { if (non_block) { if (!binder_has_thread_work(thread)) ret = -EAGAIN; } else //当线程todo队列有数据则执行往下执行;当线程todo队列没有数据,则进入休眠等待状态 ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread)); } binder_lock(__func__); //退出等待状态, 则ready_threads计数减1 if (wait_for_proc_work) proc->ready_threads--; thread->looper &= ~BINDER_LOOPER_STATE_WAITING; if (ret) return ret; while (1) { uint32_t cmd; struct binder_transaction_data tr; struct binder_work *w; struct binder_transaction *t = NULL; if (!list_empty(&thread->todo)) { //从线程todo队列中获取事务数据 w = list_first_entry(&thread->todo, struct binder_work, entry); } else if (!list_empty(&proc->todo) && wait_for_proc_work) { // 线程todo队列中没有数据, 则从进程todo队列中获取事务数据 w = list_first_entry(&proc->todo, struct binder_work, entry); } else { if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN)) //没有数据,则返回retry goto retry; break; } if (end - ptr < sizeof(tr) + 4) break; switch (w->type) { case BINDER_WORK_TRANSACTION: { //获取transaction数据 t = container_of(w, struct binder_transaction, work); } break; case BINDER_WORK_TRANSACTION_COMPLETE: { cmd = BR_TRANSACTION_COMPLETE; if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); //写入BR_TRANSACTION_COMPLETE响应码 binder_stat_br(proc, thread, cmd); list_del(&w->entry); kfree(w); binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); } break; case BINDER_WORK_NODE: ……break; case BINDER_WORK_DEAD_BINDER: case BINDER_WORK_DEAD_BINDER_AND_CLEAR: case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: …… break; } //只有BINDER_WORK_TRANSACTION命令才能继续往下执行 if (!t) continue; if (t->buffer->target_node) { //获取目标node struct binder_node *target_node = t->buffer->target_node; tr.target.ptr = target_node->ptr; tr.cookie = target_node->cookie; t->saved_priority = task_nice(current); if (t->priority < target_node->min_priority && !(t->flags & TF_ONE_WAY)) binder_set_nice(t->priority); else if (!(t->flags & TF_ONE_WAY) || t->saved_priority > target_node->min_priority) binder_set_nice(target_node->min_priority); //设置命令为BR_TRANSACTION cmd = BR_TRANSACTION; } else { tr.target.ptr = 0; tr.cookie = 0; //设置命令为BR_REPLY cmd = BR_REPLY; } tr.code = t->code; tr.flags = t->flags; tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid); if (t->from) { struct task_struct *sender = t->from->proc->tsk; tr.sender_pid = task_tgid_nr_ns(sender, task_active_pid_ns(current)); } else { tr.sender_pid = 0; } tr.data_size = t->buffer->data_size; tr.offsets_size = t->buffer->offsets_size; tr.data.ptr.buffer = (binder_uintptr_t)( (uintptr_t)t->buffer->data + proc->user_buffer_offset); tr.data.ptr.offsets = tr.data.ptr.buffer + ALIGN(t->buffer->data_size, sizeof(void *)); //将cmd和数据写回用户空间 if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); if (copy_to_user(ptr, &tr, sizeof(tr))) return -EFAULT; ptr += sizeof(tr); trace_binder_transaction_received(t); binder_stat_br(proc, thread, cmd); list_del(&t->work.entry); t->buffer->allow_user_free = 1; 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; }done: *consumed = ptr - buffer; if (proc->requested_threads + proc->ready_threads == 0 && proc->requested_threads_started < proc->max_threads && (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED))) { proc->requested_threads++; // 生成BR_SPAWN_LOOPER命令,用于创建新的线程 if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer)) return -EFAULT; binder_stat_br(proc, thread, BR_SPAWN_LOOPER); } return 0;}
下面详细介绍一下binder_thread_read的流程
最开始会将BR_NOOP响应码写到read_buffer中,此时陷入循环读取数据的ServerManager进程读到BR_NOOP,然后执行BR_NOOP响应逻辑。
判断线程的transaction_stack和todo队列是否为空,为空则陷入休眠。
如果todo队列和transaction_stack中有了数据,就会退出休眠,开始处理数据。
如果target_node不为空,则设置BR_TRANSACTION响应码,如果为空,则设置BR_REPLY响应码
将响应码和数据回写到ServerManager用户空间,ServerManager会根据响应码,执行对应的逻辑。
解析数据
我们接着看一下ServerManager如何解析数据,binder_parse的函数实现如下。
int binder_parse(struct binder_state *bs, struct binder_io *bio, uintptr_t ptr, size_t size, binder_handler func){ int r = 1; uintptr_t end = ptr + (uintptr_t) size; while (ptr < end) { uint32_t cmd = *(uint32_t *) ptr; ptr += sizeof(uint32_t); switch(cmd) { case BR_NOOP: break; case BR_TRANSACTION_COMPLETE: break; case BR_INCREFS: case BR_ACQUIRE: case BR_RELEASE: case BR_DECREFS: ptr += sizeof(struct binder_ptr_cookie); break; case BR_TRANSACTION: { struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr; if ((end - ptr) < sizeof(*txn)) { return -1; } binder_dump_txn(txn); if (func) { unsigned rdata[256/4]; struct binder_io msg; struct binder_io reply; int res; bio_init(&reply, rdata, sizeof(rdata), 4); bio_init_from_txn(&msg, txn); res = func(bs, txn, &msg, &reply); if (txn->flags & TF_ONE_WAY) { binder_free_buffer(bs, txn->data.ptr.buffer); } else { binder_send_reply(bs, &reply, txn->data.ptr.buffer, res); } } ptr += sizeof(*txn); break; } case BR_REPLY: …… case BR_DEAD_BINDER: …… case BR_FAILED_REPLY: r = -1; break; case BR_DEAD_REPLY: r = -1; break; default: ALOGE("parse: OOPS %d\n", cmd); return -1; } } return r;}
在前面将ServerMananger调用ioctl读取数据时,我们知道binder_thread_read函数会将响应码和数据回写到用户空间,可以看到,binder_parse函数就是根据响应码执行对应的逻辑操作。具体的操作实现,在后面遇到时在详细的讲解。
结尾
在这篇文章中,我主要介绍了Binder中的驱动和ServerManager部分,在下一篇文章中,我会详细介绍Clinet和Server这两个部分。