Binder IPC 机制

本文深入探讨了Android Binder IPC机制的内部工作原理。Binder作为Android系统中进程间通信的基础,通过定制化的内核模块实现高效的线程迁移模型,用于替代标准的Linux IPC设施。文章详细介绍了Binder的初始化流程、主要ioctl命令的作用以及数据结构binder_transaction_data的定义,同时还解释了对象映射和引用机制。

< Binder Process Model | Binder Kit | pidgen >

________________________________________

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,只要仅在本地进程中使用的驱动程序的状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值