android Binder设计与实现五
5.3 Binder 在驱动中的表述
驱动是Binder通信的核心,系统中所有的Binder实体以及每个实体在各个进程中的引用都登记在驱动中;驱动需要记录Binder引用 ->实体之间多对一的关系;为引用找到对应的实体;在某个进程中为实体创建或查找到对应的引用;记录Binder的归属地(位于哪个进程中);通过 管理Binder的强/弱引用创建/销毁Binder实体等等。
驱动里的Binder是什么时候创建的呢?前面提到过,为了实现实名Binder的注册,系统必须创建第一只鸡 – 为SMgr创建的,注册实名Binder专用的Binder实体,负责实名Binder注册过程中的进程间通信。既然创建了实体也要有对应的引用:驱动将所有进程中的0号引用都预留给该Binder实体,即一开始所有进程的0号引用都指注册实名Binder专用的Binder,无须特殊操作任何进程通过0 号引用都可以注册实名Binder。接下来随着应用程序通过不断地注册实名Binder,不断向SMgr索要Binder的引用,不断将Binder从一 个进程传递给另一个进程,越来越多的Binder以传输结构 – flat_binder_object的形式穿越驱动做跨进程的迁徙。由于binder_transaction_data中data.offset数组 的存在,所有流经驱动的Binder都逃不过驱动的眼睛。Binder将对每个穿越进程边界的Binder做如下操作:检查传输结构的type域,如果是 BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER则创建Binder的实体;如果是 BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE则创建Binder的引用;如果是 BINDER_TYPE_HANDLE则为进程打开文件,无须创建任何数据结构。详细过程可参考表7。随着越来越多的Binder实体或引用穿过驱动在进 程间传递,驱动会在内核里创建越来越多的节点或引用,当然这个过程对用户来说是透明的。
5.3.1 Binder 实体在驱动中的表述
驱动中的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示:
表 8 Binder节点描述结构:binder_node
成员 | 含义 |
int debug_id; | 用于调试 |
struct binder_work work; | 当本节点引用计数发生改变,需要通知所属进程时,通过该成员挂入所属进程的to-do队列里,唤醒所属进程执行Binder实体引用计数的修改。 |
union { struct rb_node rb_node; struct hlist_node dead_node; }; |
每个进程都维护一棵红黑树,以Binder实体在用户空间的指针,即本结构的ptr成员为索引存放该进程所有的Binder实体。这样驱动可以根据Binder实体在用户空间的指针很快找到其位于内核的节点。rb_node用于将本节点链入该红黑树中。 销毁节点时须将rb_node从红黑树中摘除,但如果本节点还有引用没有切断,就用dead_node将节点隔离到另一个链表中,直到通知所有进程切断与该节点的引用后,该节点才可能被销毁。 |
struct binder_proc *proc; | 本成员指向节点所属的进程,即提供该节点的进程。 |
struct hlist_head refs; | 本成员是队列头,所有指向本节点的引用都链接在该队列里。这些引用可能隶属于不同的进程。通过该队列可以遍历指向该节点的所有引用。 |
int internal_strong_refs; | 用以实现强指针的计数器:产生一个指向本节点的强引用该计数就会加1。 |
int local_weak_refs; | 驱动为传输中的Binder设置的弱引用计数。如果一个Binder打包在数据包中从一个进程发送到另一个进程,驱动会为该Binder增加引用计数,直到接收进程通过BC_FREE_BUFFER通知驱动释放该数据包的数据区为止。 |
int local_strong_refs; | 驱动为传输中的Binder设置的强引用计数。同上。 |
void __user *ptr; | 指向用户空间Binder实体的指针,来自于flat_binder_object的binder成员 |
void __user *cookie; | 指向用户空间的附加指针,来自于flat_binder_object的cookie成员 |
unsigned has_strong_ref; unsigned pending_strong_ref; unsigned has_weak_ref; unsigned pending_weak_ref |
这一组标志用于控制驱动与Binder实体所在进程交互式修改引用计数 |
unsigned has_async_transaction; | 该成员表明该节点在to-do队列中有异步交互尚未完成。驱动将所有发送往接收端的数据包暂存在接收进程或线程开辟的to-do队列里。对于异步交互,驱动做了适当流控:如果to-do队列里有异步交互尚待处理则该成员置1,这将导致新到的异步交互存放在本结构成员– asynch_todo队列中,而不直接送到to-do队列里。目的是为同步交互让路,避免长时间阻塞发送端。 |
unsigned accept_fds | 表明节点是否同意接受文件方式的Binder,来自flat_binder_object中flags成员的FLAT_BINDER_FLAG_ACCEPTS_FDS位。由于接收文件Binder会为进程自动打开一个文件,占用有限的文件描述符,节点可以设置该位拒绝这种行为。 |
int min_priority | 设置处理Binder请求的线程的最低优先级。发送线程将数据提交给接收线程处理时,驱动会将发送线程的优先级也赋予接收线程,使得数据即使跨了进程也能以同样优先级得到处理。不过如果发送线程优先级过低,接收线程将以预设的最小值运行。 该域的值来自于flat_binder_object中flags成员。 |
struct list_head async_todo | 异步交互等待队列;用于分流发往本节点的异步交互包 |
每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的 flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说内存地址是唯一的,所以不会重复建设造成混乱。
5.3.2 Binder 引用在驱动中的表述
和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示:
表 9 Binder引用描述结构:binder_ref
成员 | 含义 |
int debug_id; | 调试用 |
struct rb_node rb_node_desc; | 每个进程有一棵红黑树,进程所有引用以引用号(即本结构的desc域)为索引添入该树中。本成员用做链接到该树的一个节点。 |
struct rb_node rb_node_node; | 每个进程又有一棵红黑树,进程所有引用以节点实体在驱动中的内存地址(即本结构的node域)为所引添入该树中。本成员用做链接到该树的一个节点。 |
struct hlist_node node_entry; | 该域将本引用做为节点链入所指向的Binder实体结构binder_node中的refs队列 |
struct binder_proc *proc; | 本引用所属的进程 |
struct binder_node *node; | 本引用所指向的节点(Binder实体) |
uint32_t desc; | 本结构的引用号 |
int strong; | 强引用计数 |
int weak; | 弱引用计数 |
struct binder_ref_death *death; | 应用程序向驱动发送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令从而当Binder实体销毁时能够收到来自驱动的提醒。该域不为空表明用户订阅了对应实体销毁的‘噩耗’。 |
就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存 放所有该进程正在使用的引用。但Binder的引用可以通过两个键值索引:
· 对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体在内核中的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个 进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。
· 引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都保留给SMgr,其它值由驱动在创建引用时动态分配。向Binder 发送数据包时,应用程序通过将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的 Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信 息,实现数据包的路由。
摘自 LuoXianXiong,您的伙伴
补充:移动开发 , Android ,