http://www.angryredplanet.com/~hackbod/openbinder/docs/html/BinderProcessModel.html
Binder采用一个定制的内核小模块在进程间进行通信。这是用来代替标准的Linux的IPC设施,使我们能够有效地为IPC操作模型化为"thread migration"。也就是说,在进程间的IPC看来,如果线程触发的IPC已经跳上了到目标进程,执行该代码,然后跳下,结果跳回来。
Binder IPC机制本身不是真正实现使用线程迁移。相反,Binder的用户空间代码在每一个进程维护一个可用的线程,它是用来处理一些传入的IPC,并在这一过程中执行本地事件。内核模块通过产生一些跨进程线程优先级的模拟线程迁移模型,并确保IPC被派遣,如果一个IPC的递归回原进程中,IPC是由其原线程处理。
除了本身的IPC,Binder的内核模块还负责跟踪对象引用跨进程负责。这涉及到从一个进程中远程对象的引用,以在其宿主进程的真正对象映射,并确保对象不会被破坏,只要其他进程持有的他们的引用。
本文件的其余部分将详细描述Binder IPC如何运作。这些细节都没有接触到应用开发,使他们能够安全地忽略。
Getting Started
当一个用户空间线程想要加入Binder IPC(传一个IPC到另一个进程或接收一个IPC),首先必须做的就是打开的Binder内核模块提供的驱动程序。该线程领取一个文件描述符,这在内核模块用来标识IPC的发起者和接收者。
正是通过这个文件描述,所有与IPC机制将发生相互作用,通过一系列的ioctl()命令小集。主要的命令是:
?BINDER_WRITE_READ发送零个或多个Binder操作,然后阻塞等待接收传入的操作和结果。 (这与对一个文件描述符先做write()然后read()操作一样,只是效率高点)
?BINDER_SET_WAKEUP_TIME设置在其中一个用户空间的事件是发生在预定时间调用进程。
?BINDER_SET_IDLE_TIMEOUT设置线程将保持空闲的时间(等待一个新的transaction)超时。
?BINDER_SET_REPLY_TIMEOUT设置时间线程将阻止等待答复,直到它们超时。
?BINDER_SET_MAX_THREADS设置线程的最大数目,该驱动程序允许创建该进程的线程池。
关键是BINDER_WRITE_READ命令,这是所有IPC的业务基础。在进入有关的细节去,但是,应该指出的driver希望用户代码以维护一个线程池以等待传入的 transactions。您需要确保始终有一个可用的线程(线程到你想的最大数目),使IPC均可被处理。当在须处理在本地进程的新的异步事件(from SHandler) 的时候,该driver还须注意唤醒线程池。BINDER_WRITE_READ
如上所述,该驱动程序的核心功能是封装在BINDER_WRITE_READ运作。 ioctl的数据是这样的结构:
struct binder_write_read
{
ssize_t write_size;
const void* write_buffer;
ssize_t read_size;
void* read_buffer;
};
当调用驱动程序时,write_buffer包含一系列的命令来执行它,并在read_buffer充满一系列线程执行应答后返回。一般来说,写缓冲区将包括零个或多个book-keeping命令(通常用递增/递减对象引用),用一个命令方式结束,并需要应答(如发送的IPC transaction或试图获得对远程对象的强引用回应) 。同样,接收缓冲区将填充一系列book-keeping命令,或者是last written,或者是new nested命令操作来结束。
这里是可以由一个进程发送到驱动程序的命令列表,对如下数据缓冲区中的每个命令作了描述:
enum BinderDriverCommandProtocol {
bcNOOP = 0,
No parameters!
bcTRANSACTION,
bcREPLY,
binder_transaction_data: the sent command.
bcACQUIRE_RESULT,
int32: 0 if the last brATTEMPT_ACQUIRE was not successful.
Else you have acquired a primary reference on the object.
bcFREE_BUFFER,
void *: ptr to transaction data received on a read
bcINCREFS,
bcACQUIRE,
bcRELEASE,
bcDECREFS,
int32: descriptor
bcATTEMPT_ACQUIRE,
int32: priority
int32: descriptor
bcRESUME_THREAD,
int32: thread ID
bcSET_THREAD_ENTRY,
void *: thread entry function for new threads created to handle tasks
void *: argument passed to those threads
bcREGISTER_LOOPER,
No parameters.
Register a spawned looper thread with the device. This must be
called by the function that is supplied in bcSET_THREAD_ENTRY as
part of its initialization with the binder.
bcENTER_LOOPER,
bcEXIT_LOOPER,
No parameters.
These two commands are sent as an application-level thread
enters and exits the binder loop, respectively. They are
used so the binder can have an accurate count of the number
of looping threads it has available.
bcCATCH_ROOT_OBJECTS,
No parameters.
Call this to have your team start catching root objects
published by other teams that are spawned outside of the binder.
When this happens, you will receive a brTRANSACTION with the
tfRootObject flag set. (Note that this is distinct from receiving
normal root objects, which are a brREPLY.)
bcKILL_TEAM
No parameters.
Simulate death of a kernel team. For debugging only.
};
最有趣的命令,这里有bcTRANSACTION和bcREPLY,分别是发起的IPC transaction,答复transaction。这些命令是以下数据结构:
enum transaction_flags {
tfInline = 0x01, // not yet implemented
tfRootObject = 0x04, // contents are the component's root object
tfStatusCode = 0x08 // contents are a 32-bit status code
};
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;
uint32 code; // transaction command
// General information about the transaction.
uint32 flags;
int32 priority; // requested/current thread priority
size_t data_size; // number of bytes of data
size_t offsets_size; // number of bytes of object offsets
// If this transaction is inline, the data immediately
// follows here; otherwise, it ends with a pointer to
// the data buffer.
union {
struct {
const void *buffer; // transaction data
const void *offsets; // binder object offsets
} ptr;
uint8 buf[8];
} data;
};
因此,要启动一个IPC的transaction,您将主要执行一个BINDER_READ_WRITE的ioctl, 执行abinder_transaction_data后,写入含bcTRANSACTION,缓冲区 。此结构的目标是接收transaction对象的handle(我们将在以后讨论handle),代码应当提供对象,当它接收的transaction,应当做什么,优先级-----线程优先运行IPC的级别,和一个数据缓冲区-----包含transaction 数据,以及一个(可选)的元数据的额外offsetsbuffer。
出于目标handle,驱动程序确定对象在哪个的进程,dispatches 这个 transaction给线程池一个等待线程,(如果需要则产生一个新的线程)。该线程正在等待一个BINDER_WRITE_READ的ioctl()的驱动程序,所以通过填充执行命令在缓冲区返回它的read buffer。这些命令与写命令非常相似,另一方面的大部分对应于相应的写操作:
enum BinderDriverReturnProtocol {
brERROR = -1,
int32: error code
brOK = 0,
brTIMEOUT,
brWAKEUP,
No parameters!
brTRANSACTION,
brREPLY,
binder_transaction_data: the received command.
brACQUIRE_RESULT,
int32: 0 if the last bcATTEMPT_ACQUIRE was not successful.
Else the remote object has acquired a primary reference.
brDEAD_REPLY,
The target of the last transaction (either a bcTRANSACTION or
a bcATTEMPT_ACQUIRE) is no longer with us. No parameters.
brTRANSACTION_COMPLETE,
No parameters... always refers to the last transaction requested
(including replies). Note that this will be sent even for asynchronous
transactions.
brINCREFS,
brACQUIRE,
brRELEASE,
brDECREFS,
void *: ptr to binder
brATTEMPT_ACQUIRE,
int32: priority
void *: ptr to binder
brEVENT_OCCURRED,
This is returned when the bcSET_NEXT_EVENT_TIME has elapsed.
At this point the next event time is set to B_INFINITE_TIMEOUT,
so you must send another bcSET_NEXT_EVENT_TIME command if you
have another event pending.
brFINISHED
};
接下来我们的例子中,线程将在其缓冲区的末尾接收brTRANSACTION命令。此命令使用相同的binder_transaction_datastructure被用来发送数据,基本上包含相同的信息已发送,但现在在本地进程的可用空间。
在用户空间的接收者将传送这个transaction至目标对象,执行并返回其结果。根据结果,一个新的写缓冲区创建载有binder_transaction_data结构bcREPLY答复包,包含结果数据的命令。这是一个driver返回BINDER_WRITE_READ的ioctl(),发送的答复回到原来的进程,并离开线程等待下一个 transaction 执行等。
原来的线程最终返回自己的BINDER_WRITE_READ----一个包含回复数据的brREPLY命令。
请注意,当等待答复时,原来的线程也可能会收到brTRANSACTION命令。这是一个递归过程,在接收线程调用一个对象回到原来的进程。这是driver的责任,保持跟踪所有active transactions,因此它可以dispatch transactions向正确的线程递归时发生。
Object Mapping and Referencing
对driver的重要职责之一是从一个进程的对象执行映射到另一个。这是两个关键的沟通机制(targetting和referencing objects), 和能力模型(只允许一个特定的进程来操作远程对象----并已显示告知)。
有两种不同的对象的引用形式:作为一个进程的内存空间中的地址,或作为一个抽象的32位handle。这些表是互斥的:一个进程在一个本地的对象进程的所有引用采用地址的形式,而对另一个进程中的所有引用对象总是采用句柄的形式。
例如,请注意binder_transaction_data的目标领域。当发送一个transaction,这包含对目标对象的handle(因为你总是发送transactions到其他进程)。这项transactions的接收者则认为这个对象是本地地址空间的指向。该驱动程序维护的进程之间的指针和handles的映射 ,以便它可以执行这个解释翻译操作。
我们还必须能够通过transactions发送对象的引用。这是通过把对象的引用(指针无论是本地或远程处理)到该transactions.的缓冲区。driver必须翻译这个引用,对应于接收进程相应的引用,这样,就像我们做的transaction目标。
为了做引用翻译,driver 需要知道这些transaction 数据出现的引用在哪里。这就是额外的缓冲区偏移指向,它具有一系列的数据缓冲区索引,描述对象出现在哪里。该驱动程序可以重写缓冲区数据,翻译每一个从发送过程的对象引用至接收过程中的正确的引用。
请注意该驱动程序不知道任何特定的Binder对象,直到该对象是通过驱动程序发送到另一个进程。在这一点上,驱动程序添加对象的地址映射表,并要求其所属的进程就此进行引用。当没有其他进程知道对象,它就从映射表中删除,其过程是告诉释放driver的引用。这就避免了维护对象(相对重要的)driver state,只要仅在本地进程中使用的驱动程序的状态。