现在的位置: 首页 > 综合 > 正文

Service与Android系统设计(7)— Binder驱动 Service与Android系统实现(1)– 应用程序里的Service Service与Android系统设计(2)– Parcel Service与Android系统设计(3)– ActivityManager的实现 Service与Android系统设计(4)– ServiceManager Service与Android系统设计(5)– libbinder Service与Android系统设计(6)— Native Service

2013年06月14日 ⁄ 综合 ⁄ 共 16507字 ⁄ 字号 评论关闭

特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。作者系LiAnLab.org资深Android技术顾问吴赫老师。本系列文章交流与讨论:@宋宝华Barry

Binder驱动

最后来看一下支持这一整套Binder机制的幕后功臣,Binder驱动。作为一种IPC驱动,Binder跟Unix/Linux历史上有过的任何驱动都不相同,功能强大,同时代码又很简洁,创造这套机制的Dianne Kyra Hackborn的确也跟LinusTolvalds一样是神一级的人物。

Binder机制总共不到一千多行轻描淡写的代码,便完成了跨进程交互里各种令人头疼的设计问题和编程问题,不容易读懂,但细看明白才能明白,作者在设计每一个变量、每一个bit时的良苦用心。

对于Binder驱动,不幸的消息是,这套机制并不符合Linux内核的专款专用的设计思想,同时也将功能过多地抛到了用户态来处理,于是除了Android和另一个比较小众的ALP,几乎没人使用。Binder驱动本身也被存放在标准内核的drivers/staging/android目录下,处于待考察状态。

但好消息是,Android系统所向披靡,于是Binder驱动的使用量终于变得足够大,研究它的工作原则变得很有价值。而更重要的,Binder驱动目前在Linux内核里的IPC机制里是无敌的,没有任何IPC机制可以跟Binder媲美,Binder在性能、面向对象等多方面特性上,不可取代。以致于Linux开发者也在讨论一种能够提供跟Binder类似能力的IPC,到编写本文为止,尚无任何成果。

Binder驱动简介

Android的这套基于Binder的IPC机制,源自于传奇性但又比较悲催的OpenBinderIPC框架。

OpenBinder是由一家叫Be的公司开发,这家传奇性的法国软件公司制造了传奇性的BeOS,当年苹果公司在操作系统研发上遇到困境时,可以用来拯救苹果的操作系统方案,一个是NeXT,另一个便是BeOS。BeOS在构架上和成熟度上比NeXT更具优势,但或许是有优势的东西就会有更高的姿态,要价更高,于是机会便被留给了NeXT,于是有了今天的MacOSX和iOS。BeOS在构架上设计思路上在当年还是很先进的,就比如延用至今的OpenBinder,如果仔细看BeOS的编程文档,会发现整个系统交互与今天的Android有很大的类似之处。可惜后来BeOS最终没有避免破产的命运,BeOS就被作为软件资产,在2001年被Palm收购。

OpenBinder在Palm也曾风光一时。Palm以简洁低功耗设备迅速成长起来之后,也需要一种高效的,类似于Corba的系统级消息互通机制,以构造更复杂的系统。这样的尝试,得到了一个悲情的操作系统,Palm OS Cobalt(Palm OS 6),本来作为Palm OS 5的后继者,这一操作系统被寄予很大期望,但没有产商愿意生产基于它的设备。但得到的好处是,OpenBinder在这种商业应用前景不明的情况最终还是选择了开源,OpenBinder本身的信息可以很容易得到,见http://www.angryredplanet.com/~hackbod/openbinder/,这个网站是OpenBinder创造者Dianne
Kyra Hackborn的个人网站。而OpenBinder所依存的操作系统环境很不稳定,需要考虑兼容性又被迫随着市场需求在多种操作系统内核上移植,历经BeOS、Windows、PalmOS Cobalt的微内核、Linux内核,最终使OpenBinder具备强大的可移植性。

虽然OpenBinder在技术上是一种很优秀的方案,其命运却是一再如此悲催,使用OpenBinder技术的操作系统,都没有走入主流然后就销声匿迹。在今天的操作系统世界里,使用OpenBinder的并不多,仅ALP(ACESS Linux Platform)在使用OpenBinder作为其IPC机制,但ALP所占市场份额实在太小,发展前景很不明朗。但革命性的Android操作系统,最终选择了这套方案,使OpenBinder终于发挥了其强大潜力。Android使用OpenBinder的基本构架,但并非完整的OpenBinder,所有只称其为BinderIPC。在Android系统的设计者眼中,Android跟OpenBinder并无直接联系,会强调Android世界里的Binder是独特设计过的,可能是出于法律上的顾虑。但我们对比OpenBinder和Android里的Binder实现,就会发现这两者在本质上是一样的。相对而言,Android的Binder机制是OpenBinder的一种简化版本,学习Android底层开发,如果Binder本身不容易理解,可以参考OpenBinder的文档。

至于Android为什么会选择Binder作为其底层通信机制而不是重新设计或是借用已有方案,坊间谣传是由于 Android底层开发人员大都曾是BeOS或是Palm的开发人员,更熟悉这套开发框架。但如果不是Binder机制足够优秀,可能也会在Android系统的发展中被抛弃。Binder提供了一种功能强大、简洁、高效、面向对象的跨进程传递方式,而到目前为此,还只是Binder能够提供这样的能力。

  • 面向对象。跟传统的IPC机制不同,Binder驱动设计的目的就是用于进程之前来传递对象,面向对象是其本质之一,所以能提供更好地支持面向对象设计的系统。
  • 面向进程。Binder不是完全以文件描述符作为其分发实现,而是文件描述符与进程ID的组合,从而能更好地提供安全性、进程调度等功能。
  • 简单。Binder从原理与实现都很简单,大部分逻辑都在用户态完成,内核态的驱动仅由一个C文件提供。
  • 高性能。由于简单和灵活上实现,Binder在驱动层可以更高效地分发消息,是目前Linux内核之上的IPC通信机制中最高效的一种。
  • 自动化内存管理。结合面向对象,在Binder里很容易构建出自动化垃圾回收机制,即可良好地服务于Java虚拟机,也可以减小C++环境里编程时的内存管理方面的工作量。
  • 灵活。面对对象的特质,使Binder相关的实现与拓展都很容易,Binder可用于多种应用场景。

当然,Binder也并非毫无缺陷。作为一种IPC机制,Binder的含义又不仅只是IPC通信,还涉及进程管理、线程管理、内存管理等诸多方面的需求,违背了设计上的低耦合性原则。Binder驱动提供的功能,将更多的操作暴露到用户态,也不符合Linux内核的设计思想。最重要的是,通过Binder的灵活,使基于Binder的相关代码不再清晰,有时不容易掌握Binder具体执行。所以现在Binder驱动 ,并非Linux主流(Mainline),在Linux内核源代码里而只是被放在drivers/staging/android目录里,处于待考察状态。

整个Binder驱动只由一个驱动文件实现,是drivers/staging/android/目录里的binder.c,而其头文件binder.h会定义一些通用数据结构,用于与用户态编程时共享。

Binder驱动如何被使用

我们可以通过上层调用来分析Binder驱动在功能上的需求。

在发生 Binder交互时,都会是Proxy对象通过IPCThreadState:transact(),走到writeTransactionData()来创建BC_TRANSACTION包,然后再通过waitForResponse()找到合适的Binder操作点,最后这一BC_TRANSCTION包会通过talkWithDriver()发送出去。在这时调用waitForResponse()时,还会同时根据是否需要取得返回值来判断是否需要进一步监听BR_REPLY包。得到的完整调用路径如下:

也就是此时Proxy端通过ioctl写入了一个BC_TRANSACTION命令,而在Binder另一端进行监听的Service进程,会通过同样的waitForResponse()得到BR_TRANSACTION命令,从而执行该命令对应的onTransact()操作。如果这一操作有返回,则Binder传输会反过来,Service进程写入BC_REPLY命令,而Proxy端通过waitForRepsonse()得到BR_REPLY命令,从而可以取回返回值:

最后Proxy端就通过BR_REPLY得到了调用的返回值,并把这个值存入一个Parcel对象里,在Proxy端编写的代码里,就可以在transact()调用之后,通过一个指定义为reply的Parcel对象得到这一返回结果。从代码执行上分析可能有点麻烦,如果把一切简化到Binder驱动相关的操作,则上述步骤只有四步:

从这也可以看出Binder传输上的特点。命令都是在两个进程间成对出现,一个进程操作BC_TRANSACTION或是BC_REPLY,另一个进程则有可能收到BR_TRANSACTION或是BR_REPLY。而TRANSACTION和REPLY得到的四条命令,则是Binder操作上唯一带有数据负载的命令,也是Binder能够成为IPC机制的核心功能。

在talkWithDriver()方法里,是Binder操作的通用方法。Binder交互在操作上相对比较简单,只通过ioctl()调用来完成Binder的读写,而通过一个binder_write_read数据结构来描述如何进行Binder读写操作。

   binder_write_read bwr;
    if(ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >=0)
       err = NO_ERROR;
    else
       err = -errno;

调用ioctl()时,第一个参数是一个fd文件描述符,这个参数会是从ProcessState得到一个通过open()系统调用得到的。第二个参数,会是一个操作命令,这是一个ioctl码,是由binder.h定义,驱动所能处理的针对Binder驱动有效的ioctl码。第三个参数则是用户态与内核态进行交互的数据结构,在上面的片断里,bwr是一个处于用户空间的局部变量,也就会处于用户态程序的栈空间里,但并不仅是传递给内核态,而是进行交互,传递参数给内核时,用户态程序填写好bwr内容再交给内核态来读取,而从内核态传回数据时,内核态改写bwr的内部然后用户态程序在ioctl()之后再读取。

Binder驱动所支持的ioctl码,只有BINDER_WRITE_READ使用最频繁,而其他的只是用于提供一些Binder操作的辅助功能。Binder驱动所支持的ioctl如下所示:

#define BINDER_WRITE_READ                   _IOWR('b',1, structbinder_write_read)
#define  BINDER_SET_IDLE_TIMEOUT       	   _IOW('b',3, int64_t)
#define  BINDER_SET_MAX_THREADS       	    _IOW('b',5, size_t)
#define  BINDER_SET_IDLE_PRIORITY           _IOW('b',6, int)
#define  BINDER_SET_CONTEXT_MGR      	    _IOW('b',7, int)
#define  BINDER_THREAD_EXIT                 _IOW('b',8, int)
#define BINDER_VERSION                      _IOWR('b',9, struct binder_version)

这些Binder IOCTL码的作用有:

Binder IOCTL

作用

BINDER_WRITE_READ

完成对/dev/binder的读写操作,这会是最频繁的Binder IOCTL操作

BINDER_SET_IDLE_TIMEOUT

设置IDLE超时

BINDER_SET_MAX_THREADS

设置当前Binder实例可用的最大线程数

BINDER_SET_IDLE_PRIORITY

设置Binder在进程处于IDLE时的进程优先级

BINDER_SET_CONTEXT_MGR

让自己成为Binder的管理者,只有servicemanager会用到

BINDER_THREAD_EXIT

退出Binder处理的内核线程

BINDER_VERSION

返回Binder驱动的当前版本

上面这些ioctl码,是一个由_IOC宏拓展出来一个32位整数,用于标识通过ioctl()系统调用处理的数据类型,参考对应平台的ioctl.h文件,一般来说,ioctl码的各个位意义如下:

bits

含义

31-30

00 – 无含义,使用_IO macro宏生成

10 – 读,使用_IOR宏生成 

01 – 写,使用_IOW宏生成  

11 – 同时支持读写,使用_IOWR生成

29-16

参数的大小

15-8

这一ioctl码对应对应的ASCII码标识,最好保持系统内唯一

7-0

具体的操作码

对应到Binder的IOCTL码定义,比如BINDER_WRITE_READ,是通过_IOW宏来生成的,于是最高两位会是0x01 (bit 31-30),参数大小是binder_write_read数据结构的大小,0x18(bit 29-16),标识码是’b’:0x62(bit 15-8),具体的操作码是1:0x01(bit 7-0),拼接起来就会是32位整数,0x40186201。

就像我们代码里的例子所示,如果通过ioctl()系统调用来操作一个binder_write_read数据结构时,此时我们就需要明确描述该操作是由内核态驱动到用户态程序,还是由用户态程序到内核态驱动。根据这种命令的方向性,可以分列出两类命令:BC_*系列,Binder Command的简称,说明用户态给Binder驱动发出的命令;BR_*系列,Binder Return的简称,说明是Binder驱动给用户态发回的操作结果反馈。这些命令不仅说明数据传输时的方向,同时也可以被拓展出来多种的功能,比如对象引用计数管理等功能,这样的命令出于当前的需求会有一个系列,同时在将来被使用到时也需要能够被拓展。为了规范化这两类操作,就定义了两个Enum结构:

enum BinderDriverCommandProtocol {
       BC_TRANSACTION= _IOW('c', 0, struct binder_transaction_data),
       BC_REPLY= _IOW('c', 1, struct binder_transaction_data),
       BC_ACQUIRE_RESULT= _IOW('c', 2, int),
       BC_FREE_BUFFER= _IOW('c', 3, int),
       BC_INCREFS= _IOW('c', 4, int),
       BC_ACQUIRE= _IOW('c', 5, int),
       BC_RELEASE= _IOW('c', 6, int),
       BC_DECREFS= _IOW('c', 7, int),
       BC_INCREFS_DONE= _IOW('c', 8, struct binder_ptr_cookie),
       BC_ACQUIRE_DONE= _IOW('c', 9, struct binder_ptr_cookie),
       BC_ATTEMPT_ACQUIRE= _IOW('c', 10, struct binder_pri_desc),
       BC_REGISTER_LOOPER= _IO('c',11),
       BC_ENTER_LOOPER= _IO('c',12),
       BC_EXIT_LOOPER= _IO('c',13),
       BC_REQUEST_DEATH_NOTIFICATION= _IOW('c',14, struct binder_ptr_cookie),
       BC_CLEAR_DEATH_NOTIFICATION= _IOW('c',15, struct binder_ptr_cookie),
       BC_DEAD_BINDER_DONE= _IOW('c', 16, void *),
};
enum BinderDriverReturnProtocol {
       BR_ERROR= _IOR('r', 0, int),
       BR_OK= _IO('r',1),
       BR_TRANSACTION= _IOR('r', 2, struct binder_transaction_data),
       BR_REPLY= _IOR('r', 3, struct binder_transaction_data),
       BR_ACQUIRE_RESULT= _IOR('r', 4, int),
       BR_DEAD_REPLY= _IO('r',5),
       BR_TRANSACTION_COMPLETE= _IO('r',6),
       BR_INCREFS= _IOR('r', 7, struct binder_ptr_cookie),
       BR_ACQUIRE= _IOR('r', 8, struct binder_ptr_cookie),
       BR_RELEASE= _IOR('r', 9, struct binder_ptr_cookie),
       BR_DECREFS= _IOR('r', 10, struct binder_ptr_cookie),
       BR_ATTEMPT_ACQUIRE= _IOR('r', 11, struct binder_pri_ptr_cookie),
       BR_NOOP= _IO('r',12),
       BR_SPAWN_LOOPER= _IO('r',13),
       BR_FINISHED= _IO('r',14),
       BR_DEAD_BINDER= _IOR('r', 15, void *),
       BR_CLEAR_DEATH_NOTIFICATION_DONE= _IOR('r',16, void *),
       BR_FAILED_REPLY= _IO('r',17),
};

BinderDriverCommandProtocol和BinderDriverReturnProtocol这两个Enum,就规范了用户态将怎样与底层进行交互。我们通过ioctl()来处理一个binder_write_read数据结构,而这一数据结构指向的内容,在发命令到Binder驱动时,必然会是以一个32位的BC_系列的操作符开始;如果是从Binder驱动读取,则必然会是以一个32位的BR_系列的操作符开始。

所有的交互消息,都使用跟ioctl码的相关宏定义,_IO、_IOW或是_IOR,但这并没有实际意义,跟一个整形值1、2、3类似,只是用于标明这一消息的唯一性,如果将来我们增加了新命令,不容易因为操作命令序列的变化而出现严重错误。另外,这样的定义也辅助了解析,这些命令的定义里都包含了参数长度信息,在同一个缓冲区里封装多条命令时,可以通过4+参数长度,很快跳转到下一条命令。

在上面看到的binder_write_read操作时,无论是读还是写,都可以使用同一个BINDER_WRITE_READ的ioctl(),同时处理多个命令,同时包装多个BC_命令发多个命令到Binder驱动,或是通过一次ioctl()读出来多个返回结果,这样也节约了系统调用时的开销。此时,用户态的Binder相关的内存如下示意图所示:

作为一种IPC机制,Binder所能起到的作用是传递数据,其他命令或多或少只是提供一些辅助功能。在Binder所定义的这些命令里,能够完成数据传递功能的只有BC_TRANSACTION、BC_REPLY、BR_TRANSACTION和BR_REPLY四种。在RemoteService的分析里,也可以看到,两个进程交互时TRANSACTION和REPLY的BC_与BR_命令都有传递性,一个进程发出BC_TRANSACTION有可能触发另一个进程收到BR_TRANSACTION,进程发出BC_REPLY又会使一个进程收到BR_REPLY。

无论是TRANSACTION还是REPLY,通过ioctl()来操作时,使用的参数都会是一个叫binder_transaction_data的数据结构的指针。Transaction,一般被翻译成事务,代表着计算处理里的进行的原子操作,事务之间不会互相干扰,另外事务操作成功则会全局有效,不成功则不会产生任何影响。这样的概念一般用于数据库操作,用于维护数据库并发处理时ACID支持,在Binder传输时也借用了这种概念,也就是说每次Binder IPC操作都以事务方式进行,成功而会影响整个Binder状态,不成功则会被放弃,同时每个BinderIPC之间不会互相有干扰。

binder_transaction_data的构成如下:

struct binder_transaction_data {
       union {
              size_t     handle;  /* target descriptor of command transaction */
              void       *ptr;      /* target descriptor of return transaction */
       }target;
       void              *cookie; /* target objectcookie */
       unsigned  int     code;            /* transaction command */
       unsigned  int     flags;
       pid_t             sender_pid;
       uid_t             sender_euid;
       size_t            data_size;     /* number of bytesof data */
       size_t            offsets_size;  /* number of bytes ofoffsets */
       union {
              struct {
                     const void __user *buffer;
                     const void __user *offsets;
              }ptr;
              uint8_t   buf[8];
       }data;
};

       像binder_transaction_data这样的数据结构,将会描述Binder传输时一特征信息,一般也称为元数据,metadata。也就是说在这样的结构体时,只会描述数据变动的需求,而会避免将数据漫无目的地拷贝来拷贝去。如果数据本身没有变动,比如多次访问对同一对象进行操作,此时就有可能数据完全不拷贝,而只是有binder_transaction_data这样的meta data的拷贝。无论是读或者写,都将使用这样的结构体,使得这一结构体在Proxy和Stub端都会有不同的含义。这一数据结构成员变量的含义如下表如示:

成员变量

类型

作用

target

union

BC_命令时,size_t

在通过BC_系列命令向Binder驱动发出操作请求时,我们需要target.handle指定到需要访问的Binder对象

BR_命令时, void*

通过BR_系列命令从Binder驱动里读回返回值时,需要指定返回的一段地址,target.ptr则会指向这个地址空间

cookie

void *

附加属性,这可用于不同使用情况下加入一个附加值,辅助上层的处理。比如可用于unlinkToDeath(),一次将所有同一cookie的对象清除

code

unsigned int

这便是我们在编写AIDL和Native Service的IBinder命令,由IBinder::FIRST_CALL_TRANSACTION开始的命令序列

flags

unsigned it

定义传输时可使用的参数

sender_pid

pid_t

发出Binder命令的进程的pid

sender_euid

uid_t

发出Binder命令的进程的euid,在进程执行过程中可能通过setuid()调用改变执行时的uid,euid则用于指定当前进程处于哪个uid的执行权限之下

data_size

size_t

数据区的大小

offsets_size

size_t

所包含数据的偏移量

data

union

struct {

void *[2]

} ptr;

ptr.buffer

ptr.offsets

当我们需要包含额外的数据负载时,由data来保存缓冲区的首地址与偏移。data.ptr.buffer指向首地址,data.ptr.offsets指向具体的偏移量。同时这两个值可与上面的data_size、offsets_size结构,通过首址加大小,将某部分需要操作的内容拷贝出来,而offsets也是地址,指向的可能会是数组,于是可以在同一buffer里保存多个并且大小不一的对象

uint8_t buf[8]

如果数据量很小,可以直接重用这内置的8个字节的data来保存,通过data.buf来进行访问

 

通过这样的binder_transaction_data,就可以很精确地描述在某一次Binder IPC传递过程里需要做什么操作(target定位到对象、code定位到方法),操作的数据在什么地方(data_size、data_size、offsets_size定位作为参数的对象)。在这些信息里还会包含发起请求的pid、euid等信息,可进一步用于权限控制。至此,基于Binder的数据传输,可以表现为如下的组织形式:

无论是发送还是指收,会将需要操作的区域封装到一个TRANSACTION或是REPLY命令的列表。在需要处理内存区,而在具体的4字节的BC_或是BR_命令之后,会填写入所需要操作的binder_transaction_data内容。如果数据是内置的,直接会将数据包含在data.buf[]的8字节存储空间里,如果数据量大,可以通过data.ptr.buffer指向保存了需要被操作数据的一段用户态内存,同时由于存在data.ptr.offsets,可以进一步索引这段内存,offsets[0]是第一个部分,offset[1]是第二部分,offsets[3]是第三部分,等等。当这样的binder_write_read通过ioctl传入Binder驱动之后,Binder驱动会根据transaction的描述来进行具体的IPC操作。

如果只是使用binder_transaction_data,可以以面向对象的方式来实现RPC,但还不是全部,如果传递的参数只是按部就班地填到data.ptr索引的区域,对于参数的面向对象式的访问没有完成。试想一下,在远程调用时会使用两个方法:func1(obj1,obj2)和func2(obj1),如果只是把参数顺次填到data.ptr索引的区域,则Binder驱动需要维护(obj1,obj2)和(obj1)两份内容,但实际上obj1的存储区域是重叠的。出于纯粹面向对象的设计角度考虑,Binder驱动需要针对每个对象来维护引用。还好,用户态的libbinder在处理对象时,会通过Parcelable接口将对象平整化(flatten),对象会被肢解,然后填充到一个线性存储区域buf。每个Parcel对象的buf区域可通过data.ptr.offsets索引到,就达到传输时针对对象级别的精度。在Binder驱动里处理Parcel映射的数据结构是flat_binder_object,其结构如下:

struct flat_binder_object {
       unsigned long             type;
       unsigned long             flags;
       union {
              void __user   *binder;       /* local object */
              signed long   handle;         /* remote object */
       };
       void __user          *cookie;
};

我们可以看到flat_binder_object的结构很简单,除去辅助性的类型、flag和cookie,穷得就只剩下一个指针,而且这个指针还是union,针对其引用的不同对象有不同含义。flat_binder_object对于本地对象的引用,会是一个用户态的指针,针对远程对象,则是一个long类型的handle,引用到远程对象。所以一个完整的Binder交互数据可能会是下面这样的形式:

data.ptr.buffer指向用户态存放的某个Parcel对象的列表,而data.ptr.offsets则会标明具体是需要操作这组对象的哪个。

有了flat_binder_object这样的表示形式,我们就可以使用16字节引用任何对象,增加的开销非常小,但可以得到一个完整的面向对象的传输能力。

Binder驱动的内部数据结构

任何驱动,都会在实现上维护一些局部数据结构,以提供硬件本身的一些上下文环境等,Binder也不例外,但Binder驱动的局部数据结构,更多地是用来映射Binder在用户态的一些概念,提供一种内核态的辅助手段。比如使用Binder的进程必然会有ProcessState和IPCThreadState对象,这两个对象必须要在Binder驱动里被映射并管理起来。另外,binder_transaction_data只是上次的访问接口,需要在驱动内部管理这些传输时的事务,针对于具体对象引用,在binder驱动里也需要维护对象的引用信息,从而实现对象重用。总结下来的话,就可以得到Binder驱动会使用的内部变量:

Binder 驱动变量

映射的用户态概念

作用

binder_proc

ProcessState

用于描述当前操作Binder的进程的上下文状态,比如内存信息、线程信息、进程优先级等。Binder驱动的open()函数被调用后就会创建一个binder_proc结构,这样的结构针对于每个进程都是唯一的,跟ProcessState一样

binder_thread

IPCThreadState

虽然被命名为binder_thread,但这名字会有误导,实际上跟内核线程没任务关系,只是用于描述用户态Binder线程状态,也就是IPCThreadState的状态。比如当前Binder线程有多少任务需要完成,是否有失败等

binder_transaction

binder_transaction_data

binder_transaction_data只是操作的外部接口,内部描述则是使用binder_transaction。在binder_transaction里虽然也只存储元数据,记录如何进行数据操作,但由于需要通过binder_transaction来完成具体的传输,它会包含更多的信息,比如这一transaction所关联的进程、线程、调度优先级等信息。

binder_node

flat_binder_object

Binder传输使用过的对象,都会通过binder_node维护一个引用计数,对于任何操作过的对象,在binder_node构成的检索树里,都会保存引用记录,从而可以快速查找到这一对象,并进行所需要的处理。binder_node只用于索引用户态的Binder对象

binder_buffer

-

对象被使用过后,就可以通过binder_buffer存储起来,当下次被用到时,可以绕开内存拷贝的需求

binder_ref

-

Binder的内部对象,可索引到binder_nodebinder_proc以及用于垃圾回收的binder_ref_death,从而可以加快查找

这些内部对象,都会在binder.c里定义,出于C式的面向对象技巧,因为只用于内部使用,于是没有必要暴露出来。这些数据结构的定义都很简练,可以结构代码来看到它们具体是如何被使用的,binder.c的编写者对于Linux内核运行原理理解很深刻,有良好的Linux内核的编程风格。但由于整个Binder驱动只是辅助上层的libbinder,所以驱动本身提供的自我解释性并不好,所以也不是很受Linux内核社区欢迎。

在Binder驱动的这些内部变量里,大量借用了Linux内核里已有的数据结构与算法。使用最多的数据结构有list_head、hlist_head和rb_node。

  • list_head,list_head是Linux内核里标志性的通用算法之一,一个list_head结构里包含next和prev两个指针,通过这两个指针指向其他的list_head结构来构成双链接。但使用时则非常方便,只需要在某个数据结构的加入一个list_head成员变量即可,一般会被放在头部,这样可以自动实现存取时的地址对齐,查找时直接通过一个数据结构指针指向这一区域就得到具体的内容,一般不会手动去做,会通过list_entry()这个宏来完成。list_head可以高效地构建双链表和进行数据遍历,但每次插入节点或是删除节点时必须访问两个数据结构才能完成。
  • hlist_node,hlist_node则是list_head的一个变种,原理跟list_head一样,但它本质上只是单链表,主要是实现只访问一个结点来达到插入节点和删除节点的目的,可以更高效地用于修改频繁的链表。它在实现的上的技巧是,hlist_node包含的next指针不变,而把prev换成pprev。pprev顾名思义也就是上个指针的上个指针,这样在某个结点前加入或是删除一个新结点时,因为pprev会跳过一个结点,于是只需要修改一个结点的链接关系就可以将新结点加入链表,这点就可以减小内存访问以及有可能造成的Cache失效的开销。当然,要是在结点后加入新结点,则跟原来一样需要两个结点修改才能完成插入。当此时得到链表则会是后进先出,类似于栈的结构,就不适合顺序处理的逻辑了。
  • rb_node,rb_node是Red Black Tree(红黑树)的数据结构。红黑树是一种近似平衡树算法,查找和修改时的计算开销总会控制在O(log n)。它通过给树的每个结点标上上红黑标记,根结点和叶结点必须为黑,红结点的子结点必须是黑结点,同时保证从根结点到叶结点的每条路径上的黑结点数都一样。这样的规则保证了红黑树在插入结点后进行平衡时的开销被减小,只需要颜色改变或是少量翻转来维持这个规则,最后得到的树并不一定是完全平衡的,但接近平衡的状态,从而不像完全平衡树那样需要过多计算来保持平衡。红黑树算法在Linux内核里最开始被用于内存管理算法,后来也被CFS(Complete
    Faire Scheduler)调度器,后来几乎所有涉及频繁插入和查找的代码都使用这一算法。Binder驱动里也会大量使用红黑树,所有需要被查找的内容,像binder_node、binder_ref、binder_buffer、binder_thread都会使用红黑树来进行索引。

Binder驱动的实现

我们看看数据结构之后,再来看binder驱动的具体实现。Binder驱动最终会在系统里生成一个/dev/binder的字符设备文件,/dev/binder并不对应到任何的硬件,只是使用内存虚拟出来的字符设备。Binder驱动的实现非常简练,如果剥离到调试代码,基本上就是编写一个字符设备驱动的示例代码:

static conststruct file_operations binder_fops = {
       .owner= THIS_MODULE,
       .poll= binder_poll,
       .unlocked_ioctl= binder_ioctl,
       .mmap= binder_mmap,
       .open= binder_open,
       .flush= binder_flush,
       .release= binder_release,
};
static struct miscdevice binder_miscdev = {
       .minor= MISC_DYNAMIC_MINOR,
       .name= "binder",
       .fops= &binder_fops
};
 
static int __init binder_init(void)
{
       int ret;
 
       binder_deferred_workqueue= create_singlethread_workqueue("binder");
       if(!binder_deferred_workqueue)
              return -ENOMEM;
       ret= misc_register(&binder_miscdev);
       return ret;
}
device_initcall(binder_init);

在Linux内核启动过程中,会调用到do_initcalls()函数循环地调用.initcall段位的每个函数,于是就会调用到通过device_initcall()宏把自己编译到.initcall段位的binder_init()函数。但这里没有定义__exit,于是binder驱动不支持卸载。在binder_init()里会将一个binder_miscdev数据结构注册到系统,并创建一个维护驱动的workqueue,就此而已。也就是说,如果编译了binder驱动支持,则binder_init()函数会在启动过程中被执行,之后通过/dev/binder就可以访问到驱动,当此时binder并不提供任何功能。而在binder_miscdev数据结构,把一个binder_fops数据结构挂载在binder_miscdev的fops指针下,这样所有与/dev/binder相关的操作,都将对应到binder_fops里定义的函数,比如open()系统调用,会通过open()
è sys_open() è binder_fops->open() è binder_open(),执行具体的binder_open()函数。

binder_open()的代码也不复杂,它会根据当前的上下文件环境,初始化一些后续操作所需要的基本链接:

static int binder_open(struct inode *nodp,struct file *filp)
{
       struct binder_proc*proc;
       proc= kzalloc(sizeof(*proc), GFP_KERNEL);
       if (proc == NULL)
              return -ENOMEM;
       get_task_struct(current);
       proc->tsk= current;
       INIT_LIST_HEAD(&proc->todo);
       init_waitqueue_head(&proc->wait);
       proc->default_priority= task_nice(current);
       mutex_lock(&binder_lock);
       binder_stats_created(BINDER_STAT_PROC);
       hlist_add_head(&proc->proc_node,&binder_procs);
       proc->pid= current->group_leader->pid;
       INIT_LIST_HEAD(&proc->delivered_death);
       filp->private_data= proc;
       mutex_unlock(&binder_lock);
       return 0;
}

从binder驱动的初始化、binder设备的打这两个基本过程,可以看出binder驱动的开销非常小,引入binder所带来的开销,直到open()操作之后的状态点,都几乎可以被忽略不计。每次进入binder_open()函数之后,会对每个进程创建一个binder_proc数据结构,所以事实上每个使用Binder驱动的用户态进程都会得到一个binder_proc,而系统里所有的binder_proc都会挂载到binder_procs这个单链接之后。

在Android系统里, Binder几乎无论不在,比如基于IBinder的远程对象、基于IBinder的Parcel对象、进行系统Service管理的servicemanager进程、RemoteService或是Native Service,都是基于Binder驱动来进行通信的。对于servicemanager进程来说,我们可以看到Binder是通过binder.c里的函数来完成Binder驱动的读写;而其他情况,都会通过IPCThreadState对象的talkWithDriver()方法来完成Binder驱动的读写。

Binder本身本身并不支持直接read()/write()系统调用,而只使用ioctl()系统调用。在Unix系统里一般IPC通信机制中,可

抱歉!评论已关闭.