Binder系统中的数据结构(Android系统源代码情景分析学习笔记)

struct binder_work
kernel/goldfish/drivers/staging/android/binder.c

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
}

结构体binder_work用来描述待处理的工作项,这些工作项有可能属于一个进程,也有会可能属于一个进程中的某一个线程。成员变量entry用来将该结构体嵌入到一个宿主结构中,成员变量type用来描述工作项的类型。根据成员变量type的取值,Binder 驱动程序就可以判断出一个binder_work结构体嵌入到什么类型的宿主结构中。

struct binder_node
kernel/goldfish/drivers/staging/android/binder.c

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 : 1unsigned pending_strong_ref : 1unsigned has_weak_ref : 1unsigned pending_weak_ref : 1unsigned has_async_transaction : 1unsigned accept_fds : 1int min_priority : 8struct list_head async_todo;
};

结构体binder_node用来描述一个Binder实体对象。每一个Service组件在Binder驱动程序中都对应有一个Binder实体对象,用来描述它在内核中的状态。Binder驱动程序通过强引用计数和弱引用计数技术来维护它们的生命周期。

成员变量proc指向一个Binder实体对象的宿主进程。在Binder驱动程序中,这些宿主进程通过一个binder_proc结构体来描述。宿主进程使用一个红黑树来维护它内部所有的Binder实体对象,而每一个Binder实体对象的成员变量rb_node就正好是这个红黑树中的一个节点。如果一个Binder实体对象的宿主进程已经死亡了,那么这个Binder实体对象就会通过它的成员变量dead_node保存在一个全局的hash列表中。

由于一个Binder实体对象可能会同时被多个Client组件引用,因此,Binder驱动程序就使用结构体binder_ref来描述这些引用关系,并且将引用了同一个Binder实体对象的所有引用都保存在一个hash列表中。这个hash列表通过Binder实体对象的成员变量refs来描述,而Binder驱动程序通过这个成员变量就可以知道有哪些Client组件引用了同一个Binder实体对象。

成员变量internal_strong_refslocal_strong_refs均是用来描述一个Binder实体对象的强引用计数,而成员变量local_weak_refs用来描述一个Binder实体对象的弱引用计数。当一个Binder实体对象请求一个Service组件来执行某一个操作时,会增加该Service组件的强引用计数或者弱引用计数,相应地,Binder实体对象会将其成员变量has_strong_ref和has_weak_ref的值设置为1。当一个Service组件完成一个Binder实体对象所请求的操作之后,Binder实体对象就会请求减少该Service组件的强用计数或者弱引用计数。Binder实体对象在请求一个Service组件增加或者减少强引用计数或者弱引用计数的过程中,会将其成员变量pending_strong_ref或者pending_weak_ref的值设置为1;而当该Service组件增加或者减少了强引用计数或者弱引用计数之后,Binder实体对象就会将这两个成员变量的值设置为0。

成员变量ptr和cookie分别指向一个用户空间地址,它们用来描述用户空间中的一个Service组件,其中,成员变量cookie指向该Service组件的地址,而成员变量ptr指向该Service组件内部的一个引用计数对象(类型为weakref_impl)的地址。

成员变量has_async_transaction用来描述一个Binder实体对象是否正在处理一个异步事务。如果是,它的值就等于1,否则等于0。一般情况下,Binder驱动程序都是将一个事务保存在一个线程的一个todo队列中的,表示要由该线程来处理该事务。每一个事务都关联着一个Binder实体对象,表示该事务的目标处理对象,即要求与该Binder实体对象对应的Service组件在指定的线程中处理该事务。然而,当Binder驱动程序发现一个事务是异步事务时,就会将它保存在目标Binder实体对象的一个异步事务队列中,这个异步事务队列就是由该目标Binder实体对象的成员变量async_todo来描述的。异步事务的定义是那些单向的进程间通信请求,即不需要等待应答的进程间通信请求,与此相对的便是同步事务。因为不需要等待应答,Binder驱动程序就认为异步事务的优先级低于同步事务,具体就表现为在同一时刻,一个Binder实体对象的所有异步事务至多只有一个会得到处理,其余的都等待在异步事务队列中,而同步事务就没有这个限制。

当一个Binder实体对象的引用计数由0变成1,或者由1变成0时,Binder驱动程序就会请求相应的Service组件增加或者减少其引用计数。这时候Binder驱动程序就会将该引用计数修改操作封装成一个类型为binder_node的工作项,即将一个Binder实体对象的成员变量work的值设置为BINDER_WORKD_NODE,并且将它添加到相应进程的todo队列中去等待处理。

成员变量min_priority表示一个Binder实体对象在处理一个来自Client进程的请求时,它所要求的处理线程,即Server进程中的一个线程,应该具备的最小线程优先级,这样就保证了与该Binder实体对象对应的Service组件可以在一个具有一定优先级的线程中处理一个来自Client进程的通信请求。一个Service组件在将自己注册到Binder驱动程序时,可以指定这个最小线程优先级,而Binder驱动程序会把这个最小线程优先级保存在相应的Binder实体对象的成员变量min_priority中。

成员变量accept_fds用来描述一个Binder实体对象是否可以接收包含有文件描述符的进程间通信数据。如果它的值等于1,就表示可以接收;否则,就表示禁止接收。当一个进程向另外一个进程发送的数据中包含有文件描述符时,Binder驱动程序就会自动在目标进程中打开一个相同的文件。基于安全性考虑,Binder驱动程序就要通过成员变量accept_fds来防止源进程在目标进程中打开文件。
最后,成员变量debug_id用来标志一个Binder实体对象的身份,它是用来帮助调试Binder驱动程序的。

struct binder_ref_death
kernel/goldfish/drivers/staging/android/binder.c

struct binder_ref_death {
        struct binder_work work;
        void __user *cookie;
};

结构体binder_ref_death用来描述一个Service组件的死亡接收通知。在正常情况下,一个Service组件被其他Client进程引用时,它是不可以销毁的。然而,Client进程是无法控制它所引用的Service组件的生命周期的,因为Service组件所在的进程可能会意外地崩溃,从而导致它意外地死亡。一个折中的处理办法是,Client进程能够在它所引用的Service组件死亡时获得通知,以便可以做出相应的处理。这时候Client进程就需要将一个用来接收死亡通知的对象的地址注册到Binder驱动程序中。
成员变量cookie用来保存负责接收死亡通知的对象的地址,成员变量work的取值为BINDER_WORK_DEAD_BINDER、BINDER_WORK_CLEAR_DEATH_NOTIFICATION或者BINDER_WORK_DEAD_BINDER_AND_CLEAR,用来标志一个具体的死亡通知类型。

Binder驱动程序决定要向一个Client进程发送一个Service组件死亡通知时,会将一个binder_ref_death结构体封装成一个工作项,并且根据实际情况来设置该结构体的成员变量work的值,最后将这个工作项添加到Client进程的todo队列中去等待处理。

在下面两种情况下,Binder驱动程序会向一个Client进程发送一个Service组件的死亡通知。

(1)当Binder驱动程序监测到一个Service组件死亡时,它就会找到该Service组件对应的Binder实体对象,然后通过Binder实体对象的成员变量refs就可以找到所有引用了它的Client进程,最后就找到这些Client进程所注册的死亡接收通知,即一个binder_ref_death结构体。这时候Binder驱动程序就会将该binder_ref_death结构体添加到Client进程的todo队列中去等待处理。在这种情况下,Binder驱动程序将死亡通知的类型设置为BINDER_WORK_DEAD_BINDER。

(2)当Client进程向Binder驱动程序注册一个死亡接收通知时,如果它所引用的Service组件已经死亡,那么Binder驱动程序就会马上发送一个死亡通知给该Client进程。在这种情况下,Binder驱动程序也会将死亡通知的类型设置为BINDER_WORK_DEAD_BINDER。

另外,当Client进程向Binder驱动程序注销一个死亡接收通知时,Binder驱动程也会向该Client进程的todo队列发送一个类型为binder_ref_death的工作项,用来表示注销结果。这时候又需要分两种情况来考虑。

(1)如果Client进程在注销一个死亡接收通知时,相应的Service组件还没有死亡,那么Binder驱动程序就会找到之前所注册的一个binder_ref_death结构体,并且将它的类型work修改为BINDER_WORK_CLEAR_DEATH_NOTIFICATION,然后再将该binder_ref_death结构体封装成一个工作项添加到该Client进程的todo队列中去等待处理。

(2)如果Client进程在注销一个死亡接收通知时,相应的Service组件已经死亡,那么Binder驱动程序就会找到之前所注册的一个binder_ref_death结构体,并且将它的类型work修改为BINDER_WORK_DEAD_BINDER_AND_CLEAR,然后再将该binder_ref_death结构体封装成一个工作项添加到该Client进程的todo队列中去等待处理。

Client进程在处理这个工作项时,通过对应的binder_ref_death结构体的成员变量work就可以区分注销结果了,即它所引用的Service组件是否已经死亡。

struct binder_ref
kernel/goldfish/drivers/staging/android/binder.c

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 strongint weakstruct binder_ref_death *death;
};

结构体binder_ref用来描述一个Binder引用对象。每一个Client组件在Binder驱动程序中都对应有一个Binder引用对象,用来描述它在内核中的状态。Binder驱动程序通过强引用计数和弱引用计数技术来维护它们的生命周期。

成员变量node用来描述一个Binder引用对象所引用的Binder实体对象。前面在介绍结构体binder_node时提到,每一个Binder实体对象都有一个hash列表,用来保存那些引用了它的Binder引用对象,而这些Binder引用对象的成员变量node_entry正好是这个hash列表的节点。

成员变量desc是一个句柄值,或者称为描述符,它是用来描述一个Binder引用对象的。在Client进程的用户空间中,一个Binder引用对象是使用一个句柄值来描述的,因此,当Client进程的用户空间通过Binder驱动程序来访问一个Service组件时,它只需要指定一个句柄值,Binder驱动程序就可以通过该句柄值找到对应的Binder引用对象,然后再根据该Binder引用对象的成员变量node找到对应的Binder实体对象,最后就可以通过该Binder实体对象找到要访问的Service组件。

注意:一个Binder引用对象的句柄值在进程范围内是唯一的,因此,在两个不同的进程中,同一个句柄值可能代表的是两个不同的目标Service组件。

成员变量proc指向一个Binder引用对象的宿主进程。一个宿主进程使用两个红黑树来保存它内部所有的Binder引用对象,它们分别以句柄值和对应的Binder实体对象的地址来作为关键字保存这些Binder引用对象,而这些Binder引用对象的成员变量rb_node_desc和rb_node_node就正好是这两个红黑树中的节点。

成员变量strong和weak分别用来描述一个Binder引用对象的强引用计数和弱引用计数,Binder驱动程序正是通过它们来维护一个Binder引用对象的生命周期的。

成员变量death指向一个Service组件的死亡接收通知。当Client进程向Binder驱动程序注册一个它所引用的Service组件的死亡接收通知时,Binder驱动程序就会创建一个binder_ref_death结构体,然后保存在对应的Binder引用对象的成员变量death中。

最后,成员变量debug_id用来标志一个Binder引用对象的身份,它是用来帮助调试Binder驱动程序的。

struct binder_buffer
kernel/goldfish/drivers/staging/android/binder.c

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 : 1unsigned allow_user_free : 1unsigned async_transaction : 1unsigned debug_id : 29struct binder_transaction *transaction;

        struct binder_node *target_node;
        size_t data_size;
        size_t offsets_size;
        uint8_t data[0];
};

结构体binder_buffer用来描述一个内核缓冲区,它是用来在进程间传输数据的。每一个使用Binder进程间通信机制的进程在Binder驱动程序中都有一个内核缓冲区列表,用来保存Binder驱动程序为它所分配的内核缓冲区,而成员变量entry正好是这个内核缓冲区列表的一个节点。同时,进程又使用了两个红黑树来分别保存那些正在使用的内核缓冲区,以及空闲的内核缓冲区。如果一个内核缓冲区是空闲的,即它的成员变量free的值等于1,那么成员变量rb_node就是空闲内核缓冲区红黑树中的一个节点;否则,成员变量rb_node就是正在使用内核缓冲区红黑树中的一个节点。

成员变量transaction 和target_node用来描述一个内核缓冲区正在交给哪一个事务以及哪一个Binder实体对象使用。Binder驱动程序使用一个binder_transaction结构体来描述一个事务,每一个事务都关联有一个目标Binder实体对象。Binder驱动程序将事务数据保存在一个内核缓冲区中,然后将它交给目标Binder实体对象处理,而目标Binder实体对象再将该内核缓冲区的内容交给相应的Service组件处理。Service组件处理完成该事务之后,如果发现传递给它的内核缓冲区的成员变量allow_user_free的值为1,那么该Service组件就会请求Binder驱动程序释放该内核缓冲区。

如果与一个内核缓冲区关联的是一个异步事务,那么Binder驱动程序就会将该内核缓冲区的成员变量async_transaction的值设置为1;否则,就将它的值设置为0。Binder驱动程序限制了分配给异步事务的内核缓冲区的大小,这样做的目的是为了保证同步事务可以优先得到内核缓冲区,以便可以快速地对该同步事务进行处理。

成员变量data指向一块大小可变的数据缓冲区,它是真正用来保存通信数据的。数据缓冲区保存的数据划分为两种类型,其中一种是普通数据,另一种是Binder对象。Binder驱动程序不关心数据缓冲区中的普通数据,但是必须要知道里面的Binder对象,因为它需要根据它们来维护内核中的Binder实体对象和Binder引用对象的生命周期。例如,如果数据缓冲区中包含了一个Binder引用,并且该数据缓冲区是传递给另外一个进程的,那么Binder驱动程序就需要为另外一个进程创建一个Binder引用对象,并且增加相应的Binder实体对象的引用计数,因为它也被另外的这个进程引用了。由于数据缓冲区中的普通数据和Binder对象是混合在一起保存的,它们之间并没有固定的顺序,因此,Binder驱动程序就需要额外的数据来找到里面的Binder对象。在数据缓冲区的后面,有一个偏移数组,它记录了数据缓冲区中每一个Binder对象在数据缓冲区中的位置。偏移数组的大小保存在成员变量offsets_size中,而数据缓冲区的大小保存在成员变量data_size中。

最后,成员变量debug_id用来标志一个内核缓冲区的身份,它是用来帮助调试Binder驱动程序的。

struct binder_proc
kernel/goldfish/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;
}

结构体binder_proc用来描述一个正在使用Binder进程间通信机制的进程。当一个进程调用函数open来打开设备文件/dev/binder时,Binder驱动程序就会为它创建一个binder_proc结构体,并且将它保存在一个全局的hash列表中,而成员变量proc_node就正好是该hash列表中的一个节点。此外,成员变量pid、tsk和files分别指向了进程的进程组ID、任务控制块和打开文件结构体数组。

进程打开了设备文件/dev/binder之后,还必须调用函数mmap将它映射到进程的地址空间,实际上是请求Binder驱动程序为它分配一块内核缓冲区,以便可以用来在进程间传输数据。Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。而因为mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间,所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。

Binder驱动程序为进程分配的内核缓冲区的大小保存在成员变量buffer_size中。这些内核缓冲区有两个地址,其中一个是内核空间地址,另外一个是用户空间地址。内核空间地址是在Binder驱动程序内部使用的,保存在成员变量buffer中,而用户空间地址是在应用程序进程内部使用的,保存在成员变量vma中。这两个地址相差一个固定的值,保存在成员变量user_buffer_offset中。这样,给定一个用户空间地址或者一个内核空间地址,Binder驱动程序就可以计算出另外一个地址的大小。(注意:这两个地址指的都是虚拟地址,它们对应的物理页面保存在成员变量pages中。成员变量pages是类型为struct page*的一个数组,数组中的每一个元素都指向一个物理页面。Binder驱动程序一开始时只为该内核缓冲区分配一个物理页面,后面不够使用时,再继续分配。)

成员变量buffer指向的是一块大的内核缓冲区,Binder驱动程序为了方便对它进行管理,会将它划分成若干个小块。这些小块的内核缓冲区就是使用前面所介绍的结构体binder_buffer来描述的,它们保存在一个列表中,按照地址值从小到大的顺序来排列。成员变量buffers指向的便是该列表的头部。列表中的小块内核缓冲区有的是正在使用的,即已经分配了物理页面;有的是空闲的,即还没有分配物理页面,它们分别组织在两个红黑树中,其中,前者保存在成员变量allocated_buffers所描述的红黑树中,而后者保存在成员变量free_buffers所描述的红黑树中。此外,成员变量buffer_free保存了空闲内核缓冲区的大小,而成员变量free_async_space保存了当前可以用来保存异步事务数据的内核缓冲区的大小。

前面提到,每一个使用了Binder进程间通信机制的进程都有一个Binder线程池,用来处理进程间通信请求,这个Binder线程池是由Binder驱动程序来维护的。结构体binder_proc的成员变量threads是一个红黑树的根节点,它以线程ID作为关键字来组织一个进程的Binder线程池。进程可以调用函数ioctl将一个线程注册到Binder驱动程序中,同时,当进程没有足够的空闲线程在处理进程间通信请求时,Binder驱动程序也可以主动要求进程注册更多的线程到Binder线程池中。Binder驱动程序最多可以主动请求进程注册的线程的数量保存在成员变量max_threads中,而成员变量ready_threads表示进程当前的空闲Binder线程数目。(注意:成员变量max_threads并不是表示Binder线程池中的最大线程数目,进程本身可以主动注册任意数目的线程到Binder线程池中。Binder驱动程序每一次主动请求进程注册一个线程时,都会将成员变量requested_threads的值加1;而当进程响应这个请求之后,Binder驱动程序就会将成员变量requested_threads的值减1,而且将成员变量requested_threads_started的值加1,表示Binder驱动程序已经主动请求进程注册了多少个线程到Binder线程池中。)

当进程接收到一个进程间通信请求时,Binder驱动程序就将该请求封装成一个工作项,并且加入到进程的待处理工作项队列中,这个队列使用成员变量todo来描述。Binder线程池中的空闲Binder线程会睡眠在由成员变量wait所描述的一个等待队列中,当它们的宿主进程的待处理工作项队列增加了新的工作项之后,Binder驱动程序就会唤醒这些线程,以便它们可以去处理新的工作项。成员变量default_priority的值被初始化为进程的优先级。当一个线程处理一个工作项时,它的线程优先级有可能会被设置为其宿主进程的优先级,即设置为成员变量default_priority的值,这是由于线程是代表其宿主进程来处理一个工作项的。线程在处理一个工作项时的优先级还会受到其他因素的影响,后面我们再详细描述。

一个进程内部包含了一系列的Binder实体对象和Binder引用对象,进程使用三个红黑树来组织它们,其中,成员变量nodes所描述的红黑树是用来组织Binder实体对象的,它以Binder实体对象的成员变量ptr作为关键字;而成员变量refs_by_desc和refs_by_node所描述的红黑树是用来组织Binder引用对象的,前者以Binder引用对象的成员变量desc作为关键字,而后者以Binder引用对象的成员变量node作为关键字。

成员变量deferred_work_node是一个hash列表,用来保存进程可以延迟执行的工作项。这些延迟工作项有三种类型,如下所示。
kernel/goldfish/drivers/staging/android/binder.c

enum {
        BINDER_DEFERRED_PUT_FILES  = 0x01,
        BINDER_DEFERRED_FLUSH      = 0x02,
        BINDER_DEFERRED_RELEASE    = 0x04,
};

Binder驱动程序为进程分配内核缓冲区时,会为这个内核缓冲区创建一个文件描述符,进程可以通过这个文件描述符将该内核缓冲区映射到自己的地址空间。当进程不再需要使用Binder进程间通信机制时,它就会通知Binder驱动程序关闭该文件描述符,并且释放之前所分配的内核缓冲区。由于这不是一个马上就需要完成的操作,因此,Binder驱动程序就会创建一个BINDER_DEFERRED_PUT_FILES类型的工作项来延迟执行该操作。

前面提到,Binder线程池中的空闲Binder线程是睡眠在一个等待队列中的,进程可以通过调用函数flush来唤醒这些线程,以便它们可以检查进程是否有新的工作项需要处理。这时候Binder驱动程序就会创建一个BINDER_DEFERRED_FLUSH类型的工作项,以便可以延迟执行唤醒空闲Binder线程的操作。

当进程不再使用Binder进程间通信机制时,它就会调用函数close来关闭设备文件/dev/binder,这时候Binder驱动程序就会释放之前为它分配的资源,例如,释放进程结构体binder_proc、Binder实体对象结构体binder_node以及Binder引用对象结构体binder_ref等。由于资源的释放操作是一个比较耗时的操作,因此,Binder驱动程序会创建一个BINDER_DEFERRED_RELEASE类型的事务来延迟执行它们。

Binder驱动程序将所有的延迟执行的工作项保存在一个hash列表中。如果一个进程有延迟执行的工作项,那么成员变量deferred_work_node就刚好是该hash列表中的一个节点,并且使用成员变量deferred_work来描述该延迟工作项的具体类型。

当一个进程所引用的Service组件死亡时,Binder驱动程序就会向该进程发送一个死亡通知。这个正在发出的死亡通知被封装成一个类型为BINDER_WORK_DEAD_BINDER或者BINDER_WORK_DEAD_BINDER_AND_CLEAR的工作项,并且保存在由成员变量delivered_death所描述的一个队列中,表示Binder驱动程序正在向进程发送的死亡通知。当进程接收到这个死亡通知之后,它便会通知Binder驱动程序,这时候Binder驱动程序就会将对应的工作项从成员变量delivered_death所描述的队列中删除。

最后,成员变量stats是用来统计进程数据的,例如,进程接收到的进程间通信请求的次数。

struct binder_thread
kernel/goldfish/drivers/staging/android/binder.c

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

结构体binder_thread用来描述Binder线程池中的一个线程,其中,成员变量proc指向其宿主进程。前面在介绍进程结构体binder_proc时提到,进程结构体binder_proc使用一个红黑树来组织其Binder线程池中的线程,其中,结构体binder_thread的成员变量rb_node就是该红黑树中的一个节点。

一个Binder线程的ID和状态是通过成员变量pid和looper来描述的。线程状态的取值如下所示。
kernel/goldfish/drivers/staging/android/binder.c

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驱动程序时,Binder驱动程序就会为它创建一个binder_thread结构体,并且将它的状态初始化为BINDER_LOOPER_STATE_NEED_RETURN,表示该线程需要马上返回到用户空间。由于一个线程在注册为Binder线程时可能还没有准备好去处理进程间通信请求,因此,最好返回到用户空间去做准备工作。此外,当进程调用函数flush来刷新它的Binder线程池时,Binder线程池中的线程的状态也会被重置为BINDER_LOOPER_STATE_NEED_RETURN。

一个线程注册到Binder驱动程序之后,它接着就会通过BC_REGISTER_LOOPER或者BC_ENTER_LOOPER协议来通知Binder驱动程序,它可以处理进程间通信请求了,这时候Binder驱动程序就会将它的状态设置为BINDER_LOOPER_STATE_REGISTERED或者BINDER_LOOPER_STATE_ENTERED。如果一个线程是应用程序主动注册的,那么它就通过BC_ENTER_LOOPER协议来通知Binder驱动程序,它已经准备就绪处理进程间通信请求了;如果一个线程是Binder驱动程序请求创建的,那么它就通过BC_REGISTER_LOOPER协议来通知Binder驱动程序,这时候Binder驱动程序就会增加它所请求进程创建的Binder线程的数目。

当一个Binder线程处于空闲状态时,Binder驱动程序就会把它的状态设置为BINDER_LOOPER_STATE_WAITING;而当一个Binder线程退出时,它会通过BC_EXIT_LOOPER协议来通知Binder驱动程序,这时候Binder驱动程序就会将它的状态设置为BINDER_LOOPER_STATE_EXITED。在异常情况下,一个Binder线程的状态会被设置为BINDER_LOOPER_STATE_INVALID,例如,当该线程已经处于BINDER_LOOPER_STATE_REGISTERED状态时,如果它又再次通过BC_ENTER_LOOPER协议来通知Binder驱动程序它已经准备就绪了,那么Binder驱动程序就会将它的状态设置为BINDER_LOOPER_STATE_INVALID。

当一个来自Client进程的请求指定要由某一个Binder线程来处理时,这个请求就会加入到相应的binder_thread结构体的成员变量todo所表示的队列中,并且会唤醒这个线程来处理,因为这时候这个线程可能处于睡眠状态。

当Binder驱动程序决定将一个事务交给一个Binder线程处理时,它就会将该事务封装成一个binder_transaction结构体,并且将它添加到由线程结构体binder_thread的成员变量transaction_stack所描述的一个事务堆栈中。结构体binder_transaction的设计很巧妙,后面我们再详细介绍它的定义。

当一个Binder线程在处理一个事务T1并需要依赖于其他的Binder线程来处理另外一个事务T2时,它就会睡眠在由成员变量wait所描述的一个等待队列中,直到事务T2处理完成为止。

一个Binder线程在处理一个事务时,如果出现了异常情况,那么Binder驱动程序就会将相应的错误码保存在其成员变量return_error和reutrn_error2中,这时候线程就会将这些错误返回给用户空间应用程序处理。

最后,成员变量stats是用来统计Binder线程数据的,例如,Binder线程接收到的进程间通信请求的次数。

struct binder_transaction
kernel/goldfish/drivers/staging/android/binder.c

struct binder_transaction {
        int debug_id;
        struct binder_work work;
        struct binder_thread *from;
        struct binder_transaction *from_parent;
        struct binder_proc *to_proc;
        struct binder_thread *to_thread;
        struct binder_transaction *to_parent;
        unsigned need_reply : 1/*unsigned is_dead : 1;*/ /* not used at the moment */

        struct binder_buffer *buffer;
        unsigned int code;
        unsigned int flags;
        long priority;
        long saved_priority;
        uid_t sender_euid;
};

结构体binder_transaction用来描述进程间通信过程,这个过程又称为一个事务。成员变量need_reply用来区分一个事务是同步的还是异步的。同步事务需要等待对方回复,这时候它的成员变量need_reply的值就会设置为1;否则就设置为0,表示这是一个异步事务,不需要等待回复。

成员变量from指向发起事务的线程,称为源线程;成员变量to_proc和to_thread分别指向负责处理该事务的进程和线程,称为目标进程和目标线程。当Binder驱动程序为目标进程或者目标线程创建一个事务时,就会将该事务的成员变量work的值设置为BINDER_WORK_TRANSACTION,并且将它添加到目标进程或者目标线程的todo队列中去等待处理。

成员变量priority和sender_euid分别用来描述源线程的优先级和用户ID。通过这两个成员变量,目标进程或者目标线程就可以识别事务发起方的身份。

一个线程在处理一个事务时,Binder驱动程序需要修改它的线程优先级,以便满足源线程和目标Service组件的要求。Binder驱动程序在修改一个线程的优先级之前,会将它原来的线程优先级保存在一个事务结构体的成员变量saved_priority中,以便线程处理完成该事务后可以恢复原来的优先级。前面在介绍结构体binder_node时提到,目标线程在处理一个事务时,它的线程优先级不能低于目标Service组件所要求的线程优先级,而且也不能低于源线程的优先级。这时候Binder驱动程序就会将这二者中的较大值设置为目标线程的优先级。

成员变量buffer指向Binder驱动程序为该事务分配的一块内核缓冲区,它里面保存了进程间通信数据。成员变量code和flags是直接从进程间通信数据中拷贝过来的,后面在介绍结构体binder_transaction_data时,我们再详细分析。

成员变量from_parent和to_parent分别描述一个事务所依赖的另外一个事务,以及目标线程下一个需要处理的事务。假设线程A发起了一个事务T1,需要由线程B来处理;线程B在处理事务T1时,又需要线程C先处理事务T2;线程C在处理事务T2时,又需要线程A先处理事务T3。这样,事务T1就依赖于事务T2,而事务T2又依赖于事务T3,它们的关系如下:

T2->from_parent = T1;

T3->from_parent = T2;

对于线程A来说,它需要处理的事务有两个,分别是T1和T3,它首先要处理事务T3,然后才能处理事务T1,因此,事务T1和T3的关系如下:

T3->to_parent = T1;

考虑这样一个情景:如果线程C在发起事务T3给线程A所属的进程来处理时,Binder驱动程序选择了该进程的另外一个线程D来处理该事务,这时候会出现什么情况呢?这时候线程A就会处于空闲等待状态,什么也不能做,因为它必须要等线程D处理完成事务T3后,它才可以继续执行事务T1。在这种情况下,与其让线程A闲着,还不如把事务T3交给它来处理,这样线程D就可以去处理其他事务,提高了进程的并发性。

现在,关键的问题又来了——Binder驱动程序在分发事务T3给目标进程处理时,它是如何知道线程A属于目标进程,并且正在等待事务T3的处理结果的?当线程C在处理事务T2时,就会将事务T2放在其事务堆栈transaction_stack的最前端。这样当线程C发起事务T3给线程A所属的进程处理时,Binder驱动程序就可以沿着线程C的事务堆栈transaction_stack向下遍历,即沿着事务T2的成员变量from_parent向下遍历,最后就会发现事务T3的目标进程等于事务T1的目标进程,并且事务T1是由线程A发起来的,这时候它就知道线程A正在等待事务T3的处理结果了。注意:线程A在处理事务T3时,Binder驱动程序会将事务T3放在其事务堆栈transaction_stack的最前端,而在此之前,该事务堆栈transaction_stack的最前端指向的是事务T1。为了能够让线程A处理完成事务T3之后,接着处理事务T1,Binder驱动程序会将事务T1保存在事务T3的成员变量to_parent中。等到线程A处理完成事务T3之后,就可以通过事务T3的成员变量to_parent找到事务T1,再将它放在线程A的事务堆栈transaction_stack的最前端了。

最后,成员变量debug_id用来标志一个事务结构体的身份,它是用来帮助调试Binder驱动程序的。

以上介绍的结构体都是在Binder驱动程序内部使用的。前面提到,应用程序进程在打开了设备文件/dev/binder之后,需要通过IO控制函数ioctl来进一步与Binder驱动程序进行交互,因此,Binder驱动程序就提供了一系列的IO控制命令来和应用程序进程通信。在这些IO控制命令中,最重要的便是BINDER_WRITE_READ命令了,它的定义如下所示。

kernel/goldfish/drivers/staging/android/binder.h

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; /* bytes to read */
        signed long read_consumed; /* bytes consumed by driver */
        unsigned long read_buffer;
}

结构体binder_write_read用来描述进程间通信过程中所传输的数据。这些数据包括输入数据和输出数据,其中,成员变量write_size、write_consumed和write_buffer用来描述输入数据,即从用户空间传输到Binder驱动程序的数据;而成员变量read_size、read_consumed和read_buffer用来描述输出数据,即从Binder驱动程序返回给用户空间的数据,它也是进程间通信结果数据。

成员变量write_buffer指向一个用户空间缓冲区的地址,里面保存的内容即为要传输到Binder驱动程序的数据。缓冲区write_buffer的大小由成员变量write_size来指定,单位是字节。成员变量write_consumed用来描述Binder驱动程序从缓冲区write_buffer中处理了多少个字节的数据。

成员变量read_buffer也是指向一个用户空间缓冲区的地址,里面保存的内容即为Binder驱动程序返回给用户空间的进程间通信结果数据。缓冲区read_buffer的大小由成员变量read_size来指定,单位是字节。成员变量read_consumed用来描述用户空间应用程序从缓冲区read_buffer中处理了多少个字节的数据。

缓冲区write_buffer和read_buffer的数据格式如下图所示:
这里写图片描述
它们都是一个数组,数组的每一个元素都由一个通信协议代码及其通信数据组成。协议代码又分为两种类型,其中一种是在输入缓冲区write_buffer中使用的,称为命令协议代码,另一种是在输出缓冲区read_buffer中使用的,称为返回协议代码。命令协议代码通过BinderDriverCommandProtocol枚举值来定义,而返回协议代码通过BinderDriverReturnProtocol枚举值来定义。

struct binder_ptr_cookie

struct binder_ptr_cookie {
        void *ptr;
        void *cookie;
};

结构体binder_ptr_cookie用来描述一个Binder实体对象或者一个Service组件的死亡接收通知。当结构体binder_ptr_cookie描述的是一个Binder实体对象时,成员变量ptr和cookie的含义等同于前面所介绍的结构体binder_node的成员变量ptr和cookie;当结构体binder_ptr_cookie描述的是一个Service组件的死亡接收通知时,成员变量ptr指向的是一个Binder引用对象的句柄值,而成员变量cookie指向的是一个用来接收死亡通知的对象的地址。

struct binder_transaction_data
kernel/goldfish/drivers/staging/android/binder.h

struct binder_transaction_data {
        /* The first two are only used 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;      /* transaction 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 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 buf[8];
        } data;
};

结构体binder_transaction_data用来描述进程间通信过程中所传输的数据。

成员变量target是一个联合体,用来描述一个目标Binder实体对象或者目标Binder引用对象。如果它描述的是一个目标Binder实体对象,那么它的成员变量ptr就指向与该Binder实体对象对应的一个Service组件内部的一个弱引用计数对象(weakref_impl)的地址;如果它描述的是一个目标Binder引用对象,那么它的成员变量handle就指向该Binder引用对象的句柄值。

成员变量cookie是由应用程序进程指定的额外参数。当Binder驱动程序使用返回命令协议BR_TRANSACTION向一个Server进程发出一个进程间通信请求时,这个成员变量才有实际意义,它指向的是目标Service组件的地址。

成员变量code是由执行进程间通信的两个进程互相约定好的一个通信代码,Binder驱动程序完全不关心它的含义。

成员变量flags是一个标志值,用来描述进程间通信行为特征,它的取值如下所示:
kernel/goldfish/drivers/staging/android/binder.h

enum transaction_flags {
        TF_ONE_WAY     = 0x01, /* this is a one-way call: async, no return */
        TF_ROOT_OBJECT = 0x04, /* contents are the component's root object */
        TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */
        TF_ACCEPT_FDS  = 0x10, /* allow replies with file descriptors */
};

目前,只使用了TF_ONE_WAY、TF_STATUS_CODE和TF_ACCEPT_FDS这三个标志值。如果成员变量flags的TF_ONE_WAY位被设置为1,就表示这是一个异步的进程间通信过程;如果成员变量flags的TF_ACCEPT_FDS位被设置为0,就表示源进程不允许目标进程返回的结果数据中包含有文件描述符;如果成员变量flags的TF_STATUS_CODE位被设置为1,就表示成员变量data所描述的数据缓冲区的内容是一个4字节的状态码。

成员变量sender_pid和sender_euid表示发起进程间通信请求的进程的PID和UID。这两个成员变量的值是由Binder驱动程序来填写的,因此,目标进程通过这两个成员变量就可以识别出源进程的身份,以便进行安全检查。

成员变量data_size和offsets_size分别用来描述一个通信数据缓冲区以及一个偏移数组的大小。成员变量data是一个联合体,它指向一个通信数据缓冲区。当通信数据较小时,就直接使用联合体内静态分配的数组buf来传输数据;当通信数据较大时,就需要使用一块动态分配的缓冲区来传输数据了。这块动态分配的缓冲区通过一个包含两个指针的结构体来描述,即通过联合体内的成员变量ptr来描述。结构体ptr的成员变量buffer指向一个数据缓冲区,它是真正用来保存通信数据的,它的大小由前面所描述的成员变量data_size来指定。当数据缓冲区中包含有Binder对象时,那么紧跟在这个数据缓冲区的后面就会有一个偏移数组offsets,用来描述数据缓冲区中每一个Binder对象的位置。有了这个偏移数组之后,Binder驱动程序就可以正确地维护其内部的Binder实体对象和Binder引用对象的引用计数。

数据缓冲区中的每一个Binder对象都使用一个flat_binder_object结构体来描述。下面我们就通过一个例子来描述数据缓冲区的内存布局,如下图所示:
Binder系统中的数据结构(Android系统源代码情景分析学习笔记)_第1张图片
有两个Binder对象,相应地,偏移数组的大小就等于2,里面保存的就是这两个Binder对象在数据缓冲区中的位置n1和n2。

struct flat_binder_object
kernel/goldfish/drivers/staging/android/binder.h

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

结构体flat_binder_object除了可以描述一个Binder实体对象和一个Binder引用对象之外,还可以用来描述一个文件描述符,它们是通过成员变量type来加以区别的。

成员变量type的取值范围如下所示:
kernel/goldfish/drivers/staging/android/binder.h

#define B_PACK_CHARS(c1, c2, c3, c4) \
         ((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4))
#define B_TYPE_LARGE 0x85

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('w''h''*', B_TYPE_LARGE),
        BINDER_TYPE_FD             =   B_PACK_CHARS('f''d''*', B_TYPE_LARGE),
};

其中,BINDER_TYPE_BINDER和BINDER_TYPE_WEAK_BINDER都是用来描述一个Binder实体对象的,前者描述的是一个强类型的Binder实体对象,而后者描述的是一个弱类型的Binder实体对象;BINDER_TYPE_HANDLE和BINDER_TYPE_WEAK_HANDLE用来描述一个Binder引用对象,前者描述的是一个强类型的Binder引用对象,而后者描述的是一个弱类型的Binder引用对象;BINDER_TYPE_FD用来描述一个文件描述符。

成员变量flags是一个标志值,只有当结构体flat_binder_object描述的是一个Binder实体对象时,它才有实际意义。目前只用到该成员变量的第0位到第8位。其中,第0位到第7位描述的是一个Binder实体对象在处理一个进程间通信请求时,它所运行在的线程应当具有的最小线程优先级;第8位用来描述一个Binder实体对象是否可以将一块包含有文件描述符的数据缓冲区传输给目标进程,如果它的值等于1,就表示允许,否则就不允许。

成员变量binder和handle组成了一个联合体。当结构体flat_binder_object描述的是一个Binder实体对象时,那么就使用成员变量binder来指向与该Binder实体对象对应的一个Service组件内部的一个弱引用计数对象(weakref_impl)的地址,并且使用成员变量cookie来指向该Service组件的地址;当结构体flat_binder_object描述的是一个Binder引用对象时,那么就使用成员变量handle来描述该Binder引用对象的句柄值。

至此,Binder驱动程序的基础数据结构就介绍完了。接下来,我们开始分析Binder设备的初始化过程。

你可能感兴趣的:(Android,android)