红茶一杯话Binder(初始篇)
https://blog.csdn.net/codefly/article/details/17058607
红茶一杯话Binder
(初始篇)
侯 亮
1 什么是Binder?
简单地说,Binder是Android平台上的一种跨进程交互技术。该技术最早并不是由Google公司提出的,它的前身是Be Inc公司开发的OpenBinder,而且在Palm中也有应用。后来OpenBinder的作者Dianne Hackborn加入了Google公司,并负责Android平台的开发工作,所以把这项技术也带进了Android。
我们知道,在Android的应用层次上,基本上已经没有过去的进程概念了。然而在实现层次,它毕竟还是要建构在一个个进程之上的。实际上,在Android内部,那些支撑应用的组件往往会身处于不同的进程,那么应用的底层必然会牵涉大量的跨进程通信。为了保证通信的高效性,Android提供了Binder机制。
Binder机制具有两层含义:
1) 是一种跨进程通信手段(IPC,Inter-Process Communication)。
2) 是一种远程过程调用手段(RPC,Remote Procedure Call)。
从实现的角度来说,Binder核心被实现成一个Linux驱动程序,并运行于内核态。这样它才能具有强大的跨进程访问能力。
1.1 简述Binder的跨进程机制
为了理解Binder,我们可以先画一张最简单的跨进程通信示意图:
这个很容易理解,不需赘言。到了Android平台上,IPC机制就变成了Binder机制,情况类似,只不过为了便于说明问题,我们需要稍微调整一下示意图:
图中A侧的圆形块,表示“Binder代理方”,主要用于向远方发送语义,而B侧的方形块则表示“Binder响应方”,主要用于响应语义。需要说明的是,这种图形表示方法是我自己杜撰的,并没有正规的出处。我个人觉得这种图形非常简便,所以在分析Android架构时,会经常使用这种表示法。
在后文中,我们可以看到,Binder代理方大概对应于C++层次的BpBinder对象,而Binder响应方则对应于BBinder对象。这两个对象在后文会详细阐述,此处不必太细究。
然而,上图的Binder代理方主要只负责了“传递信息”的工作,并没有起到“远程过程调用”的作用,如果要支持远程过程调用,我们还必须提供“接口代理方”和“接口实现体”。这样,我们的示意图就需要再调整一下,如下:
从图中可以看到,A进程并不直接和BpBinder(Binder代理)打交道,而是通过调用BpInterface(接口代理)的成员函数来完成远程调用的。此时,BpBinder已经被聚合进BpInterface了,它在BpInterface内部完成了一切跨进程的机制。另一方面,与BpInterface相对的响应端实体就是BnInterface(接口实现)了。需要注意的是,BnInterface是继承于BBinder的,它并没有采用聚合的方式来包含一个BBinder对象,所以上图中B侧的BnInterface块和BBinder块的背景图案是相同的。
这样看来,对于远程调用的客户端而言,主要搞的就是两个东西,一个是“Binder代理”,一个是“接口代理”。而服务端主要搞的则是“接口实现体”。因为binder是一种跨进程通信机制,所以还需要一个专门的管理器来为通信两端牵线搭桥,这个管理器就是Service Manager Service。不过目前我们可以先放下Service Manager Service,以后再详细研究。
2 Binder相关接口和类
Android的整个跨进程通信机制都是基于Binder的,这种机制不但会在底层使用,也会在上层使用,所以必须提供Java和C++两个层次的支持。
2.1 Java层次的binder元素
Java层次里并没有我们前文图中所表示的BpBinder、BpInterface、BBinder等较低层次的概念,取而代之的是IBinder接口、IInterface等接口。Android要求所有的Binder实体都必须实现IBinder接口,该接口的定义截选如下:
【frameworks/base/core/java/android/os/IBinder.java】
public interface IBinder
{
. . . . . .
public String getInterfaceDescriptor() throws RemoteException;
public boolean pingBinder();
public boolean isBinderAlive();
public IInterface queryLocalInterface(String descriptor);
public void dump(FileDescriptor fd, String[] args) throws RemoteException;
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
public boolean transact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException;
public interface DeathRecipient
{
public void binderDied();
}
public void linkToDeath(DeathRecipient recipient, int flags)throws RemoteException;
public boolean unlinkToDeath(DeathRecipient recipient, int flags);
}
另外,不管是代理方还是实体方,都必须实现IInterface接口:
public interface IInterface
{
public IBinder asBinder();
}
Java层次中,与Binder相关的接口或类的继承关系如下:
在实际使用中,我们并不需要编写上图的XXXXNative、XXXXProxy,它们会由ADT根据我们编写的aidl脚本自动生成。用户只需继承XXXXNative编写一个具体的XXXXService即可,这个XXXXService就是远程通信的服务实体类,而XXXXProxy则是其对应的代理类。
关于Java层次的binder组件,我们就先说这么多,主要是先介绍一个大概。就研究跨进程通信而言,其实质内容基本上都在C++层次,Java层次只是一个壳而已。以后我会写专文来打通Java层次和C++层次,看看它们是如何通过JNI技术关联起来的。现在我们还是把注意力集中在C++层次吧。
2.2 C++层次的binder元素
在C++层次,就能看到我们前文所说的BpBinder类和BBinder类了。这两个类都继承于IBinder,IBinder的定义截选如下:
【frameworks/native/include/binder/IBinder.h】
class IBinder : public virtual RefBase
{
public:
. . . . . .
IBinder();
virtual sp<IInterface> queryLocalInterface(const String16& descriptor);
virtual const String16& getInterfaceDescriptor() const = 0;
virtual bool isBinderAlive() const = 0;
virtual status_t pingBinder() = 0;
virtual status_t dump(int fd, const Vector<String16>& args) = 0;
virtual status_t transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags = 0) = 0;
class DeathRecipient : public virtual RefBase
{
public:
virtual void binderDied(const wp<IBinder>& who) = 0;
};
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0) = 0;
virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0,
wp<DeathRecipient>* outRecipient = NULL) = 0;
virtual bool checkSubclass(const void* subclassID) const;
typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
virtual void attachObject(const void* objectID, void* object,
void* cleanupCookie, object_cleanup_func func) = 0;
virtual void* findObject(const void* objectID) const = 0;
virtual void detachObject(const void* objectID) = 0;
virtual BBinder* localBinder();
virtual BpBinder* remoteBinder();
protected:
virtual ~IBinder();
private:
};
C++层次的继承关系图如下:
其中有以下几个很关键的类:
BpBinder
BpInterface
BBinder
BnInterface
它们扮演着很重要的角色。
2.2.1 BpBinder
BpBinder的定义截选如下:
class BpBinder : public IBinder
{
public:
BpBinder(int32_t handle);
inline int32_t handle() const { return mHandle; }
virtual const String16& getInterfaceDescriptor() const;
virtual bool isBinderAlive() const;
virtual status_t pingBinder();
virtual status_t dump(int fd, const Vector<String16>& args);
virtual status_t transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags = 0);
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0);
virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0,
wp<DeathRecipient>* outRecipient = NULL);
. . . . . .
. . . . . .
作为代理端的核心,BpBinder最重要的职责就是实现跨进程传输的传输机制,至于具体传输的是什么语义,它并不关心。我们观察它的transact()函数的参数,可以看到所有的语义都被打包成Parcel了。其他的成员函数,我们先不深究,待我们储备了足够的基础知识后,再回过头研究它们不迟。
2.2.2 BpInterface
另一个重要的类是BpInterface,它的定义如下:
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);
protected:
virtual IBinder* onAsBinder();
};
其基类BpRefBase的定义如下:
class BpRefBase : public virtual RefBase
{
protected:
BpRefBase(const sp<IBinder>& o);
virtual ~BpRefBase();
virtual void onFirstRef();
virtual void onLastStrongRef(const void* id);
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
inline IBinder* remote() { return mRemote; }
inline IBinder* remote() const { return mRemote; }
private:
BpRefBase(const BpRefBase& o);
BpRefBase& operator=(const BpRefBase& o);
IBinder* const mRemote;
RefBase::weakref_type* mRefs;
volatile int32_t mState;
};
BpInterface使用了模板技术,而且因为它继承了BpRefBase,所以先天上就聚合了一个mRemote成员,这个成员记录的就是前面所说的BpBinder对象啦。以后,我们还需要继承BpInterface<>实现我们自己的代理类。
在实际的代码中,我们完全可以创建多个聚合同一BpBinder对象的代理对象,这些代理对象就本质而言,对应着同一个远端binder实体。在Android框架中,常常把指向同一binder实体的多个代理称为token,这样即便这些代理分别处于不同的进程中,它们也具有了某种内在联系。这个知识点需要大家关注。
2.2.3 BBinder
Binder远程通信的目标端实体必须继承于BBinder类,该类和BpBinder相对,主要关心的只是传输方面的东西,不太关心所传输的语义。
class BBinder : public IBinder
{
public:
BBinder();
virtual const String16& getInterfaceDescriptor() const;
virtual bool isBinderAlive() const;
virtual status_t pingBinder();
virtual status_t dump(int fd, const Vector<String16>& args);
virtual status_t transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags = 0);
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0);
virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0,
wp<DeathRecipient>* outRecipient = NULL);
virtual void attachObject(const void* objectID, void* object,
void* cleanupCookie, object_cleanup_func func);
virtual void* findObject(const void* objectID) const;
virtual void detachObject(const void* objectID);
virtual BBinder* localBinder();
protected:
virtual ~BBinder();
virtual status_t onTransact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags = 0);
private:
BBinder(const BBinder& o);
BBinder& operator=(const BBinder& o);
class Extras;
Extras* mExtras;
void* mReserved0;
};
我们目前只需关心上面的transact()成员函数,其他函数留待以后再分析。transact函数的代码如下:
【frameworks/native/libs/binder/Binder.cpp】
status_t BBinder::transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
data.setDataPosition(0);
status_t err = NO_ERROR;
switch (code)
{
case PING_TRANSACTION:
reply->writeInt32(pingBinder());
break;
default:
err = onTransact(code, data, reply, flags);
break;
}
if (reply != NULL)
{
reply->setDataPosition(0);
}
return err;
}
看到了吗,transact()内部会调用onTransact(),从而走到用户所定义的子类的onTransact()里。这个onTransact()的一大作用就是解析经由Binder机制传过来的语义了。
2.2.4 BnInterface
远程通信目标端的另一个重要类是BnInterface<>,它是与BpInterface<>相对应的模板类,比较关心传输的语义。一般情况下,服务端并不直接使用BnInterface<>,而是使用它的某个子类。为此,我们需要编写一个新的BnXXX子类,并重载它的onTransact()成员函数。
BnInterface<>的定义如下:
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;
protected:
virtual IBinder* onAsBinder();
};
如上所示,BnInterface<>继承于BBinder,但它并没有实现一个默认的onTransact()成员函数,所以在远程通信时,前文所说的BBinder::transact()调用的onTransact()应该就是BnInterface<>的某个子类的onTransact()成员函数。
2.3 几个重要的C++宏或模板
为了便于编写新的接口和类,Android在C++层次提供了几个重要的宏和模板,比如我们在IInterface.h文件中,可以看到DECLARE_META_INTERFACE、IMPLEMENT_META_INTERFACE的定义。
2.3.1 DECLARE_META_INTERFACE()
DECLARE_META_INTERFACE()的定义如下:
#define DECLARE_META_INTERFACE(INTERFACE) \
static const android::String16 descriptor; \
static android::sp<I##INTERFACE> asInterface( \
const android::sp<android::IBinder>& obj); \
virtual const android::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE(); \
我们举个实际的例子,来说明如何使用这个宏:
上例中ICamera内部使用了DECLARE_META_INTERFACE(Camera),我们把宏展开后,可以看到ICamera类的定义相当于:
class ICamera: public IInterface
{
public:
static const android::String16 descriptor;
static android::sp<ICamera> asInterface( const android::sp<android::IBinder>& obj);
virtual const android::String16& getInterfaceDescriptor() const;
ICamera();
virtual ~ICamera();
virtual void disconnect() = 0;
. . . . . .
宏展开的部分就是中间那5行代码,其中最关键的就是asInterface()函数了,这个函数将承担把BpBinder打包成BpInterface的职责。
2.3.2 IMPLEMENT_META_INTERFACE()
与DECLARE_META_INTERFACE相对的就是IMPLEMENT_META_INTERFACE宏。它的定义如下:
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const android::String16 I##INTERFACE::descriptor(NAME); \
const android::String16& \
I##INTERFACE::getInterfaceDescriptor() const { \
return I##INTERFACE::descriptor; \
} \
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL) { \
intr = new Bp##INTERFACE(obj); \
} \
} \
return intr; \
} \
I##INTERFACE::I##INTERFACE() { } \
I##INTERFACE::~I##INTERFACE() { } \
其中,实现了关键的asInterface()函数。
实际使用IMPLEMENT_META_INTERFACE时,我们只需把它简单地写在binder实体所处的cpp文件中即可,举例如下:
其中的IMPLEMENT_META_INTERFACE(Camera, “android.hardware.ICamera”);一句相当于以下这段代码:
const android::String16 ICamera::descriptor(“android.hardware.ICamera”);
const android::String16& ICamera::getInterfaceDescriptor() const
{
return ICamera::descriptor;
}
android::sp<ICamera> ICamera::asInterface(const android::sp<android::IBinder>& obj)
{
android::sp<ICamera > intr;
if (obj != NULL)
{
intr = static_cast<ICamera*>(obj->queryLocalInterface(
ICamera::descriptor).get());
if (intr == NULL)
{
intr = new BpCamera(obj);
}
}
return intr;
}
ICamera::ICamera() { }
ICamera::~ICamera () { }
看来,其中重点实现了asInterface()成员函数。请注意,asInterface()函数中会先尝试调用queryLocalInterface()来获取intr。此时,如果asInterface()的obj参数是个代理对象(BpBinder),那么intr = static_cast<ICamera*>(obj->queryLocalInterface(...)一句得到的intr基本上就是NULL啦。这是因为除非用户编写的代理类重载queryLocalInterface()函数,否则只会以默认函数为准。而IBinder类中的默认queryLocalInterface()函数如下:
【frameworks/native/libs/binder/Binder.cpp】
sp<IInterface> IBinder::queryLocalInterface(const String16& descriptor)
{
return NULL;
}
另一方面,如果obj参数是个实现体对象(BnInterface对象)的话,那么queryLocalInterface()函数的默认返回值就是实体对象的this指针了,代码如下:
【frameworks/native/include/binder/IInterface.h】
template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(const String16& _descriptor)
{
if (_descriptor == INTERFACE::descriptor)
return this;
return NULL;
}
在我们所举的Camera例子中,我们要研究的是如何将BpBinder转成BpInterface,所以现在我们只阐述obj参数为BpBinder的情况。此时asInterface()函数中obj->queryLocalInterface()的返回值为NULL,于是asInterface()会走到new BpCamera(obj)一句,这一句是最关键的一句。我们知道,BpCamera继承于BpInterface<ICamera>,所以此时所创建的BpCamera对象正是可被App使用的BpInterface代理对象。
BpCamera的定义如下:
class BpCamera: public BpInterface<ICamera>
{
public:
BpCamera(const sp<IBinder>& impl)
: BpInterface<ICamera>(impl)
{
}
// disconnect from camera service
void disconnect()
{
LOGV("disconnect");
Parcel data, reply;
data.writeInterfaceToken(ICamera::getInterfaceDescriptor());
remote()->transact(DISCONNECT, data, &reply);
}
. . . . . .
至此,IMPLEMENT_META_INTERFACE宏和asInterface()函数的关系就分析完毕了。
2.3.3 interface_cast
不过,我们经常使用的其实并不是asInterface()函数,而是interface_cast(),它简单包装了asInterface():
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
以上就是关于C++层次中一些binder元素的介绍,下面我们再进一步分析其他细节。
3 ProcessState
前文我们已经提到过,在Android的上层架构中,已经大幅度地弱化了进程的概念。应用程序员能看到的主要是activity、service、content provider等概念,再也找不到以前熟悉的main()函数了。然而,底层程序(C++层次)毕竟还是得跑在一个个进程之上,现在我们就来看底层进程是如何运用Binder机制来完成跨进程通信的。
在每个进程中,会有一个全局的ProcessState对象。这个很容易理解,ProcessState的字面意思不就是“进程状态”吗,当然应该是每个进程一个ProcessState。ProcessState的定义位于frameworks/native/include/binder/ProcessState.h中,我们只截选其中的一部分:
class ProcessState : public virtual RefBase
{
public:
static sp<ProcessState> self();
. . . . . .
void startThreadPool();
. . . . . .
void spawnPooledThread(bool isMain);
status_t setThreadPoolMaxThreadCount(size_t maxThreads);
private:
friend class IPCThreadState;
. . . . . .
struct handle_entry
{
IBinder* binder;
RefBase::weakref_type* refs;
};
handle_entry* lookupHandleLocked(int32_t handle);
int mDriverFD;
void* mVMStart;
mutable Mutex mLock; // protects everything below.
Vector<handle_entry> mHandleToObject;
. . . . . .
KeyedVector<String16, sp<IBinder> > mContexts;
. . . . . .
};
我们知道,Binder内核被设计成一个驱动程序,所以ProcessState里专门搞了个mDriverFD域,来记录binder驱动对应的句柄值,以便随时和binder驱动通信。ProcessState对象采用了典型的单例模式,在一个应用进程中,只会有唯一的一个ProcessState对象,它将被进程中的多个线程共用,因此每个进程里的线程其实是共用所打开的那个驱动句柄(mDriverFD)的,示意图如下:
每个进程基本上都是这样的结构,组合起来的示意图就是:
我们常见的使用ProcessState的代码如下:
int main(int argc, char** argv)
{
sp<ProcessState> proc(ProcessState::self());
. . . . . .
. . . . . .
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
因为ProcessState采用的是单例模式,所以它的构造函数是private的,我们只能通过调用ProcessState::self()来获取进程中唯一的一个ProcessState对象。self()函数的代码如下:
sp<ProcessState> ProcessState::self()
{
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) {
return gProcess;
}
gProcess = new ProcessState;
return gProcess;
}
ProcessState对象构造之时,就会打开binder驱动:
ProcessState::ProcessState()
: mDriverFD(open_driver()) // 打开binder驱动。
, mVMStart(MAP_FAILED)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
{
. . . . . .
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
. . . . . .
}
注意上面那句mDriverFD(open_driver()),其中的open_driver()就负责打开“/dev/binder”驱动:
static int open_driver()
{
int fd = open("/dev/binder", O_RDWR);
. . . . . .
status_t result = ioctl(fd, BINDER_VERSION, &vers);
. . . . . .
size_t maxThreads = 15;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
. . . . . .
return fd;
}
ProcessState中另一个比较有意思的域是mHandleToObject:
Vector<handle_entry> mHandleToObject;
它是本进程中记录所有BpBinder的向量表噢,非常重要。我们前文已经说过,BpBinder是代理端的核心,现在终于看到它的藏身之处了。在Binder架构中,应用进程是通过“binder句柄”来找到对应的BpBinder的。从这张向量表中我们可以看到,那个句柄值其实对应着这个向量表的下标。这张表的子项类型为handle_entry,定义如下:
struct handle_entry
{
IBinder* binder;
RefBase::weakref_type* refs;
};
其中的binder域,记录的就是BpBinder对象。
Ok,有关Binder的初步知识,我们就先说这么多。我也不想一下子把所有的信息都塞到一篇文章中,所以打算把更多技术细节安排到其他文章中阐述,呵呵,这需要一点儿时间。
如需转载本文内容,请注明出处。
谢谢
(本文也发布在我的oschina博客上)
红茶一杯话Binder(传输机制篇_上)
2013年12月01日 19:27:49 悠然红茶 阅读数:3413
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/codefly/article/details/17058673
红茶一杯话Binder
(传输机制篇_上)
侯 亮
1 Binder是如何做到精确打击的?
我们先问一个问题,binder机制到底是如何从代理对象找到其对应的binder实体呢?难道它有某种制导装置吗?要回答这个问题,我们只能静下心来研究binder驱动的代码。在本系列文档的初始篇中,我们曾经介绍过ProcessState,这个结构是属于应用层次的东西,仅靠它当然无法完成精确打击。其实,在binder驱动层,还有个与之相对的结构,叫做binder_proc。为了说明问题,我修改了初始篇中的示意图,得到下图:
1.1 创建binder_proc
当构造ProcessState并打开binder驱动之时,会调用到驱动层的binder_open()函数,而binder_proc就是在binder_open()函数中创建的。新创建的binder_proc会作为一个节点,插入一个总链表(binder_procs)中。具体代码可参考kernel/drivers/staging/android/Binder.c。
驱动层的binder_open()的代码如下:
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
. . . . . .
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
get_task_struct(current);
proc->tsk = current;
. . . . . .
hlist_add_head(&proc->proc_node, &binder_procs);
proc->pid = current->group_leader->pid;
. . . . . .
filp->private_data = proc;
. . . . . .
}
注意,新创建的binder_proc会被记录在参数filp的private_data域中,以后每次执行binder_ioctl(),都会从filp->private_data域重新读取binder_proc的。
binder_procs总表的定义如下:
static HLIST_HEAD(binder_procs);
我们可以在List.h中看到HLIST_HEAD的定义:
【kernel/include/linux/List.h】
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
于是binder_procs的定义相当于:
struct hlist_head binder_procs = { .first = NULL };
随着后续不断向binder_procs表中添加节点,这个表会不断加长,示意图如下:
1.2 binder_proc中的4棵红黑树
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;
. . . . . .
. . . . . .
};
注意其中的那4个rb_root域,“rb”的意思是“red black”,可见binder_proc里搞出了4个红黑树。
其中,nodes树用于记录binder实体,refs_by_desc树和refs_by_node树则用于记录binder代理。之所以会有两个代理树,是为了便于快速查找,我们暂时只关心其中之一就可以了。threads树用于记录执行传输动作的线程信息。
在一个进程中,有多少“被其他进程进行跨进程调用的”binder实体,就会在该进程对应的nodes树中生成多少个红黑树节点。另一方面,一个进程要访问多少其他进程的binder实体,则必须在其refs_by_desc树中拥有对应的引用节点。
这4棵树的节点类型是不同的,threads树的节点类型为binder_thread,nodes树的节点类型为binder_node,refs_by_desc树和refs_by_node树的节点类型相同,为binder_ref。这些节点内部都会包含rb_node子结构,该结构专门负责连接节点的工作,和前文的hlist_node有点儿异曲同工,这也是linux上一个常用的小技巧。我们以nodes树为例,其示意图如下:
rb_node和rb_root的定义如下:
struct rb_node
{
unsigned long rb_parent_color;
#define RB_RED 0
#define RB_BLACK 1
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
/* The alignment might seem pointless, but allegedly CRIS needs it */
struct rb_root
{
struct rb_node *rb_node;
};
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;
};
我们前文已经说过,nodes树是用于记录binder实体的,所以nodes树中的每个binder_node节点,必须能够记录下相应binder实体的信息。因此请大家注意binder_node的ptr域和cookie域。
另一方面,refs_by_desc树和refs_by_node树的每个binder_ref节点则和上层的一个BpBinder对应,而且更重要的是,它必须具有和“目标binder实体的binder_node”进行关联的信息。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; // 注意这个node域
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
请注意那个node域,它负责和binder_node关联。另外,binder_ref中有两个类型为rb_node的域:rb_node_desc域和rb_node_node域,它们分别用于连接refs_by_desc树和refs_by_node。也就是说虽然binder_proc中有两棵引用树,但这两棵树用到的具体binder_ref节点其实是复用的。
大家应该还记得,在《初始篇》中我是这样表达BpBinder和BBinder关系的:
现在,我们有了binder_ref和binder_node知识,可以再画一张图,来解释BpBinder到底是如何和BBinder联系上的:
上图只表示了从进程1向进程2发起跨进程传输的意思,其实反过来也是可以的,即进程2也可以通过自己的“引用树”节点找到进程1的“实体树”节点,并进行跨进程传输。大家可以自己补充上图。
OK,现在我们可以更深入地说明binder句柄的作用了,比如进程1的BpBinder在发起跨进程调用时,向binder驱动传入了自己记录的句柄值,binder驱动就会在“进程1对应的binder_proc结构”的引用树中查找和句柄值相符的binder_ref节点,一旦找到binder_ref节点,就可以通过该节点的node域找到对应的binder_node节点,这个目标binder_node当然是从属于进程2的binder_proc啦,不过不要紧,因为binder_ref和binder_node都处于binder驱动的地址空间中,所以是可以用指针直接指向的。目标binder_node节点的cookie域,记录的其实是进程2中BBinder的地址,binder驱动只需把这个值反映给应用层,应用层就可以直接拿到BBinder了。这就是Binder完成精确打击的大体过程。
2 BpBinder和IPCThreadState
接下来我们来谈谈Binder传输机制。
在《初始篇》中,我们已经提到了BpBinder和ProcessState。当时只是说BpBinder是代理端的核心,主要负责跨进程传输,并且不关心所传输的内容。而ProcessState则是进程状态的记录器,它里面记录着打开binder驱动后得到的句柄值。因为我们并没有进一步展开来讨论BpBinder和ProcessState,所以也就没有进一步打通BpBinder和ProcessState之间的关系。现在,我们试着补充一些内容。
作为代理端的核心,BpBinder总要通过某种方式和binder驱动打交道,才可能完成跨进程传递语义的工作。既然binder驱动对应的句柄在ProcessState中记着,那么现在就要看BpBinder如何和ProcessState联系了。此时,我们需要提到IPCThreadState。
从名字上看,IPCThreadState是“和跨进程通信(IPC)相关的线程状态”。那么很显然,一个具有多个线程的进程里应该会有多个IPCThreadState对象了,只不过每个线程只需一个IPCThreadState对象而已。这有点儿“局部单例”的意思。所以,在实际的代码中,IPCThreadState对象是存放在线程的局部存储区(TLS)里的。
2.1 BpBinder的transact()动作
每当我们利用BpBinder的transact()函数发起一次跨进程事务时,其内部其实是调用IPCThreadState对象的transact()。BpBinder的transact()代码如下:
status_t BpBinder::transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
if (mAlive)
{
status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
当然,进程中的一个BpBinder有可能被多个线程使用,所以发起传输的IPCThreadState对象可能并不是同一个对象,但这没有关系,因为这些IPCThreadState对象最终使用的是同一个ProcessState对象。
2.1.1 调用IPCThreadState的transact()
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
. . . . . .
// 把data数据整理进内部的mOut包中
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
. . . . . .
if ((flags & TF_ONE_WAY) == 0)
{
. . . . . .
if (reply)
{
err = waitForResponse(reply);
}
else
{
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
. . . . . .
}
else
{
err = waitForResponse(NULL, NULL);
}
return err;
}
IPCThreadState::transact()会先调用writeTransactionData()函数将data数据整理进内部的mOut包中,这个函数的代码如下:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code,
const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
. . . . . .
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
tr.data.ptr.offsets = data.ipcObjects();
. . . . . .
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
接着IPCThreadState::transact()会考虑本次发起的事务是否需要回复。“不需要等待回复的”事务,在其flag标志中会含有TF_ONE_WAY,表示一去不回头。而“需要等待回复的”,则需要在传递时提供记录回复信息的Parcel对象,一般发起transact()的用户会提供这个Parcel对象,如果不提供,transact()函数内部会临时构造一个假的Parcel对象。
上面代码中,实际完成跨进程事务的是waitForResponse()函数,这个函数的命名不太好,但我们也不必太在意,反正Android中写得不好的代码多了去了,又不只多这一处。waitForResponse()的代码截选如下:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
int32_t cmd;
int32_t err;
while (1)
{
// talkWithDriver()内部会完成跨进程事务
if ((err = talkWithDriver()) < NO_ERROR)
break;
// 事务的回复信息被记录在mIn中,所以需要进一步分析这个回复
. . . . . .
cmd = mIn.readInt32();
. . . . . .
switch (cmd)
{
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
case BR_DEAD_REPLY:
err = DEAD_OBJECT;
goto finish;
case BR_FAILED_REPLY:
err = FAILED_TRANSACTION;
goto finish;
. . . . . .
. . . . . .
default:
// 注意这个executeCommand()噢,它会处理BR_TRANSACTION的。
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
finish:
. . . . . .
return err;
}
2.1.2 talkWithDriver()
waitForResponse()中是通过调用talkWithDriver()来和binder驱动打交道的,说到底会调用ioctl()函数。因为ioctl()函数在传递BINDER_WRITE_READ语义时,既会使用“输入buffer”,也会使用“输出buffer”,所以IPCThreadState专门搞了两个Parcel类型的成员变量:mIn和mOut。总之就是,mOut中的内容发出去,发送后的回复写进mIn。
talkWithDriver()的代码截选如下:
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
. . . . . .
binder_write_read bwr;
. . . . . .
bwr.write_size = outAvail;
bwr.write_buffer = (long unsigned int)mOut.data();
. . . . . .
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (long unsigned int)mIn.data();
. . . . . .
. . . . . .
do
{
. . . . . .
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
. . . . . .
} while (err == -EINTR);
. . . . . .
. . . . . .
return err;
}
看到了吗?mIn和mOut的data会先整理进一个binder_write_read结构,然后再传给ioctl()函数。而最关键的一句,当然就是那句ioctl()了。此时使用的文件描述符就是前文我们说的ProcessState中记录的mDriverFD,说明是向binder驱动传递语义。BINDER_WRITE_READ表示我们希望读写一些数据。
至此,应用程序通过BpBinder向远端发起传输的过程就交代完了,数据传到了binder驱动,一切就看binder驱动怎么做了。至于驱动层又做了哪些动作,我们留在下一篇文章再介绍。
如需转载本文内容,请注明出处。
谢谢。
(本文也发布在我的oschina博客上)
红茶一杯话Binder(传输机制篇_中)
2013年12月01日 19:29:30 悠然红茶 阅读数:4189
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/codefly/article/details/17058689
红茶一杯话Binder
(传输机制篇_中)
侯 亮
1 谈谈底层IPC机制吧
在上一篇文章的最后,我们说到BpBinder将数据发到了Binder驱动。然而在驱动层,这部分数据又是如何传递到BBinder一侧的呢?这里面到底藏着什么猫腻?另外,上一篇文章虽然阐述了4棵红黑树,但是并未说明红黑树的节点到底是怎么产生的。现在,我们试着回答这些问题。
1.1 概述
在Binder驱动层,和ioctl()相对的动作是binder_ioctl()函数。在这个函数里,会先调用类似copy_from_user()这样的函数,来读取用户态的数据。然后,再调用binder_thread_write()和binder_thread_read()进行进一步的处理。我们先画一张调用关系图:
binder_ioctl()调用binder_thread_write()的代码是这样的:
if (bwr.write_size > 0)
{
ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer,
bwr.write_size, &bwr.write_consumed);
if (ret < 0)
{
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto err;
}
}
注意binder_thread_write()的前两个参数,一个是binder_proc指针,另一个是binder_thread指针,表示发起传输动作的进程和线程。binder_proc不必多说了,那个binder_thread是怎么回事?大家应该还记得前文提到的binder_proc里的4棵树吧,此处的binder_thread就是从threads树中查到的节点。
thread = binder_get_thread(proc);
binder_get_thread()的代码如下:
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
struct binder_thread *thread = NULL;
struct rb_node *parent = NULL;
struct rb_node **p = &proc->threads.rb_node;
// 尽量从threads树中查找和current线程匹配的binder_thread节点
while (*p)
{
parent = *p;
thread = rb_entry(parent, struct binder_thread, rb_node);
if (current->pid < thread->pid)
p = &(*p)->rb_left;
else if (current->pid > thread->pid)
p = &(*p)->rb_right;
else
break;
}
// “找不到就创建”一个binder_thread节点
if (*p == NULL)
{
thread = kzalloc(sizeof(*thread), GFP_KERNEL);
if (thread == NULL)
return NULL;
binder_stats_created(BINDER_STAT_THREAD);
thread->proc = proc;
thread->pid = current->pid;
init_waitqueue_head(&thread->wait);
INIT_LIST_HEAD(&thread->todo);
// 新binder_thread节点插入红黑树
rb_link_node(&thread->rb_node, parent, p);
rb_insert_color(&thread->rb_node, &proc->threads);
thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
thread->return_error = BR_OK;
thread->return_error2 = BR_OK;
}
return thread;
}
binder_get_thread()会尽量从threads树中查找和current线程匹配的binder_thread节点,如果找不到,就会创建一个新的节点并插入树中。这种“找不到就创建”的做法,在后文还会看到,我们暂时先不多说。
在调用binder_thread_write()之后,binder_ioctl()接着调用到binder_thread_read(),此时往往需要等待远端的回复,所以binder_thread_read()会让线程睡眠,把控制权让出来。在未来的某个时刻,远端处理完此处发去的语义,就会着手发回回复。当回复到达后,线程会从以前binder_thread_read()睡眠的地方醒来,并进一步解析收到的回复。
以上所说,都只是概要性的阐述,下面我们要深入一些细节了。
1.2 要进行跨进程调用,需要考虑什么?
我们可以先考虑一下,要设计跨进程调用机制,大概需要考虑什么东西呢?我们列一下:
1) 发起端:肯定包括发起端所从属的进程,以及实际执行传输动作的线程。当然,发起端的BpBinder更是重中之重。
2) 接收端:包括与发起端对应的BBinder,以及目标进程、线程。
3) 待传输的数据:其实就是前文IPCThreadState::writeTransactionData()代码中的binder_transaction_data了,需要注意的是,这份数据中除了包含简单数据,还可能包含其他binder对象噢,这些对象或许对应binder代理对象,或许对应binder实体对象,视具体情况而定。
4) 如果我们的IPC动作需要接收应答(reply),该如何保证应答能准确无误地传回来?
5) 如何让系统中的多个传输动作有条不紊地进行。
我们可以先画一张示意图:
然而这张图似乎还是串接不起整个传输过程,图中的“传输的数据”到底是怎么发到目标端的呢?要回答这个问题,我们还得继续研究Binder IPC机制的实现机理。
1.3 传输机制的大体运作
Binder IPC机制的大体思路是这样的,它将每次“传输并执行特定语义的”工作理解为一个小事务,既然所传输的数据是binder_transaction_data类型的,那么这种事务的类名可以相应地定为binder_transaction。系统中当然会有很多事务啦,那么发向同一个进程或线程的若干事务就必须串行化起来,因此binder驱动为进程节点(binder_proc)和线程节点(binder_thread)都设计了个todo队列。todo队列的职责就是“串行化地组织待处理的事务”。
下图绘制了一个进程节点,以及一个从属于该进程的线程节点,它们各带了两个待处理的事务(binder_transaction):
这样看来,传输动作的基本目标就很明确了,就是想办法把发起端的一个binder_transaction节点,插入到目标端进程或其合适子线程的todo队列去。
可是,该怎么找目标进程和目标线程呢?基本做法是先从发起端的BpBinder开始,找到与其对应的binder_node节点,这个在前文阐述binder_proc的4棵红黑树时已经说过了,这里不再赘述。总之拿到目标binder_node之后,我们就可以通过其proc域,拿到目标进程对应的binder_proc了。如果偷懒的话,我们直接把binder_transaction节点插到这个binder_proc的todo链表去,就算完成传输动作了。当然,binder驱动做了一些更精细的调整。
binder驱动希望能把binder_transaction节点尽量放到目标进程里的某个线程去,这样可以充分利用这个进程中的binder工作线程。比如一个binder线程目前正睡着,它在等待其他某个线程做完某个事情后才会醒来,而那个工作又偏偏需要在当前这个binder_transaction事务处理结束后才能完成,那么我们就可以让那个睡着的线程先去做当前的binder_transaction事务,这就达到充分利用线程的目的了。反正不管怎么说,如果binder驱动可以找到一个合适的线程,它就会把binder_transaction节点插到它的todo队列去。而如果找不到合适的线程,还可以把节点插入目标binder_proc的todo队列。
1.4 红黑树节点的产生过程
另一个要考虑的东西就是binder_proc里的那4棵树啦。前文在阐述binder_get_thread()时,已经看到过向threads树中添加节点的动作。那么其他3棵树的节点该如何添加呢?其实,秘密都在传输动作中。要知道,binder驱动在传输数据的时候,可不是仅仅简单地递送数据噢,它会分析被传输的数据,找出其中记录的binder对象,并生成相应的树节点。如果传输的是个binder实体对象,它不仅会在发起端对应的nodes树中添加一个binder_node节点,还会在目标端对应的refs_by_desc树、refs_by_node树中添加一个binder_ref节点,而且让binder_ref节点的node域指向binder_node节点。我们把前一篇文章的示意图加以修改,得到下图:
图中用红色线条来表示传输binder实体时在驱动层会添加的红黑树节点以及节点之间的关系。
可是,驱动层又是怎么知道所传的数据中有多少binder对象,以及这些对象的确切位置呢?答案很简单,是你告诉它的。大家还记得在向binder驱动传递数据之前,都是要把数据打成parcel包的吧。比如:
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service); // 把一个binder实体“打扁”并写入parcel
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
请大家注意上面data.writeStrongBinder()一句,它专门负责把一个binder实体“打扁”并写入parcel。其代码如下:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
status_t flatten_binder(const sp<ProcessState>& proc, const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
. . . . . .
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
. . . . . .
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
}
. . . . . .
return finish_flatten_binder(binder, obj, out);
}
看到了吗?“打扁”的意思就是把binder对象整理成flat_binder_object变量,如果打扁的是binder实体,那么flat_binder_object用cookie域记录binder实体的指针,即BBinder指针,而如果打扁的是binder代理,那么flat_binder_object用handle域记录的binder代理的句柄值。
然后flatten_binder()调用了一个关键的finish_flatten_binder()函数。这个函数内部会记录下刚刚被扁平化的flat_binder_object在parcel中的位置。说得更详细点儿就是,parcel对象内部会有一个buffer,记录着parcel中所有扁平化的数据,有些扁平数据是普通数据,而另一些扁平数据则记录着binder对象。所以parcel中会构造另一个mObjects数组,专门记录那些binder扁平数据所在的位置,示意图如下:
一旦到了向驱动层传递数据的时候,IPCThreadState::writeTransactionData()会先把Parcel数据整理成一个binder_transaction_data数据,这个在上一篇文章已有阐述,但是当时我们并没有太关心里面的关键句子,现在我们把关键句子再列一下:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code,
const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
. . . . . .
// 这部分是待传递数据
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
// 这部分是扁平化的binder对象在数据中的具体位置
tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
tr.data.ptr.offsets = data.ipcObjects();
. . . . . .
mOut.write(&tr, sizeof(tr));
. . . . . .
}
其中给tr.data.ptr.offsets赋值的那句,所做的就是记录下“待传数据”中所有binder对象的具体位置,示意图如下:
因此,当binder_transaction_data传递到binder驱动层后,驱动层可以准确地分析出数据中到底有多少binder对象,并分别进行处理,从而产生出合适的红黑树节点。此时,如果产生的红黑树节点是binder_node的话,binder_node的cookie域会被赋值成flat_binder_object所携带的cookie值,也就是用户态的BBinder地址值啦。这个新生成的binder_node节点被插入红黑树后,会一直严阵以待,以后当它成为另外某次传输动作的目标节点时,它的cookie域就派上用场了,此时cookie值会被反映到用户态,于是用户态就拿到了BBinder对象。
我们再具体看一下IPCThreadState::waitForResponse()函数,当它辗转从睡眠态跳出来时,会进一步解析刚收到的命令,此时会调用executeCommand(cmd)一句。
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
int32_t cmd;
int32_t err;
while (1)
{
if ((err = talkWithDriver()) < NO_ERROR) break;
. . . . . .
switch (cmd)
{
. . . . . .
. . . . . .
default:
err = executeCommand(cmd);
. . . . . .
break;
}
}
. . . . . .
return err;
}
executeCommand()的代码截选如下:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
. . . . . .
switch (cmd)
{
. . . . . .
. . . . . .
case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
. . . . . .
. . . . . .
if (tr.target.ptr)
{
sp<BBinder> b((BBinder*)tr.cookie);
const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
if (error < NO_ERROR) reply.setError(error);
}
. . . . . .
if ((tr.flags & TF_ONE_WAY) == 0)
{
LOG_ONEWAY("Sending reply to %d!", mCallingPid);
sendReply(reply, 0);
}
else
{
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
}
. . . . . .
}
break;
. . . . . .
. . . . . .
default:
printf("*** BAD COMMAND %d received from Binder driver\n", cmd);
result = UNKNOWN_ERROR;
break;
}
. . . . . .
return result;
}
请注意上面代码中的sp<BBinder> b((BBinder*)tr.cookie)一句,看到了吧,驱动层的binder_node节点的cookie值终于发挥它的作用了,我们拿到了一个合法的sp<BBinder>。
接下来,程序走到b->transact()一句。transact()函数的代码截选如下:
status_t BBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
. . . . . .
switch (code)
{
. . . . . .
default:
err = onTransact(code, data, reply, flags);
break;
}
. . . . . .
}
其中最关键的一句是调用onTransaction()。因为我们的binder实体在本质上都是继承于BBinder的,而且我们一般都会重载onTransact()函数,所以上面这句onTransact()实际上调用的是具体binder实体的onTransact()成员函数。
Ok,说了这么多,我们大概明白了binder驱动层的红黑树节点是怎么产生的,以及binder_node节点的cookie值是怎么派上用场的。限于篇幅,我们先在这里打住。下一篇文章我们再来阐述binder事务的传递和处理方面的细节。
如需转载本文内容,请注明出处。
谢谢。
(本文也发布在我的oschina博客上)
红茶一杯话Binder(传输机制篇_下)
2013年12月01日 19:31:13 悠然红茶 阅读数:2751
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/codefly/article/details/17058701
红茶一杯话Binder
(传输机制篇_下)
侯 亮
1 事务的传递和处理
从IPCThreadState的角度看,它的transact()函数是通过向binder驱动发出BC_TRANSACTION语义,来表达其传输意图的,而后如有必要,它会等待从binder发回的回馈,这些回馈语义常常以“BR_”开头。另一方面,当IPCThreadState作为处理命令的一方需要向发起方反馈信息的话,它会调用sendReply()函数,向binder驱动发出BC_REPLY语义。当BC_语义经由binder驱动递送到目标端时,会被binder驱动自动修改为相应的BR_语义,这个我们在后文再细说。
当语义传递到binder驱动后,会走到binder_ioctl()函数,该函数又会调用到binder_thread_write()和binder_thread_read():
在上一篇文章中,我们大体阐述了一下binder_thread_write()和binder_thread_read()的唤醒与被唤醒关系,而且还顺带在“传输机制的大体运作”小节中提到了todo队列的概念。本文将在此基础上再补充一些知识。需要强调的是,我们必须重视binder_thread_write()和binder_thread_read(),因为事务的传递和处理就位于这两个函数中,它们的调用示意图如下:
binder_thread_write()的代码截选如下。因为本文主要关心传输方面的问题,所以只摘取了case BC_TRANSACTION、case BC_REPLY部分的代码:
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
void __user *buffer, int size, signed long *consumed)
{
. . . . . .
while (ptr < end && thread->return_error == BR_OK)
{
. . . . . .
switch (cmd)
{
. . . . . .
. . . . . .
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
break;
}
. . . . . .
. . . . . .
}
*consumed = ptr - buffer;
}
return 0;
}
这部分代码比较简单,主要是从用户态拷贝来binder_transaction_data数据,并传给binder_transaction()函数进行实际的传输。而binder_transaction()可是需要我们费一点儿力气去分析的,大家深吸一口气,准备开始。
1.1 BC_TRANSACTION事务(携带TF_ONE_WAY标记)的处理
首先我们要认识到,同样是BC_TRANSACTION事务,带不带TF_ONE_WAY标记还是有所不同的。我们先看相对简单的携带TF_ONE_WAY标记的BC_TRANSACTION事务,这种事务是不需要回复的。
1.1.1 binder_transaction()
此时,binder_transaction()所做的工作大概有:
找目标binder_node;
找目标binder_proc;
分析并插入红黑树节点;(我们在上一篇文章中已在说过这部分的机理了,只是当时没有贴出相应的代码)
创建binder_transaction节点,并将其插入目标进程的todo列表;
尝试唤醒目标进程。
binder_transaction()代码截选如下:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
. . . . . .
struct binder_proc *target_proc;
struct binder_thread *target_thread = NULL;
struct binder_node *target_node = NULL;
struct list_head *target_list;
wait_queue_head_t *target_wait;
. . . . . .
. . . . . .
{
// 先从tr->target.handle句柄值,找到对应的binder_ref节点,及binder_node节点
if (tr->target.handle)
{
struct binder_ref *ref;
ref = binder_get_ref(proc, tr->target.handle);
. . . . . .
target_node = ref->node;
}
else
{
// 如果句柄值为0,则获取特殊的binder_context_mgr_node节点,
// 即Service Manager Service对应的节点
target_node = binder_context_mgr_node;
. . . . . .
}
// 得到目标进程的binder_proc
target_proc = target_node->proc;
. . . . . .
}
// 对于带TF_ONE_WAY标记的BC_TRANSACTION来说,此时target_thread为NULL,
// 所以准备向binder_proc的todo中加节点
. . . . . .
target_list = &target_proc->todo;
target_wait = &target_proc->wait;
. . . . . .
// 创建新的binder_transaction节点。
t = kzalloc(sizeof(*t), GFP_KERNEL);
. . . . . .
t->from = NULL;
t->sender_euid = proc->tsk->cred->euid;
t->to_proc = target_proc;
t->to_thread = target_thread;
// 将binder_transaction_data的code、flags域记入binder_transaction节点。
t->code = tr->code;
t->flags = tr->flags;
t->priority = task_nice(current);
t->buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offsets_size,
!reply && (t->flags & TF_ONE_WAY));
. . . . . .
t->buffer->transaction = t;
t->buffer->target_node = target_node;
. . . . . .
// 下面的代码分析所传数据中的所有binder对象,如果是binder实体的话,要在红黑树中添加相应的节点。
// 首先,从用户态获取所传输的数据,以及数据里的binder对象的偏移信息
offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size))
. . . . . .
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size))
. . . . . .
. . . . . .
// 遍历每个flat_binder_object信息,创建必要的红黑树节点 ....
for (; offp < off_end; offp++)
{
struct flat_binder_object *fp;
. . . . . .
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
switch (fp->type)
{
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER:
{
// 如果是binder实体
struct binder_ref *ref;
struct binder_node *node = binder_get_node(proc, fp->binder);
if (node == NULL)
{
// 又是“没有则创建”的做法,创建新的binder_node节点
node = binder_new_node(proc, fp->binder, fp->cookie);
. . . . . .
}
. . . . . .
// 必要时,会在目标进程的binder_proc中创建对应的binder_ref红黑树节点
ref = binder_get_ref_for_node(target_proc, node);
. . . . . .
// 修改所传数据中的flat_binder_object信息,因为远端的binder实体到了目标
// 端,就变为binder代理了,所以要记录下binder句柄了。
fp->handle = ref->desc;
. . . . . .
} break;
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct binder_ref *ref = binder_get_ref(proc, fp->handle);
// 有时候需要对flat_binder_object做必要的修改,比如将BINDER_TYPE_HANDLE
// 改为BINDER_TYPE_BINDER
. . . . . .
} break;
case BINDER_TYPE_FD: {
. . . . . .
} break;
. . . . . .
}
. . . . . .
{
. . . . . .
if (target_node->has_async_transaction)
{
target_list = &target_node->async_todo;
target_wait = NULL;
}
else
target_node->has_async_transaction = 1;
}
t->work.type = BINDER_WORK_TRANSACTION;
// 终于把binder_transaction节点插入target_list(即目标todo队列)了。
list_add_tail(&t->work.entry, target_list);
. . . . . .
list_add_tail(&tcomplete->entry, &thread->todo);
// 传输动作完毕,现在可以唤醒系统中其他相关线程了,wake up!
if (target_wait)
wake_up_interruptible(target_wait);
return;
. . . . . .
. . . . . .
}
虽然已经是截选,代码却仍然显得冗长。这也没办法,Android frameworks里的很多代码都是这个样子,又臭又长,大家凑合着看吧。我常常觉得google的工程师多少应该因这样的代码而感到脸红,不过,哎,这有点儿说远了。
我们画一张示意图,如下:
上图体现了从binder_ref找到“目标binder_node”以及“目标binder_proc”的意思,其中“A端”表示发起方,“B端”表示目标方。可以看到,携带TF_ONE_WAY标记的事务,其实是比较简单的,驱动甚至不必费心去找目标线程,只需要创建一个binder_transaction节点,并插入目标binder_proc的todo链表即可。
另外,在将binder_transaction节点插入目标todo链表之前,binder_transaction()函数用一个for循环分析了需要传输的数据,并为其中包含的binder对象生成了相应的红黑树节点。
再后来,binder_transaction节点成功插入目标todo链表,此时说明目标进程有事情可做了,于是binder_transaction()函数会调用wake_up_interruptible()唤醒目标进程。
1.1.2 binder_thread_read()
当目标进程被唤醒时,会接着执行自己的binder_thread_read(),尝试解析并执行那些刚收来的工作。无论收来的工作来自于“binder_proc的todo链表”,还是来自于某“binder_thread的todo链表”,现在要开始从todo链表中摘节点了,而且在完成工作之后,会彻底删除binder_transaction节点。
binder_thread_read()的代码截选如下:
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
void __user *buffer, int size,
signed long *consumed, int non_block)
{
. . . . . .
retry:
// 优先考虑thread节点的todo链表中有没有工作需要完成
wait_for_proc_work = thread->transaction_stack == NULL
&& list_empty(&thread->todo);
. . . . . .
. . . . . .
if (wait_for_proc_work)
{
. . . . . .
ret = wait_event_interruptible_exclusive(proc->wait,
binder_has_proc_work(proc, thread));
}
else
{
. . . . . .
ret = wait_event_interruptible(thread->wait, binder_has_thread_work(thread));
}
. . . . . .
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
// 如果是非阻塞的情况,ret值非0表示出了问题,所以return。
// 如果是阻塞(non_block)情况,ret值非0表示等到的结果出了问题,所以也return。
if (ret)
return ret;
while (1)
{
. . . . . .
// 读取binder_thread或binder_proc中todo列表的第一个节点
if (!list_empty(&thread->todo))
w = list_first_entry(&thread->todo, struct binder_work, entry);
else if (!list_empty(&proc->todo) && wait_for_proc_work)
w = list_first_entry(&proc->todo, struct binder_work, entry);
. . . . . .
switch (w->type)
{
case BINDER_WORK_TRANSACTION: {
t = container_of(w, struct binder_transaction, work);
} break;
case BINDER_WORK_TRANSACTION_COMPLETE: {
cmd = BR_TRANSACTION_COMPLETE;
. . . . . .
// 将binder_transaction节点从todo队列摘下来
list_del(&w->entry);
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
. . . . . .
. . . . . .
}
if (!t)
continue;
. . . . . .
if (t->buffer->target_node)
{
struct binder_node *target_node = t->buffer->target_node;
tr.target.ptr = target_node->ptr;
// 用目标binder_node中记录的cookie值给binder_transaction_data的cookie域赋值,
// 这个值就是目标binder实体的地址
tr.cookie = target_node->cookie;
t->saved_priority = task_nice(current);
. . . . . .
cmd = BR_TRANSACTION;
}
. . . . . .
tr.code = t->code;
tr.flags = t->flags;
tr.sender_euid = t->sender_euid;
. . . . . .
tr.data_size = t->buffer->data_size;
tr.offsets_size = t->buffer->offsets_size;
// binder_transaction_data中的data只是记录了binder缓冲区中的地址信息,并再做copy动作
tr.data.ptr.buffer = (void *)t->buffer->data +
proc->user_buffer_offset;
tr.data.ptr.offsets = tr.data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));
// 将cmd命令写入用户态,此时应该是BR_TRANSACTION
if (put_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
// 当然,binder_transaction_data本身也是要copy到用户态的
if (copy_to_user(ptr, &tr, sizeof(tr)))
return -EFAULT;
. . . . . .
. . . . . .
// 将binder_transaction节点从todo队列摘下来
list_del(&t->work.entry);
t->buffer->allow_user_free = 1;
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
} else {
t->buffer->transaction = NULL;
// TF_ONE_WAY情况,此时会删除binder_transaction节点
kfree(t);
binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
break;
}
. . . . . .
. . . . . .
return 0;
}
简单说来就是,如果没有工作需要做,binder_thread_read()函数就进入睡眠或返回,否则binder_thread_read()函数会从todo队列摘下了一个节点,并把节点里的数据整理成一个binder_transaction_data结构,然后通过copy_to_user()把该结构传到用户态。因为这次传输带有TF_ONE_WAY标记,所以copy完后,只是简单地调用kfree(t)把这个binder_transaction节点干掉了。
binder_thread_read()尝试调用wait_event_interruptible()或wait_event_interruptible_exclusive()来等待待处理的工作。wait_event_interruptible()是个宏定义,和wait_event()类似,不同之处在于前者不但会判断“苏醒条件”,还会判断当前进程是否带有挂起的系统信号,当“苏醒条件”满足时(比如binder_has_thread_work(thread)返回非0值),或者有挂起的系统信号时,表示进程有工作要做了,此时wait_event_interruptible()将跳出内部的for循环。如果的确不满足跳出条件的话,wait_event_interruptible()会进入挂起状态。
请注意给binder_transaction_data的cookie赋值的那句:
tr.cookie = target_node->cookie;
binder_node节点里储存的cookie值终于发挥作用了,这个值反馈到用户态就是目标binder实体的BBinder指针了。
另外,在调用copy_to_user()之前,binder_thread_read()先通过put_user()向上层拷贝了一个命令码,在当前的情况下,这个命令码是BR_TRANSACTION。想当初,内核态刚刚从用户态拷贝来的命令码是BC_TRANSACTION,现在要发给目标端了,就变成了BR_TRANSACTION。
1.2 BC_TRANSACTION事务(不带TF_ONE_WAY标记)
1.2.1 再说binder_transaction()
然而,对于不带TF_ONE_WAY标记的BC_TRANSACTION事务来说,情况就没那么简单了。因为binder驱动不仅要找到目标进程,而且还必须努力找到一个明确的目标线程。正如我们前文所说,binder驱动希望可以充分复用目标进程中的binder工作线程。
那么,哪些线程(节点)是可以被复用的呢?我们再整理一下binder_transaction()代码,本次主要截选不带TF_ONE_WAY标记的代码部分:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
. . . . . .
. . . . . .
if (tr->target.handle)
{
. . . . . .
target_node = ref->node;
}
else
{
target_node = binder_context_mgr_node;
. . . . . .
}
. . . . . .
// 先确定target_proc
target_proc = target_node->proc;
. . . . . .
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack)
{
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
. . . . . .
// 找到from_parent这条链表中,最后一个可以和target_proc匹配
// 的binder_transaction节点,
// 这个节点的from就是我们要找的“目标线程”
while (tmp)
{
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from;
tmp = tmp->from_parent;
}
}
. . . . . .
// 要确定target_list和target_wait了,如果能找到“目标线程”,它们就来自目标线程,否则
// 就只能来自目标进程了。
if (target_thread)
{
e->to_thread = target_thread->pid;
target_list = &target_thread->todo;
target_wait = &target_thread->wait;
}
else {
target_list = &target_proc->todo;
target_wait = &target_proc->wait;
}
. . . . . .
// 创建新的binder_transaction节点。
t = kzalloc(sizeof(*t), GFP_KERNEL);
. . . . . .
. . . . . .
t->from = thread; // 新节点的from域记录事务的发起线程
t->sender_euid = proc->tsk->cred->euid;
t->to_proc = target_proc;
t->to_thread = target_thread; // 新节点的to_thread域记录事务的目标线程
t->code = tr->code;
t->flags = tr->flags;
t->priority = task_nice(current);
// 从binder buffer中申请一个区域,用于存储待传输的数据
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size,
!reply && (t->flags & TF_ONE_WAY));
. . . . . .
t->buffer->transaction = t;
t->buffer->target_node = target_node;
. . . . . .
// 从用户态拷贝来待传输的数据
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
. . . . . .
}
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
. . . . . .
}
// 遍历每个flat_binder_object信息,创建必要的红黑树节点 ....
for (; offp < off_end; offp++)
{
struct flat_binder_object *fp;
. . . . . .
. . . . . .
}
. . . . . .
t->need_reply = 1;
// 新binder_transaction节点成为发起端transaction_stack栈的新栈顶
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
. . . . . .
t->work.type = BINDER_WORK_TRANSACTION;
// 终于把binder_transaction节点插入target_list(即目标todo队列)了。
list_add_tail(&t->work.entry, target_list);
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
list_add_tail(&tcomplete->entry, &thread->todo);
if (target_wait)
wake_up_interruptible(target_wait);
return;
. . . . . .
. . . . . .
}
其中,获取目标binder_proc的部分和前一小节没什么不同,但是因为本次传输不再携带TF_ONE_WAY标记了,所以函数中会尽力去查一个合适的“目标binder_thread”,此时会用到binder_thread里的“事务栈”(transaction_stack)概念。
那么,怎么找“目标binder_thread”呢?首先,我们很清楚“发起端”的binder_thread节点是哪个,而且也可以找到“目标端”的binder_proc,这就具有了搜索的基础。在binder_thread节点的transaction_stack域里,记录了和它相关的若干binder_transaction,这些binder_transaction事务在逻辑上具有类似堆栈的属性,也就是说“最后入栈的事务”会最先处理。
从逻辑上说,线程节点的transaction_stack域体现了两个方面的意义:
这个线程需要别的线程帮它做某项工作;
别的线程需要这个线程做某项工作;
因此,一个工作节点(即binder_transaction节点)往往会插入两个transaction_stack堆栈,示意图如下:
当binder_transaction节点插入“发起端”的transaction_stack栈时,它是用from_parent域来连接堆栈中其他节点的。而当该节点插入“目标端”的transaction_stack栈时,却是用to_parent域来连接其他节点的。关于插入目标端堆栈的动作,位于binder_thread_read()中,我们在后文会看到。
这么看来,from_parent域其实将一系列逻辑上有先后关系的若干binder_transaction节点串接起来了,而且这些binder_transaction节点可能是由不同进程、线程发起的。那么我们只需遍历一下这个堆栈里的事务,看哪个事务的“from线程所属的进程”和“目标端的binder_proc”一致,就说明这个from线程正是我们要找的目标线程。为什么这么说呢?这是因为我们的新事务将成为binder_transaction的新栈顶,而这个堆栈里其他事务一定是在新栈顶事务处理完后才会处理的,因此堆栈里某个事务的发起端线程可以理解为正处于等待状态,如果这个发起端线程所从属的进程恰恰又是我们新事务的目标进程的话,那就算合拍了,这样就找到“目标binder_thread”了。我把相关的代码再抄一遍:
struct binder_transaction *tmp;
tmp = thread->transaction_stack;
while (tmp) {
if (tmp->from && tmp->from->proc == target_proc)
target_thread = tmp->from;
tmp = tmp->from_parent;
}
代码用while循环来遍历thread->transaction_stack,发现tmp->from->proc == target_proc,就算找到了。
如果能够找到“目标binder_thread”的话,binder_transaction事务就会插到它的todo队列去。不过有时候找不到“目标binder_thread”,那么就只好退而求其次,插入binder_proc的todo队列了。再接下来的动作没有什么新花样,大体上会尝试唤醒目标进程。
1.2.2 再说binder_thread_read()
目标进程在唤醒后,会接着当初阻塞的地方继续执行,这个已在前一小节阐述过,我们不再赘述。值得一提的是binder_thread_read()中的以下句子:
// 将binder_transaction节点从todo队列摘下来
list_del(&t->work.entry);
t->buffer->allow_user_free = 1;
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
} else {
t->buffer->transaction = NULL;
// TF_ONE_WAY情况,此时会删除binder_transaction节点
kfree(t);
binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
因为没有携带TF_ONE_WAY标记,所以此处会有一个入栈操作,binder_transaction节点插入了目标线程的transaction_stack堆栈,而且是以to_parent域来连接堆栈中的其他节点的。
总体说来,binder_thread_read()的动作大体也就是:
1) 利用wait_event_xxxx()让自己挂起,等待下一次被唤醒;
2) 唤醒后找到合适的待处理的工作节点,即binder_transaction节点;
3) 把binder_transaction中的信息整理到一个binder_transaction_data中;
4) 整理一个cmd整数值,具体数值或者为BR_TRANSACTION,或者为BR_REPLY;
5) 将cmd数值和binder_transaction_data拷贝到用户态;
6) 如有必要,将得到的binder_transaction节点插入目标端线程的transaction_stack堆栈中。
1.2.3 目标端如何处理传来的事务
binder_thread_read()本身只负责读取数据,它并不解析得到的语义。具体解析语义的动作并不在内核态,而是在用户态。
我们再回到用户态的IPCThreadState::waitForResponse()函数。
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
while (1)
{
// talkWithDriver()内部会完成跨进程事务
if ((err = talkWithDriver()) < NO_ERROR)
break;
// 事务的回复信息被记录在mIn中,所以需要进一步分析这个回复
. . . . . .
cmd = mIn.readInt32();
. . . . . .
err = executeCommand(cmd);
. . . . . .
}
. . . . . .
}
当发起端调用binder_thread_write()唤醒目标端的进程时,目标进程会从其上次调用binder_thread_read()的地方苏醒过来。辗转跳出上面的talkWithDriver()函数,并走到executeCommand()一句。
因为binder_thread_read()中已经把BR_命令整理好了,所以executeCommand()当然会走到case BR_TRANSACTION分支:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
. . . . . .
. . . . . .
case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
. . . . . .
mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
mOrigCallingUid = tr.sender_euid;
. . . . . .
Parcel reply;
. . . . . .
if (tr.target.ptr) {
sp<BBinder> b((BBinder*)tr.cookie);
const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
if (error < NO_ERROR) reply.setError(error);
} else {
const status_t error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
if (error < NO_ERROR) reply.setError(error);
}
. . . . . .
if ((tr.flags & TF_ONE_WAY) == 0)
{
LOG_ONEWAY("Sending reply to %d!", mCallingPid);
sendReply(reply, 0);
}
. . . . . .
. . . . . .
}
break;
. . . . . .
. . . . . .
return result;
}
最关键的一句当然是b->transact()啦,此时b的值来自于binder_transaction_data的cookie域,本质上等于驱动层所记录的binder_node节点的cookie域值,这个值在用户态就是BBinder指针。
在调用完transact()动作后,executeCommand()会判断tr.flags有没有携带TF_ONE_WAY标记,如果没有携带,说明这次传输是需要回复的,于是调用sendReply()进行回复。
2 小结
至此,《红茶一杯话Binder(传输机制篇)》的上、中、下三篇文章总算写完了。限于个人水平,文中难免有很多细节交代不清,还请各位看官海涵。作为我个人而言,只是尽力尝试把一些底层机制说得清楚一点儿,奈何Android内部的代码细节繁杂,逻辑交叠,往往搞得人头昏脑涨,所以我也只是针对其中很小的一部分进行阐述而已。因为本人目前的主要兴趣已经不在binder了,所以这篇文章耽误了好久才写完,呵呵,见谅见谅。
如需转载本文内容,请注明出处。
谢谢。
(本文也发布在我的oschina博客上)
红茶一杯话Binder(ServiceManager篇)
2013年12月01日 19:25:47 悠然红茶 阅读数:3456
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/codefly/article/details/17058643
红茶一杯话Binder
(ServiceManager篇)
侯亮
1.先说一个大概
Android平台的一个基本设计理念是构造一个相对平坦的功能集合,这些功能可能会身处于不同的进程中,然而却可以高效地整合到一起,实现不同的用户需求。这就必须打破过去各个孤立App所形成的天然藩篱。为此,Android提供了Binder机制。
在Android中,系统提供的服务被包装成一个个系统级service,这些service往往会在设备启动之时添加进Android系统。在上一篇文档中,我们已经了解了BpBinder和BBinder的概念,而service实体的底层说到底就是一个BBinder实体。
我们知道,如果某个程序希望享受系统提供的服务,它就必须调用系统提供的外部接口,向系统发出相应的请求。因此,Android中的程序必须先拿到和某个系统service对应的代理接口,然后才能通过这个接口,享受系统提供的服务。说白了就是我们得先拿到一个和目标service对应的合法BpBinder。
然而,该怎么获取和系统service对应的代理接口呢?Android是这样设计的:先启动一个特殊的系统服务,叫作Service Manager Service(简称SMS),它的基本任务就是管理其他系统服务。其他系统服务在系统启动之时,就会向SMS注册自己,于是SMS先记录下与那个service对应的名字和句柄值。有了句柄值就可以用来创建合法的BpBinder了。只不过在实际的代码中,SMS并没有用句柄值创建出BpBinder,这个其实没什么,反正指代目标service实体的目的已经达到了。后续当某程序需要享受某系统服务时,它必须先以“特定手法”获取SMS代理接口,并经由这个接口查询出目标service对应的合法Binder句柄,然后再创建出合法的BpBinder对象。
在此,我们有必要交代一下“Binder句柄”的作用。句柄说穿了是个简单的整数值,用来告诉Binder驱动我们想找的目标Binder实体是哪个。但是请注意,句柄只对发起端进程和Binder驱动有意义,A进程的句柄直接拿到B进程,是没什么意义的。也就是说,不同进程中指代相同Binder实体的句柄值可能是不同的。示意图如下:
SMS记录了所有系统service所对应的Binder句柄,它的核心功能就是维护好这些句柄值。后续,当用户进程需要获取某个系统service的代理时,SMS就会在内部按service名查找到合适的句柄值,并“逻辑上”传递给用户进程,于是用户进程会得到一个新的合法句柄值,这个新句柄值可能在数值上和SMS所记录的句柄值不同,然而,它们指代的却是同一个Service实体。句柄的合法性是由Binder驱动保证的,这一点我们不必担心。
前文我们提到要以“特定手法”获取SMS代理接口,这是什么意思呢?在IServiceManager.cpp文件中,我们可以看到一个defaultServiceManager()函数,代码如下:
【frameworks/native/libs/binder/IServiceManager.cpp】
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL)
return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
if (gDefaultServiceManager == NULL)
{
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}
这个函数里调用interface_cast的地方是用一句getContextObject(NULL)来获取BpBinder对象的。我们先不深入讲解这个函数,只需要知道这一句里的getContextObject(NULL)实际上相当于new BpBinder(0)就可以了。噢,看来要得到BpBinder对象并不复杂嘛,直接new就好了。然而,我之所以使用“特定手法”一词,是因为这种直接new BpBinder(xxx)的做法,只能用于获取SMS的代理接口。大家可不要想当然地随便用这种方法去创建其他服务的代理接口噢。
在Android里,对于Service Manager Service这个特殊的服务而言,其对应的代理端的句柄值已经预先定死为0了,所以我们直接new BpBinder(0)拿到的就是个合法的BpBinder,其对端为“Service Manager Service实体”(至少目前可以先这么理解)。那么对于其他“服务实体”对应的代理,句柄值又是多少呢?使用方又该如何得到这个句柄值呢?我们总不能随便蒙一个句柄值吧。正如我们前文所述,要得到某个服务对应的BpBinder,主要得借助Service Manager Service系统服务,查询出一个合法的Binder句柄,并进而创建出合法的BpBinder。
这里有必要澄清一下,利用SMS获取合法BpBinder的方法,并不是Android中得到BpBinder的唯一方法。另一种方法是,“起始端”经由一个已有的合法BpBinder,将某个binder实体或代理对象作为跨进程调用的参数,“传递”给“目标端”,这样目标端也可以拿到一个合法的BpBinder。
我们把以上介绍的知识绘制成示意图,如下:
请顺着图中标出的1)、2)、3)、4)序号,读一下图中的说明。
在跨进程通信方面,所谓的“传递”一般指的都是逻辑上的传递,所以应该打上引号。事实上,binder实体对象是不可能完全打包并传递到另一个进程的,而且也没有必要这么做。目前我们只需理解,binder架构会保证“传递”动作的目标端可以拿到一个和binder实体对象对应的代理对象即可。详细情况,要到分析binder驱动的部分再阐述。
既然SMS承担着让客户端获取合法BpBinder的责任,那么它的重要性就不言而喻了。现在我们就来详细看看具体如何使用它。
2. 具体使用Service Manager Service
2.1 必须先得到IServiceManager代理接口
要获取某系统service的代理接口,必须先得到IServiceManager代理接口。还记得前文C++代码中获取IServiceManager代理接口的句子吗?
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
我们在前一篇文档中已经介绍过interface_cast了,现在再贴一下这个函数的代码:
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
也就是说,其实调用的是IServiceManager::asInterface(obj),而这个obj参数就是new BpBinder(0)得到的对象。当然,这些都是C++层次的概念,Java层次把这些概念都包装起来了。
在Java层次,是这样获取IServiceManager接口的:
【frameworks/base/core/java/android/os/ServiceManager.java】
private static IServiceManager getIServiceManager()
{
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
噢,又出现了一个asInterface,看来Java层次和C++层的代码在本质上是一致的。
ServiceManagerNative的asInterface()代码如下:
static public IServiceManager asInterface(IBinder obj)
{
if (obj == null)
{
return null;
}
IServiceManager in = (IServiceManager)obj.queryLocalInterface(descriptor);
if (in != null)
{
return in;
}
return new ServiceManagerProxy(obj);
}
目前我们只需了解,用户进程在调用到getIServiceManager()时,最终会走到return new ServiceManagerProxy(obj)即可。
哎呀,又出现了两个名字:ServiceManagerProxy和ServiceManagerNative。简单地说:
1) ServiceManagerProxy就是IServiceManager代理接口;
2) ServiceManagerNative显得很鸡肋;
它们的继承关系图如下:
下面我们分别来说明。
2.1.1 ServiceManagerProxy就是IServiceManager代理接口
用户要访问Service Manager Service服务,必须先拿到IServiceManager代理接口,而ServiceManagerProxy就是代理接口的实现。这个从前文代码中的new ServiceManagerProxy(obj)一句就可以看出来了。ServiceManagerProxy的构造函数内部会把obj参数记录到mRemote域中:
public ServiceManagerProxy(IBinder remote)
{
mRemote = remote;
}
mRemote的定义是:
private IBinder mRemote;
其实说白了,mRemote的核心包装的就是句柄为0的BpBinder对象,这个应该很容易理解。
日后,当我们通过IServiceManager代理接口访问SMS时,其实调用的就是ServiceManagerProxy的成员函数。比如getService()、checkService()等等。
2.1.2 ServiceManagerNative显得很鸡肋
另一方面,ServiceManagerNative就显得很鸡肋了。
ServiceManagerNative是个抽象类:
public abstract class ServiceManagerNative extends Binder implements IServiceManager
它继承了Binder,实现了IServiceManager,然而却是个虚有其表的class。它唯一有用的大概就是前文列出的那个静态成员函数asInterface()了,而其他成员函数(像onTransact())就基本上没什么用。
如果我们花点儿时间在工程里搜索一下ServiceManagerNative,会发现根本找不到它的子类。一个没有子类的抽象类不就是虚有其表吗。到头来我们发现,关于ServiceManagerNative的用法只有一种,就是:
ServiceManagerNative.asInterface(BinderInternal.getContextObject());
用一下它的asInterface()静态函数而已。
为什么会这样呢?我想这可能是某种历史的遗迹吧。同理,我们看它的onTransact()函数,也会发现里面调用的类似addService()那样的函数,也都是找不到对应的实现体的。当然,因为ServiceManagerNative本身是个抽象类,所以即便它没有实现IServiceManager的addService()等成员函数,也是可以编译通过的。
这里透出一个信息,既然Java层的ServiceManagerNative没什么大用处,是不是表示C++层也缺少对应的SMS服务实体呢?在后文我们可以看到,的确是这样的,Service Manager Service在C++层被实现成一个独立的进程,而不是常见的Binder实体。
2.2 通过addService()来注册系统服务
我们还是回过头接着说对于IServiceManager接口的使用吧。最重要的当然是注册系统服务。比如在System Server进程中,是这样注册PowerManagerService系统服务的:
public void run()
{
. . . . . .
power = new PowerManagerService();
ServiceManager.addService(Context.POWER_SERVICE, power);
. . . . . .
addService()的第一个参数就是所注册service的名字,比如上面的POWER_SERVICE对应的字符串就是"power"。第二个参数传入的是service Binder实体。Service实体在Service Manager Service一侧会被记录成相应的句柄值,如图:
有关addService()内部机理,我们会在后文讲述,这里先不细说。
2.3 通过getService()来获取某系统服务的代理接口
除了注册系统服务,Service Manager Service的另一个主要工作就是让用户进程可以获取系统service的代理接口,所以其getService()函数就非常重要了。
其实,ServiceManagerProxy中的getService()等成员函数,仅仅是把语义整理进parcel,并通过mRemote将parcel传递到目标端而已。所以我们只看看getService()就行了,其他的函数都大同小异。
public IBinder getService(String name) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
传递的语义就是GET_SERVICE_TRANSACTION,非常简单。mRemote从本质上看就是句柄为0的BpBinder,所以binder驱动很清楚这些语义将去向何方。
关于Service Manager Service的使用,我们就先说这么多。下面我们要开始探索SMS内部的运作机制了。
3.Service Manager Service的运作机制
3.1 Service Manager Service服务的启动
既然前文说ServiceManagerNative虚有其表,而且没有子类,那么Service Manager Service服务的真正实现代码位于何处呢?答案就在init.rc脚本里。关于init.rc的详细情况,可参考其他阐述Android启动流程的文档,此处不再赘述。
init.rc脚本中,在描述zygote service之前就已经写明service manager service的信息了:
service servicemanager /system/bin/servicemanager
user system
critical
onrestart restart zygote
onrestart restart media
可以看到,servicemanager是一种native service。这种native service都是需要用C/C++编写的。Service Manager Service对应的实现代码位于frameworks/base/cmds/servicemanager/Service_manager.c文件中。这个文件中有每个C程序员都熟悉的main()函数,其编译出的可执行程序就是/system/bin/servicemanager。
另外,还有一个干扰我们视线的cpp文件,名为IServiceManager.cpp,位于frameworks/base/libs/binder/目录中,这个文件里的BnServiceManager应该和前文的ServiceManagerNative类似,它的onTransact()也不起什么作用。
3.2 Service Manager Service是如何管理service句柄的?
在C语言层次,简单地说并不存在一个单独的ServiceManager结构。整个service管理机制都被放在一个独立的进程里了,该进程对应的实现文件就是Service_manager.c。
进程里有一个全局性的svclist变量:
struct svcinfo *svclist = 0;
它记录着所有添加进系统的“service代理”信息,这些信息被组织成一条单向链表,我们不妨称这条链表为“服务向量表”。示意图如下:
链表节点类型为svcinfo。
因为svcinfo里要记录下service的名字字符串,所以它需要的buffer长度是(len + 1) * sizeof(uint16_t),记得要留一个’\0’的结束位置。另外,svcinfo的ptr域,实际上记录的就是系统service对应的binder句柄值。
日后,当应用调用getService()获取系统服务的代理接口时,SMS就会搜索这张“服务向量表”,查找是否有节点能和用户传来的服务名匹配,如果能查到,就返回对应的sp<IBinder>,这个接口在远端对应的实体就是“目标Service实体”。如此一来,系统中就会出现如下关系:
3.3 Service Manager Service的主程序(C++层)
要更加深入地了解Service Manager进程的运作,我们必须研究其主程序。参考代码是frameworks\base\cmds\servicemanager\Service_manager.c。
Service_manager.c中的main()函数如下:
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
if (binder_become_context_manager(bs))
{
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
main()函数一开始就打开了binder驱动,然后调用binder_become_context_manager()让自己成为整个系统中唯一的上下文管理器,其实也就是service管理器啦。接着main()函数调用binder_loop()进入无限循环,不断监听并解析binder驱动发来的命令。
binder_loop()中解析驱动命令的函数是binder_parse(),其最后一个参数func来自于binder_loop()的最后一个参数——svcmgr_handler函数指针。这个svcmgr_handler()应该算是Service Manager Service的核心回调函数了。
为了方便查看,我把main()函数以及其间接调用的ioctl()语句绘制成如下的调用关系图:
下面我们逐个分析其中调用的函数。
3.3.1 binder_open()
Service Manager Service必须先调用binder_open()来打开binder驱动,驱动文件为“/dev/binder”。binder_open()的代码截选如下:
struct binder_state * binder_open(unsigned mapsize)
{
struct binder_state *bs;
bs = malloc(sizeof(*bs));
. . . . . .
bs->fd = open("/dev/binder", O_RDWR);
. . . . . .
bs->mapsize = mapsize;
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
. . . . . .
return bs;
. . . . . .
}
binder_open()的参数mapsize表示它希望把binder驱动文件的多少字节映射到本地空间。可以看到,Service Manager Service和普通进程所映射的binder大小并不相同。它把binder驱动文件的128K字节映射到内存空间,而普通进程则会映射binder文件里的BINDER_VM_SIZE(即1M减去8K)字节。
具体的映射动作由mmap()一句完成,该函数将binder驱动文件的一部分映射到进程空间。mmap()的函数原型如下:
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset );
该函数会把“参数fd所指代的文件”中的一部分映射到进程空间去。这部分文件内容以offset为起始位置,以len为字节长度。其中,参数offset表明从文件起始处开始算起的偏移量。参数prot表明对这段映射空间的访问权限,可以是PROT_READ(可读)、PROT_WRITE (可写)、PROT_EXEC (可执行)、PROT_NONE(不可访问)。参数addr用于指出文件应被映射到进程空间的起始地址,一般指定为空指针,此时会由内核来决定起始地址。
binder_open()的返回值类型为binder_state*,里面记录着刚刚打开的binder驱动文件句柄以及mmap()映射到的最终目标地址。
struct binder_state
{
int fd;
void *mapped;
unsigned mapsize;
};
以后,SMS会不断读取这段映射空间,并做出相应的动作。
3.3.2 binder_become_context_manager()
我们前面已经说过,binder_become_context_manager()的作用是让当前进程成为整个系统中唯一的上下文管理器,即service管理器。其代码非常简单:
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
仅仅是把BINDER_SET_CONTEXT_MGR发送到binder驱动而已。驱动中与ioctl()对应的binder_ioctl()是这样处理的:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
. . . . . .
. . . . . .
case BINDER_SET_CONTEXT_MGR:
. . . . . .
. . . . . .
binder_context_mgr_uid = current->cred->euid;
binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
if (binder_context_mgr_node == NULL)
{
ret = -ENOMEM;
goto err;
}
binder_context_mgr_node->local_weak_refs++;
binder_context_mgr_node->local_strong_refs++;
binder_context_mgr_node->has_strong_ref = 1;
binder_context_mgr_node->has_weak_ref = 1;
break;
. . . . . .
. . . . . .
}
代码的意思很明确,要为整个系统的上下文管理器专门生成一个binder_node节点,并记入静态变量binder_context_mgr_node。
我们在这里多说两句,一般情况下,应用层的每个binder实体都会在binder驱动层对应一个binder_node节点,然而binder_context_mgr_node比较特殊,它没有对应的应用层binder实体。在整个系统里,它是如此特殊,以至于系统规定,任何应用都必须使用句柄0来跨进程地访问它。现在大家可以回想一下前文在获取SMS接口时说到的那句new BpBinder(0),是不是能加深一点儿理解。
3.3.3 binder_loop()
我们再回到SMS的main()函数。
接下来的binder_loop()会先向binder驱动发出了BC_ENTER_LOOPER命令,接着进入一个for循环不断调用ioctl()读取发来的数据,接着解析这些数据。参考代码在:
【frameworks/base/cmds/servicemanager/Binder.c】(注意!这个Binder.c文件不是binder驱动层那个Binder.c文件噢。)
void binder_loop(struct binder_state *bs, binder_handler func)
{
int res;
struct binder_write_read bwr;
unsigned readbuf[32];
bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;
readbuf[0] = BC_ENTER_LOOPER;
binder_write(bs, readbuf, sizeof(unsigned));
for (;;)
{
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (unsigned) readbuf;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
LOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
break;
}
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
if (res == 0) {
LOGE("binder_loop: unexpected reply?!\n");
break;
}
if (res < 0) {
LOGE("binder_loop: io error %d %s\n", res, strerror(errno));
break;
}
}
}
注意binder_loop()的参数func,它的值是svcmgr_handler()函数指针。而且这个参数会进一步传递给binder_parse()。
3.3.3.1 BC_ENTER_LOOPER
binder_loop()中发出BC_ENTER_LOOPER命令的目的,是为了告诉binder驱动“本线程要进入循环状态了”。在binder驱动中,凡是用到跨进程通信机制的线程,都会对应一个binder_thread节点。这里的BC_ENTER_LOOPER命令会导致这个节点的looper状态发生变化:
thread->looper |= BINDER_LOOPER_STATE_ENTERED;
有关binder_thread的细节,也会在阐述Binder驱动一节进行说明。
3.3.3.2 binder_parse()
在binder_loop()进入for循环之后,最显眼的就是那句binder_parse()了。binder_parse()负责解析从binder驱动读来的数据,其代码截选如下:
int binder_parse(struct binder_state *bs, struct binder_io *bio,
uint32_t *ptr, uint32_t size, binder_handler func)
{
int r = 1;
uint32_t *end = ptr + (size / 4);
while (ptr < end)
{
uint32_t cmd = *ptr++;
. . . . . .
case BR_TRANSACTION:
{
struct binder_txn *txn = (void *) ptr;
if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) {
ALOGE("parse: txn too small!\n");
return -1;
}
binder_dump_txn(txn);
if (func)
{
unsigned rdata[256/4];
struct binder_io msg;
struct binder_io reply;
int res;
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data, res);
}
ptr += sizeof(*txn) / sizeof(uint32_t);
break;
}
. . . . . .
. . . . . .
}
return r;
}
从前文的代码我们可以看到,binder_loop()声明了一个128字节的buffer(即unsigned readbuf[32]),每次用BINDER_WRITE_READ命令从驱动读取一些内容,并传入binder_parse()。
binder_parse()在合适的时机,会回调其func参数(binder_handler func)指代的回调函数,即前文说到的svcmgr_handler()函数。
binder_loop()就这样一直循环下去,完成了整个service manager service的工作。
4.Service Manager Service解析收到的命令
现在,我们专门用一个小节来说说Service Manager Service内循环解析命令时的一些细节。我们要确定binder_loop()从驱动侧读到的数据到底如何解析?我们重贴一下binder_parse()的声明部分:
int binder_parse(struct binder_state *bs,
struct binder_io *bio,
uint32_t *ptr,
uint32_t size,
binder_handler func)
之前利用ioctl()读取到的数据都记录在第三个参数ptr所指的缓冲区中,数据大小由size参数记录。其实这个buffer就是前文那个128字节的buffer。
从驱动层读取到的数据,实际上是若干BR命令。每个BR命令是由一个命令号(uint32)以及若干相关数据组成的,不同BR命令的长度可能并不一样。如下表所示:
BR命令 | 需进一步读取的uint32数 |
BR_NOOP | 0 |
BR_TRANSACTION_COMPLETE | 0 |
BR_INCREFS | 2 |
BR_ACQUIRE | 2 |
BR_RELEASE | 2 |
BR_DECREFS | 2 |
BR_TRANSACTION | sizeof(binder_txn) / sizeof(uint32_t) |
BR_REPLY | sizeof(binder_txn) / sizeof(uint32_t) |
BR_DEAD_BINDER | 1 |
BR_FAILED_REPLY | 0 |
BR_DEAD_REPLY | 0 |
每次ioctl()操作所读取的数据,可能会包含多个BR命令,所以binder_parse()需要用一个while循环来解析buffer中所有的BR命令。我们随便画个示意图,如下:
图中的buffer中含有3条BR命令,分别为BR_TRANSACTION、BR_TRANSACTION_COMPLETE、BR_NOOP命令。一般而言,我们最关心的就是BR_TRANSACTION命令啦,因此前文截选的binder_parse()代码,主要摘录了处理BR_TRANSACTION命令的代码,该命令的命令号之后跟着的是一个binder_txn结构。现在我们来详细看这个结构。
4.1 解析binder_txn信息
binder_txn的定义如下:
【frameworks/base/cmds/servicemanager/Binder.h】
struct binder_txn
{
void *target;
void *cookie;
uint32_t code; // 所传输的语义码
uint32_t flags;
uint32_t sender_pid;
uint32_t sender_euid;
uint32_t data_size;
uint32_t offs_size;
void *data;
void *offs;
};
binder_txn说明了transaction到底在传输什么语义,而语义码就记录在其code域中。不同语义码需要携带的数据也是不同的,这些数据由data域指定。示意图如下:
简单地说,我们从驱动侧读来的binder_txn只是一种“传输控制信息”,它本身并不包含传输的具体内容,而只是指出具体内容位于何处。现在,工作的重心要转到如何解析传输的具体内容了,即binder_txn的data域所指向的那部分内容。
为了解析具体内容,binder_parse()声明了两个类型为binder_io的局部变量:msg和reply。从binder_io这个类型的名字,我们就可以看出要用它来读取binder传递来的数据了。其实,为了便于读取binder_io所指代的内容,工程提供了一系列以bio_打头的辅助函数。在读取实际数据之前,我们必须先调用bio_init_from_txn(),把binder_io变量(比如msg变量)和binder_txn所指代的缓冲区联系起来。示意图如下:
从图中可以看到,binder_io结构已经用binder_txn结构初始化了自己,以后我们就可以调用类似bio_get_uint32()、bio_get_string16()这样的函数,来读取这块buffer了。
4.2 svcmgr_handler()回调函数
初始化后的binder_io数据,就可以传给svcmgr_handler()回调函数做进一步的解析了。
此时我们可以调用下面这些辅助函数进行读写:
void bio_put_uint32(struct binder_io *bio, uint32_t n)
void bio_put_obj(struct binder_io *bio, void *ptr)
uint32_t bio_get_uint32(struct binder_io *bio)
uint16_t *bio_get_string16(struct binder_io *bio, unsigned *sz)
void *bio_get_ref(struct binder_io *bio)
. . . . . .
其中,bio_get_xxx()函数在读取数据时,是以binder_io的data域为读取光标的,每读取一些数据,data值就会增加,并且data_avail域会相应减少。而data0域的值则保持不变,一直指着数据区最开始的位置,它的作用就是作为计算偏移量的基准值。
bio_get_uint32()非常简单,会从binder_io.data所指的地方,读取4个字节的内容。bio_get_string16()就稍微复杂一点儿,先要读取一个32bits的整数,这个整数值就是字符串的长度,因为字符串都要包含最后一个’\0’,所以需要读取((len + 1) * sizeof(uint16_t))字节的内容。还有一个是bio_get_ref(),它会读取一个binder_object结构。binder_object的定义如下:
struct binder_object
{
uint32_t type;
uint32_t flags;
void *pointer;
void *cookie;
};
在svcmgr_handler()函数中,一个传输语义码(txn->code)可能会对应几次bio_get操作,比如后文我们要说的SVC_MGR_ADD_SERVICE语义码。具体情况请大家参考svcmgr_handler()的代码。svcmgr_handler()的调用示意图如下:
4.2.1 如何解析add service
我们先研究add service的动作。前文我们已经介绍过,service manager进程里有一个全局性的svclist变量,记录着所有添加进系统的“service代理”信息,这些信息被组织成一条单向链表,即“服务向量表”。现在我们要看service manager是如何向这张表中添加新节点的。
假设某个服务进程调用Service Manager Service接口,向其注册service。这个注册动作到最后就会走到svcmgr_handler()的case SVC_MGR_ADD_SERVICE分支。此时会先获取三个数据,而后再调用do_add_service()函数,代码如下:
uint16_t * s;
void * ptr;
. . . . . .
s = bio_get_string16(msg, &len);
ptr = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
do_add_service(bs, s, len, ptr, txn->sender_euid);
也就是说,当binder_txn的code为SVC_MGR_ADD_SERVICE时,binder_txn所指的数据区域中应该包含一个字符串,一个binder对象以及一个uint32数据。示意图如下:
其中那个binder_object,记录的就是新注册的service所对应的代理信息。此时binder_object的pointer域实际上已经不是指针值了,而是一个binder句柄值。
do_add_service()的函数截选如下:
struct svcinfo *svclist = 0; // 核心service链表(即服务向量表)
int do_add_service(struct binder_state *bs, uint16_t *s, unsigned len,
void *ptr, unsigned uid)
{
struct svcinfo *si;
if (!ptr || (len == 0) || (len > 127))
return -1;
if (!svc_can_register(uid, s)) {
ALOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n",
str8(s), ptr, uid);
return -1;
}
si = find_svc(s, len);
if (si) {
if (si->ptr) {
svcinfo_death(bs, si);
}
si->ptr = ptr;
} else {
// 新创建一个svcinfo节点。
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
if (!si) {
return -1;
}
si->ptr = ptr; // 在svcinfo节点的ptr域中,记录下service对应的binder句柄值
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
si->name[len] = '\0';
si->death.func = svcinfo_death;
si->death.ptr = si;
// 把新节点插入svclist链表
si->next = svclist;
svclist = si;
}
binder_acquire(bs, ptr);
binder_link_to_death(bs, ptr, &si->death);
return 0;
}
现在我们来解读这部分代码。首先,并不是随便找个进程就能向系统注册service噢。do_add_service()函数一开始先调用svc_can_register(),判断发起端是否可以注册service。如果不可以,do_add_service()就返回-1值。svc_can_register()的代码如下:
int svc_can_register(unsigned uid, uint16_t *name)
{
unsigned n;
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
}
上面的代码表示,如果发起端是root进程或者system server进程的话,是可以注册service的,另外,那些在allowed[]数组中有明确记录的用户进程,也是可以注册service的,至于其他绝大部分普通进程,很抱歉,不允许注册service。在以后的软件开发中,我们有可能需要编写新的带service的用户进程(uid不为0或AID_SYSTEM),并且希望把service注册进系统,此时不要忘了修改allowed[]数组。下面是allowed[]数组的一部分截选:
static struct {
unsigned uid;
const char *name;
} allowed[] = {
{ AID_MEDIA, "media.audio_flinger" },
{ AID_MEDIA, "media.player" },
{ AID_MEDIA, "media.camera" },
. . . . . .
接下来,do_add_service()开始尝试在service链表里查询对应的service是否已经添加过了。如果可以查到,那么就不用生成新的service节点了。否则就需要在链表起始处再加一个新节点。节点类型为svcinfo。请注意上面代码的si->ptr = ptr一句,此时的ptr参数其实来自于前文所说的binder_object的pointer域。
为了说明问题,我们重新列一下刚刚的case SVC_MGR_ADD_SERVICE代码:
case SVC_MGR_ADD_SERVICE:
s = bio_get_string16(msg, &len);
ptr = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
if (do_add_service(bs, s, len, ptr, txn->sender_euid, allow_isolated))
return -1;
break;
那个ptr来自于bio_get_ref(msg),而bio_get_ref()的实现代码如下:
void *bio_get_ref(struct binder_io *bio)
{
struct binder_object *obj;
obj = _bio_get_obj(bio);
if (!obj)
return 0;
if (obj->type == BINDER_TYPE_HANDLE)
return obj->pointer;
return 0;
}
因为现在是要向service manager注册服务,所以obj->type一定是BINDER_TYPE_HANDLE,也就是说会返回binder_object的pointer域。这个域的类型虽为void*,实际上换成uint32可能更合适。通过这个binder句柄值,我们最终可以找到远端的具体service实体。
4.2.2 如何解析get service
现在我们接着来看get service动作。我们知道,在service被注册进service manager之后,其他应用都可以调用ServiceManager的getService()来获取相应的服务代理,并调用代理的成员函数。这个getService()函数最终会向service manager进程发出SVC_MGR_GET_SERVICE命令,并由svcmgr_handler()函数这样处理:
switch(txn->code)
{
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len);
ptr = do_find_service(bs, s, len, txn->sender_euid);
if (!ptr)
break;
bio_put_ref(reply, ptr);
return 0;
一开始从msg中读取希望get的服务名,然后调用do_find_service()函数查询服务名对应的句柄值,最后把句柄值写入reply。do_find_service()的代码如下:
void *do_find_service(struct binder_state *bs, uint16_t *s, unsigned len, unsigned uid)
{
struct svcinfo *si;
si = find_svc(s, len);
if (si && si->ptr)
{
if (!si->allow_isolated)
{
unsigned appid = uid % AID_USER;
if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END)
{
return 0;
}
}
return si->ptr; // 返回service代理的句柄!
}
else
{
return 0;
}
}
可以看到,do_find_service()返回的就是所找到的服务代理对应的句柄值(si->ptr)。而svcmgr_handler()在拿到这个句柄值后,会把它写入reply对象:
bio_put_ref(reply, ptr);
bio_put_ref()的代码如下:
void bio_put_ref(struct binder_io *bio, void *ptr)
{
struct binder_object *obj;
if (ptr)
obj = bio_alloc_obj(bio);
else
obj = bio_alloc(bio, sizeof(*obj));
if (!obj)
return;
obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj->type = BINDER_TYPE_HANDLE;
obj->pointer = ptr;
obj->cookie = 0;
}
bio_alloc_obj()一句说明会从reply所关联的buffer中划分出一个binder_object区域,然后开始对这个区域写值。于是BINDER_TYPE_HANDLE赋给了obj->type,句柄值赋给了obj->pointer。另外,reply所关联的buffer只是binder_parse()里的局部数组噢:
unsigned rdata[256/4];
大家应该还记得svcmgr_handler()是被binder_parse()回调的,当svcmgr_handler()返回后,会接着把整理好的reply对象send出去:
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data, res);
也就是把查找到的信息,发送给发起查找的一方。
binder_send_reply()的代码如下:
void binder_send_reply(struct binder_state *bs, struct binder_io *reply,
void *buffer_to_free, int status)
{
struct
{
uint32_t cmd_free;
void *buffer;
uint32_t cmd_reply;
struct binder_txn txn;
} __attribute__((packed)) data;
data.cmd_free = BC_FREE_BUFFER;
data.buffer = buffer_to_free;
data.cmd_reply = BC_REPLY;
data.txn.target = 0;
data.txn.cookie = 0;
data.txn.code = 0;
if (status)
{
data.txn.flags = TF_STATUS_CODE;
data.txn.data_size = sizeof(int);
data.txn.offs_size = 0;
data.txn.data = &status;
data.txn.offs = 0;
}
else
{
data.txn.flags = 0;
data.txn.data_size = reply->data - reply->data0;
data.txn.offs_size = ((char*) reply->offs) - ((char*) reply->offs0);
data.txn.data = reply->data0;
data.txn.offs = reply->offs0;
}
binder_write(bs, &data, sizeof(data));
}
观察代码中最后那几行,看来还是在摆弄reply所指代的那个buffer。当初binder_parse()在创建reply对象之时,就给它初始化了一个局部buffer,即前文所说的unsigned rdata[256/4],在svcmgr_handler()中又调用bio_put_ref()在这个buffer中开辟了一块binder_object,并在其中赋予了ptr句柄。现在终于要向binder驱动传递reply信息了,此时调用的binder_write()的代码如下:
int binder_write(struct binder_state *bs, void *data, unsigned len)
{
struct binder_write_read bwr;
int res;
bwr.write_size = len;
bwr.write_consumed = 0;
bwr.write_buffer = (unsigned) data;
bwr.read_size = 0;
bwr.read_consumed = 0;
bwr.read_buffer = 0;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
fprintf(stderr,"binder_write: ioctl failed (%s)\n",
strerror(errno));
}
return res;
}
噢,又见ioctl(),数据就在bwr.write_buffer,数据里打出了两个binder命令,BC_FREE_BUFFER和BC_REPLY。
这些数据被传递给get service动作的发起端,虽然这些数据会被binder驱动做少许修改,不过语义是不会变的,于是发起端就获得了所查service的合法句柄。
5.小结
至此,有关ServiceManager的基本知识就大体交代完毕了,文行于此,暂告段落。必须承认,受限于个人的认识和文章的篇幅,我不可能涉及其中所有的细节,这里只能摘其重点进行阐述。如果以后又发现什么有趣的东西,我还会补充进来。