Android 的进程通信机制(一)

在 Android 系统中,每个应用程序都是由 Activity 和 Service 组成的,一般 Service 运行在独立的进程中,而 Activity 有可能运行在同一个进程中,也有可能运行在不同的进程中。那么不在同一个进程的 Activity 或者 Service 之间究竟是如何通信的呢?下面将介绍的 Binder 进程间通信机制来实现这个功能。

众所周知,Android 系统是基于 Linux 内核的,而 Linux 内核继承和兼容了丰富的 Unix 系统进程间通信(IPC)机制。有传统的管道(Pipe)、信号(Signal)和跟踪(Trace),这三项通信手段只能用于父进程和子进程之间,或者只用于兄弟进程之间。随着技术的发展,后来又增加了命令管道(Named Pipe),这样使得进程之间的通信不再局限于父子进程或者兄弟进程之间。为了更好地支持商业应用中的事务处理,在 AT&T 的 Unix 系统 V 中,又增加了如下三种称为“System V IPC”的进程间通信机制。

• 报文队列(Message)。
• 共享内存(Share Memory)。
• 信号量(Semaphore)。

后来 BSD Unix 对“System V IPC”机制进行了重要的扩充,提供了一种称为插口(Socket)的进程间通信机制。但是 Android 系统没有采用上述提到的各种进程间通信机制,而是采用 Binder 机制,这难道仅仅是因为考虑了移动设备硬件性能较差、内存较低的特点吗?这答案是不得而知。Binder 其实也不是 Android 提出来的一套新的进程间通信机制,它是基于 OpenBinder 来实现的。OpenBinder 最先是由 Be Inc.开发的,后来 Palm Inc.也跟着借鉴使用。

再次强调一下,Binder 是一种进程间通信机制,类似于 COM 和 CORBA 分布式组件架构。通俗来讲,其实是提供远程过程调用(RPC)功能。从英文字面的意思看,Binder 具有黏结剂的意思,那么它把什么东西黏结在一起呢?Android 系统的 Binder 机制由一系统组件组成,分别是 Client、Server、Service Manager 和 Binder 驱动程序,其中 Client、Server 和 Service Manager 运行在用户空间,Binder 驱动程序运行内核空间。Binder 就是把这 4 个组件粘合在一起的黏结剂,其中,核心组件便是 Binder 驱动程序,Service Manager 提供了辅助管理的功能,Client 和 Server 正是在 Binder 驱动和 Service Manager 提供的基础设施上,进行 Client/Server 之间的通信。Service Manager 和 Binder驱动已经在 Android 平台中实现完毕,开发者只要按照规范实现自己的 Client 和 Server 组件即可。但是说起来简单,具体做起来却很难。对初学者来说,Android 系统的 Binder 机制是最难理解的,而 Binder 机制无论从系统开发还是应用开发的角度来看,都是 Android 系统中最重要的组成,所以很有必要深入了解 Binder 的工作方式。

要想理解 Binder 机制,必须了解 Binder 在用户空间的三个组件 Client、Server 和 Service Manager

之间的相互关系,了解内核空间中Binder 驱动程序的数据结构和设计原理。具体来说,Android 系统Binder机制中的 4 个组件 Client、Server、Service Manager 和 Binder 驱动程序的关系如图 6-1 所示

android 进程 service Android 进程通信机制_Server

对图 6-1 所示关系的具体说明如下。

(1)Client、Server 和 Service Manager 实现在用户空间中,Binder 驱动程序实现在内核空间中。

(2)Binder 驱动程序和 Service Manager 在 Android 平台中已经实现,开发者只需要在用户空间实现自己的 Client 和 Server。

(3) Binder 驱动程序提供设备文件“/dev/binder “与用户空间交互, Client、 Server 和 Service Manager通过文件操作函数 open()和 ioctl()与 Binder 驱动程序进行通信。

(4)Client 和 Server 之间的进程间通信通过 Binder 驱动程序间接实现。

(5)Service Manager 是一个守护进程,用来管理 Server,并向 Client 提供查询 Server 接口的能力。

Service Manager 是 Binder 机制的上下文管理者

分析 Binder 源代码时,需要先弄清楚 Service Manager 是如何告知 Binder 驱动程序它是 Binder机制的上下文管理者的。Service Manager 是整个 Binder 机制的守护进程,用来管理开发者创建的各种 Server,并且向 Client 提供查询 Server 远程接口的功能。

因为 Service Manager 组件用来管理 Server 并且向 Client 提供查询 Server 远程接口的功能,所以Service Manager 必然要和 Server 及 Client 进行通信。Service Manger、Client 和 Server 三者分别运行在独立的进程当中,这样它们之间的通信也属于进程间的通信,而且是采用 Binder 机制进行进程间通信。因此, Service Manager 在充当 Binder 机制的守护进程的角色的同时,也在充当 Server 的角色,但是它是一种特殊的 Server。

Service Manager 在用户空间的源代码位于 frameworks/base/cmds/servicemanager 目录下, 主要由文件 binder.h、 binder.c 和 service_manager.c 组成。 Service Manager 的入口位于文件 service_manager.c中的函数 main()中,代码如下。

int main(int argc, char **argv){
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
if (binder_become_context_manager(bs)) {
LOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}

上述函数 main()主要有如下三个功能。
• 打开 Binder 设备文件。
• 告诉 Binder 驱动程序自己是 Binder 上下文管理者,即守护进程。
• 进入一个无穷循环,充当 Server 的角色,等待 Client 的请求。

在进入上述三个功能之前,先来看一下这里用到的结构体 binder_state、宏 BINDER_SERVICE_MANAGER 的定义。结构体 binder_state 定义在文件 frameworks/base/cmds/servicemanager/binder.c中,代码如下。

struct binder_state {
int fd;
void *mapped;
unsigned mapsize;
};

其中 fd 表示文件描述符,即表示打开的“/dev/binder”设备文件描述符;mapped 表示将设备文
件“/dev/binder”映射到进程空间的起始地址;mapsize 表示上述内存映射空间的大小。

宏 BINDER_SERVICE_MANAGER 在文件“frameworks/base/cmds/servicemanager/binder.h”中定义,代码如下。

// the one magic object
#define BINDER_SERVICE_MANAGER ((void*) 0)

这表示 Service Manager 的句柄值为 0。Binder 通信机制使用句柄来代表远程接口,此句柄的意义和 Windows 编程中用到的句柄差不多。前面内容提到,Service Manager 在充当守护进程的同时,还充当 Server 的角色,当它作为远程接口使用时,它的句柄值便为 0,这就是它的特殊之处,其余的 Server 的远程接口句柄值都大于 0 而且是由 Binder 驱动程序自动进行分配的。
首先打开 Binder 设备文件的操作函数 binder_open(),此函数的定义位于文件 frameworks/base/cmds/servicemanager/binder.c 中,代码如下。

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);
if (bs->fd < 0) {
fprintf(stderr,"binder: cannot open device (%s)\n",
strerror(errno));
goto fail_open;
}
bs->mapsize = mapsize;
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
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;
}

通过文件操作函数 open()打开设备文件“/dev/binder”,此设备文件是在 Binder 驱动程序模块初始化时创建的。接下来先看一下这个设备文件的创建过程,在 kernel/common/drivers/ staging/android目录中打开文件“binder.c”,可以看到如下模块初始化入口 binder_init。

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,
};
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);
if (binder_proc_dir_entry_root)
binder_proc_dir_entry_proc = proc_mkdir("proc", binder_proc_dir_entry_root);
ret = misc_register(&binder_miscdev);
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;
}
device_initcall(binder_init);

在函数 misc_register()中实现了创建设备文件的功能,并实现了 misc 设备的注册工作,在/proc目录中创建了各种 Binder 相关的文件供用户访问。从设备文件的操作方法 binder_fops 可以看出,通过如下函数 binder_open 的执行语句:

bs->fd = open("/dev/binder", O_RDWR);

即可进入 Binder 驱动程序的 binder_open()函数,内容如下。

static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE)
printk(KERN_INFO "binder_open: %d:%d\n", current->group_leader->pid, current->pid);
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
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);
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
filp->private_data = proc;
mutex_unlock(&binder_lock);
if (binder_proc_dir_entry_proc) {
char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
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);
}
return 0;
}

上述函数的主要作用是创建一个名为 binder_proc 的数据结构,用此数据结构来保存打开设备文
件“/dev/binder”的进程的上下文信息,并且将此进程上下文信息保存在打开文件结构 file 的私有数
据成员变量 private_data 中。这样在执行其他文件操作时,就通过打开文件结构 file 来取回这个进程
上下文信息了。这个进程上下文信息同时还会保存在一个全局哈希表 binder_procs 中,供驱动程序
内部使用。哈希表 binder_procs 定义在文件的开头。
static HLIST_HEAD(binder_procs);

而结构体 struct binder_proc 也被定义在文件 kernel/common/drivers/staging/android/binder.c 中。

struct binder_proc {
struct hlist_node proc_node;
struct rb_root threads;
struct rb_root nodes;
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
int pid;
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;
struct rb_root free_buffers;
struct rb_root allocated_buffers;
size_t free_async_space;
struct page **pages;
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;
int requested_threads;
int requested_threads_started;
int ready_threads;
long default_priority;
};

上述结构体的成员比较多,其中最重要的有 4 个成员变量。
• threads。
• nodes。
• refs_by_desc。
• refs_by_node。
上述 4 个成员变量都是表示红黑树的节点,即 binder_proc 分别挂在 4 个红黑树下,具体说明如下。
• thread 树: 用来保存 binder_proc 进程内用于处理用户请求的线程,它的最大数量由 max_threads
决定。
• node 树:用来保存 binder_proc 进程内的 Binder 实体。
• refs_by_desc 树和 refs_by_node 树:用来保存 binder_proc 进程内的 Binder 引用,即引用的其
他进程的 Binder 实体,分别用两种方式来组织红黑树,一种是以句柄作为 key 值来组织,一
种是以引用的实体节点的地址值作为 key 值来组织,它们都表示同一个内容,只不过为了内
部查找方便而用两个红黑树来表示。

这样,打开设备文件/dev/binder 的操作就完成了,接下来需要对打开的设备文件进行内存映射操作 mmap

bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

对应 Binder 驱动程序的是函数 binder_mmap(),实现代码如下。

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
struct vm_struct *area;
struct binder_proc *proc = filp->private_data;
const char *failure_string;
struct binder_buffer *buffer;
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE)
printk(KERN_INFO
"binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
proc->pid, vma->vm_start, vma->vm_end,
(vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
(unsigned long)pgprot_val(vma->vm_page_prot));
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;
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;
}
proc->buffer = area->addr;
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
#ifdef CONFIG_CPU_CACHE_VIPT
if (cache_is_vipt_aliasing()) {
while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {
printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p bad alignment\n",
proc->pid, vma->vm_start, vma->vm_end, proc->buffer);
vma->vm_start += PAGE_SIZE;
}
}
#endif
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start)
/ PAGE_SIZE), GFP_KERNEL);
if (proc->pages == NULL) {
ret = -ENOMEM;
failure_string = "alloc page array";
goto err_alloc_pages_failed;
}
proc->buffer_size = vma->vm_end - vma->vm_start;
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
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;
/*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n", proc->pid, vma->vm_start,
vma->vm_end, proc->buffer);*/
return 0;
err_alloc_small_buf_failed:
kfree(proc->pages);
proc->pages = NULL;
err_alloc_pages_failed:
vfree(proc->buffer);
proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
err_bad_arg:
printk(KERN_ERR "binder_mmap: %d %lx-%lx
vma->vm_start, vma->vm_end, failure_string, ret);
return ret;
}

在上述函数 binder_mmap()中,首先通过 filp->private_data 得到打开设备文件“/dev/binder”时
创建的结构 binder_proc。内存映射信息放在 vma 参数中。读者需要注意,这里的 vma 的数据类型
是结构 vm_area_struct,它表示的是一块连续的虚拟地址空间区域。在函数变量声明的地方,还看到
有一个类似的结构体 vm_struct,该数据结构也表示一块连续的虚拟地址空间区域。那么,这两者的
区别是什么呢?在 Linux 系统中,结构体 vm_area_struct 表示的虚拟地址是给进程使用的,而结构
体 vm_struct 表示的虚拟地址是给内核使用的,它们对应的物理页面都可以是不连续的。结构体
vm_area_struct 表示的地址空间范围是 0~3GB,而结构体 vm_struct 表示的地址空间范围是(3GB +
896MB + 8MB) ~ 4GB。
为什么结构体 vm_struct 表示的地址空间范围不是 3GB~4GB 呢?因为 3GB ~
(3GB + 896MB)范围的地址是用来映射连续的物理页面的,这个范围的虚拟地址和对应的实际物理
地址有着简单的对应关系, 即对应 0~896MB 的物理地址空间,而(3GB + 896MB) ~ (3GB + 896MB +
8MB)是安全保护区域。例如所有指向这 8MB 地址空间的指针都是非法的,所以结构体 vm_struct
使用(3GB + 896MB + 8MB) ~ 4G 地址空间来映射非连续的物理页面。

此处为何同时使用进程虚拟地址空间和内核虚拟地址空间来映射同一个物理页面呢?原因正是Binder 进程间通信机制的精髓所在。在同一个物理页面,一方面映射到进程虚拟地址空间,一方面映射到内核虚拟地址空间,这样进程和内核之间就可以减少一次内存拷贝工作,提高了进程之间的通信效率。

讲解了 binder_mmap 的原理之后,整个函数的逻辑就很好理解了。但是在此还是先要解释一下binder_proc 结构体中的如下成员变量。

• buffer:是一个 void*指针,它表示要映射的物理内存在内核空间中的起始位置。
• buffer_size:是一个 size_t 类型的变量,表示要映射的内存的大小。
• pages:是一个 struct page*类型的数组,struct page 用来描述物理页面的数据结构。
• user_buffer_offset:是一个 ptrdiff_t 类型的变量,它表示的是内核使用的虚拟地址与进程使用
  的虚拟地址之间的差值,即如果某个物理页面在内核空间中对应的虚拟地址是 addr,那么这
  个物理页面在进程空间对应的虚拟地址就为“addr + user_buffer_offset”格式。

接下来还需要了解 Binder 驱动程序管理内存映射地址空间的方法, 即如何管理 buffer ~ (buffer +buffer_size)这段地址空间,这个地址空间被划分为几段来管理,每一段都是用结构体 binder_buffer来描述的,代码如下。

struct binder_buffer {
struct list_head entry; /* free and allocated entries by addesss */
struct rb_node rb_node; /* free entry by size or allocated entry */
/* by address */
unsigned free : 1;
unsigned allow_user_free : 1;
unsigned async_transaction : 1;
unsigned debug_id : 29;
struct binder_transaction *transaction;
struct binder_node *target_node;
size_t data_size;
size_t offsets_size;
uint8_t data[0];
};

每一个 binder_buffer 通过其成员 entry 按从低地址到高地址的顺序连入 struct binder_proc 中的
buffers 表示的链表,同时,每一个 binder_buffer 又分为正在使用的和空闲的,通过 free 成员变量来
区分,空闲的 binder_buffer 通过成员变量 rb_node 的帮助,连入 struct binder_proc 中的
free_buffers表示的红黑树。而那些正在使用的 binder_buffer,通过成员变量 rb_node 连入 
binder_proc 中的allocated_buffers 表示的红黑树。这样做是为了方便查询和维护这块地址空间。

然后回到函数 binder_mmap(),首先对参数作一些检查,例如要映射的内存大小不能超过SIZE_4M, 即 4MB。再来到文件 service_manager.c 中的 main()函数,这里传进来的值是 128 × 1024字节, 即 128KB,这个检查没有问题。通过检查后,调用函数 get_vm_area()获得一个空闲的 vm_struct区间,并初始化 proc 结构体的 buffer、user_buffer_offset、pages 和 buffer_size 等成员变量,接着调用 binder_update_page_range 为虚拟地址空间 proc->buffer ~ proc->buffer + PAGE_SIZE 分配一个空闲的物理页面,同时这段地址空间使用一个 binder_buffer 来描述,分别插入 proc->buffers 链表和proc->free_buffers 红黑树中,最后还初始化了 proc 结构体的 free_async_space、files 和 vma 三个成员变量。

然后继续分析函数 binder_update_page_range(),分析 Binder 驱动程序如何实现将一个物理页面同时映射到内核空间和进程空间。

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 (binder_debug_mask & BINDER_DEBUG_BUFFER_ALLOC)
printk(KERN_INFO "binder: %d: %s pages %p-%p\n",
proc->pid, allocate ? "allocate" : "free", start, end);
if (end <= start)
return 0;
if (vma)
mm = NULL;
else
mm = get_task_mm(proc->tsk);
if (mm) {
down_write(&mm->mmap_sem);
vma = proc->vma;
}
if (allocate == 0)
goto free_range;
if (vma == NULL) {
printk(KERN_ERR "binder: %d: binder_alloc_buf failed to "
"map pages in userspace, no vma\n", proc->pid);
goto err_no_vma;
}
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;
struct page **page_array_ptr;
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
BUG_ON(*page);
*page = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (*page == NULL) {
printk(KERN_ERR "binder: %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? */;
page_array_ptr = page;
ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
if (ret) {
printk(KERN_ERR "binder: %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]);
if (ret) {
printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
"to map page at %lx in userspace\n",
proc->pid, user_page_addr);
goto err_vm_insert_page_failed;
}
/* vm_insert_page does not seem to increment the refcount */
}
if (mm) {
up_write(&mm->mmap_sem);
mmput(mm);
}
return 0;
free_range:
for (page_addr = end - PAGE_SIZE; page_addr >= start;
page_addr -= PAGE_SIZE) {
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
if (vma)
zap_page_range(vma, (uintptr_t)page_addr +
proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
__free_page(*page);
*page = NULL;
err_alloc_page_failed:
;
}
err_no_vma:
if (mm) {
up_write(&mm->mmap_sem);
mmput(mm);
}
return -ENOMEM;
}

通过上述函数不但可以分配物理页面,而且可以用来释放物理页面,这可以通过参数 allocate
来区别,在此只需关注分配物理页面的情况。要分配物理页面的虚拟地址空间范围为 start ~ end,函
数前面的一些检查逻辑就不分析了,只需直接分析中间的 for 循环。
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;
struct page **page_array_ptr;
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
BUG_ON(*page);
*page = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (*page == NULL) {
printk(KERN_ERR "binder: %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? */;
page_array_ptr = page;
ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
if (ret) {
printk(KERN_ERR "binder: %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]);
if (ret) {
printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
"to map page at %lx in userspace\n",
proc->pid, user_page_addr);
goto err_vm_insert_page_failed;
}
/* vm_insert_page does not seem to increment the refcount */
}

在上述代码中,首先调用 alloc_page()分配一个物理页面,此函数返回一个结构体 page 物理页
面描述符,根据这个描述的内容初始化结构体 vm_struct tmp_area,然后通过 map_vm_area 将这个物
理页面插入 tmp_area 描述的内核空间,接着通过 page_addr + proc->user_buffer_offset 获得进程虚拟
空间地址,并通过函数 vm_insert_page()将这个物理页面插入进程地址空间,参数 vma 表示要插入
的进程的地址空间。

这样,文件“frameworks/base/cmds/servicemanager/binder.c”中的函数 binder_open()讲解完毕。再次回到文件“frameworks/base/cmds/servicemanager/service_manager.c”中的 main()函数,接下来需要调用 binder_become_context_manager 来通知 Binder 驱动程序自己是 Binder 机制的上下文管理者,即守护进程。 函数 binder_become_context_manager()位于文件“frameworks/base/cmds/servicemanager/binder.c”中,具体代码如下。

int binder_become_context_manager(struct binder_state *bs){
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

在 此 通 过 调 用 ioctl 文 件 操 作 函 数 通 知 Binder 驱 动 程 序 自 己 是 守 护 进 程 , 命 令 号 是BINDER_SET_CONTEXT_MGR,并没有任何参数。BINDER_SET_CONTEXT_MGR 定义如下。

#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)

这 样 就 进 入 Binder 驱 动 程 序 的 函 数 binder_ioctl() , 在 此 只 关 注 如 下 BINDER_SET_CONTEXT_MGR 命令即可。

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) {
printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
ret = -EBUSY;
goto err;
}
if (binder_context_mgr_uid != -1) {
if (binder_context_mgr_uid != current->cred->euid) {
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
binder_context_mgr_uid = current->cred->euid;
binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
if (binder_context_mgr_node == 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;
......
default:
ret = -EINVAL;
goto err;
}
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_ioctl()之前,需要先了解如下两个数据结构。
• 结构体 binder_thread:表示一个线程,这里就是执行 binder_become_context_manager()函数的线程。

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

在上述结构体中,proc 表示这个线程所属的进程。结构体 binder_proc 中成员变量 thread 的类型是
rb_root,它表示一棵红黑树,将属于这个进程的所有线程都组织起来,结构体 binder_thread 的成员变量
rb_node 就是用来链入这棵红黑树的节点。looper 成员变量表示线程的状态,它可以取值,内容如下。
enum {
BINDER_LOOPER_STATE_REGISTERED = 0x01,
BINDER_LOOPER_STATE_ENTERED= 0x02,
BINDER_LOOPER_STATE_EXITED= 0x04,
BINDER_LOOPER_STATE_INVALID= 0x08,
BINDER_LOOPER_STATE_WAITING= 0x10,
BINDER_LOOPER_STATE_NEED_RETURN = 0x20
};

至于其余的成员变量,transaction_stack 表示线程正在处理的事务,todo 表示发往该线程的数据列表,return_error 和 return_error2 表示操作结果返回码,wait 用来阻塞线程等待某个事件的发生,stats 用来保存统计信息。

• 数据结构 binder_node:表示一个 binder 实体,定义如下:

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;
struct hlist_head refs;
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
void __user *ptr;
void __user *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;
int min_priority : 8;
struct list_head async_todo;
};