Android 大话binder通信 (上)

戳蓝字"牛晓伟"关注我哦!

用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章

本文摘要

用故事的方式把binder通信的整个过程都描述出来,binder通信都经历了哪些节点,在这些节点上的数据有哪些变化,同时还对binder通信的关键细节进行介绍。通过本文您能对binder通信整体和细节都有一个全面的认识,比如startActivity方法到底都经历了哪些过程。(文章基于Android13代码分析)

下面是我以前写的binder通信的几篇文章,欢迎大家取阅:

通熟易懂分析binder:1.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 clientbinder 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。就这么简单,帮我实现下吧。"

"太简单了,看我的表演。"

疯狂写代码中......

完了,看下面代码

java 复制代码
//定义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 方法就是接收情书的方法,可以通过BindergetCallingPid 方法获取追求者的pid,可以通过BIndergetCallingUid方法可以获取追求者的uid,你就可以根据这些值来做权限判断了,读取情书以及返回哪些值,这得你自己来实现了。"

"忘记了,还有非常重要的一步:需要调用ServiceManageraddService 方法把你的BeautifuLGirl 对象进行添加,因为ServiceManager 也是一个具名binder server,它是处于servicemanager 进程,因此在调用它的方法的时候需要特殊处理,下面的伪代码会把你的BeautifuLGirl 类实例添加到Servicemanager " (当然app是不可以调用ServiceManageraddService 方法的,这里是为了说明问题把白富美当成一个系统的进程)

添加BeautifuLGirlServiceManager的伪代码:

less 复制代码
//获取ServiceManager实现了接口IServiceManager,而下面的方法获取的IServiceManager是一个代理类
IServiceManager serviceManager = ServiceManagerNative.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));

//添加BeautifuLGirl
serviceManager.addService("beautiful",new BeautifulGirl());

如上代码,通过字符串beautifulBeautifulGirl 实例添加到了ServiceManager中。

如何使用?

白富美 :"那我的追求者如何使用呢?" binder :"你是这么的美丽动人,我觉得你不输任何明星,明星们都有自己的经纪人,那我也为你量身定做了一个'代理人',他的名字就是IBeautifulGirl.Proxy ,从名字上可以看出它使用了代理模式,只要你有的功能他都有。下面是如何使用的伪代码。"

ini 复制代码
/获取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类有啥接口,我同样也有。"

typescript 复制代码
//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了。"

情书不知所措的说:"为啥啊?一路上咱们不是相处的很好嘛。"

"是这样的,把你送到白富美 的路上需要很多的小伙伴来参与,我的使命暂时告一段落了,当然我也会等待白富美 的回复,我会把回复交给你的主人矮挫丑。"

"当然还有非常重要的一个事情要做,为了让你路上不孤单,给你找了几个小伙伴,如下"

  1. 其中有methodCode 它是int类型的,它的值是TRANSACTION_receiveLoveLetter白富美根据它就可以知道调用哪个方法了
  2. _data 它是Parcel 类型的,为了让你能顺利的进入别的进程,会调用它的writeString方法把你序列化,当然在这里你是非常的安全的
  3. _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。

同样也用一张图代表情书传递过程中经历的方法和参数的变化

如下是相关代码,请自行取阅:

scss 复制代码
//文件路径: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 这个家伙像个骗子,你看前面的BinderProxyBpBinder 最起码从名字上就能看出来和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类型;而后面的...一般是一个指向数据的指针。

arduino 复制代码
int ioctl(int fd, unsigned long request, ...);

IPCThreadState 与binder驱动发送信息就是用的是ioctl这个系统调用函数。

介绍了科普知识后,那我在来介绍一些数据结构和cmd吧。

数据结构

大家在使用socket 通信的时候,serverclient 双方是不是要约定好一个cmd + 数据结构 ,cmd代表要执行哪些操作,而数据结构则是执行这些操作要使用到的数据。同理使用ioctl 函数与binder驱动进行通信的时候也需要定义这样的cmd + 数据结构 ,那我与binder驱动定义了 BINDER_WRITE_READ + binder_write_readBINDER_THREAD_EXIT + 0BINDER_FREEZE + binder_freeze_info 等cmd和数据结构,而在binder通信中最常用的就是BINDER_WRITE_READ + binder_write_read这套组合。

那就先来介绍下binder_write_read这个数据结构

1 binder_write_read

如下是它的定义

arduino 复制代码
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_dataBC_REPLY + binder_transaction_data 这俩组合是经常要用到的,那来介绍下binder_transaction_data数据结构吧。

2 binder_transaction_data

如下是它的定义

arduino 复制代码
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;
};

如上代码,这里主要介绍几个关键的属性:

  1. targetunion 类型的,它的值是handle 或者ptr ,看到handle不知道你是否有印象。

情书 拖着腮帮想了想:"知道的,BpBinder 说过它有一个非常重要的属性说的就是它,还有BpBinder 在把我和我的小伙伴交给你的时候也把自己的handle 交给了你。也就是说handle 其实对应的是上层的BpBinder 类,而ptr 则对应的就是上层的BBinder类了?"

"都会推测了,你说的非常对。ptr它是一个地址"

  1. cookie 它其实对应的是上层的BBinder 类,它也是一个地址,这个在白富美那边会涉及到。
  2. code这个你肯定也能猜出来吧

情书 :"那它肯定是我的小伙伴TRANSACTION_receiveLoveLetter了。"

"没错,code就是指向了方法对应的值,这样在binder server端会根据code值来进入对应的方法。"

  1. data_sizeoffsets_sizedata 就是对应的类型为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。

如下是相关代码,请自行取阅:

ini 复制代码
//文件路径:/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驱动的代码:

ini 复制代码
//文件路径:/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/binderProcessState 类的实例在一个进程中是只存在一个,在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驱动,我可是等待着白富美的回复呢,如下是等待回复代码:

c 复制代码
//文件路径:/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没有来到内核,但是它又觉得不能这样想事情,因为它还多了三个小伙伴mDriverFDBINDER_WRITE_READbinder_write_read对象的地址。

突然一个声音打断了它的思绪:"快点来个干活儿的,从用户空间又来了一批数据,快点先根据mDriverFD 找到对应的file 结构,再把找到的file 地址、BC_TRANSACTIONbinder_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之间是可以互相引用的。

说了这么多上干货吧,来看下它的数据结构的关键属性吧

arduino 复制代码
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_threadbinder_nodebinder_ref

何时创建binder_proc

在用户空间进程打开驱动的时候也就是调用open 方法,参数为/dev/binder 的时候,最终会调用到binder_open 方法,在该方法中,会根据用户空间进程pid等创建对应的binder_proc

对了用户空间的binder相关的系统调用方法在binder驱动都有对应的方法,比如ioctl 对应binder_ioctlopen 对应binder_openmmap 对应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线程了。"

同样上干货,看它的关键属性:

arduino 复制代码
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 类的一个封装,其中ptrcookie 属性指向了BBinder对象,如下是它的关键属性

arduino 复制代码
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_procnodes 属性中有没有与BBinder 对象对应的binder_node ,如果没有则会为BBinder 生成对应的binder_node ,并且放入binder_procnodes属性中。

上面的一坨话比较枯燥,那就举个例子比如ActivityManagerService 把自己放入ServiceManager 中,会在systemserver 进程中调用IServiceManageraddService 方法,参数为activityActivityManagerService 的实例,因为ServiceManager 是一个binder server它在servicemanager 进程,因此调用addService 方法会进入binder驱动层,binder驱动层会检测systemserver 进程对应的binder_procnodes 属性中有没有ActivityManagerService 实例对应的binder_node ,如果没有则会为ActivityManagerService 实例生成对应的binder_node ,并且放入binder_procnodes 属性中。(这个例子里binder client是在systemserver进程,binder server是在servicemanager进程,ActivityManagerService 继承了BinderBinder 类其实又是对native层BBinder的封装)

情书 :"哦,我明白了,我曾经见过白富美 调用了ServiceManageraddService 方法,那它对应的binder_node 就在它的binder_procnodes属性中存在了。"

binder驱动 :"是的,非常正确,当然了并不是只有调用ServiceManageraddService 方法才会生成binder_node ,只要进行binder通信的方法都可以,比如在调用startActivity 方法的时候,往Intent 对象中放入一个Binder 对象,这时候binder驱动也会为这个Binder 对象生成一个binder_node,只不过这时候的binder server是匿名的。"

binder_ref

binder_ref 就是对用户空间的BpBinder 的封装,它的data.desc 属性与BpBinderhandle属性是一样的。如下是它的关键属性

objectivec 复制代码
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_procrefs_by_node 属性中有没有与BBinder 或者BpBinder 对象对应的binder_ref ,如果没有则会为BBinder 或者BpBinder 对象生成对应的binder_ref ,并且放入binder_procrefs_by_noderefs_by_desc属性中。

上面的一坨话比较枯燥,那就还是上面ActivityManagerService 把自己放入ServiceManager 中的例子,activityActivityManagerService 的实例数据进入binder驱动后,binder驱动会检测servicemanager 进程对应的binder_procrefs_by_node 属性中有没有ActivityManagerService 实例对应的binder_ref ,如果没有则会为ActivityManagerService 实例生成对应的binder_ref ,并且放入binder_procrefs_by_noderefs_by_desc 属性中。而ServiceManageraddService 方法被调用后,这时候的参数为变为activityBpBinder 对象,这时候的BpBinder 它的handle 值就是binderr_refdesc值。

或者可以这样说,java层的BinderProxy 对象或者native层的BpBinder 对象它们的初始化,是在binder驱动层的对应binder_ref 生成后 (它是根据对应的binder_node 或者binder_ref ),在根据binder_refdata.desc 属性值构造BpBinder 对象,BpBInder 对象构造成功后,根据BpBinder 在构造BinderProxy对象。

情书 :"哈哈,我明白了,和我一起的小伙伴handle ,它其实已经在矮挫丑 进程的binder_proc 的的refs_by_noderefs_by_desc 属性中已经存在对应的binder_ref 了,并且它的data.deschandle是一样的。"

binder驱动:"你真的太聪明了,赞。"

小结

用一张图展示下binder_procbinder_nodebinder_ref与用户空间进程的关系

查找目标思路

binder驱动:"关键数据结构介绍完毕,有了这个基础那我就来讲下查找目标的思路。"

  1. 找到当前用户空间进程的binder_proc (当前进程比如矮挫丑进程)
  2. 根据handlebinder_procrefs_by_desc 属性中查找对应的binder_ref
  3. 若找到binder_refbinder_refnode 属性就是目标binder_node ,这个binder_nodeptrcookie 属性就指向用户空间的BBinder (BBinder就是用户空间native层或者java层的binder server)
  4. 根据binder_node 也可以找到目标binder_proc ,目标binder_proc可以知道用户空间目标进程的一些信息。

查找目标

binder驱动情书 说:"有了上面的思路,那我就先暂时让binder_ioctl 方法来帮助你找白富美 吧。我会把file 地址、BINDER_WRITE_READbinder_write_read 对象的地址交给binder_ioctl。我一会儿就过来。"

情书 :"谢谢了binder驱动。"

binder_ioctl情书 说:"准备好了吧,那咱们就开始吧,首先需要从fileprivate_data 属性中可以拿到你的进程的binder_proc。"

情书 :"冒昧的问一句,这个file是啥?"

"它啊,你还记得调用ioctl 方法的时候需要传递一个mDriverFD 的文件描述符吗?mDriverFD 会被转换为对应的filefile 里面存储了很多文件相关的属性,当用户空间进程在打开binder驱动的时候生成的binder_proc 会存放在fileprivate_data 属性中。这样凡是用户空间进程在调用binder相关的系统调用函数的时候如mmapioctl 都需要携带mDriverFD ,相关的函数如binder_mmapbinder_ioctl 都会从fileprivate_data 中拿到binder_proc。"

"那我们接着继续,这时候我还做了一件事情就是生成binder_thread ,它会被放置在binder_procthreads 属性中,生成binder_thread的作用是为了等待binder server的回复。"

这时binder 驱动对binder_ioctl说:"我忙完了,剩下的过程就交给我来说吧。"

binder驱动 :"情书 你应该还记得你的小伙伴binder_write_read 对象吧,它的write_buffer 属性中存放了BC_TRANSACTIONbinder_transaction_data ,而codehandle 以及情书 你都在binder_transaction_data 内,那我们接下来的事情就是拆包,从binder_transaction_data把这些数据拆出来。"

"从binder_transaction_data 中拆出handle 后就可以依据上面的查找思路找到目标binder_node 和目标binder_proc了。"

预告

要想知道白富美 是否接受了矮挫丑,请看下篇分解。

欢迎关注我的公众号 --牛晓伟(搜索或者点击牛晓伟链接)

相关推荐
CYRUS_STUDIO1 小时前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO1 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴5 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我5 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止6 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭6 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech6 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831676 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥6 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin