收藏 分享(赏)

Binder深入讲解 底层 内核实现.doc

上传人:gnk289057 文档编号:9468133 上传时间:2019-08-09 格式:DOC 页数:38 大小:451.50KB
下载 相关 举报
Binder深入讲解  底层 内核实现.doc_第1页
第1页 / 共38页
Binder深入讲解  底层 内核实现.doc_第2页
第2页 / 共38页
Binder深入讲解  底层 内核实现.doc_第3页
第3页 / 共38页
Binder深入讲解  底层 内核实现.doc_第4页
第4页 / 共38页
Binder深入讲解  底层 内核实现.doc_第5页
第5页 / 共38页
点击查看更多>>
资源描述

1、第一节 Android Binder星期四, 06/17/2010 - 00:03 williamAndroid Binder是一种在Android里广泛使用的一种远程过程调用接口。从结构上来说Android Binder系统是一种服 务器/ 客户机模式,包括Binder Server、Binder Client和Android Binder驱动,实际的数据传输就是通 过Android Binder驱动来完成的,这里我们就来详细的介绍Android Binder驱动程序。通常来说,Binder是Android系统中的内部进程通讯(IPC)之一。在Android 系统中共有三种IPC机制,分别是

2、:标 准Linux Kernel IPC接口标 准D-BUS 接口Binder接口尽管Google宣称 Binder具有更加简洁、快速,消耗更小内存资源的优点,但并没有证据表明D-BUS就很差。实际上D-BUS可能会更合适些,或 许只是当时Google 并没有注意到它吧,或者Google不想使用GPL协议的D-BUS库。我们不去探究具体的原因了,你只要清楚 Android系统中支持了多个IPC接口,而且大部分程序使用的是我们并不熟悉的Binder 接口。Binder是OpenBinder的Google精简实现,它包括一个 Binder驱动程序、一个Binder服务器及Binder 客户端(?)

3、。这 里我们只要介 绍内核中的 Binder驱动的实现。对于Android Binder,它也可以称为是Android系统的一种RPC(远程过程调用)机制,因 为Binder实现的功能就是在本地“执行” 其他服 务进 程的功能的函数调用。不管是IPC也好,还是RPC也好,我们所要知道的就是Android Binder的功能是如何 实现的。Openbinder介 绍2.1.1 Android Binder协议Android 的Binder机制是基于OpenBinder (http:/ OpenBinder的Linux实现。Android Binder的协议定义在binder.h 头文件中, An

4、droid的通讯 就是基于这样的一个协议的。 Binder Type(描述 binder type的功能)Android定义了五个(三大类)Binder类型,如下:enum BINDER_TYPE_BINDER = B_PACK_CHARS(s, b, *, B_TYPE_LARGE),BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS(w, b, *, B_TYPE_LARGE),BINDER_TYPE_HANDLE = B_PACK_CHARS(s, h, *, B_TYPE_LARGE),BINDER_TYPE_WEAK_HANDLE= B_PACK_CHARS

5、(w, h, *, B_TYPE_LARGE),BINDER_TYPE_FD = B_PACK_CHARS(f, d, *, B_TYPE_LARGE),; Binder Object进程间传输的数据被称为Binder对象(Binder Object),它是一个flat_binder_object,定 义如下:struct flat_binder_object /* 8 bytes for large_flat_header. */unsigned long type;unsigned long flags;/* 8 bytes of data. */union void *binder; /

6、* local object */signed long handle; /* remote object */;/* extra data associated with local object */void *cookie;其中,类 型字段描述了 Binder 对 象的类型,flags 描述了传输方式,比如同步、异步等。enum transaction_flags TF_ONE_WAY = 0x01, /* this is a one-way call: async, no return */TF_ROOT_OBJECT = 0x04, /* contents are the compo

7、nents root object */TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */TF_ACCEPT_FDS = 0x10, /* allow replies with file descriptors */;传输的数据是一个复用数据联合体,对于BINDER 类型,数据就是一个 binder本地对象,如果是 HANDLE类型,这数据就是一个远程的handle对象。该如何理解本地binder对象和远程handle对象呢?其实它们都指向同一个对象,不过是从不同的角度来 说。 举例来说,假如A有个对象X, 对于A 来说

8、, X就是一个本地的binder对象;如果B想访问A 的X对象, 这对于B来说,X就是一个handle。因此,从根本上来说handle和binder都指向X。本地对象还可以带有额外的数据,保存在cookie中。Binder对象的传递 是通过binder_transaction_data 来实现的,即Binder对象实际是封装在binder_transaction_data结构体中。 binder_transaction_data这个数据结构才是真正要传输的数据。它的定义如下:struct binder_transaction_data /* The first two are only use

9、d for bcTRANSACTION and brTRANSACTION,* identifying the target and contents of the transaction.*/union size_t handle; /* target descriptor of command transaction */void *ptr; /* target descriptor of return transaction */ target;void *cookie; /* target object cookie */unsigned int code; /* transactio

10、n command */* General information about the transaction. */unsigned int flags;pid_t sender_pid;uid_t sender_euid;size_t data_size; /* number of bytes of data */size_t offsets_size; /* number of bytes of offsets */* If this transaction is inline, the data immediately* follows here; otherwise, it ends

11、 with a pointer to* the data buffer.*/union struct /* transaction data */const void *buffer;/* offsets from buffer to flat_binder_object structs */const void *offsets; ptr;uint8_t buf8; data;结构体中的数据成员target是一个复合联合体对象,请参考前面的关于binder本地对象及handle远程对象的描述。code是一个命令,描述了请求Binder 对象执行的操作。 对象的索引和映射Binder中的一个重

12、要概念就是 对 象的映射和索引。就是要把 对象从一个进程映射到另一个进程中,以实现线程迁移的概念。前面描述过Binder的一个重要概念是进程/线程迁移,即当一个进程需要同另一个进程通信时,它可以 “迁移”远程的 进程/线程到本地来执行。对于调用进程来说,看起来就像是在本地执行一样。这是Binder与其他IPC机制的不同点或者 说是优点。当然迁移的工作是由Binder驱动来完成的,而实现的基础和核心就是 对象的映射和索引。Binder中有两种索引,一是本地 进 程地址空间的一个地址,另一个是一个抽象的32位句柄(HANDLE ),它们之间是互斥的:所有的进程本地对象的索引都是本地进程的一个地址(

13、address, ptr, binder),所有的 远程进程的对象的索引都是一个句柄(handle )。对于发送者进程来说,索引就是一个远端对象的一个句柄,当Binder 对 象数据被发送到远端接收进程时,远端接受进程则会认为索引是一个本地对象地址,因此从第三方的角度来 说,尽管名称不同, 对于一次Binder 调用,两种索引指的是同一个对象,Binder驱动则负责两种索引的映射,这样才能把数据发送给正确的进程。对于Android的Binder 来说,对象的索引和映射是通过binder_node和binder_ref两个核心数据结构来完成的,对于Binder本地对象,对象的Binder地址保存

14、在binder_node-ptr里,对于远程 对象,索引就保存在binder_ref-desc里,每一个binder_node都有一个binder_ref对象与之相联系,他们就是是通过ptr和desc 来做映射的,如下图:flat_binder_object就是进程间传递的Binder对象,每一个 flat_binder_object对象内核都有一个唯一的binder_node对象,这个对象挂接在binder_proc的一颗二叉树上。对于一个binder_node 对象,内核也会有一个唯一的binder_ref对象,可以这么理解, binder_ref的desc唯一的映射到binder_nod

15、e的ptr 和 cookie上,同时也唯一的映射到了flat_binder_object的handler 上。而 binder_ref又按照node和desc两种方式映射到binder_proc对象上,也就是可以通 过 binder_node对象或者desc两种方式在binder_proc上查找到binder_ref或binder_node 。所以, 对于flat_binder_object对象来说,它的binder+cookie和handler指向了同一个binder_node 对象上,即同一个binder对象。 BinderDriverCommandProtocolBinder驱动的命令

16、协议(BC_命令),定义了Binder 驱动支持的命令格式及数据定义(协议)。不同的命令所带有的数据是不同的。Binder的命令由binder_write_read 数据 结构描述,它是ioctl命令(BINDER_WRITE_READ)的参数。struct binder_write_read signed long write_size; /* bytes to write */signed long write_consumed; /* bytes consumed by driver */unsigned long write_buffer;signed long read_size;

17、/* bytes to read */signed long read_consumed; /* bytes consumed by driver */unsigned long read_buffer;对于写操作,write_buffer 包含了一系列 请求线程执行的Binder 命令;对于读(返回)操作,read_buffer包含了一系列线程执行后填充的返回值。Binder命令(BC_)用于BINDER_WRITE_READ的write操作。Binder的BC的命令格式是:| CMD | Data. |Binder CommandsCMD Data Format NotesBC_TRANS

18、ACTIONBC_REPLYbinder_transaction_data BC_ACQUIRE_RESULTBC_ATTEMPT_ACQUIRE Not implementBC_FREE_BUFFER data_ptr ptr to transaction data received on a readBC_INCREFSBC_ACQUIREBC_RELEASEBC_DECREFSint target descriptorBC_INCREFS_DONEBC_ACQUIRE_DONE node_ptr | cookie BC_REGISTER_LOOPERBC_ENTER_LOOPERBC_E

19、XIT_LOOPERNo parametersBC_REQUEST_DEATH_NOTIFICATION target_ptr | cookie BC_DEAD_BINDER_DONE cookie BinderDriverReturnProtocolBinder驱动的响 应(返回, BR_)协议,定义了Binder命令的数据返回格式。同Binder 命令协议一样,Binder驱动 返回协议 也是通过BINDER_WRITE_READ ioctl命令实现 的,不同的是它是read操作。Binder BR的命令格式是:| CMD | Data. |Binder BR 命令CMDS Data Fo

20、rmat NotesBR_ERROR int Error codeBR_OKBR_NOOPBR_SPAWN_LOOPERNo parametersBR_TRANSACTIONBR_REPLYbinder_transaction_data the received commandBR_ACQUIRE_RESULTBR_ATTEMPT_ACQUIREBR_FINISHEDNot implementBR_DEAD_REPLY The target of the last transaction is no longer with us.bcTRANSACTION or bcATTEMPT_ACQUI

21、REBR_TRANSACTION_COMPLETE No parameters.Always refers to the last transaction requested (including replies).Note that this will be sent even for asynchronous transactionsBR_INCREFSBR_ACQUIREBR_RELEASEBR_DECREFStarget_ptr | cookie BR_DEAD_BINDERBR_CLEAR_DEATH_NOTIFICATION_DONEcookie BR_FAILED_REPLY T

22、he the last transaction(either a bcTRANSACTION or a bcATTEMPT_ACQUIRE) failed(e.g. out of memory). Android Binder进程/线程模型(描述 Android的 进 程模型)2.1.2 驱动接口Android Binder设备驱动接口函数是device_initcall(binder_init);我们知道一般来说设备驱动的接口函数是module_init 和module_exit,这么做是为了同时兼容支持静态编译的驱动模块(buildin)和动态编译的驱动模块(module )。但是Andr

23、oid的Binder驱动显然不想支持动态编译的驱动,如果你需要将Binder 驱动修改为动态的内核模块,可以直接将device_initcall修改为module_init,但不要忘了增加module_exit的驱动卸载接口函数。 binder_init初始化函数首先创建了一个内核工作队列对象(workqueue),用于执行可以延期执行的工作任务:static struct workqueue_struct *binder_deferred_workqueue;binder_deferred_workqueue = create_singlethread_workqueue(“binder“)

24、;挂在这个workqueue上的work是 binder_deferred_work,定义如下。当内核需要 执行work任务时,就通过workqueue来调度执行这个work 了。static DECLARE_WORK(binder_deferred_work, binder_deferred_func);queue_work(binder_deferred_workqueue, 既然说到了binder_deferred_work ,这里有必要来进一步说明一下,binder_deferred_work是在函数binder_defer_work里调度的:static voidbinder_def

25、er_work(struct binder_proc *proc, enum binder_deferred_state defer)mutex_lock(proc-deferred_work |= defer;if (hlist_unhashed(queue_work(binder_deferred_workqueue, mutex_unlock(deferred_work有三种类型,分别是BINDER_DEFERRED_PUT_FILES, BINDER_DEFERRED_FLUSH 和BINDER_DEFERRED_RELEASE 。它们都操作在binder_proc对象上。enum b

26、inder_deferred_state BINDER_DEFERRED_PUT_FILES = 0x01,BINDER_DEFERRED_FLUSH = 0x02,BINDER_DEFERRED_RELEASE = 0x04,;就现介绍到这里了,关于 deferred的具体操作在后面还会有详细的介绍。下面回到我们的初始化函数主题上。初始化函数接着使用proc_mkdir创建了一个Binder的proc 文件系统的根节点(binder_proc_dir_entry_root,/proc/binder),并为binder创建了binder proc节点(binder_proc_dir_entry

27、_proc,/proc/binder/proc),注意不要混淆Linux Proc和Binder Proc。然后Binder驱动 使用misc_register把自己注册为一个Misc设备( /dev/misc/binder)。最后,如果驱动成功的 创建了/proc/binder根节点,就调用create_proc_read_entry创建只读proc文件:/proc/binder/state, /proc/binder/stats,/proc/binder/transactions,/proc/binder/transaction_log,/proc/binder/failed_transa

28、ction_log。这个初始化函数有个小小的问题,它没有判断Misc设备是否注册成功了,如果注册失败了,那么 Binder就不能正常工作了,因此这里应该 有个错误处理流程。注:workqueue是 Linux2.6内核的一种延期 执 行任 务 的一种机制,用于提到古老的任 务队 列( task queue)机制, workqueue机制非常灵活, 简单 ,易于使用。mutex_lock和 mutex_unlock是一种内核同步机制。2.1.3 Binder核心数据在进一步介绍Binder 驱动之前,我们有必要了解一下Binder的核心数据。 binder_procstruct binder_p

29、roc 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

30、_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_thre

31、ads;int requested_threads;int requested_threads_started;int ready_threads;long default_priority;binder_proc用于保存调用binder的各个进程或线程信息,比如线程id、进程id、 binder状态信息等。(各个数据的意 义 ?) binder_nodestruct binder_node int debug_id;struct binder_work work;union struct rb_node rb_node;struct hlist_node dead_node;struct b

32、inder_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;unsigne

33、d accept_fds : 1;int min_priority : 8;struct list_head async_todo; binder_threadstruct 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

34、 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_refstruct binder_ref /* Lookups needed: */* node + proc = ref (transaction) */*

35、 desc + proc = ref (transaction, inc/dec ref) */* node = refs + procs (proc exit) */int debug_id;struct rb_node rb_node_desc;struct rb_node rb_node_node;struct hlist_node node_entry;struct binder_proc *proc;struct binder_node *node;uint32_t desc;int strong;int weak;struct binder_ref_death *death;2.1

36、.4 用户接口驱动程序的一个主要同能就是向用户空间的程序提供操作接口, 这个接口是标准的, 对于Android Binder驱动,包含的接口有:Proc接口(/proc/binder). /proc/binder/state. /proc/binder/stats. /proc/binder/transactions. /proc/binder/transaction_log. /proc/binder/failed_transaction_log. /proc/binder/proc/- 设备 接口(/dev/binder). binder_open. binder_release. bin

37、der_flush. binder_mmap. binder_poll. binder_ioctl这些内核接口函数是在驱动程序的初始化函数(binder_init)中初始化的,感兴趣的读者可以阅读相关函数的实现代码,很简单明了,一共才十几行的代码。 binder_open通常来说,驱动程序的 open函数是用户调用驱动接口来使用驱动功能的第一个函数,称为入口函数。同其他驱动一样,对 于Android驱动,任何一个进程及其内的所有线程都可以打开一个binder设备。首先来看看Binder驱动 是如何打开设备 的。首先,binder驱动分配内存以保存 binder_proc数据结构。然后,bind

38、er填充binder_proc数据(初始化),增加当前线程/进程的引用计数并赋值给tskget_task_struct(current);proc-tsk = current;初始化binder_proc的队列及默认优先级INIT_LIST_HEAD(init_waitqueue_head(proc-default_priority = task_nice(current);增加BINDER_STAT_PROC的对 象计数,并把创建的binder_proc对象添加到全局的binder_proc哈希列表中,这样任何一个 进程就都可以 访问到其他进程的binder_proc对象了。binder_s

39、tats.obj_createdBINDER_STAT_PROC+;hlist_add_head(把当前进程/ 线程的 线程组的pid (pid指向线程id)赋值给proc的pid字段,可以理解为一个进程id(thread_group指向线程组中的第一个线程的task_struct结构)。同 时把binder_proc对象指针赋值给filp的private_data对象保存起来。proc-pid = current-group_leader-pid;INIT_LIST_HEAD(filp-private_data = proc;最后,在bindr proc目录中创建只读文件(/proc/bin

40、der/proc/$pid)用来输出当前binder proc对象的状态。这里要注意的是, proc-pid字段,按字面理解它 应该是保存当前 进程/线程的id,但实际上它保存的是线程组的pid,也就是线程组中的第一个线程的pid(等于tgid,进程id)。这样当一个进程或线程打开一个binder设备时,驱动就会在内核中 为其创建binder_proc 结构来保存打开此设备的进程/线程信息。 binder_releasebinder_release是一个驱动的出口函数,当进程退出(exit)时,进程需要显示或隐式的调用release函数来关闭打开的文件。 Release函数一般来清理进程的内核

41、数据,释放申请的内存。Binder驱动的release函数相对比较简单,它 删除open是创建的binder proc文件,然后 调 度一个workqueue来释放这个进程/ 线程的binder_proc 对象(BINDER_DEFERRED_RELEASE)。这里要提的一点就是使用workqueue(deffered)可以提高系统的响应速度和性能,因为Android Binder的release及flush等操作是一个复杂费事的任务,而且也没有必要在系统调用里完成它,因此最好的方法是延迟执行这个费时的任务。其实在中断处理函数中我们经常要把一些耗时的操作放到底半部中处理(bottom half

42、),这是一样的道理。当然真正的释放工作是在binder_deferred_release函数里完成的,后面在做详尽的介绍。workqueue是 Linux系 统 中延 迟执 行任 务 的一种机制 binder_flushflush操作在关 闭一个设备文件描述符拷贝时被调用,Binder的flush函数十分简单,它只是简单的调度一个workqueue执行BINDER_DEFERRED_FLUSH操作。flush操作比较简单,内核只是唤醒所有睡眠在proc对象及其thread对象上的所有函数。 binder_mmapmmap(memory map)用于把设备内存映射到用户进程地址空间中,这样就可以

43、像操作用 户内存那样操作设备内存。( 还有一种说法,mmap用于把用户进程地址空间映射到设备内存上,尽管说法不同,但是说的是同一个事情)Binder设备对内存映射是有些限制的,比如binder设备最大能映射 4M的内存区域;binder不能映射具有写权限的内存区域。不同于一般的设备驱动,大多的 设备 映射的设备内存是设备本身具有的,或者在驱动初始化时由vmalloc或kmalloc等内核内存函数分配的,Binder 的设备内存是在mmap操作时分配的,分配的方法是先在内核虚拟映射表上获取一个可以使用的区域,然后分配物理页,并把物理页映射到获取的虚拟空间上。由于设备内存是在mmap 操作中实现的

44、,因此每个进程/线程只能做映射操作一次,其后的操作都会返回错误。具体来说,binder_mmap首先检查mmap 调用是否合法,即是否满足binder内存映射的条件,主要检查映射内存的大小、flags,是否是第一次调用。if (vma-vm_end - vma-vm_start) SZ_4M)vma-vm_end = vma-vm_start + SZ_4M;if (vma-vm_flags if (proc-buffer) 然后,binder驱动从系统申请可用的虚 拟内存空间(注意不是物理内存空 间), 这是通过get_vm_area内核函数实现的:(get_vm_area是一个内核,功能是

45、在内核中申 请并保留一块连续的内核虚拟内存空间区域)area = get_vm_area(vma-vm_end - vma-vm_start, VM_IOREMAP);proc-buffer = area-addr;proc-user_buffer_offset = vma-vm_start - (uintptr_t)proc-buffer;然后根据请求映射的内存空间大小,分配binder核心数据结构binder_proc的pages 成员,它主要用来保存指向申请的物理页的指针。proc-pages = kzalloc(sizeof(proc-pages0) * (vma-vm_end - v

46、ma-vm_start) / PAGE_SIZE), GFP_KERNEL);proc-buffer_size = vma-vm_end - vma-vm_start;一切都准备就绪了,现在开始分配物理内存( page)了。这是通过binder驱动的帮助函数binder_update_page_range来实现的。尽管名字上称为update page,但在这里它是在分配物理页 并映射到刚才保留的虚拟内存空间上。当然根据参数,它也可以释放物理页面。在 这里,Binder使用alloc_page分配页面,使用 map_vm_area为分配的内存做映射关系,使用vm_insert_page把分配的物

47、理页插入到用户vma区域。函数传递的参数很有意识,我们来看一下:binder_update_page_range(proc, 1, proc-buffer, proc-buffer + PAGE_SIZE, vma);开始地址是proc-buffer 很容易理解,但是也许你会奇怪为什么结束地址是proc-buffer+PAGE_SIZE?这是因为,在这里并没有全部分配物理内存,其实只是分配了一个页的物理内存(第一,函数是在分配物理页,就是说物理内容的分配是以页面 为单位的,因此所谓的开始地址和结束地址是用来计算需要分配多少物理页面的,这是开始地址和结束地址的含义。第二,前面已经提到mmap的最

48、大物理内存是4M,因此需要的最多的pages就是1K ,一个指针是4个字节,因此最大的 page指针buffer就是4K,也就是一个 页的大小。第三,不管申 请mmap物理内存是多大,内核总是分配一个页的指针数据,也就是每次都分配最多的物理页。) 下面来看看物理 页是如何分配的:*page = alloc_page(GFP_KERNEL | _GFP_ZERO);tmp_area.addr = page_addr;tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;page_array_ptr = page;ret = map_vm_

49、area(user_page_addr = (uintptr_t)page_addr + proc-user_buffer_offset;ret = vm_insert_page(vma, user_page_addr, page0);注: alloc_page, map_vm_area和 vm_insert_page都是 Linux内核中内存相关函数。物理页分配完后, 这个物理内存就交 给binder_buffer 来管理了,刚刚申请的物理内存以binder_buffer的方式放到proc-buffers链表里。struct binder_buffer *buffer;buffer = proc-buffer

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报