分享

android的binder驱动2 - 基本数据结构

 霜菊子 2011-02-26

android的binder驱动2 - 基本数据结构

by Nefarius@newsmth.net

我发现得先写一下binder的基本结构,不然很多函数写不清楚,所以决定先写这篇。在这之前,先得简单介绍一下binder是什么。简单的说,binder就是个跨进程的指针。

现代操作系统里,一个进程的地址空间是确定的,地址是没有二义性的,进程里的一个指针就对应一个内存地址,不可能同时对应到多个地址,给定一个指针,就能获得想要的东西。但跨进程的情况下,事情就完全不一样了,不同进程的线性地址空间都是一样的,一个进程里的指针放到另一个进程里,就是完全不同的东西了。要实现跨进程的指针,就必须通过操作系统层,只有在系统底层才能将一个进程里的地址映射到另一个进程里的地址,这也就是binder驱动所做的事情。跨进程的指针(以下直接记为binder)有两种存在方式,一是在本地进程里的存在,一是在远程进程里的存在,驱动所做的其实就是实现这两种存在方式的转换。当进程A要使用一个活在进程B里的binder时,驱动根据这个binder在进程A中的表示,找到这个binder的本地进程表示,获取其所在进程和实际指针,然后让它来完成进程A的需求。
由于binder有着这两种不同的存在方式,写程序时得区分binder是不是本进程的指针,这就给用户带来了很多麻烦,失去了跨进程指针的本来意义。为了让用户接口统一,不论是用本地的还是用远程的指针,都使用一套接口,c++/java粉墨登场。在这两种高级语言里,这个跨进程的指针实际上是一个对象的指针,所以binder事实上就升级成了跨进程的对象,用户只需要调用对象的函数就能使用binder,不必关心这个对象是活在本进程的还是活在别的进程中。实现上,提炼这个对象的基本操作成基本接口,分别用两个子类继承之,一个用来实现本地对象,一个用来实现远程对象,用户使用时用基本接口就行了,不必关心对象所在进程,底下的细节由c++的binder库来完成。另一方面,由于JNI的存在,android应用程序赖以生存的java层binder实际上是通过调用c++实现的。因此,android的binder,一半功劳属于binder驱动(kernel/drivers/staging/android/binder.c),而另一半功劳则属于c++的binder库(framework/base/libs/binder),这个库的具体细节以后有空再写。

binder的两种存在方式,一是本地进程里的存在方式,在本地进程里来看,很简单,用他自己的一个指针就能表示,但是从驱动角度来看,光一个进程内指针是不够的,驱动需要区分所有进程的指针,必须再加上一个参数,由于进程本身是操作系统独一无二的,所以驱动里用进程和进程内指针这两个参数就能唯一代表一个binder。而binder在远程进程中的存在,驱动里也是用二元组来表示的,第一维依旧是进程,驱动必须知道这个表示是为哪个远程进程维护的,第二维是一个数,叫做ref, handle, desc都行(以下记作desc,免得混淆),它是一个从0开始编排的数字,它只跟这个binder在这个进程中的出场顺序有关,进程中出现的第一个非本地(远程)的binder被记住0,第二个被记住1,以此类推,跟这个binder实际所在的进程,实际的指针都没关系,一个进程里的所有远程binder是统一排序的。

终于该写驱动里的实际内容了,第一个结构体出场,自然便是用来表示进程的binder_proc。
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;
struct dentry *debugfs_entry;
};
首先,这个结构体得能表示一个进程以及记录一个进程的资源,成员变量pid, tsk, files就是用来干这些的,另外还有threads和page, buffer之类的成员也是用来表示进程资源的,不过他们还有其他用途,以后再详细写。其次,既然进程在两种存在方式里都是第一维,那么通过它,我们应该能够得到进程所有的binder(本地的以及远程的),他的nodes, refs_by_desc, refs_by_node就是用来实现这个的,这三个都是红黑树根节点,具体的意义下面会详细解释。debugfs_entry对应于上篇文章里的proc文件/proc/binder/proc/pid。还有一些变量与work相关,以后再写。

接下来是结构体binder_node。
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;
unsigned min_priority:8;
struct list_head async_todo;
};
他是用来表示本地binder的,通过这个结构体能找到binder所在进程,也能找到binder在这个进程里的指针。proc记录了所在进程,cookie和ptr记录了他在所在进程中的指针,其实cookie才是真正的指针,而ptr是应用层为了引用计数弄出来的一个东西,对于驱动来说,两者有些重复。而debug_id则是binder_node在驱动里的全局id,前面一篇文章里的/proc/binder/下的信息里就会大量使用这个debug_id,有了它就知道指的是哪个binder_node了,除了binder_node,还有几类信息也有debug_id,这几类的debug_id在驱动里从1开始统一排的。其实也可以用debug_id这个一维的索引来表示binder,不过效率会比红黑树低,所以它只能当做debug信息用。这个结构体还有一堆ref相关的成员,用于维护引用计数,以后再写。

binder_proc里的nodes这棵树便是用来遍历一个进程的本地binder即binder_node的,为了快速查找,nodes是颗二叉搜索数,key是binder_node的ptr。按照linux的惯例,binder_node里需要有个成员,才能把它加到树或者列表里,而这个成员便是rb_node,通过rb_node,可以把binder_node与binder_proc的nodes关联,从而实现对一个进程的本地binder的管理。另外,这个rb_node是一个union,另一个名字叫做dead_node,这个身份是为了处理死亡的binder的,当一个进程结束时,他的本地binder自然也就挂了,binder_node也就没必要存在了,但是由于别的进程可能正在使用这个binder,所以一时半会,驱动还没法直接移除这个binder_node,不过由于进程挂了,驱动没法继续把这个binder_node挂靠在对应的binder_proc下,只好把它挂靠在binder_dead_nodes下面。不过,这个binder_dead_nodes其实也没啥意义,也就是打印/proc/binder/state时用了一下,看看当前有多少已经死亡但没移除的binder_node。驱动里真正移除一个死亡的binder_node是靠引用计数的机制来完成的。

接下来便是binder在远程进程中的表示,这个便是结构体binder_ref。
struct binder_ref {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* 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;
};
与binder_node类似,拥有proc和debug_id。然后便是重要的desc,即前面提到的0开始递增的数字,这个数字和proc也就能唯一确定一个远程的binder,也就是跨进程binder在另一个进程里的表示,而node指向这个binder的本地进程表示binder_node。由于一个本地binder可以被多个进程使用,于是一个binder_node可能有多个binder_ref来对应,所以binder_node中有个refs的链表用来记录他的远程表示,而node_entry就是用于这个链表的,这个没用红黑树,因为这个表一般比较小,而且用不着搜索。rb_node_desc用于binder_proc的refs_by_desc,rb_node_node用于binder_proc的refs_by_node,这两都是二叉搜索树,前一个key是desc,后一个key是node。如果已知binder的远程表示proc+desc,就可以用refs_by_desc查找,如果知道本地表示则用refs_by_node查找,看看某个远程proc是否已经具备该binder_node的远程表示。strong和weak两个变量是用于引用的,death则是用于死亡通知的,以后再写。

总结一下,binder是跨进程的指针,有两种形式,一是在本地进程里,他就是个进程指针,驱动中记为binder_node,其中proc为它的本地进程,ptr/cookie为进程里的指针,另一种是在远程进程里,他就是个从0开始增加的数(按该进程中远程binder出场顺序记录),驱动里记为binder_desc,其中proc为远程进程,desc为序号。驱动中维护远程binder和本地binder的转换,包括远程(proc, desc)与本地(proc, ptr)的互相转换,也包括远程(proc, desc)和另一个远程(proc, desc)的互相转换,当然也支持本地到本地的转换(这种情况就是完全相同的一个东西),不过这种转换驱动里是不会发生的,上层的c++的binder库中已经识别了binder是不是本地的,本地的binder自行就处理了,不需要经过驱动这层额外开销。

下面列几个相关的函数,以下省略struct关键字。
1. binder_node* binder_get_node(binder_proc *proc, void __user *ptr)
给定ptr(binder_node中的ptr)和进程proc,在proc的nodes树中查找并获取binder_node结构,如果这个proc的这个指针在驱动里还没有建立本地binder,则返回null。
2. binder_node* binder_new_node(binder_proc *proc, void __user *ptr, void __user *cookie)
给定ptr和cookie以及进程proc,在驱动中创建该指针的本地binder项,并添加到proc的nodes树中。
3. binder_ref *binder_get_ref(binder_proc *proc, uint32_t desc)
给定desc和proc,获取远程binder的表示binder_desc,如果没有则返回null。
4. binder_ref *binder_get_ref_for_node(binder_proc *proc, binder_node *node)
给定proc和node,获取某个本地binder在该proc进程里的远程表示,如果该本地binder在该proc中还没有建立远程表示,则创建它并返回。此函数会用到并可能更新proc的refs_by_desc, refs_by_node树以及node的refs链表。
5. binder_delete_ref(binder_ref *ref)
删除给定的远程binder。并不存在binder_delete_node函数,node的删除是在node的引用计数减到0时发生的,被整合到了binder_dec_node函数。

最后是驱动跟c++的binder库交互的结构体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; /* local object */
signed long handle; /* remote object */
};

/* extra data associated with local object */
void *cookie;
};
前面说过binder有两种存在方式,所以用户程序必须区分这两种存在,但是对于应用程序来说,由于使用了对象的公共接口,高层用户不必知道到底是哪一种binder,这个工作是由c++的binder库完成的。对于binder库来说,他必须区分是哪种binder,他与驱动的沟通便是用的这个结构体。这个结构体是驱动和binder库共用的,在binder.h里定义。
type表示binder的类型,一共5种:
BINDER_TYPE_BINDER,
BINDER_TYPE_WEAK_BINDER,
BINDER_TYPE_HANDLE,
BINDER_TYPE_WEAK_HANDLE
BINDER_TYPE_FD,
以下略去BINDER_TYPE_。
BINDER和WEAK_BINDER都是本地binder,对应于binder_node,HANDLE和WEAK_HANDLE都是远程binder,对应于binder_desc。至于WEAK不WEAK跟指针的引用强度有关,以后再写。binder除了是个跨进程的指针之外,还能当做跨进程文件描述符用,FD类型就是干这个的,这个没怎么用过,以后再写吧。
联合体binder/handle就对应于binder_node的ptr或者binder_desc的desc,取决于他是BINDER还是HANDLE。
cookie在BINDER类型时就是binder_node的cookie,在HANDLE时是null,没啥用。
flags的7~0bits是用来设置binder_node的线程优先级的,在binder的实际应用中,远程进程的对binder的操作请求会由binder本地进程的一个线程完成,而这个优先级就是用来设置该线程处理这个binder事物时的优先级。flags的8th bits是用来表示这个binder是否接受文件描述符的,不支持文件描述符的binder是没法用BINDER_TYPE_FD的。

这个结构体非常重要,用起来也很隐蔽。俗话说一个巴掌拍不响,如果没有这个结构体,光有驱动里的binder_node几个结构体,跨进程的指针也是不好实现的,因为无论ptr还是desc归根到底只是个32比特的数(假设是32位系统),一个进程光靠这个数是没法区分这是本地还是远程binder,而驱动光知道proc和一个数也是没法清楚用户程序要的是本地还是远程binder,需要type这个值才能分清楚是远程还是本地binder(其实我觉得也可以通过debug_id来标识,可惜慢了点^-^)。虽然对于普通的事物请求(transaction),驱动和应用程序可以假定目标binder指的远程binder,但是想要在两个进程之间传递一个binder就无法做到了,而这个正是android的binder头号程序service manager做的事情,android各种服务程序和应用程序之间也是通过这种方式传递对象binder的,这些内容以后有空再详细说明。

正如前面所说,这个结构体主要是用来在进程间传递binder而设计的,只会发生在进程间transaction时,transaction数据里的offsets和offsets_size便是用来存放这个结构体的,当transaction发生时,驱动会修改这里面的数据,把请求方进程的binder转换成transaction目标进程的里的binder,可能是远程到本地,本地到远程甚至是远程到远程binder的转换。

驱动不仅仅只是完成binder的转换和传递,必要时还得创建binder_node和binder_ref。binder_node的创建只会发生在两种情况中,一个是ioctl(BINDER_SET_CONTEXT_MGR),这个是某个进程(注:即service manager进程,比较特殊,这个binder_node不是对象,ptr和cookie都为0)把自己注册为binder的服务程序,这个service manager有必要单独写一篇。而binder_ref的创建也有一种情形是跟service manager相关的,进程与service manager通信时,如果还没有service manager的远程binder时,便会创建一个。

除了上面的特殊情况,binder_node和binder_ref的创建只可能发生在前面提到的在进程间通过transaction传递binder时。当transaction传递一个本地binder时,如果该binder在驱动中并没有记录,驱动便调用binder_new_node为其创建一个binder_node。不论传递远程还是本地binder,如果该binder在目标进程中没有记录,驱动调用binder_get_ref_for_node为目标进程创建一个binder_ref。由于binder和c++的binder库使用了引用计数, binder的创建到此才刚上路,接下来是极其复杂的引用计数,下周再写。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多