`
hududumo
  • 浏览: 239398 次
文章分类
社区版块
存档分类
最新评论

android的binder驱动 进程,线程,线程池

 
阅读更多

init函数binder_init
1. create_singlethread_workqueue(“binder”)创建一个workqueue来做一些延迟工作。以前是静态创建的,2.2改为在init中创建。
2. proc文件系统中建立目录binder和binder/proc。
3. 注册binder驱动。
4. /proc/binder下建立几个proc文件state, stats, transactions, transaction_log, failed_transaction_log,这些个proc文件和/binder/proc/pid都可以读出binder相关的状态和统计信息,/binder/proc/pid是各个binder进程的信息,会在open函数中建立。这些个proc文件能帮助理解binder,可惜的是,有长度限制,最多只会打印一个内存页面那么多字节,对于node或者ref多的进程,信息就显示不全,比如system_server的binder信息。具体每个文件的信息等把binder的数据结构写完了再加上。

exit函数,无,built-in的,不需要这个

module参数
1. 参数可在/sys/module/binder/parameters下找到,可读可写。
2. debug_mask,调试标志位,参考enum BINDER_DEBUG_XXX,相应bit置1能看到相应的调试信息,都是KERN_INFO级别。不过adb下是看不到printk的,得dmesg或者用串口看。这个参数的设置可以帮助理解binder驱动的工作流程。
3. proc_no_lock,查看proc文件时是否加binder全局锁,一般不加,查看proc一般也不需要得到最准确的信息,没必要锁着,影响binder工作。
4. stop_on_user_error,设置为0时,不起作用,设置为1时,如果某个进程调用binder时发生错误,则该进程sleep,以后别的进程再调用binder的ioctl也会sleep,此时你若往这个参数写入一个小于2的数,进程会被唤醒。设置为2以上的值,那悲剧了,ioctl直接睡觉。该参数默认为0。

文件操作open - binder_open
1.创建binder_proc结构体用来记录调用进程的信息,并进行相关初始化工作。将该结构体指针记录到filp->private_data,这样以后别的文件操作都能得到当前进程的binder信息。
2. 建立/proc/binder/pid文件,用于查询进程的binder信息。

文件操作mmap - binder_mmap
1.内核内存空间中分配最大4M的连续空间给进程,并关联到进程的用户空间。之所以要连续的,是为了方便的维护与用户空间地址的映射,内核地址只需要加上一个偏移量proc->user_buffer_offset就能得到用户地址了。
2.该空间用于transaction交互数据用。假设A是服务程序,B是客户程序,B发出请求后,驱动会把B传送的数据从B的用户空间拷贝到内核中分配给A进程的内存空间,然后通知A进程工作,由于内核空间的内存已经通过mmap映射到A的用户空间,驱动只需要给A进程一个指向内核空间的用户指针即可,A就能获得从B考过来的数据,于是节省了一次从驱动到A用户空间的数据拷贝。
3.由于该操作负责分配空间给进程,所以使用binder的进程必须调用一次,设定其用于transaction的总buffer空间,一般在open之后就会执行,否则无法进行transaction服务,会得到BR_FAILED_REPLY,如果空间不够用的时候,同样会得到这个BR。
4.操作流程,get_vm_area获取内核空间的一段连续虚拟地址,kzalloc一段空间给proc->pages用于保存页表节点,binder_update_page_range调用alloc_page分配页面并用map_vm_area关联到内核的虚拟地址,同时用vm_insert_page把分配到的页面添加到进程的用户空间。注意,mmap的时候只分配了第一个页面,其余的页面在以后调用binder_alloc_buf的时候才分配,也算是一种page on demand吧。
5. 这个操作还会保存进程的files_struct到binder_struct中用于binder的fd操作。
6. fd操作和buffer的操作写binder具体操作的时候再加上。

文件操作poll - binder_poll
1. 检查是否有proc或thread的工作要做,有的话返回POLLIN。
2. 没有的话进行poll_wait。
3. 醒来之后再次检查,有工作要做的话返回POLLIN,否则返回0。

文件操作unlocked_ioctl - binder_ioctl
1.进入时检查binder_stop_on_user_error(前面提到的模块参数stop_on_user_error),确定是否wait。
2. binder_get_thread(proc),获取或者初始化当前线程在binder中的节点。binder_proc中有颗红黑树threads用结构体binder_thread记录了各个thread的信息,这是颗按pid排列的二叉查找树。比较重要的初始化,每个binder_thread的looper的BINDER_LOOPER_STATE_NEED_RETURN标记被设置,这将使得任何一个线程的第一次ioctl一定回返回,而不会阻塞在read阶段。binder writeread操作的时候再详细说明。
3. 各种binder ioctl操作。
4.清空线程looper的BINDER_LOOPER_STATE_NEED_RETURN,没有这个标记之后,以后的ioctl就有可能阻塞在read阶段,写binder writeread操作的时候再详细说明。
5.返回前再次检查binder_stop_on_user_error(前面提到的模块参数stop_on_user_error),确定是否wait。
6. 以下是几个ioctl所做的事情,其中BINDER_WRITE_READ过于复杂,以后writeread操作时再写。
7. BINDER_SET_MAX_THREADS,设置进程的最大生成线程max_threads,具体操作BC_REGISTER_LOOPER时用到。
8. BINDER_SET_CONTEXT_MGR,设置当前进程为service manager,用全局变量记录下来。binder是一个服务和客户通讯的协议,客户为了能跟服务搭上线,需要有个地方能查找服务的实际所在,只有在获取服务的实际所在才能提出后面的各种要求,而为客户牵线的便是这个service manager进程,这个得等下binder框架层才能说的更清楚了。
9. BINDER_THREAD_EXIT,当前线程退出binder驱动,清理其在binder_get_thread中创建的节点,同时会给那些跟他transaction的线程发个BR_DEAD_REPLY表明自己挂了。
10. BINDER_VERSION,获取binder协议版本。

文件操作flush – binder_flush
1. 调用binder_defer_work(proc, BINDER_DEFERED_FLUSH)把flush的工作交给binder_init中创建的workqueue来延迟执行。
2. workqueue最终会执行binder_deferred_flush来进行flush。flush函数会设置该进程的每个线程的looper标记的BINDER_LOOPER_STATE_NEED_RETURN位,前面提过,该标记会导致read操作立刻返回,当然,还需要唤醒那些已经睡觉的线程,不然光设置一个标记也没用。总之,这个操作就是让所有的线程不再睡觉,主要是针对那些等待read的线程。

文件操作release – binder_release
1. 删除/binder/proc/pid。
2. 调用binder_defer_work(proc, BINDER_DEFERED_RELEASE)把release的工作交给binder_init中创建的workqueue来延迟执行。
3. workqueue最终会执行binder_deferred_release来释放该进程在binder驱动中相关的内容,包括page, buffer, node, ref, thread, proc。node的释放是将其先放到dead_node表中,并想那些注册过死亡通知的线程发出死亡通知,而dead_node需要等到该node所有的ref都删除时才会真的删除。

<wbr></wbr>

我发现得先写一下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 {




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 {

unsigned long type;
unsigned long flags;


union {
void *binder;
signed long handle;
};


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的创建到此才刚上路,接下来是极其复杂的引用计数,下周再写。

<wbr></wbr>

<wbr></wbr>

<wbr></wbr>

首先说一下进程,即linux进程。驱动会使用上篇提到的binder_proc记录进程信息。每个要使用binder驱动的进程都会在使用驱动前open binder驱动(废话…),驱动在open的时候会创建该结构体,初始化一些信息,并把该结构体的指针记录到文件结构体的private_data,以后的驱动操作就能通过文件结构体获取该进程信息。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;
};
前面说过,进程是有自己独立的内存空间的,所以binder这个东西是基于进程的,而不是线程,因为同一进程的不同线程是共用一个内存空间,没必要把binder细分到线程,而且也不适合后面要说的线程池。因此驱动里都是以binder_proc为单元来分配和记录资源的,驱动会给每个进程创建一个/proc/binder/proc/pid文件,用来展示进程相关的binder信息,而进程的binder清理工作则是通过标记量deferred_work以及链表deferred_work_node实现的。至于transaction需要的内存空间则是通过一系列名字包含buffer和page的成员变量完成的,这个以后再写。

最重要的是,上一篇说的远程和本地binder都是在binder_proc中有记录的,用的便是proc_node, refs_by_desc, refs_by_node三棵树。还有一棵重要的树,叫做threads,这个是用来记录进程中与binder有一腿的线程的,与线程相关的还有各种含thread的变量以及todo,wait和default_priority。

至于delivered_death则是用于死亡通知的,而stats是用于统计binder信息的,/proc/binder/stats和/proc/binder/proc/pid能查到相应信息。

第二个是线程,即linux的线程。主要结构体是binder_thread,如下:
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;
uint32_t return_error2;


wait_queue_head_t wait;
struct binder_stats stats;
};
proc用来记录该线程所属进程的binder_proc结构体,pid记录线程的id,stats同proc的,用于统计线程的binder信息,return_error和return_error2用来记录ioctl的一些错误信息,transaction_stack用于transaction,looper记录线程的状态,与后面的线程池相关。wait和todo则跟线程的工作相关。

rb_node用来关联binder_proc的threads树,这也是一棵二叉搜索树,key很显然便是线程的pid。有一个很重要的函数跟这棵树相关:
binder_thread *binder_get_thread(binder_proc *proc)
任何一个线程执行binder驱动的poll和ioctl操作时,驱动都会从文件结构体获取binder_proc信息,然后尝试从这个进程信息里获取当前线程的信息,如果没找到,则为当前线程创建一个binder_thread,并记录到进程的threads中。所以,进程的threads只是记录了那些跟binder有一腿的线程的,而不会记录进程的所有线程。

第三个便是线程池。
写它之前,先简单的提一下ioctl(BINDER _WRITE_READ)。这个ioctl有两步,第一步是write,会传给驱动一些以BC_开头的命令,让驱动去执行,一次可以传多个命令。第二步是read,会从驱动获取一些以BR_开头的回复,也可能是多个,上层binder库可以根据这些回复做相应的操作,read这一步有可能阻塞当前线程,直到驱动给他回复并唤醒它。

所谓的线程池跟平时说的线程区别并不大,进程会产生一些线程,让他们做无限循环,在循环里等待,直到外界有工作传来,该线程便被唤醒,待到该线程干完活之后,又继续循环,再次进入等待状态,直到下一个工作来临。不同的是,传统的线程池的工作是本地进程里的某个不加入线程池的线程给的,而android的线程池等待的工作则是由别的进程里的线程给的。这个工作由别的进程里的线程通过BINDER_WRITE_READ的write步骤提交给驱动,线程池中的线程则一直在循环做着BINDER_WRITE_READ,他们的read步骤会阻塞,直到驱动把别的线程通过write写过来的工作传给他们,线程池中的线程的read得以返回,便根据得到的BR进行相应的操作,之后再次BINDER_WRITE_READ,通过write把结果传回驱动,然后线程又可能再次BINDER_WRITE_READ阻塞在read阶段等待新的工作。

驱动除了转交别的进程的工作,也可能自己产生工作给线程池做,同样是在线程池的read阶段返回给线程池。这一类包括一堆引用计数的工作,以及接下来要写的LOOPER工作。

所谓的LOOPER就是线程池的无限循环,先来看看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
};
这些标记(以下枚举量省略前缀BINDER_LOOPER_STATE_)会用于proc_thread的looper变量,用来表明线程的状态,下面会一一提到。

前面说的work(翻译成工作感觉太土了,不翻了)分为两类,一类是给指定线程的,叫做线程work,一类是给线程池的,叫做进程(线程池)work,这一类可能被线程池中任意一个线程通过read获取,而前者只会给指定线程获取。如果一个线程想加入线程池,获取进程work时,必须先告知驱动,需要通过write发送BC_ENTER_LOOPER或者BC_REGISTER_LOOPER命令给驱动以表明自己进入了线程池循环,可以接受进程work,驱动会根据命令设置线程的looper的ENTERED或者REGISTERED标志位。而一个线程如果要退出线程池,需要发送BC_EXIT_LOOPER给驱动,此时looper的EXITED标记被设置。一个线程(不一定加入了线程池)如果没有事情可做,在read阶段阻塞时,WAITING标记被设置。

而NEED_RETURN状态则是所有线程的初始状态,这个状态如果被设置,线程不管有没有work要做,read都将立刻返回,而这个标记在每次ioctl后都被清零,就是说这个标记正常情况只在第一次ioctl时起作用,而几乎所有的线程的第一次ioctl都是write命令BC_ENTER_LOOPER或者BC_REGISTER_LOOPER把自己加入线程池,所以不管线程池有没有work可做,这些线程在把自己注册到线程池后都会返回,这样用户程序才好得知线程是否成功加入线程池。

有一个例外,任何一个进程用于open驱动的那个线程,一般是进程的主线程,binder库中,这个线程在open驱动之后会进行ioctl(BINDER_VERSION)用来检测驱动版本,便产生了副作用,此ioctl清除了主线程的NEED_RETURN标记,导致主线程在ENTER_LOOPER会被阻塞。于是有一个有趣的现象,应用程序里的主线程如果调用joinThreadPool,BC_ENTER_LOOPER时不会立刻返回,也就不会生成一个新的线程,而程序里用startThreadPool新起的线程在BC_ENTER_LOOPER后会返回,而且几乎肯定会再生成一个新的线程。至于为啥会生成一个新的线程,后面写BC_ENTER_LOOPER和BC_REGISTER_LOOPER的区别时会写到,那里也将涉及到最后一个状态INVALID。

关于NEED_RETURN还有一个非正常情况,那就是文件操作中的flush,flush情况下会设置所有线程的NEED_RETURN标记并唤醒WAITING中的线程,这样所有的线程的read都会得到返回。

BC_ENTER_LOOPER和BC_REGISTER_LOOPER都是用来将线程注册到驱动,以便他们能够获取进程work。前者适用于应用程序自己主动产生的线程,比如主线程以及startThreadPool产生的第一个线程。

任何一个线程在read阶段返回时,会检查进程的ready_threads,这个变量表示当前进程有多少个线程正在等待进程work,这类线程必然处于WAITING状态(反过来不一定成立^-^)。如果当前没有线程等待进程work,驱动在read的返回BR队列最前面加上一个BR_SPAWN_LOOPER通知该线程,线程池已经没有可用于处理进程work的线程了,你得赶紧给我产生一个新的,线程在read返回之后便会根据该BR创建一个新的线程,并让它BC_REGISTER_LOOPER加入线程池并待命,然后自己才去处理这次read到的真正work,这样可以缩短线程池的真空期。

这里有点小优化,如果驱动已经要求某个线程生成一个新的线程,在这个线程还没加入线程池前,驱动不会在别的线程read返回时再次要求生成新线程,这样可以避免要求进程生成过多的线程。这件事情是通过进程的requested_threads变量实现的,在read返回BR_SPAWN_LOOPER时,此变量被增加,在新的线程BC_REGISTER_LOOPER成功时此变量被减小,用来表明生成线程的工作已经完成,而通过这种方式生成的线程数会被记录到进程的reqeusted_threads_started,这种方式生成的线程数不能超过进程的max_threads,此数通过ioctl(BINDER_SET_MAX_THREADS)设置,binder库中设置为15。注意,这个MAX并不约束进程的线程数,也不约束线程池中的线程数,仅仅约束在驱动要求下生成的线程数,即通过BC_REGISTER_LOOPER加入线程池的线程数。

如果到了MAX后,线程池中的线程还不够响应远程请求时,用户进程可以自行生成新的线程并用BC_ENTER_LOOPER注册到线程池用来提供服务。不过一般没必要这么做,线程多了并不见得能改善服务速度,还不如让请求等待着,直到线程池已有的线程完成手头工作再去响应新的请求。

最后一个状态INVALID便是与这两种BC相关,如果一个ENTERED的线程发起BC_REGISTER_LOOPER或者REGISTERD的线程发起BC_ENTER_LOOPER,都会被标记为INVALID状态。而一个线程发起BC_REGISTER_LOOPER时,驱动会检查requested_threads,如果它发现自己并没有要求哪个线程生成新的线程并让其BC_REGISTER_LOOPER,也会将该线程标记为INVALID。

最后要写的是work。work分两种,进程work和线程work,分别记录在binder_proc和binder_thread的todo列表里。线程在ioctl(BINDER_WRITE_READ)的read阶段如果任何一种work以及NEED_RETURN都没有的话,线程便会阻塞,根据线程的情况可能加入到进程的wait队列,这个wait是exclusive的,因为进程的一个work只需要唤醒一个线程来做即可,也有可能加入到线程的wait队列(这个队列最多也就线程本身),这些写BINDER_WRITE_READ的时候再详述。

work本身用binder_work表示,如下:
struct binder_work {
struct list_head entry;
enum {
BINDER_WORK_TRANSACTION = 1,
BINDER_WORK_TRANSACTION_COMPLETE,
BINDER_WORK_NODE,
BINDER_WORK_DEAD_BINDER,
BINDER_WORK_DEAD_BINDER_AND_CLEAR,
BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
} type;
};
entry是用来把work加入到相应todo里的。而type(以下略去前缀BINDER_WORK_)则是work的性质。

TRANSACTION类型的work就是前面多次提到的transaction,android里进程之间普通的请求就是这个东西,这里先简单说下流程,以后再详写实现。

通常,一个线程需要另一个进程的binder进行服务时,它会write BC_TRANSCATION向某个binder来发起transaction,驱动会创建一个TRANSACTION类型的work,并把它加到binder所在进程work里,然后唤醒线程池中的一个线程,让他去完成这个请求。binder完成任务时,所在的线程会write BC_REPLY发回transaction完成的结果,此时驱动也会创建一个TRANSACTION类型的work,并把它加到transaction发起者的线程work里,然后唤醒发起线程。这一来一去有个很大不同之处便是接受者,发起transaction时面对是整个线程池,所以work会被加到目标进程的work里,而transaction回复时是知道发起者线程的,所以是加到线程work中。

在上述过程中,BC_TRANSACTION和BC_REPLY的write完成时,驱动会立刻往写入命令的线程work中增加一个TRANSACTION_COMPLETE类型的work,以便让线程再接下来的read阶段能够立刻返回。这点跟我最开始想象的不同,刚开始接触binder时没看驱动,一直以为线程发出BC_TRANSACTION后会阻塞在read阶段,直到收到目标binder发回的BC_REPLY才返回。看了驱动才知道,这个操作是会立刻返回BR_TRANSACTION_COMPLETE的,但是binder库中的transact在发出BC_TRANSACTION并收到这个BR后,会立刻再次进行iocl(BINDER_WRITE_READ)进入等待状态,直到远端的binder实际完成transaction,所以看起来线程就像是在发出transaction后便阻塞了。

NODE类型的work是用于引用计数的,当binder_node的引用计数发生某些变化时,驱动就会往进程或者线程的work里添加这个binder_node的work,还记得binder_node里的work成员变量么。写引用计数的时候再详细写这个。

DEAD_BINDER,DEAD_BINDER_AND_CLEAR,CLEAR_DEATH_NOTIFICATION这三种类型是用于死亡通知的,有空再写。

最后要提一点,驱动在处理各种work时,会考虑当前线程LOOPER状态,如果没有ENTERED或者REGISTERED的,便一定会把work添加到进程work里,否则很可能加到当前线程的work里,这个得看实际情况了。

总结一下binder和进程,线程,线程池的关系。binder本质是个跨进程的指针,只需要跟进程挂钩,所以它可以被进程的任意一个线程处理,于是android这种服务于其他进程的线程池便有了存在的根基。android中一个本地binder一般就对应着一个服务,会有很多请求者,所以用线程池来响应是比较合适的。各种LOOPER状态使得应用程序的线程池能够很好的投影到驱动里,保持高度的一致性。而驱动要求线程spawn线程的机制与binder库的无缝结合,使得高层用户不用关心线程池的容量,高层用户只需要知道线程池他不是一个线程在战斗就行了,线程池搞不定时他会自己再生出一个线程来帮手的。而进程中的远程binder一般却是活在特定线程中,他们是服务请求的发起者,所以无所谓用线程池来响应请求,而且他们一般是由应用程序某个特定线程调度,用来完成特殊命令的,这样的线程是不愿意让别的线程获取自己发出的请求的答复的,所以远程binder虽然是活在进程里,但是他永远住在某个特定的线程,从不搬家。至于work的性质,添加和移除,则跟transaction,引用以及死亡通知相关,以后再详写。

转自:http://blog.csdn.net/sucjhwaxp/article/details/7915386

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics