Android 大话binder通信 (上)
戳蓝字“牛晓伟”关注我哦!
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章
本文摘要
用故事的方式把binder通信的整个过程都描述出来,binder通信都经历了哪些节点,在这些节点上的数据有哪些变化,同时还对binder通信的关键细节进行介绍。通过本文您能对binder通信整体和细节都有一个全面的认识,比如startActivity方法到底都经历了哪些过程。(文章基于Android13代码分析)
下面是我以前写的binder通信的几篇文章,欢迎大家取阅:
通熟易懂的分析binder--2. binder进程通信协议及“记录链路”结构体
通熟易懂的分析binder--3. 探究binder全流程通信之请求篇
通熟易懂的分析binder--3. 探究binder全流程通信之回复篇
通熟易懂的分析binder--4.ServiceManager
由于篇幅的原因,故分为上下两篇。故事要从两个主人公白富美和高富帅讲起。
白富美
我是白富美,性别女,我是Android系统里的一个进程,我有很多的追求者,它们不断的通过socket发送情书给我,而我却因此而很烦恼,因为其中很多的追求者我对他们都不感兴趣,但是socket通信又没有一个很好的办法来进行权限控制,比如针对不喜欢的追求者发送的情书,我可以根据他们的 pid (进程id)以及 uid (App的id) 来决定要不要接收他们的情书。
为了解决这个问题,我发布了各种悬赏公告,最终一个叫binder的小伙子找到了我。
他说:“白富美大小姐,你好啊,我先做下自我介绍,我叫binder,主要解决Android系统进程之间的通信。”
白富美问到:“打扰下,咱们直接进入主题吧,因为我现在真的很困惑,我的追求者通过socket来给我发送情书,而我要做权限控制,你能做到吗?”
binder非常自信的说到:“小菜一碟,我与socket相比,优势多了去了。首先在一次进程之间通信过程,socket需要两次拷贝,而我只需要一次;其次你可以知道是哪个追求者发送的情书,而socket我对它了如指掌,它实现这个功能很难;最后追求者在发送情书的时候是面向对象调用,犹如在调用他自己本地的对象方法一样。”
binder偷偷的瞄了瞄白富美,看到她的注意力非常集中,接着说:“上面介绍了我在哪些方面强于socket,那接下来介绍下关于我自己的吧。”
“binder通信是client/server模式,也就是分为binder client和binder server两部分,binder server提供各种能力供binder client来调用,在java层只有继承了Binder类才是一个binder server,而在native层只有继承了BBinder的类才是一个binder server。Binder是对BBinder的封装。”
“而binder client在调用binder server提供的能力之前,是需要从ServiceManager获取对应信息的,而这个对应信息在java层是BinderProxy类,在native层是BpBinder类,BinderProxy是对BpBinder的一个封装。关于我的介绍就到此为止,你看下是否还有问题。” (这里需要备注下:binder server分为具名和匿名两种,具名就是可以从ServiceManager中通过name获取到的,否则就是匿名。而本文介绍的binder server是具名的。并且ServiceManager管理的也是具名binder server)
白富美:“那看来我是真找对人了,我的需求很简单,我只需要提供一个receiveLoveLetter的接口,对于同意接收的情书,会返回给追求者true,否则返回false。就这么简单,帮我实现下吧。”
“太简单了,看我的表演。”
疯狂写代码中......
完了,看下面代码
//定义IBeautifulGirl接口,它继承了IInterface接口
public interface IBeautifulGirl extends android.os.IInterface {
//binder server需要继承Stub类,实现receiveLoveLetter方法
public static abstract class Stub extends android.os.Binder implements IBeautifulGirl {
private static final java.lang.String DESCRIPTOR
= "IBeautifulGirl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static IBeautifulGirl asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
//如果obj是Binder,则queryLocalInterface是可以获取到值的,否则获取不到
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof IBeautifulGirl))) {
return ((IBeautifulGirl)iin);
}
return new IBeautifulGirl.Stub.Proxy(obj);
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
int flags) throws android.os.RemoteException {
switch (code) {
case TRANSACTION_receiveLoveLetter: {
data.enforceInterface(descriptor);
//binder server需要实现receiveLoveLetter方法
boolean _result = this.receiveLoveLetter(data.readString());
reply.writeNoException();
reply.writeBoolean(_result);
return true;
}
}
}
//代理类,在binder client使用
private static class Proxy implements IBeautifulGirl {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public boolean receiveLoveLetter(String param)throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((cb != null)) ? (cb.asBinder()) : (null)));
_data.writeString(param);
mRemote.transact(Stub.TRANSACTION_receiveLoveLetter, _data, _reply, 0);
_reply.readException();
return _reply.readXXX();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_receiveLoveLetter = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
//接收情书抽象方法,
public boolean receiveLoveLetter(String param) throws android.os.RemoteException;
}
//BeautifuGirl类继承了IBeautifulGirl.Stub类
public BeautifulGirl extends IBeautifulGirl.Stub{
//返回true:就代表接受情书并且同意交往;返回false:则代表不同意
public boolean receiveLoveLetter(String letter){
检查权限代码
读情书内容代码
返回结果代码
}
}
“hi 白富美上面的代码,BeautifuLGirl类的receiveLoveLetter方法就是接收情书的方法,可以通过Binder的getCallingPid方法获取追求者的pid,可以通过BInder的getCallingUid方法可以获取追求者的uid,你就可以根据这些值来做权限判断了,读取情书以及返回哪些值,这得你自己来实现了。”
“忘记了,还有非常重要的一步:需要调用ServiceManager的addService方法把你的BeautifuLGirl对象进行添加,因为ServiceManager也是一个具名binder server,它是处于servicemanager进程,因此在调用它的方法的时候需要特殊处理,下面的伪代码会把你的BeautifuLGirl类实例添加到Servicemanager” (当然app是不可以调用ServiceManager的addService方法的,这里是为了说明问题把白富美当成一个系统的进程)
添加BeautifuLGirl到ServiceManager的伪代码:
//获取ServiceManager实现了接口IServiceManager,而下面的方法获取的IServiceManager是一个代理类
IServiceManager serviceManager = ServiceManagerNative.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
//添加BeautifuLGirl
serviceManager.addService("beautiful",new BeautifulGirl());
如上代码,通过字符串beautiful把BeautifulGirl实例添加到了ServiceManager中。
如何使用?
白富美:“那我的追求者如何使用呢?”binder:“你是这么的美丽动人,我觉得你不输任何明星,明星们都有自己的经纪人,那我也为你量身定做了一个‘代理人’,他的名字就是IBeautifulGirl.Proxy,从名字上可以看出它使用了代理模式,只要你有的功能他都有。下面是如何使用的伪代码。”
/获取ServiceManager实现了接口IServiceManager,而下面的方法获取的IServiceManager是一个代理类
IServiceManager serviceManager = ServiceManagerNative.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
BinderProxy bp = serviceManager.getService("beautiful");
IBeautifulGirl beautifuGirl = IBeautifulGirl.Stub.asInterface(bp);
beautifuGirl.receiveLoveLetter("情书");
白富美:“广大的追求者们,我现在发个通告:以前的socket发送情书的方法已经废弃了。你们如果要想给我发送情书,请先获取到我的‘代理人’IBeautifulGirl.Proxy,进而调用receiveLoveLetter方法就可以把你们的情书发送给我,我可是整个Android系统中最美丽最动人最富有的没有之一的进程。错过了绝对没机会了。”
矮挫丑
我是矮挫丑,性别男,我是Android系统里的一个普通进程,我也是白富美众多追求者之一,你们都说我是癞蛤蟆想吃天鹅肉,我可不这么认为,只是你们认为我是癞蛤蟆,而我可不这么认为,并且谁说白富美就一定喜欢高富帅呢,我自有我的优点:那就是脸皮厚、情商高、文字功底一流。
那就看看我是如何用我优美的文字征服白富美的吧。
矮挫丑还真是厉害,没过几个小时就把情书写好了,而很多的追求者甚至用了好几天才把情书写好。
他自言自语的对情书说到:“情书啊情书,我创作你可是不易啊,我的终身大事可就完全拜托你了。”
情书突然张口说到:“放心吧主人,你的事情就是我的事情,我肯定会尽心尽力的。”
“啥情况,我写的情书竟然会说话,这也太奇妙了,有如此之情书,我志在必得。”
发送情书
矮挫丑细细的琢磨着:我记得白富美给大家公开了给她发送情书的方法,那就是需要先获取她的“代理人”IBeautifulGirl.Proxy,那就按照她公布的方式先获取“代理人”。
代理人
“代理人,你好啊,帮忙把我写好的情书交给白富美吧,我对她是非常非常真心的,见到她后也帮忙在她面前美言几句啊,谢谢了。”
代理人:“你不是对自己非常的自信吗?怎么还需要我美言几句呢。我特别想帮你美言几句,但是我做不到啊,因为我也根本见不到白富美本人。”
矮挫丑不耐心的插了一句:“不想帮忙就不想帮吧,还不说实话,你作为她的‘代理人’我就不信你见不到她。”
“你对于我不了解,说出这样的话我也不怪你,是这样的,我其实是使用了代理模式,首先我是一个Proxy类,我和白富美的BeautifuGirl类都实现了同一个接口IBeautifulGirl,因此BeautifuGirl类有啥接口,我同样也有。”
//Proxy实现了IBeautifulGirl接口
private static class Proxy implements IBeautifulGirl {
//binder server代理人,mRemote是BinderProxy
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public boolean receiveLoveLetter(String param)throws android.os.RemoteException {
省略代码......
}
}
//BeautifuGirl继承了IBeautifulGirl.Stub,IBeautifulGirl.Stub实现了IBeautifulGirl接口
public BeautifuGirl extends IBeautifulGirl.Stub{
public boolean receiveLoveLetter(String letter){
省略代码......
}
}
public static abstract class Stub extends android.os.Binder implements IBeautifulGirl {
省略代码......
}
“不知道你是否明白了,这就是我为啥叫‘代理人’的原因,我被创造出来的一个非常重要的原因就是为了让你们使用者通过obj.method调用某个对象的某个方法来使用。首先这样使用起来毋庸置疑肯定是非常方便的;其次让使用者感觉不到自己在调用的是一个别的进程的方法,犹如在使用本地对象的一个方法一样。”
“赞,binder的设计者真的是考虑的相当的周到、细致、用心,必须点个大大的赞。”
“谢谢,再来说下我为啥见不到白富美,其实我这个代理人只存在于你矮挫丑的进程,我是不会被传递到别的进程的,我只是在binder通信中起一个非常微小的作用,我存在的目的就是为了让你们使用者使用方便。”
“不好意思,是我错怪你了,我为我的不礼貌的话语向你道歉。”
“没事,你调用我的receiveLoveLetter方法,就可以把情书交给我,剩下的事情就是等待好消息了。”
矮挫丑:“receiveLoveLetter方法已调用,我就把情书交给你了,就劳驾您了,一路上注意安全。”
对了还有个小秘密告诉你:“我的情书会讲话啊,它可以在路上陪你聊聊天、解解乏。”
“啊!还有这等神奇的事情,那太好了。”
就这样代理人带着情书上路了,他们一路上有说有笑极其欢乐,在经过一个驿站的时候,趁着稍作休息的功夫,代理人有些伤感的对情书说:“情书啊,从这个驿站开始我就与你分离了,我会把你交给我的属性mRemote它的值是BinderProxy对象,其实也就是交给BinderProxy了。”
情书不知所措的说:“为啥啊?一路上咱们不是相处的很好嘛。”
“是这样的,把你送到白富美的路上需要很多的小伙伴来参与,我的使命暂时告一段落了,当然我也会等待白富美的回复,我会把回复交给你的主人矮挫丑。”
“当然还有非常重要的一个事情要做,为了让你路上不孤单,给你找了几个小伙伴,如下”
其中有methodCode它是int类型的,它的值是TRANSACTION_receiveLoveLetter,白富美根据它就可以知道调用哪个方法了 _data它是Parcel类型的,为了让你能顺利的进入别的进程,会调用它的writeString方法把你序列化,当然在这里你是非常的安全的 _reply它也是Parcel类型的,它的作用是会把白富美的回复给带回来,我会从*_reply*中把回复拿到交给矮挫丑
我是代理类,我在binder通信中的作用主要是让binder server的使用者像使用本地对象的方法一样,使用起来非常方便,同时会创建methodCode变量,类型为Parcel的_data变量 (所有的参数都会放入该变量中),并且会构造一个类型为Parcel的*_reply*变量 (它会存储返回结果包括是否有异常)
“BinderProxy你好,我调用你的transact方法就把TRANSACTION_receiveLoveLetter、_data、_reply这几个小伙伴交给你了,发送情书的事情就交给你了,对了情书它可是会说话的啊,路上可以和你聊天。”
下面这张图代表情书传递过程中经历的方法和参数的变化
BinderProxy
一路上情书与BinderProxy有说有笑,渐渐熟悉了,它对BinderProxy说:“BinderProxy你能介绍下你自己吗?以及在binder通信中发挥了什么作用。”
BinderProxy:“我是java层的类,看我的名字后面有个单词proxy,可以猜出我其实也是个代理,我是与Binder类相对应的。而说到我在binder通信中发挥了什么作用?这个着实很惭愧,因为很多的事情根本都不是我做的,我只是一味的在调用native层的方法。非要说自己的作用的话就是把java层方法的各种参数传递到native层。”
我突然想起来了,我有一个独特的能力,你们肯定没听过:“我虽然身处java层,但是java层却没有任何类可以实例化一个BinderProxy对象,就是在java层没办法直接new一个BinderProxy对象。而谁来实例化我呢?那就是native层的代码,不信你们可以看我的构造方法是私有的。”
情书:“那如果在java层通过反射来实例化呢?”
BinderProxy瞟了一眼情书,心想它不单单会说话,竟然还懂java语言,这就奇了个大怪了。
“哈哈,是这样的,即使通过反射实例化了,也没用,因为我有一个long类型的mNativeData属性,它是native层BpBinder对象的地址。设计如此的目的与binder驱动有非常大的关系,到了内核的时候,让内核大佬们讲给你听吧。”
咱们不聊了,继续赶路吧,正如BinderProxy说的,它其实就是一味的在调用native方法,经过jni调用后,我和我的小伙伴到达了native层,到达native层后,第一件事情就是把我们几个小伙伴转换为native层的对象,java层Parcel对象转换为native层的Parcel对象,甚至BinderProxy对象也转换为native层的BpBinder对象。
情书突然感觉到有事要发生了,因为刚刚已经经历过了。
BinderProxy有些不舍的对情书说:“我的使命也暂时告一段落了,接下来我会把你们交给BpBinder,后会有期。BpBinder,调用你的transact方法后,情书和它的小伙伴就交给你了。你要好好照顾它们。”
我是BinderProxy,我在binder通信中的作用是起连接java层与native层的桥梁,同时把java层的各种参数传递到native层。我也大概能理解:为啥在java层不能new一个BinderProxy实例了,其实主要原因是先有native层的BpBinder,而在根据它的地址在native层new一个BinderProxy实例。我其实就是把BpBinder的各种方法都封装起来供java层来使用。在Android系统中像我这种例子是非常多的,比如Bitmap类也是只有native层才能new。
同样也用一张图代表情书传递过程中经历的方法和参数的变化
如下是相关代码,请自行取阅:
//文件路径:frameworks/base/core/jni/android_util_Binder.cpp
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
省略代码......
//转data
Parcel* data = parcelForJavaObject(env, dataObj);
if (data == NULL) {
return JNI_FALSE;
}
//转reply
Parcel* reply = parcelForJavaObject(env, replyObj);
if (reply == NULL && replyObj != NULL) {
return JNI_FALSE;
}
//根据BinderProxy的mNativeData获取到对应的BpBinder,这时候target就是BpBinder
IBinder* target = getBPNativeData(env, obj)->mObject.get();
省略代码......
//调用BpBinder的transact方法
status_t err = target->transact(code, *data, reply, flags);
省略代码......
}
BpBinder
BpBinder:“情书你好,欢迎你来到native层,看到我的名字,应该不知道是啥意思吧?那我就来介绍下。”
Bp是binderProxy的缩写,为了与java层BinderProxy区别,我的名字就是BpBinder了。java层BinderProxy类对应的是Binder类,而native层BpBinder类对应的是BBinder类。在native层也是支持binder server的,那就是要继承BBinder类即可成为native层的binder server。
情书:“那您同样也是一个代理类吧,能介绍下您在binder通信中的作用吗?”
“是的,在binder通信中的作用我觉得我起了两个作用。首先我会把BpBinder传递下来的各种数据传递给我的底层,至于我的底层是谁稍后会说;其次我有一个非常重要的属性mHandle,这个属性在旧版Android系统是int类型,在Android13上是Handle类型 (Handle类型的BinderHandle类的handle也是一个int类型),也就是说不管在Android新旧版本上都有一个int类型的值,这个值的作用是非常非常重要的,在binder驱动也只有通过这个值才能找到目标binder server。这个值可不是凭空捏造出来的,它可是binder驱动生成的,也就是在binder驱动同样也保存了这么一个值。”
“情书到了咱们告别的时候了,我给你和你的小伙伴又找了一个小伙伴,它就是上面提到的int类型的值,在binder驱动层是需要它的。”
“IPCThreadState你好啊,那我就调用你transact方法,我把情书和它的小伙伴就交给你了。”
还是老规矩,用一张图代表情书传递过程中经历的方法和参数的变化
IPCThreadState
一路上情书心里面总有些忐忑,为啥呢?因为它觉得IPCThreadState这个家伙像个骗子,你看前面的BinderProxy和BpBinder最起码从名字上就能看出来和binder通信有关系,而IPCThreadState这名字呢鬼才能看出来和binder通信有关系。
终于情书鼓起勇气问IPCThreadState,但是话刚到嘴边又咽了回去,就赶紧换了个问题:“你好啊,我是要去白富美那里,我和我的小伙伴离目的地还有多远啊?”
IPCThreadState:“你们走了差不多三分之一的路程了,过了内核,就离目的地不远了。”
情书心里面嘀咕着,听着他的回答是有那么一点意思,但是自己心里面还是不确定,于是再次鼓足了勇气问到:“是这样的,咱们走了一路,我只知道你是护送我们到达目的地的,但是从你的名字上看,我感觉你和binder通信没有任何的关系啊,你不会是个骗子吧?还有如果你不是骗子那就证明下吧。”
IPCThreadState:“啊!BpBinder在把你们交给我的时候,告诉过我说你会说话,这一路上也没见你说一句话,原来是觉得我是一个骗子啊。好吧那我可要证明自己的清白。”
“话又说回来也不怪你,都是我这糟糕的名字惹的祸,当时我的设计者在给我起这名字的时候,我是强烈反对的,但是呢反对无效啊。首先我名字的IPC是Inter-Process Communication的缩写,翻译为中文就是进程之间通信的意思,而后面的ThreadState就是线程状态了。别看我的名字起的糟糕,但是我的工作内容可不糟糕。”
“我的工作内容主要就是与binder驱动进行通信,我会把像你们这样的信息发送给binder驱动 (binder驱动可是位于内核空间),而binder驱动也会随时把它那边的工作进度、工作情况等内容发送给我。”
情书:“我相信你了,那能展开说下,你是如何把我和我的小伙伴发送到binder驱动的吗?我特别感兴趣。”
IPCThreadState:“那我就讲一讲吧,毕竟稍等片刻你也会从我这离开的。为了让你听的更明白些,我觉得非常有必要先从科普知识开始。”
科普知识
系统调用
系统调用的英文是System Call,啥意思呢,就是Android操作系统分为用户空间和内核空间,用户空间的进程只有通过系统调用才能与内核空间进行通信。进行系统调用,用户空间进程的线程会由用户态切换到内核态,当在内核空间处理完毕任务后,用户空间线程恢复原先状态。
ioctl
它的全称是Input/Output Control,它是一个系统调用函数,它的主要作用是实现用户空间进程与内核空间驱动之间通信。
下面是该函数的声明,其中fd是设备驱动对应的文件描述符,它是一个int类型;request是一个无符号long类型;而后面的...一般是一个指向数据的指针。
int ioctl(int fd, unsigned long request, ...);
我IPCThreadState与binder驱动发送信息就是用的是ioctl这个系统调用函数。
介绍了科普知识后,那我在来介绍一些数据结构和cmd吧。
数据结构
大家在使用socket通信的时候,server和client双方是不是要约定好一个cmd + 数据结构,cmd代表要执行哪些操作,而数据结构则是执行这些操作要使用到的数据。同理使用ioctl函数与binder驱动进行通信的时候也需要定义这样的cmd + 数据结构,那我与binder驱动定义了 BINDER_WRITE_READ + binder_write_read 、BINDER_THREAD_EXIT + 0 、 BINDER_FREEZE + binder_freeze_info 等cmd和数据结构,而在binder通信中最常用的就是BINDER_WRITE_READ + binder_write_read这套组合。
那就先来介绍下binder_write_read这个数据结构
1 binder_write_read
如下是它的定义
struct binder_write_read {
binder_size_t write_size; /* bytes to write */
binder_size_t write_consumed; /* bytes consumed by driver */
binder_uintptr_t write_buffer;
binder_size_t read_size; /* bytes to read */
binder_size_t read_consumed; /* bytes consumed by driver */
binder_uintptr_t read_buffer;
};
如上它的属性,其中write_xxx的都是我IPCThreadState发送给binder驱动的数据,而read_xxx是binder驱动发送给我的数据。
而在binder_write_read中发送给binder驱动的数据中也定义了一套cmd + 数据结构,如BC_TRANSACTION + binder_transaction_data (调用binder server方法的时候用这个组合)、BC_REPLY + binder_transaction_data (当binder server方法返回结果的时候用这个组合) 等。而针对cmd也定义了一些规则:发送给binder驱动的使用BC_xxxx格式,而接收binder驱动的使用BR_xxxx格式。
BC_TRANSACTION + binder_transaction_data、BC_REPLY + binder_transaction_data这俩组合是经常要用到的,那来介绍下binder_transaction_data数据结构吧。
2 binder_transaction_data
如下是它的定义
struct binder_transaction_data {
/* The first two are only used for bcTRANSACTION and brTRANSACTION,
* identifying the target and contents of the transaction.
*/
union {
/* target descriptor of command transaction */
__u32 handle;
/* target descriptor of return transaction */
binder_uintptr_t ptr;
} target;
binder_uintptr_t cookie; /* target object cookie */
__u32 code; /* transaction command */
/* General information about the transaction. */
__u32 flags;
pid_t sender_pid;
uid_t sender_euid;
binder_size_t data_size; /* number of bytes of data */
binder_size_t offsets_size; /* number of bytes of offsets */
/* If this transaction is inline, the data immediately
* follows here; otherwise, it ends with a pointer to
* the data buffer.
*/
union {
struct {
/* transaction data */
binder_uintptr_t buffer;
/* offsets from buffer to flat_binder_object structs */
binder_uintptr_t offsets;
} ptr;
__u8 buf[8];
} data;
};
如上代码,这里主要介绍几个关键的属性:
target是union类型的,它的值是handle或者ptr,看到handle不知道你是否有印象。
情书拖着腮帮想了想:“知道的,BpBinder说过它有一个非常重要的属性说的就是它,还有BpBinder在把我和我的小伙伴交给你的时候也把自己的handle交给了你。也就是说handle其实对应的是上层的BpBinder类,而ptr则对应的就是上层的BBinder类了?”
“都会推测了,你说的非常对。ptr它是一个地址”
cookie它其实对应的是上层的BBinder类,它也是一个地址,这个在白富美那边会涉及到。 code这个你肯定也能猜出来吧
情书:“那它肯定是我的小伙伴TRANSACTION_receiveLoveLetter了。”
“没错,code就是指向了方法对应的值,这样在binder server端会根据code值来进入对应的方法。”
data_size、offsets_size、data就是对应的类型为Parce的data参数,也就是在调用binder server某个方法的时候,方法的参数都与这几个属性有关。其中data.ptr.offsets是binder对象的个数。
情书:“哦,原来我是放置在这块啊!那我的类型为Parcel的_reply小伙伴呢?怎么没有看到它对应的存放位置。”
IPCThreadState:“_reply是不会被传递到binder驱动层的,因为我可以从binder驱动层拿到回复数据,拿到后我会把回复数据放置到_reply内。”
发送数据到binder驱动
IPCThreadState自信的对情书说:“我就是这样一个做事认真仔细的人,要想让别人听明白你讲的东西,就得站在小白的角度考虑,把一些基础性的东西先科普下,在由浅入深的去讲,就可以非常容易的让别人听懂了。发送数据到binder驱动分为写数据和发送数据两步,那就来介绍下它们。”
写数据
写数据其实就是情书和你的小伙伴还有非常重要的handle写到binder_transaction_data数据结构中,同时还需要写入BC_TRANSACTION这个cmd。
如下是相关代码,请自行取阅:
//文件路径:/frameworks/native/libs/binder/IPCThreadState.cpp
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;
//因为是BpBinder,所以target.ptr是0
tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
//因为是BpBinder,所以cookie是0
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
//情书在这写入
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
}
省略代码......
//写入cmd
mOut.writeInt32(cmd);
//写入binder_transaction_data
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
发送数据
binder server的方法分为两种one way和非one way两种。
心急的情书有些听不懂了,着急忙慌的就问:“one way和非one way都是啥子嘛?赶紧解释下呗!”
IPCThreadState:“one way就是说不需要等待binder server的回复,而反之非one way自然就是需要等待binder server的回复了。”
不管是one way还是非one way类型,发送数据都用的是ioctl方法,如下是我把情书和你们小伙伴发送到binder驱动的代码:
//文件路径:/frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
//mDriverFD小于0返回
if (mProcess->mDriverFD < 0) {
return -EBADF;
}
//构造binder_write_read实例
binder_write_read bwr;
省略代码......
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
//把mOut放入bwr的write_xxx属性中
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();
//省略代码......
do {
省略代码......
//调用ioctl把bwr和BINDER_WRITE_READ发送到binder驱动
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
省略代码......
} while (err == -EINTR);
省略代码......
return err;
}
但是对于one way类型,我IPCThreadState把数据发送给binder驱动后,只要等待到BR_TRANSACTION_COMPLETE (binder驱动发送上来的数据)这个cmd就可以离开了,而对于非one way类型,把数据发生给binder驱动后,是需要等待binder server的回复的。
在调用ioctl系统调用后,咱们当前的线程会由用户态切换为内核态,并且咱们发送情书肯定是需要等待白富美的回复的,因此需要继续等待。
IPCThreadState松了口气,慢慢的说:“情书你还有啥不明白的吗?”
情书:“当然有了,调用ioctl函数的时候,传递的mProcess->mDriverFD这个文件描述符我看它非常重要,那它是啥时候初始化的,以及它的作用是啥?”
IPCThreadState心想这小子问的问题还真的很关键,那我就来讲讲它。
mDriverFD的由来和作用
java层的进程都是被zygote进程fork的,在fork成功后,ProcessState类的构造方法就会被调用,参数是/dev/binder,ProcessState类的实例在一个进程中是只存在一个,在ProcessState的构造方法中会调用open方法,参数为/dev/binder,这时候的binder驱动层的binder_open方法会被调用,进而会返回一个fd (文件描述符),这个fd会赋值给mDriverFD。
每个进程的mDriverFD都是不一样的,它作为ioctl函数的参数,在binder驱动层会根据mDriverFD来获取相应的信息,但这个信息具体是啥,我也不清楚了,我也只能介绍到这了,因为关于binder驱动的事情需要由它们来介绍了,它们更专业。
我是IPCThreadState,我在binder通信中的作用就是与binder驱动交互信息,我会通过ioctl系统调用函数把数据发送给binder驱动,binder驱动也会返回给我信息。
IPCThreadState:“调用ioctl方法后,我会把情书和你的小伙伴发送到内核,当然除了类型为Parcel的reply小伙伴不会进入内核,调用ioctl系统方法后,我对应的线程由用户态变为内核态,我也会处于等待阶段,等待白富美的回复,回复会放入replay中。”
情书:“我还真有些不舍,到了内核是谁来接待我们啊。”
IPCThreadState:“这个我还真不知道,我一辈子了都没进过内核,因此那边我没有熟人,不过你比我强多了,你还能到达内核甚至还能到别的进程溜达溜达。不过放心binder通信都已经非常成熟了,自然有人接待你,别害怕勇敢的往前走吧,加油。”
还是老规矩,用一张图代表情书传递过程中经历的方法和参数的变化
我IPCThreadState把你情书和你的小伙伴发送到binder驱动,我可是等待着白富美的回复呢,如下是等待回复代码:
//文件路径:/frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
//不断循序,除非遇到break
while (1) {
//发送数据到binder驱动后,会阻塞等待binder驱动返回数据
if ((err=talkWithDriver()) < NO_ERROR) break;
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;
//binder驱动发送的cmd 是以*BR*开头的
cmd = (uint32_t)mIn.readInt32();
IF_LOG_COMMANDS() {
alog << "Processing waitForResponse Command: "
<< getReturnString(cmd) << endl;
}
switch (cmd) {
省略代码......
case BR_TRANSACTION_COMPLETE:
//one way类型调用,直接就可以返回了
if (!reply && !acquireResult) goto finish;
break;
省略代码......
//白富美的回复会发送BR_REPLY cmd
case BR_REPLY:
省略代码......
goto finish;
default:
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
省略代码......
return err;
}
情书到达内核
情书和它的小伙伴虽然被包裹在binder_transaction_data对象中,而binder_transaction_data对象和BC_TRANSACTION又被包裹在binder_write_read对象的write_buffer属性中,但它还是有些许的不安全感,毕竟来到了一个非常陌生的地方。
情书这时候有些伤感因为它的小伙伴reply没有来到内核,但是它又觉得不能这样想事情,因为它还多了三个小伙伴mDriverFD、BINDER_WRITE_READ和binder_write_read对象的地址。
突然一个声音打断了它的思绪:“快点来个干活儿的,从用户空间又来了一批数据,快点先根据mDriverFD找到对应的file结构,再把找到的file地址、BC_TRANSACTION和binder_write_read对象的地址交给binder_ioctl方法来处理。”
情书心想他一定是个大人物,那我需要赶紧上前和他打打招呼,认识认识:“你好很高兴认识你,请问你怎么称呼,我是用户空间的情书,我是第一次来内核空间,请多多关照。”
“你好,我是binder驱动,整个内核的binder事情都归我管理,不用客气,有事情尽管说,还有你是要去往何处啊?”
情书急忙答到:“用户空间的矮挫丑进程要我去往白富美进程,刚来内核,人生地不熟,麻烦您能介绍下我该如何到达目的地。”
binder驱动:“好的,在内核空间您到达目的地可以分为查找目标、复制数据、激活目标这三步。那我就细说下这三步吧。”
查找目标
在介绍之前先来说四个关键的数据结构吧,它们在查找目标过程中起了非常重要的作用。
关键数据结构
binder_proc
从名字的后缀_proc来看是不是看到了进程啊,没错它确实是与进程有关系的,它与用户空间的除了zygote之外的java进程以及只要打开了binder驱动的native进程是一一对应的关系,比如systemserver进程、launcher、vold native进程、installd native进程在binder驱动都存在对应的binder_proc。也就是只有打开binder驱动的用户空间进程才会在binder驱动有一个对应的binder_proc,而除了zygote之外的java进程是只要进程被fork成功后,就会自动打开binder驱动。
在用户空间,进程之间是隔离的,而在binder驱动,那可不是,binder_proc之间是可以互相引用的。
说了这么多上干货吧,来看下它的数据结构的关键属性吧
struct binder_proc {
省略其他属性......
//使用红黑树存储所有的binder_thread
struct rb_root threads;
//使用红黑树存储所有的binder_node
struct rb_root nodes;
//使用红黑树存储所有的binder_ref (以desc的方式查找binder_ref)
struct rb_root refs_by_desc;
//使用红黑树存储所有的binder_ref(以binder_node的方式查找binder_ref)
struct rb_root refs_by_node;
省略其他属性......
};
如上代码,分别用红黑树存储了所有的binder_thread、binder_node、binder_ref。
何时创建binder_proc
在用户空间进程打开驱动的时候也就是调用open方法,参数为/dev/binder的时候,最终会调用到binder_open方法,在该方法中,会根据用户空间进程pid等创建对应的binder_proc。
对了用户空间的binder相关的系统调用方法在binder驱动都有对应的方法,比如ioctl对应binder_ioctl、open对应binder_open、mmap对应binder_mmap。
binder_thread
既然用户空间打开binder驱动的进程会存在对应的binder_proc,那进程中与binder驱动交互的线程也存在对应的binder_thread,情书关于这点你明白吗?
情书挠挠头想了想,好像有点思路急忙说到:“我好像有印象,我记得在矮挫丑是在他的主线程里面调用了发送情书的方法,那这个主线程也会存在对应的binder_thread是吗?”
binder驱动:“是的没错,在哪个线程里面进行了binder调用,该线程就会被记录到binder_thread,这里记录这些线程的作用是为了回复做准备。“
”当然除了这个还有用户空间的IPCThreadState启动的binder线程,它们也会被记录到binder_thread,为啥要叫它们为binder线程呢?因为它们主要的作用就是与我binder驱动进行通信的,这里记录binder线程的作用可就多了,比如binder线程离开会发送消息给我,binder线程是否是主binder线程也会发送消息给我。在App打开的时候,IPCThreadState会创建一个主binder线程,这个线程不会死掉,会一直循环下去,它可以监听binder驱动发送上来的消息比如启动一个binder线程,则启动成功后会发送消息通知binder驱动。App进程和systemserver进程启动的最大binder线程数是不一样的。超过了最大启动binder线程数,就不能再启动binder线程了。”
同样上干货,看它的关键属性:
struct binder_thread {
//指向它的binder_proc
struct binder_proc *proc;
struct rb_node rb_node;
struct list_head waiting_thread_node;
//线程id
int pid;
//是否是循环的binder线程
int looper; /* only modified by this thread */
bool looper_need_return; /* can be written by other thread */
struct binder_transaction *transaction_stack;
//todo队列
struct list_head todo;
省略属性......
};
binder_node
binder_node其实是对用户空间的BBinder类的一个封装,其中ptr和cookie属性指向了BBinder对象,如下是它的关键属性
struct binder_node {
省略属性......
//指向binder_proc
struct binder_proc *proc;
//所有的binder_ref
省略属性......
//下面两个属性都指向用户空间的BBinder
binder_uintptr_t ptr;
binder_uintptr_t cookie;
省略属性......
};
何时生成binder_node
binder client调用binder server的某个方法时候,如果该方法的参数中包含了BBinder对象,当参数等数据则进入binder驱动后,binder驱动会检查binder client进程对应的binder_proc的nodes属性中有没有与BBinder对象对应的binder_node,如果没有则会为BBinder生成对应的binder_node,并且放入binder_proc的nodes属性中。
上面的一坨话比较枯燥,那就举个例子比如ActivityManagerService把自己放入ServiceManager中,会在systemserver进程中调用IServiceManager的addService方法,参数为activity和ActivityManagerService的实例,因为ServiceManager是一个binder server它在servicemanager进程,因此调用addService方法会进入binder驱动层,binder驱动层会检测systemserver进程对应的binder_proc的nodes属性中有没有ActivityManagerService实例对应的binder_node,如果没有则会为ActivityManagerService实例生成对应的binder_node,并且放入binder_proc的nodes属性中。(这个例子里binder client是在systemserver进程,binder server是在servicemanager进程,ActivityManagerService继承了Binder,Binder类其实又是对native层BBinder的封装)
情书:“哦,我明白了,我曾经见过白富美调用了ServiceManager的addService方法,那它对应的binder_node就在它的binder_proc的nodes属性中存在了。”
binder驱动:“是的,非常正确,当然了并不是只有调用ServiceManager的addService方法才会生成binder_node,只要进行binder通信的方法都可以,比如在调用startActivity方法的时候,往Intent对象中放入一个Binder对象,这时候binder驱动也会为这个Binder对象生成一个binder_node,只不过这时候的binder server是匿名的。”
binder_ref
那binder_ref就是对用户空间的BpBinder的封装,它的data.desc属性与BpBinder的handle属性是一样的。如下是它的关键属性
struct binder_ref_data {
int debug_id;
//它与用户空间的BpBinder的handle是一致的
uint32_t desc;
int strong;
int weak;
};
struct binder_ref {
//指向上面的binder_ref_data
struct binder_ref_data data;
省略属性......
//指向binder_proc
struct binder_proc *proc;
//指向binder_node
struct binder_node *node;
struct binder_ref_death *death;
};
何时生成binder_ref
binder client调用binder server的某个方法时候,如果该方法的参数中包含了BBinder或者BpBinder对象,当参数等数据则进入binder驱动后,binder驱动会检查binder server进程对应的binder_proc的refs_by_node属性中有没有与BBinder或者BpBinder对象对应的binder_ref,如果没有则会为BBinder或者BpBinder对象生成对应的binder_ref,并且放入binder_proc的refs_by_node和refs_by_desc属性中。
上面的一坨话比较枯燥,那就还是上面ActivityManagerService把自己放入ServiceManager中的例子,activity和ActivityManagerService的实例数据进入binder驱动后,binder驱动会检测servicemanager进程对应的binder_proc的refs_by_node属性中有没有ActivityManagerService实例对应的binder_ref,如果没有则会为ActivityManagerService实例生成对应的binder_ref,并且放入binder_proc的refs_by_node和refs_by_desc属性中。而ServiceManager的addService方法被调用后,这时候的参数为变为activity和BpBinder对象,这时候的BpBinder它的handle值就是binderr_ref的desc值。
或者可以这样说,java层的BinderProxy对象或者native层的BpBinder对象它们的初始化,是在binder驱动层的对应binder_ref生成后 (它是根据对应的binder_node或者binder_ref),在根据binder_ref的data.desc属性值构造BpBinder对象,BpBInder对象构造成功后,根据BpBinder在构造BinderProxy对象。
情书:“哈哈,我明白了,和我一起的小伙伴handle,它其实已经在矮挫丑进程的binder_proc的的refs_by_node和refs_by_desc属性中已经存在对应的binder_ref了,并且它的data.desc与handle是一样的。”
binder驱动:“你真的太聪明了,赞。”
小结
用一张图展示下binder_proc、binder_node、binder_ref与用户空间进程的关系
查找目标思路
binder驱动:“关键数据结构介绍完毕,有了这个基础那我就来讲下查找目标的思路。”
找到当前用户空间进程的binder_proc (当前进程比如矮挫丑进程) 根据handle从binder_proc的refs_by_desc属性中查找对应的binder_ref 若找到binder_ref,binder_ref的node属性就是目标binder_node,这个binder_node的ptr和cookie属性就指向用户空间的BBinder (BBinder就是用户空间native层或者java层的binder server) 根据binder_node也可以找到目标binder_proc,目标binder_proc可以知道用户空间目标进程的一些信息。
查找目标
binder驱动对情书说:“有了上面的思路,那我就先暂时让binder_ioctl方法来帮助你找白富美吧。我会把file地址、BINDER_WRITE_READ和binder_write_read对象的地址交给binder_ioctl。我一会儿就过来。”
情书:“谢谢了binder驱动。”
binder_ioctl对情书说:“准备好了吧,那咱们就开始吧,首先需要从file的private_data属性中可以拿到你的进程的binder_proc。”
情书:“冒昧的问一句,这个file是啥?”
“它啊,你还记得调用ioctl方法的时候需要传递一个mDriverFD的文件描述符吗?mDriverFD会被转换为对应的file,file里面存储了很多文件相关的属性,当用户空间进程在打开binder驱动的时候生成的binder_proc会存放在file的private_data属性中。这样凡是用户空间进程在调用binder相关的系统调用函数的时候如mmap、ioctl都需要携带mDriverFD,相关的函数如binder_mmap、binder_ioctl都会从file的private_data中拿到binder_proc。”
“那我们接着继续,这时候我还做了一件事情就是生成binder_thread,它会被放置在binder_proc的threads属性中,生成binder_thread的作用是为了等待binder server的回复。”
这时binder驱动对binder_ioctl说:“我忙完了,剩下的过程就交给我来说吧。”
binder驱动:“情书你应该还记得你的小伙伴binder_write_read对象吧,它的write_buffer属性中存放了BC_TRANSACTION和binder_transaction_data,而code、handle以及情书你都在binder_transaction_data内,那我们接下来的事情就是拆包,从binder_transaction_data把这些数据拆出来。”
“从binder_transaction_data中拆出handle后就可以依据上面的查找思路找到目标binder_node和目标binder_proc了。”
预告
要想知道白富美是否接受了矮挫丑,请看下篇分解。