标签: Android Binder

Android 基于Message的进程间通信 Messenger完全解析

Android 基于Message的进程间通信 Messenger完全解析

1、概述

Binder能干什么?Binder可以提供系统中任何程序都可以访问的全局服务。这个功能当然是任何系统都应该提供的,下面我们简单看一下Android的Binder的框架

Android Binder框架分为服务器接口、Binder驱动、以及客户端接口;简单想一下,需要提供一个全局服务,那么全局服务那端即是服务器接口,任何程序即客户端接口,它们之间通过一个Binder驱动访问。

服务器端接口:实际上是Binder类的对象,该对象一旦创建,内部则会启动一个隐藏线程,会接收Binder驱动发送的消息,收到消息后,会执行Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务器端代码。

Binder驱动:该对象也为Binder类的实例,客户端通过该对象访问远程服务。

客户端接口:获得Binder驱动,调用其transact()发送消息至服务器

如果大家对上述不了解,没关系,下面会通过例子来更好的说明,实践是检验真理的唯一标准嘛

2、AIDL的使用

如果对Android比较熟悉,那么一定使用过AIDL,如果你还不了解,那么也没关系,下面会使用一个例子展示AIDL的用法。

我们使用AIDL实现一个跨进程的加减法调用

1、服务端

新建一个项目,创建一个包名:com.zhy.calc.aidl,在包内创建一个ICalcAIDL文件:

  1. package com.zhy.calc.aidl;  
  2. interface ICalcAIDL  
  3. {
  4.     int add(int x , int y);  
  5.     int min(int x , int y );  
  6. }

注意,文件名为ICalcAIDL.aidl

然后在项目的gen目录下会生成一个ICalcAIDL.Java文件,暂时不贴这个文件的代码了,后面会详细说明

然后我们在项目中新建一个Service,代码如下:

  1. package com.example.zhy_binder;  
  2. import com.zhy.calc.aidl.ICalcAIDL;  
  3. import android.app.Service;  
  4. import android.content.Intent;  
  5. import android.os.IBinder;  
  6. import android.os.RemoteException;  
  7. import android.util.Log;  
  8. public class CalcService extends Service  
  9. {
  10.     private static final String TAG = “server”;  
  11.     public void onCreate()  
  12.     {
  13.         Log.e(TAG, “onCreate”);  
  14.     }
  15.     public IBinder onBind(Intent t)  
  16.     {
  17.         Log.e(TAG, “onBind”);  
  18.         return mBinder;  
  19.     }
  20.     public void onDestroy()  
  21.     {
  22.         Log.e(TAG, “onDestroy”);  
  23.         super.onDestroy();  
  24.     }
  25.     public boolean onUnbind(Intent intent)  
  26.     {
  27.         Log.e(TAG, “onUnbind”);  
  28.         return super.onUnbind(intent);  
  29.     }
  30.     public void onRebind(Intent intent)  
  31.     {
  32.         Log.e(TAG, “onRebind”);  
  33.         super.onRebind(intent);  
  34.     }
  35.     private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub()  
  36.     {
  37.         @Override  
  38.         public int add(int x, int y) throws RemoteException  
  39.         {
  40.             return x + y;  
  41.         }
  42.         @Override  
  43.         public int min(int x, int y) throws RemoteException  
  44.         {
  45.             return x – y;  
  46.         }
  47.     };
  48. }

在此Service中,使用生成的ICalcAIDL创建了一个mBinder的对象,并在Service的onBind方法中返回

*后记得在AndroidManifest中注册

  1. <service android:name=“com.example.zhy_binder.CalcService” >  
  2.            <intent-filter>  
  3.                <action android:name=“com.zhy.aidl.calc” />  
  4.                <category android:name=“android.intent.category.DEFAULT” />  
  5.            </intent-filter>  
  6.        </service>  

这里我们指定了一个name,因为我们一会会在别的应用程序中通过Intent来查找此Service;这个不需要Activity,所以我也就没写Activity,安装完成也看不到安装图标,悄悄在后台运行着。

到此,服务端编写完毕。下面开始编写客户端

2、客户端

客户端的代码比较简单,创建一个布局,里面包含4个按钮,分别为绑定服务,解除绑定,调用加法,调用减法

布局文件:

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     android:orientation=“vertical” >  
  6.     <Button  
  7.         android:layout_width=“fill_parent”  
  8.         android:layout_height=“wrap_content”  
  9.         android:onClick=“bindService”  
  10.         android:text=“BindService” />  
  11.     <Button  
  12.         android:layout_width=“fill_parent”  
  13.         android:layout_height=“wrap_content”  
  14.         android:onClick=“unbindService”  
  15.         android:text=“UnbindService” />  
  16.     <Button  
  17.         android:layout_width=“fill_parent”  
  18.         android:layout_height=“wrap_content”  
  19.         android:onClick=“addInvoked”  
  20.         android:text=“12+12” />  
  21.     <Button  
  22.         android:layout_width=“fill_parent”  
  23.         android:layout_height=“wrap_content”  
  24.         android:onClick=“minInvoked”  
  25.         android:text=“50-12” />  
  26. </LinearLayout>  

主Activity

  1. package com.example.zhy_binder_client;  
  2. import android.app.Activity;  
  3. import android.content.ComponentName;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.content.ServiceConnection;  
  7. import android.os.Bundle;  
  8. import android.os.IBinder;  
  9. import android.util.Log;  
  10. import android.view.View;  
  11. import android.widget.Toast;  
  12. import com.zhy.calc.aidl.ICalcAIDL;  
  13. public class MainActivity extends Activity  
  14. {
  15.     private ICalcAIDL mCalcAidl;  
  16.     private ServiceConnection mServiceConn = new ServiceConnection()  
  17.     {
  18.         @Override  
  19.         public void onServiceDisconnected(ComponentName name)  
  20.         {
  21.             Log.e(“client”, “onServiceDisconnected”);  
  22.             mCalcAidl = null;  
  23.         }
  24.         @Override  
  25.         public void onServiceConnected(ComponentName name, IBinder service)  
  26.         {
  27.             Log.e(“client”, “onServiceConnected”);  
  28.             mCalcAidl = ICalcAIDL.Stub.asInterface(service);
  29.         }
  30.     };
  31.     @Override  
  32.     protected void onCreate(Bundle savedInstanceState)  
  33.     {
  34.         super.onCreate(savedInstanceState);  
  35.         setContentView(R.layout.activity_main);
  36.     }
  37.     /** 
  38.      * 点击BindService按钮时调用 
  39.      * @param view 
  40.      */  
  41.     public void bindService(View view)  
  42.     {
  43.         Intent intent = new Intent();  
  44.         intent.setAction(“com.zhy.aidl.calc”);  
  45.         bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
  46.     }
  47.     /** 
  48.      * 点击unBindService按钮时调用 
  49.      * @param view 
  50.      */  
  51.     public void unbindService(View view)  
  52.     {
  53.         unbindService(mServiceConn);
  54.     }
  55.     /** 
  56.      * 点击12+12按钮时调用 
  57.      * @param view 
  58.      */  
  59.     public void addInvoked(View view) throws Exception  
  60.     {
  61.         if (mCalcAidl != null)  
  62.         {
  63.             int addRes = mCalcAidl.add(12, 12);  
  64.             Toast.makeText(this, addRes + “”, Toast.LENGTH_SHORT).show();  
  65.         } else  
  66.         {
  67.             Toast.makeText(this, “服务器被异常杀死,请重新绑定服务端”, Toast.LENGTH_SHORT)  
  68.                     .show();
  69.         }
  70.     }
  71.     /** 
  72.      * 点击50-12按钮时调用 
  73.      * @param view 
  74.      */  
  75.     public void minInvoked(View view) throws Exception  
  76.     {
  77.         if (mCalcAidl != null)  
  78.         {
  79.             int addRes = mCalcAidl.min(58, 12);  
  80.             Toast.makeText(this, addRes + “”, Toast.LENGTH_SHORT).show();  
  81.         } else  
  82.         {
  83.             Toast.makeText(this, “服务端未绑定或被异常杀死,请重新绑定服务端”, Toast.LENGTH_SHORT)  
  84.                     .show();
  85.         }
  86.     }
  87. }

很标准的绑定服务的代码。

直接看运行结果:

%title插图%num

我们首先点击BindService按钮,查看log

  1. 08-09 22:56:38.959: E/server(29692): onCreate
  2. 08-09 22:56:38.959: E/server(29692): onBind
  3. 08-09 22:56:38.959: E/client(29477): onServiceConnected

可以看到,点击BindService之后,服务端执行了onCreate和onBind的方法,并且客户端执行了onServiceConnected方法,标明服务器与客户端已经联通

然后点击12+12,50-12可以成功的调用服务端的代码并返回正确的结果

下面我们再点击unBindService

  1. 08-09 22:59:25.567: E/server(29692): onUnbind
  2. 08-09 22:59:25.567: E/server(29692): onDestroy

由于我们当前只有一个客户端绑定了此Service,所以Service调用了onUnbind和onDestory

然后我们继续点击12+12,50-12,通过上图可以看到,依然可以正确执行,也就是说即使onUnbind被调用,连接也是不会断开的,那么什么时候会端口呢?

即当服务端被异常终止的时候,比如我们现在在手机的正在执行的程序中找到该服务:

%title插图%num

点击停止,此时查看log

  1. 08-09 23:04:21.433: E/client(30146): onServiceDisconnected

可以看到调用了onServiceDisconnected方法,此时连接被断开,现在点击12+12,50-12的按钮,则会弹出Toast服务端断开的提示。

说了这么多,似乎和Binder框架没什么关系,下面我们来具体看一看AIDL为什么做了些什么。

3、分析AIDL生成的代码

1、服务端

先看服务端的代码,可以看到我们服务端提供的服务是由

  1. private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub()  
  2.     {
  3.         @Override  
  4.         public int add(int x, int y) throws RemoteException  
  5.         {
  6.             return x + y;  
  7.         }
  8.         @Override  
  9.         public int min(int x, int y) throws RemoteException  
  10.         {
  11.             return x – y;  
  12.         }
  13.     };

ICalcAILD.Stub来执行的,让我们来看看Stub这个类的声明:

  1. public static abstract class Stub extends android.os.Binder implements com.zhy.calc.aidl.ICalcAIDL  

清楚的看到这个类是Binder的子类,是不是符合我们文章开通所说的服务端其实是一个Binder类的实例

接下来看它的onTransact()方法:

  1. @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException  
  2. {
  3. switch (code)  
  4. {
  5. case INTERFACE_TRANSACTION:  
  6. {
  7. reply.writeString(DESCRIPTOR);
  8. return true;  
  9. }
  10. case TRANSACTION_add:  
  11. {
  12. data.enforceInterface(DESCRIPTOR);
  13. int _arg0;  
  14. _arg0 = data.readInt();
  15. int _arg1;  
  16. _arg1 = data.readInt();
  17. int _result = this.add(_arg0, _arg1);  
  18. reply.writeNoException();
  19. reply.writeInt(_result);
  20. return true;  
  21. }
  22. case TRANSACTION_min:  
  23. {
  24. data.enforceInterface(DESCRIPTOR);
  25. int _arg0;  
  26. _arg0 = data.readInt();
  27. int _arg1;  
  28. _arg1 = data.readInt();
  29. int _result = this.min(_arg0, _arg1);  
  30. reply.writeNoException();
  31. reply.writeInt(_result);
  32. return true;  
  33. }
  34. }
  35. return super.onTransact(code, data, reply, flags);  
  36. }

文章开头也说到服务端的Binder实例会根据客户端依靠Binder驱动发来的消息,执行onTransact方法,然后由其参数决定执行服务端的代码。

可以看到onTransact有四个参数

code , data ,replay , flags

code 是一个整形的唯一标识,用于区分执行哪个方法,客户端会传递此参数,告诉服务端执行哪个方法

data客户端传递过来的参数

replay服务器返回回去的值

flags标明是否有返回值,0为有(双向),1为没有(单向)

我们仔细看case TRANSACTION_min中的代码

data.enforceInterface(DESCRIPTOR);与客户端的writeInterfaceToken对用,标识远程服务的名称

int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();

接下来分别读取了客户端传入的两个参数

int _result = this.min(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);

然后执行this.min,即我们实现的min方法;返回result由reply写回。

add同理,可以看到服务端通过AIDL生成Stub的类,封装了服务端本来需要写的代码。

2、客户端

客户端主要通过ServiceConnected与服务端连接

  1. private ServiceConnection mServiceConn = new ServiceConnection()  
  2.     {
  3.         @Override  
  4.         public void onServiceDisconnected(ComponentName name)  
  5.         {
  6.             Log.e(“client”, “onServiceDisconnected”);  
  7.             mCalcAidl = null;  
  8.         }
  9.         @Override  
  10.         public void onServiceConnected(ComponentName name, IBinder service)  
  11.         {
  12.             Log.e(“client”, “onServiceConnected”);  
  13.             mCalcAidl = ICalcAIDL.Stub.asInterface(service);
  14.         }
  15.     };

如果你比较敏锐,应该会猜到这个onServiceConnected中的IBinder实例,其实就是我们文章开通所说的Binder驱动,也是一个Binder实例

在ICalcAIDL.Stub.asInterface中*终调用了:

  1. return new com.zhy.calc.aidl.ICalcAIDL.Stub.Proxy(obj);  

这个Proxy实例传入了我们的Binder驱动,并且封装了我们调用服务端的代码,文章开头说,客户端会通过Binder驱动的transact()方法调用服务端代码

直接看Proxy中的add方法

  1. @Override public int add(int x, int y) throws android.os.RemoteException  
  2. {
  3. android.os.Parcel _data = android.os.Parcel.obtain();
  4. android.os.Parcel _reply = android.os.Parcel.obtain();
  5. int _result;  
  6. try {  
  7. _data.writeInterfaceToken(DESCRIPTOR);
  8. _data.writeInt(x);
  9. _data.writeInt(y);
  10. mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);  
  11. _reply.readException();
  12. _result = _reply.readInt();
  13. }
  14. finally {  
  15. _reply.recycle();
  16. _data.recycle();
  17. }
  18. return _result;  
  19. }

首先声明两个Parcel对象,一个用于传递数据,一个用户接收返回的数据

_data.writeInterfaceToken(DESCRIPTOR);与服务器端的enforceInterfac对应

_data.writeInt(x);
_data.writeInt(y);写入需要传递的参数

mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

终于看到了我们的transact方法,*个对应服务端的code,_data,_repay分别对应服务端的data,reply,0表示是双向的

_reply.readException();
_result = _reply.readInt();

*后读出我们服务端返回的数据,然后return。可以看到和服务端的onTransact基本是一行一行对应的。

到此,我们已经通过AIDL生成的代码解释了Android Binder框架的工作原理。Service的作用其实就是为我们创建Binder驱动,即服务端与客户端连接的桥梁。

AIDL其实通过我们写的aidl文件,帮助我们生成了一个接口,一个Stub类用于服务端,一个Proxy类用于客户端调用。那么我们是否可以不通过写AIDL来实现远程的通信呢?下面向大家展示如何完全不依赖AIDL来实现客户端与服务端的通信。

4、不依赖AIDL实现程序间通讯

1、服务端代码

我们新建一个CalcPlusService.java用于实现两个数的乘和除

  1. package com.example.zhy_binder;  
  2. import android.app.Service;  
  3. import android.content.Intent;  
  4. import android.os.Binder;  
  5. import android.os.IBinder;  
  6. import android.os.Parcel;  
  7. import android.os.RemoteException;  
  8. import android.util.Log;  
  9. public class CalcPlusService extends Service  
  10. {
  11.     private static final String DESCRIPTOR = “CalcPlusService”;  
  12.     private static final String TAG = “CalcPlusService”;  
  13.     public void onCreate()  
  14.     {
  15.         Log.e(TAG, “onCreate”);  
  16.     }
  17.     @Override  
  18.     public int onStartCommand(Intent intent, int flags, int startId)  
  19.     {
  20.         Log.e(TAG, “onStartCommand”);  
  21.         return super.onStartCommand(intent, flags, startId);  
  22.     }
  23.     public IBinder onBind(Intent t)  
  24.     {
  25.         Log.e(TAG, “onBind”);  
  26.         return mBinder;  
  27.     }
  28.     public void onDestroy()  
  29.     {
  30.         Log.e(TAG, “onDestroy”);  
  31.         super.onDestroy();  
  32.     }
  33.     public boolean onUnbind(Intent intent)  
  34.     {
  35.         Log.e(TAG, “onUnbind”);  
  36.         return super.onUnbind(intent);  
  37.     }
  38.     public void onRebind(Intent intent)  
  39.     {
  40.         Log.e(TAG, “onRebind”);  
  41.         super.onRebind(intent);  
  42.     }
  43.     private MyBinder mBinder = new MyBinder();  
  44.     private class MyBinder extends Binder  
  45.     {
  46.         @Override  
  47.         protected boolean onTransact(int code, Parcel data, Parcel reply,  
  48.                 int flags) throws RemoteException  
  49.         {
  50.             switch (code)  
  51.             {
  52.             case 0x110:  
  53.             {
  54.                 data.enforceInterface(DESCRIPTOR);
  55.                 int _arg0;  
  56.                 _arg0 = data.readInt();
  57.                 int _arg1;  
  58.                 _arg1 = data.readInt();
  59.                 int _result = _arg0 * _arg1;  
  60.                 reply.writeNoException();
  61.                 reply.writeInt(_result);
  62.                 return true;  
  63.             }
  64.             case 0x111:  
  65.             {
  66.                 data.enforceInterface(DESCRIPTOR);
  67.                 int _arg0;  
  68.                 _arg0 = data.readInt();
  69.                 int _arg1;  
  70.                 _arg1 = data.readInt();
  71.                 int _result = _arg0 / _arg1;  
  72.                 reply.writeNoException();
  73.                 reply.writeInt(_result);
  74.                 return true;  
  75.             }
  76.             }
  77.             return super.onTransact(code, data, reply, flags);  
  78.         }
  79.     };
  80. }

我们自己实现服务端,所以我们自定义了一个Binder子类,然后复写了其onTransact方法,我们指定服务的标识为CalcPlusService,然后0x110为乘,0x111为除;

记得在AndroidMenifest中注册

  1. <service android:name=“com.example.zhy_binder.CalcPlusService” >  
  2.            <intent-filter>  
  3.                <action android:name=“com.zhy.aidl.calcplus” />  
  4.                <category android:name=“android.intent.category.DEFAULT” />  
  5.            </intent-filter>  
  6.        </service>  

服务端代码结束。

2、客户端代码

单独新建了一个项目,代码和上例很类似

首先布局文件:

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     android:orientation=“vertical” >  
  6.     <Button  
  7.         android:layout_width=“fill_parent”  
  8.         android:layout_height=“wrap_content”  
  9.         android:onClick=“bindService”  
  10.         android:text=“BindService” />  
  11.     <Button  
  12.         android:layout_width=“fill_parent”  
  13.         android:layout_height=“wrap_content”  
  14.         android:onClick=“unbindService”  
  15.         android:text=“UnbindService” />  
  16.     <Button  
  17.         android:layout_width=“fill_parent”  
  18.         android:layout_height=“wrap_content”  
  19.         android:onClick=“mulInvoked”  
  20.         android:text=“50*12” />  
  21.     <Button  
  22.         android:layout_width=“fill_parent”  
  23.         android:layout_height=“wrap_content”  
  24.         android:onClick=“divInvoked”  
  25.         android:text=“36/12” />  
  26. </LinearLayout>  

可以看到加入了乘和除

然后是Activity的代码

  1. package com.example.zhy_binder_client03;  
  2. import android.app.Activity;  
  3. import android.content.ComponentName;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.content.ServiceConnection;  
  7. import android.os.Bundle;  
  8. import android.os.IBinder;  
  9. import android.os.RemoteException;  
  10. import android.util.Log;  
  11. import android.view.View;  
  12. import android.widget.Toast;  
  13. public class MainActivity extends Activity  
  14. {
  15.     private IBinder mPlusBinder;  
  16.     private ServiceConnection mServiceConnPlus = new ServiceConnection()  
  17.     {
  18.         @Override  
  19.         public void onServiceDisconnected(ComponentName name)  
  20.         {
  21.             Log.e(“client”, “mServiceConnPlus onServiceDisconnected”);  
  22.         }
  23.         @Override  
  24.         public void onServiceConnected(ComponentName name, IBinder service)  
  25.         {
  26.             Log.e(“client”, ” mServiceConnPlus onServiceConnected”);  
  27.             mPlusBinder = service;
  28.         }
  29.     };
  30.     @Override  
  31.     protected void onCreate(Bundle savedInstanceState)  
  32.     {
  33.         super.onCreate(savedInstanceState);  
  34.         setContentView(R.layout.activity_main);
  35.     }
  36.     public void bindService(View view)  
  37.     {
  38.         Intent intentPlus = new Intent();  
  39.         intentPlus.setAction(“com.zhy.aidl.calcplus”);  
  40.         boolean plus = bindService(intentPlus, mServiceConnPlus,  
  41.                 Context.BIND_AUTO_CREATE);
  42.         Log.e(“plus”, plus + “”);  
  43.     }
  44.     public void unbindService(View view)  
  45.     {
  46.         unbindService(mServiceConnPlus);
  47.     }
  48.     public void mulInvoked(View view)  
  49.     {
  50.         if (mPlusBinder == null)  
  51.         {
  52.             Toast.makeText(this, “未连接服务端或服务端被异常杀死”, Toast.LENGTH_SHORT).show();  
  53.         } else  
  54.         {
  55.             android.os.Parcel _data = android.os.Parcel.obtain();
  56.             android.os.Parcel _reply = android.os.Parcel.obtain();
  57.             int _result;  
  58.             try  
  59.             {
  60.                 _data.writeInterfaceToken(“CalcPlusService”);  
  61.                 _data.writeInt(50);  
  62.                 _data.writeInt(12);  
  63.                 mPlusBinder.transact(0x110, _data, _reply, 0);  
  64.                 _reply.readException();
  65.                 _result = _reply.readInt();
  66.                 Toast.makeText(this, _result + “”, Toast.LENGTH_SHORT).show();  
  67.             } catch (RemoteException e)  
  68.             {
  69.                 e.printStackTrace();
  70.             } finally  
  71.             {
  72.                 _reply.recycle();
  73.                 _data.recycle();
  74.             }
  75.         }
  76.     }
  77.     public void divInvoked(View view)  
  78.     {
  79.         if (mPlusBinder == null)  
  80.         {
  81.             Toast.makeText(this, “未连接服务端或服务端被异常杀死”, Toast.LENGTH_SHORT).show();  
  82.         } else  
  83.         {
  84.             android.os.Parcel _data = android.os.Parcel.obtain();
  85.             android.os.Parcel _reply = android.os.Parcel.obtain();
  86.             int _result;  
  87.             try  
  88.             {
  89.                 _data.writeInterfaceToken(“CalcPlusService”);  
  90.                 _data.writeInt(36);  
  91.                 _data.writeInt(12);  
  92.                 mPlusBinder.transact(0x111, _data, _reply, 0);  
  93.                 _reply.readException();
  94.                 _result = _reply.readInt();
  95.                 Toast.makeText(this, _result + “”, Toast.LENGTH_SHORT).show();  
  96.             } catch (RemoteException e)  
  97.             {
  98.                 e.printStackTrace();
  99.             } finally  
  100.             {
  101.                 _reply.recycle();
  102.                 _data.recycle();
  103.             }
  104.         }
  105.     }
  106. }

为了明了,我直接在mulInvoked里面写了代码,和服务端都没有抽象出一个接口。首先绑定服务时,通过onServiceConnected得到Binder驱动即mPlusBinder;

然后准备数据,调用transact方法,通过code指定执行服务端哪个方法,代码和上面的分析一致。

下面看运行结果:

%title插图%num

是不是很好的实现了我们两个应用程序间的通讯,并没有使用aidl文件,也从侧面分析了我们上述分析是正确的。

 

好了,就到这里,相信大家看完这篇博文,对aidl和Binder的理解也会更加深刻。

Android Binder 设计与实现 – 设计篇

Android Binder 设计与实现 – 设计篇

关键词

Binder Android IPC Linux 内核 驱动

摘要

Binder是Android系统进程间通信(IPC)方式之一。Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。深入了解Binder并将之与传统IPC做对比有助于我们深入领会进程间通信的实现和性能优化。本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通信模型和Binder通信协议了解Binder的设计需求;然后分别阐述Binder在系统不同部分的表述方式和起的作用;*后还会解释Binder在数据接收端的设计考虑,包括线程池管理,内存映射和等待队列管理等。通过本文对Binder的详细介绍以及与其它IPC通信方式的对比,读者将对Binder的优势和使用Binder作为Android主要IPC方式的原因有深入了解。

1 引言

基于Client-Server的通信方式广泛应用于从互联网和数据库访问到嵌入式手持设备内部通信等各个领域。智能手机平台特别是Android系统中,为了向应用开发者提供丰富多样的功能,这种通信方式更是无处不在,诸如媒体播放,视音频频捕获,到各种让手机更智能的传感器(加速度,方位,温度,光亮度等)都由不同的Server负责管理,应用程序只需做为Client与这些Server建立连接便可以使用这些服务,花很少的时间和精力就能开发出令人眩目的功能。Client-Server方式的广泛采用对进程间通信(IPC)机制是一个挑战。目前linux支持的IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。当然也可以在这些底层机制上架设一套协议来实现Client-Server通信,但这样增加了系统的复杂性,在手机这种条件复杂,资源稀缺的环境下可靠性也难以保证。

另一方面是传输性能。socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。

表 1 各种IPC方式数据拷贝次数

IPC 数据拷贝次数
共享内存 0
Binder 1
Socket/管道/消息队列 2

还有一点是出于安全性考虑。Android作为一个开放式,拥有众多开发者的的平台,应用程序的来源广泛,确保智能终端的安全是非常重要的。终端用户不希望从网上下载的程序在不知情的情况下偷窥隐私数据,连接无线网络,长期操作底层设备导致电池很快耗尽等等。传统IPC没有任何安全措施,完全依赖上层协议来确保。首先传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。

基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。

2 面向对象的 Binder IPC

Binder使用Client-Server通信方式:一个进程作为Server提供诸如视频/音频解码,视频捕获,地址本查询,网络连接等服务;多个进程作为Client向Server发起服务请求,获得所需要的服务。要想实现Client-Server通信据必须实现以下两点:一是server必须有确定的访问接入点或者说地址来接受Client的请求,并且Client可以通过某种途径获知Server的地址;二是制定Command-Reply协议来传输数据。例如在网络通信中Server的访问接入点就是Server主机的IP地址+端口号,传输协议为TCP协议。对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点, Client通过这个‘地址’向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的管道入口,要想和某个Server通信首先必须建立这个管道并获得管道入口。

与其它IPC不同,Binder使用了面向对象的思想来描述作为访问接入点的Binder及其在Client中的入口:Binder是一个实体位于Server中的对象,该对象提供了一套方法用以实现对服务的请求,就象类的成员函数。遍布于client中的入口可以看成指向这个binder对象的‘指针’,一旦获得了这个‘指针’就可以调用该对象的方法访问server。在Client看来,通过Binder‘指针’调用其提供的方法和通过指针调用其它任何本地对象的方法并无区别,尽管前者的实体位于远端Server中,而后者实体位于本地内存中。‘指针’是C++的术语,而更通常的说法是引用,即Client通过Binder的引用访问Server。而软件领域另一个术语‘句柄’也可以用来表述Binder在Client中的存在方式。从通信的角度看,Client中的Binder也可以看作是Server Binder的‘代理’,在本地代表远端Server为Client提供服务。本文中会使用‘引用’或‘句柄’这个两广泛使用的术语。

面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。*诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。

当然面向对象只是针对应用程序而言,对于Binder驱动和内核其它模块一样使用C语言实现,没有类和对象的概念。Binder驱动为面向对象的进程间通信提供底层支持。

3 Binder 通信模型

Binder框架定义了四个角色:Server,Client,ServiceManager(以后简称SMgr)以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,SMgr是域名服务器(DNS),驱动是路由器。

3.1 Binder 驱动

和路由器一样,Binder驱动虽然默默无闻,却是通信的核心。尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的:它工作于内核态,提供open(),mmap(),poll(),ioctl()等标准文件操作,以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。驱动和应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现,不提供read(),write()接口,因为ioctl()灵活方便,且能够一次调用实现先写后读以满足同步交互,而不必分别调用write()和read()。Binder驱动的代码位于linux目录的drivers/misc/binder.c中。

3.2 ServiceManager 与实名Binder

和DNS类似,SMgr的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给SMgr,通知SMgr注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点以及SMgr对实体的引用,将名字及新建的引用打包传递给SMgr。SMgr收数据包后,从中取出名字和引用填入一张查找表中。

细心的读者可能会发现其中的蹊跷:SMgr是一个进程,Server是另一个进程,Server向SMgr注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋。Binder的实现比较巧妙:预先创造一只鸡来孵蛋:SMgr和其它进程同样采用Binder通信,SMgr是Server端,有自己的Binder对象(实体),其它进程都是Client,需要通过这个Binder的引用来实现Binder的注册,查询和获取。SMgr提供的Binder比较特殊,它没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成SMgr时Binder驱动会自动为它创建Binder实体(这就是那只预先造好的鸡)。其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向SMgr注册自己Binder就必需通过0这个引用号和SMgr的Binder通信。类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。要注意这里说的Client是相对SMgr而言的,一个应用程序可能是个提供服务的Server,但对SMgr来说它仍然是个Client。

3.3 Client 获得实名Binder的引用

Server向SMgr注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Client也利用保留的0号引用向SMgr请求访问某个Binder:我申请获得名字叫张三的Binder的引用。SMgr收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象现在有了两个引用:一个位于SMgr中,一个位于发起请求的Client中。如果接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。通过以上过程可以看出,SMgr象个火车票代售点,收集了所有火车的车票,可以通过它购买到乘坐各趟火车的票-得到某个Binder的引用。

3.4 匿名 Binder

并不是所有Binder都需要注册给SMgr广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向SMgr注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。

下图展示了参与Binder通信的所有角色,将在以后章节中一一提到。

binder_overview

图 1 Binder通信示例

4 Binder 协议

Binder协议基本格式是(命令+数据),使用ioctl(fd, cmd, arg)函数实现交互。命令由参数cmd承载,数据由参数arg承载,随cmd不同而不同。下表列举了所有命令及其所对应的数据:

表 2 Binder通信命令字

命令 含义 arg
BINDER_WRITE_READ 该命令向Binder写入或读取数据。参数分为两段:写部分和读部分。如果write_size不为0就先将write_buffer里的数据写入Binder;如果read_size不为0再从Binder中读取数据存入read_buffer中。write_consumed和read_consumed表示操作完成时Binder驱动实际写入或读出的数据个数。 struct binder_write_read {

signed long write_size;

signed long write_consumed;

unsigned long write_buffer;

signed long read_size;

signed long read_consumed;

unsigned long read_buffer;

};

BINDER_SET_MAX_THREADS 该命令告知Binder驱动接收方(通常是Server端)线程池中*大的线程数。由于Client是并发向Server端发送请求的,Server端必须开辟线程池为这些并发请求提供服务。告知驱动线程池的*大值是为了让驱动发现线程数达到该值时不要再命令接收端启动新的线程。 int max_threads;
BINDER_SET_CONTEXT_MGR 将当前进程注册为SMgr。系统中同时只能存在一个SMgr。只要当前的SMgr没有调用close()关闭Binder驱动就不能有别的进程可以成为SMgr。
BINDER_THREAD_EXIT 通知Binder驱动当前线程退出了。Binder会为所有参与Binder通信的线程(包括Server线程池中的线程和Client发出请求的线程)建立相应的数据结构。这些线程在退出时必须通知驱动释放相应的数据结构。
BINDER_VERSION 获得Binder驱动的版本号。

这其中*常用的命令是BINDER_WRITE_READ。该命令的参数包括两部分数据:一部分是向Binder写入的数据,一部分是要从Binder读出的数据,驱动程序先处理写部分再处理读部分。这样安排的好处是应用程序可以很灵活地处理命令的同步或异步。例如若要发送异步命令可以只填入写部分而将read_size置成0;若要只从Binder获得数据可以将写部分置空即write_size置成0;若要发送请求并同步等待返回数据可以将两部分都置上。

4.1 BINDER_WRITE_READ 之写操作

Binder写操作的数据时格式同样也是(命令+数据)。这时候命令和数据都存放在binder_write_read 结构write_buffer域指向的内存空间里,多条命令可以连续存放。数据紧接着存放在命令后面,格式根据命令不同而不同。下表列举了Binder写操作支持的命令:

表 3 Binder写操作命令字

cmd 含义 arg
BC_TRANSACTION
BC_REPLY
BC_TRANSACTION用于Client向Server发送请求数据;BC_REPLY用于Server向Client发送回复(应答)数据。其后面紧接着一个binder_transaction_data结构体表明要写入的数据。 struct binder_transaction_data
BC_ACQUIRE_RESULT
BC_ATTEMPT_ACQUIRE
暂未实现
BC_FREE_BUFFER 释放一块映射的内存。Binder接收方通过mmap()映射一块较大的内存空间,Binder驱动基于这片内存采用*佳匹配算法实现接收数据缓存的动态分配和释放,满足并发请求对接收缓存区的需求。应用程序处理完这片数据后必须尽快使用该命令释放缓存区,否则会因为缓存区耗尽而无法接收新数据。 指向需要释放的缓存区的指针;该指针位于收到的Binder数据包中
BC_INCREFS
BC_ACQUIRE
BC_RELEASE
BC_DECREFS
这组命令增加或减少Binder的引用计数,用以实现强指针或弱指针的功能。 32位Binder引用号
BC_INCREFS_DONE
BC_ACQUIRE_DONE
*次增加Binder实体引用计数时,驱动向Binder实体所在的进程发送BR_INCREFS, BR_ACQUIRE消息;Binder实体所在的进程处理完毕回馈BC_INCREFS_DONE,BC_ACQUIRE_DONE void *ptr:Binder实体在用户空间中的指针

void *cookie:与该实体相关的附加数据

BC_REGISTER_LOOPER
BC_ENTER_LOOPER
BC_EXIT_LOOPER
这组命令同BINDER_SET_MAX_THREADS一道实现Binder驱动对接收方线程池管理。BC_REGISTER_LOOPER通知驱动线程池中一个线程已经创建了;BC_ENTER_LOOPER通知驱动该线程已经进入主循环,可以接收数据;BC_EXIT_LOOPER通知驱动该线程退出主循环,不再接收数据。
BC_REQUEST_DEATH_NOTIFICATION 获得Binder引用的进程通过该命令要求驱动在Binder实体销毁得到通知。虽说强指针可以确保只要有引用就不会销毁实体,但这毕竟是个跨进程的引用,谁也无法保证实体由于所在的Server关闭Binder驱动或异常退出而消失,引用者能做的是要求Server在此刻给出通知。 uint32 *ptr; 需要得到死亡通知的Binder引用

void **cookie: 与死亡通知相关的信息,驱动会在发出死亡通知时返回给发出请求的进程。

BC_DEAD_BINDER_DONE 收到实体死亡通知书的进程在删除引用后用本命令告知驱动。 void **cookie

在这些命令中,*常用的是BC_TRANSACTION/BC_REPLY命令对,Binder请求和应答数据就是通过这对命令发送给接收方。这对命令所承载的数据包由结构体struct binder_transaction_data定义。Binder交互有同步和异步之分,利用binder_transaction_data中flag域区分。如果flag域的TF_ONE_WAY位为1则为异步交互,即Client端发送完请求交互即结束, Server端不再返回BC_REPLY数据包;否则Server会返回BC_REPLY数据包,Client端必须等待接收完该数据包方才完成一次交互。

4.2 BINDER_WRITE_READ :从Binder读出数据

从Binder里读出的数据格式和向Binder中写入的数据格式一样,采用(消息ID+数据)形式,并且多条消息可以连续存放。下表列举了从Binder读出的命令字及其相应的参数:

表 4 Binder读操作消息ID

消息 含义 参数
BR_ERROR 发生内部错误(如内存分配失败)
BR_OK
BR_NOOP
操作完成
BR_SPAWN_LOOPER 该消息用于接收方线程池管理。当驱动发现接收方所有线程都处于忙碌状态且线程池里的线程总数没有超过BINDER_SET_MAX_THREADS设置的*大线程数时,向接收方发送该命令要求创建更多线程以备接收数据。
BR_TRANSACTION
BR_REPLY
这两条消息分别对应发送方的BC_TRANSACTION和BC_REPLY,表示当前接收的数据是请求还是回复。 binder_transaction_data
BR_ACQUIRE_RESULT
BR_ATTEMPT_ACQUIRE
BR_FINISHED
尚未实现
BR_DEAD_REPLY 交互过程中如果发现对方进程或线程已经死亡则返回该消息
BR_TRANSACTION_COMPLETE 发送方通过BC_TRANSACTION或BC_REPLY发送完一个数据包后,都能收到该消息做为成功发送的反馈。这和BR_REPLY不一样,是驱动告知发送方已经发送成功,而不是Server端返回请求数据。所以不管同步还是异步交互接收方都能获得本消息。
BR_INCREFS
BR_ACQUIRE
BR_RELEASE
BR_DECREFS
这一组消息用于管理强/弱指针的引用计数。只有提供Binder实体的进程才能收到这组消息。 void *ptr:Binder实体在用户空间中的指针

void *cookie:与该实体相关的附加数据

BR_DEAD_BINDER
BR_CLEAR_DEATH_NOTIFICATION_DONE
向获得Binder引用的进程发送Binder实体死亡通知书;收到死亡通知书的进程接下来会返回BC_DEAD_BINDER_DONE做确认。 void **cookie:在使用BC_REQUEST_DEATH_NOTIFICATION注册死亡通知时的附加参数。
BR_FAILED_REPLY 如果发送非法引用号则返回该消息

和写数据一样,其中*重要的消息是BR_TRANSACTION 或BR_REPLY,表明收到了一个格式为binder_transaction_data的请求数据包(BR_TRANSACTION)或返回数据包(BR_REPLY)。

4.3 struct binder_transaction_data :收发数据包结构

该结构是Binder接收/发送数据包的标准格式,每个成员定义如下:

表 5 Binder收发数据包结构:binder_transaction_data

成员 含义
union {

size_t handle;

void *ptr;

} target;

对于发送数据包的一方,该成员指明发送目的地。由于目的是在远端,所以这里填入的是对Binder实体的引用,存放在target.handle中。如前述,Binder的引用在代码中也叫句柄(handle)。

当数据包到达接收方时,驱动已将该成员修改成Binder实体,即指向Binder对象内存的指针,使用target.ptr来获得。该指针是接收方在将Binder实体传输给其它进程时提交给驱动的,驱动程序能够自动将发送方填入的引用转换成接收方Binder对象的指针,故接收方可以直接将其当做对象指针来使用(通常是将其reinterpret_cast成相应类)。

void *cookie; 发送方忽略该成员;接收方收到数据包时,该成员存放的是创建Binder实体时由该接收方自定义的任意数值,做为与Binder指针相关的额外信息存放在驱动中。驱动基本上不关心该成员。
unsigned int code; 该成员存放收发双方约定的命令码,驱动完全不关心该成员的内容。通常是Server端定义的公共接口函数的编号。
unsigned int flags; 与交互相关的标志位,其中*重要的是TF_ONE_WAY位。如果该位置上表明这次交互是异步的,Server端不会返回任何数据。驱动利用该位来决定是否构建与返回有关的数据结构。另外一位TF_ACCEPT_FDS是出于安全考虑,如果发起请求的一方不希望在收到的回复中接收文件形式的Binder可以将该位置上。因为收到一个文件形式的Binder会自动为数据接收方打开一个文件,使用该位可以防止打开文件过多。
pid_t sender_pid;

uid_t sender_euid;

该成员存放发送方的进程ID和用户ID,由驱动负责填入,接收方可以读取该成员获知发送方的身份。
size_t data_size; 该成员表示data.buffer指向的缓冲区存放的数据长度。发送数据时由发送方填入,表示即将发送的数据长度;在接收方用来告知接收到数据的长度。
size_t offsets_size; 驱动一般情况下不关心data.buffer里存放什么数据,但如果有Binder在其中传输则需要将其相对data.buffer的偏移位置指出来让驱动知道。有可能存在多个Binder同时在数据中传递,所以须用数组表示所有偏移位置。本成员表示该数组的大小。
union {

struct {

const void *buffer;

const void *offsets;

} ptr;

uint8_t buf[8];

} data;

data.bufer存放要发送或接收到的数据;data.offsets指向Binder偏移位置数组,该数组可以位于data.buffer中,也可以在另外的内存空间中,并无限制。buf[8]是为了无论保证32位还是64位平台,成员data的大小都是8个字节。

这里有必要再强调一下offsets_size和data.offsets两个成员,这是Binder通信有别于其它IPC的地方。如前述,Binder采用面向对象的设计思想,一个Binder实体可以发送给其它进程从而建立许多跨进程的引用;另外这些引用也可以在进程之间传递,就象java里将一个引用赋给另一个引用一样。为Binder在不同进程中建立引用必须有驱动的参与,由驱动在内核创建并注册相关的数据结构后接收方才能使用该引用。而且这些引用可以是强类型,需要驱动为其维护引用计数。然而这些跨进程传递的Binder混杂在应用程序发送的数据包里,数据格式由用户定义,如果不把它们一一标记出来告知驱动,驱动将无法从数据中将它们提取出来。于是就使用数组data.offsets存放用户数据中每个Binder相对data.buffer的偏移量,用offsets_size表示这个数组的大小。驱动在发送数据包时会根据data.offsets和offset_size将散落于data.buffer中的Binder找出来并一一为它们创建相关的数据结构。在数据包中传输的Binder是类型为struct flat_binder_object的结构体,详见后文。

对于接收方来说,该结构只相当于一个定长的消息头,真正的用户数据存放在data.buffer所指向的缓存区中。如果发送方在数据中内嵌了一个或多个Binder,接收到的数据包中同样会用data.offsets和offset_size指出每个Binder的位置和总个数。不过通常接收方可以忽略这些信息,因为接收方是知道数据格式的,参考双方约定的格式定义就能知道这些Binder在什么位置。

binder_proto

图 2 BINDER_WRITE_READ数据包实例

5 Binder 的表述

考察一次Binder通信的全过程会发现,Binder存在于系统以下几个部分中:

· 应用程序进程:分别位于Server进程和Client进程中

· Binder驱动:分别管理为Server端的Binder实体和Client端的引用

· 传输数据:由于Binder可以跨进程传递,需要在传输数据中予以表述

在系统不同部分,Binder实现的功能不同,表现形式也不一样。接下来逐一探讨Binder在各部分所扮演的角色和使用的数据结构。

5.1 Binder 在应用程序中的表述

虽然Binder用到了面向对象的思想,但并不限制应用程序一定要使用面向对象的语言,无论是C语言还是C++语言都可以很容易的使用Binder来通信。例如尽管Android主要使用java/C++,象SMgr这么重要的进程就是用C语言实现的。不过面向对象的方式表述起来更方便,所以本文假设应用程序是用面向对象语言实现的。

Binder本质上只是一种底层通信方式,和具体服务没有关系。为了提供具体服务,Server必须提供一套接口函数以便Client通过远程访问使用各种服务。这时通常采用Proxy设计模式:将接口函数定义在一个抽象类中,Server和Client都会以该抽象类为基类实现所有接口函数,所不同的是Server端是真正的功能实现,而Client端是对这些函数远程调用请求的包装。如何将Binder和Proxy设计模式结合起来是应用程序实现面向对象Binder通信的根本问题。

5.1.1 Binder 在Server端的表述 – Binder实体

做为Proxy设计模式的基础,首先定义一个抽象接口类封装Server所有功能,其中包含一系列纯虚函数留待Server和Proxy各自实现。由于这些函数需要跨进程调用,须为其一一编号,从而Server可以根据收到的编号决定调用哪个函数。其次就要引入Binder了。Server端定义另一个Binder抽象类处理来自Client的Binder请求数据包,其中*重要的成员是虚函数onTransact()。该函数分析收到的数据包,调用相应的接口函数处理请求。

接下来采用继承方式以接口类和Binder抽象类为基类构建Binder在Server中的实体,实现基类里所有的虚函数,包括公共接口函数以及数据包处理函数:onTransact()。这个函数的输入是来自Client的binder_transaction_data结构的数据包。前面提到,该结构里有个成员code,包含这次请求的接口函数编号。onTransact()将case-by-case地解析code值,从数据包里取出函数参数,调用接口类中相应的,已经实现的公共接口函数。函数执行完毕,如果需要返回数据就再构建一个binder_transaction_data包将返回数据包填入其中。

那么各个Binder实体的onTransact()又是什么时候调用呢?这就需要驱动参与了。前面说过,Binder实体须要以Binde传输结构flat_binder_object形式发送给其它进程才能建立Binder通信,而Binder实体指针就存放在该结构的handle域中。驱动根据Binder位置数组从传输数据中获取该Binder的传输结构,为它创建位于内核中的Binder节点,将Binder实体指针记录在该节点中。如果接下来有其它进程向该Binder发送数据,驱动会根据节点中记录的信息将Binder实体指针填入binder_transaction_data的target.ptr中返回给接收线程。接收线程从数据包中取出该指针,reinterpret_cast成Binder抽象类并调用onTransact()函数。由于这是个虚函数,不同的Binder实体中有各自的实现,从而可以调用到不同Binder实体提供的onTransact()。

5.1.2 Binder 在Client端的表述 – Binder引用

做为Proxy设计模式的一部分,Client端的Binder同样要继承Server提供的公共接口类并实现公共函数。但这不是真正的实现,而是对远程函数调用的包装:将函数参数打包,通过Binder向Server发送申请并等待返回值。为此Client端的Binder还要知道Binder实体的相关信息,即对Binder实体的引用。该引用或是由SMgr转发过来的,对实名Binder的引用或是由另一个进程直接发送过来的,对匿名Binder的引用。

由于继承了同样的公共接口类,Client Binder提供了与Server Binder一样的函数原型,使用户感觉不出Server是运行在本地还是远端。Client Binder中,公共接口函数的包装方式是:创建一个binder_transaction_data数据包,将其对应的编码填入code域,将调用该函数所需的参数填入data.buffer指向的缓存中,并指明数据包的目的地,那就是已经获得的对Binder实体的引用,填入数据包的target.handle中。注意这里和Server的区别:实际上target域是个联合体,包括ptr和handle两个成员,前者用于接收数据包的Server,指向 Binder实体对应的内存空间;后者用于作为请求方的Client,存放Binder实体的引用,告知驱动数据包将路由给哪个实体。数据包准备好后,通过驱动接口发送出去。经过BC_TRANSACTION/BC_REPLY回合完成函数的远程调用并得到返回值。

5.2 Binder 在传输数据中的表述

Binder可以塞在数据包的有效数据中越进程边界从一个进程传递给另一个进程,这些传输中的Binder用结构flat_binder_object表示,如下表所示:

表 6 Binder传输结构:flat_binder_object

成员 含义
unsigned long type 表明该Binder的类型,包括以下几种:

BINDER_TYPE_BINDER:表示传递的是Binder实体,并且指向该实体的引用都是强类型;

BINDER_TYPE_WEAK_BINDER:表示传递的是Binder实体,并且指向该实体的引用都是弱类型;

BINDER_TYPE_HANDLE:表示传递的是Binder强类型的引用

BINDER_TYPE_WEAK_HANDLE:表示传递的是Binder弱类型的引用

BINDER_TYPE_FD:表示传递的是文件形式的Binder,详见下节

unsigned long flags 该域只对*次传递Binder实体时有效,因为此刻驱动需要在内核中创建相应的实体节点,有些参数需要从该域取出:

第0-7位:代码中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示处理本实体请求数据包的线程的*低优先级。当一个应用程序提供多个实体时,可以通过该参数调整分配给各个实体的处理能力。

第8位:代码中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示该实体可以接收其它进程发过来的文件形式的Binder。由于接收文件形式的Binder会在本进程中自动打开文件,有些Server可以用该标志禁止该功能,以防打开过多文件。

union {

void *binder;

signed long handle;

};

当传递的是Binder实体时使用binder域,指向Binder实体在应用程序中的地址。

当传递的是Binder引用时使用handle域,存放Binder在进程中的引用号。

void *cookie; 该域只对Binder实体有效,存放与该Binder有关的附加信息。

无论是Binder实体还是对实体的引用都从属与某个进程,所以该结构不能透明地在进程之间传输,必须经过驱动翻译。例如当Server把Binder实体传递给Client时,在发送数据流中,flat_binder_object中的type是BINDER_TYPE_BINDER,binder指向Server进程用户空间地址。如果透传给接收端将毫无用处,驱动必须对数据流中的这个Binder做修改:将type该成BINDER_TYPE_HANDLE;为这个Binder在接收进程中创建位于内核中的引用并将引用号填入handle中。对于发生数据流中引用类型的Binder也要做同样转换。经过处理后接收进程从数据流中取得的Binder引用才是有效的,才可以将其填入数据包binder_transaction_data的target.handle域,向Binder实体发送请求。

这样做也是出于安全性考虑:应用程序不能随便猜测一个引用号填入target.handle中就可以向Server请求服务了,因为驱动并没有为你在内核中创建该引用,必定会被驱动拒*。唯有经过身份认证确认合法后,由‘权威机构’(Binder驱动)亲手授予你的Binder才能使用,因为这时驱动已经在内核中为你使用该Binder做了注册,交给你的引用号是合法的。

下表总结了当flat_binder_object结构穿过驱动时驱动所做的操作:

表 7 驱动对flat_binder_object的操作

Binder 类型( type域) 在发送方的操作 在接收方的操作
BINDER_TYPE_BINDER

BINDER_TYPE_WEAK_BINDER

只有实体所在的进程能发送该类型的Binder。如果是*次发送驱动将创建实体在内核中的节点,并保存binder,cookie,flag域。 如果是*次接收该Binder则创建实体在内核中的引用;将handle域替换为新建的引用号;将type域替换为BINDER_TYPE_(WEAK_)HANDLE
BINDER_TYPE_HANDLE

BINDER_TYPE_WEAK_HANDLE

获得Binder引用的进程都能发送该类型Binder。驱动根据handle域提供的引用号查找建立在内核的引用。如果找到说明引用号合法,否则拒*该发送请求。 如果收到的Binder实体位于接收进程中:将ptr域替换为保存在节点中的binder值;cookie替换为保存在节点中的cookie值;type替换为BINDER_TYPE_(WEAK_)BINDER。

如果收到的Binder实体不在接收进程中:如果是*次接收则创建实体在内核中的引用;将handle域替换为新建的引用号

BINDER_TYPE_FD 验证handle域中提供的打开文件号是否有效,无效则拒*该发送请求。 在接收方创建新的打开文件号并将其与提供的打开文件描述结构绑定。
5.2.1 文件形式的 Binder

除了通常意义上用来通信的Binder,还有一种特殊的Binder:文件Binder。这种Binder的基本思想是:将文件看成Binder实体,进程打开的文件号看成Binder的引用。一个进程可以将它打开文件的文件号传递给另一个进程,从而另一个进程也打开了同一个文件,就象Binder的引用在进程之间传递一样。

一个进程打开一个文件,就获得与该文件绑定的打开文件号。从Binder的角度,linux在内核创建的打开文件描述结构struct file是Binder的实体,打开文件号是该进程对该实体的引用。既然是Binder那么就可以在进程之间传递,故也可以用flat_binder_object结构将文件Binder通过数据包发送至其它进程,只是结构中type域的值为BINDER_TYPE_FD,表明该Binder是文件Binder。而结构中的handle域则存放文件在发送方进程中的打开文件号。我们知道打开文件号是个局限于某个进程的值,一旦跨进程就没有意义了。这一点和Binder实体用户指针或Binder引用号是一样的,若要跨进程同样需要驱动做转换。驱动在接收Binder的进程空间创建一个新的打开文件号,将它与已有的打开文件描述结构struct file勾连上,从此该Binder实体又多了一个引用。新建的打开文件号覆盖flat_binder_object中原来的文件号交给接收进程。接收进程利用它可以执行read(),write()等文件操作。

传个文件为啥要这么麻烦,直接将文件名用Binder传过去,接收方用open()打开不就行了吗?其实这还是有区别的。首先对同一个打开文件共享的层次不同:使用文件Binder打开的文件共享linux VFS中的struct file,struct dentry,struct inode结构,这意味着一个进程使用read()/write()/seek()改变了文件指针,另一个进程的文件指针也会改变;而如果两个进程分别使用同一文件名打开文件则有各自的struct file结构,从而各自独立维护文件指针,互不干扰。其次是一些特殊设备文件要求在struct file一级共享才能使用,例如android的另一个驱动ashmem,它和Binder一样也是misc设备,用以实现进程间的共享内存。一个进程打开的ashmem文件只有通过文件Binder发送到另一个进程才能实现内存共享,这大大提高了内存共享的安全性,道理和Binder增强了IPC的安全性是一样的。

5.3 Binder 在驱动中的表述

驱动是Binder通信的核心,系统中所有的Binder实体以及每个实体在各个进程中的引用都登记在驱动中;驱动需要记录Binder引用->实体之间多对一的关系;为引用找到对应的实体;在某个进程中为实体创建或查找到对应的引用;记录Binder的归属地(位于哪个进程中);通过管理Binder的强/弱引用创建/销毁Binder实体等等。

驱动里的Binder是什么时候创建的呢?前面提到过,为了实现实名Binder的注册,系统必须创建*只鸡–为SMgr创建的,用于注册实名Binder的Binder实体,负责实名Binder注册过程中的进程间通信。既然创建了实体就要有对应的引用:驱动将所有进程中的0号引用都预留给该Binder实体,即所有进程的0号引用天然地都指向注册实名Binder专用的Binder,无须特殊操作即可以使用0号引用来注册实名Binder。接下来随着应用程序不断地注册实名Binder,不断向SMgr索要Binder的引用,不断将Binder从一个进程传递给另一个进程,越来越多的Binder以传输结构 – flat_binder_object的形式穿越驱动做跨进程的迁徙。由于binder_transaction_data中data.offset数组的存在,所有流经驱动的Binder都逃不过驱动的眼睛。Binder将对这些穿越进程边界的Binder做如下操作:检查传输结构的type域,如果是BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER则创建Binder的实体;如果是BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE则创建Binder的引用;如果是BINDER_TYPE_HANDLE则为进程打开文件,无须创建任何数据结构。详细过程可参考表7。随着越来越多的Binder实体或引用在进程间传递,驱动会在内核里创建越来越多的节点或引用,当然这个过程对用户来说是透明的。

5.3.1 Binder 实体在驱动中的表述

驱动中的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示:

表 8 Binder节点描述结构:binder_node

成员 含义
int debug_id; 用于调试
struct binder_work work; 当本节点引用计数发生改变,需要通知所属进程时,通过该成员挂入所属进程的to-do队列里,唤醒所属进程执行Binder实体引用计数的修改。
union {

struct rb_node rb_node;

struct hlist_node dead_node;

};

每个进程都维护一棵红黑树,以Binder实体在用户空间的指针,即本结构的ptr成员为索引存放该进程所有的Binder实体。这样驱动可以根据Binder实体在用户空间的指针很快找到其位于内核的节点。rb_node用于将本节点链入该红黑树中。

销毁节点时须将rb_node从红黑树中摘除,但如果本节点还有引用没有切断,就用dead_node将节点隔离到另一个链表中,直到通知所有进程切断与该节点的引用后,该节点才可能被销毁。

struct binder_proc *proc; 本成员指向节点所属的进程,即提供该节点的进程。
struct hlist_head refs; 本成员是队列头,所有指向本节点的引用都链接在该队列里。这些引用可能隶属于不同的进程。通过该队列可以遍历指向该节点的所有引用。
int internal_strong_refs; 用以实现强指针的计数器:产生一个指向本节点的强引用该计数就会加1。
int local_weak_refs; 驱动为传输中的Binder设置的弱引用计数。如果一个Binder打包在数据包中从一个进程发送到另一个进程,驱动会为该Binder增加引用计数,直到接收进程通过BC_FREE_BUFFER通知驱动释放该数据包的数据区为止。
int local_strong_refs; 驱动为传输中的Binder设置的强引用计数。同上。
void __user *ptr; 指向用户空间Binder实体的指针,来自于flat_binder_object的binder成员
void __user *cookie; 指向用户空间的附加指针,来自于flat_binder_object的cookie成员
unsigned has_strong_ref;

unsigned pending_strong_ref;

unsigned has_weak_ref;

unsigned pending_weak_ref

这一组标志用于控制驱动与Binder实体所在进程交互式修改引用计数
unsigned has_async_transaction; 该成员表明该节点在to-do队列中有异步交互尚未完成。驱动将所有发送往接收端的数据包暂存在接收进程或线程开辟的to-do队列里。对于异步交互,驱动做了适当流控:如果to-do队列里有异步交互尚待处理则该成员置1,这将导致新到的异步交互存放在本结构成员 – asynch_todo队列中,而不直接送到to-do队列里。目的是为同步交互让路,避免长时间阻塞发送端。
unsigned accept_fds 表明节点是否同意接受文件方式的Binder,来自flat_binder_object中flags成员的FLAT_BINDER_FLAG_ACCEPTS_FDS位。由于接收文件Binder会为进程自动打开一个文件,占用有限的文件描述符,节点可以设置该位拒*这种行为。
int min_priority 设置处理Binder请求的线程的*低优先级。发送线程将数据提交给接收线程处理时,驱动会将发送线程的优先级也赋予接收线程,使得数据即使跨了进程也能以同样优先级得到处理。不过如果发送线程优先级过低,接收线程将以预设的*小值运行。

该域的值来自于flat_binder_object中flags成员。

struct list_head async_todo 异步交互等待队列;用于分流发往本节点的异步交互包

每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说内存地址是唯一的,所以不会重复建设造成混乱。

5.3.2 Binder 引用在驱动中的表述

和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示:

表 9 Binder引用描述结构:binder_ref

成员 含义
int debug_id; 调试用
struct rb_node rb_node_desc; 每个进程有一棵红黑树,进程所有引用以引用号(即本结构的desc域)为索引添入该树中。本成员用做链接到该树的一个节点。
struct rb_node rb_node_node; 每个进程又有一棵红黑树,进程所有引用以节点实体在驱动中的内存地址(即本结构的node域)为所引添入该树中。本成员用做链接到该树的一个节点。
struct hlist_node node_entry; 该域将本引用做为节点链入所指向的Binder实体结构binder_node中的refs队列
struct binder_proc *proc; 本引用所属的进程
struct binder_node *node; 本引用所指向的节点(Binder实体)
uint32_t desc; 本结构的引用号
int strong; 强引用计数
int weak; 弱引用计数
struct binder_ref_death *death; 应用程序向驱动发送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令从而当Binder实体销毁时能够收到来自驱动的提醒。该域不为空表明用户订阅了对应实体销毁的‘噩耗’。

就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存放所有正在使用的引用。不同的是Binder的引用可以通过两个键值索引:

· 对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体在内核中的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。

· 引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都固定保留给SMgr,其它值由驱动动态分配。向Binder发送数据包时,应用程序将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信息,实现数据包的路由。

6 Binder 内存映射和接收缓存区管理

暂且撇开Binder,考虑一下传统的IPC方式中,数据是怎样从发送端到达接收端的呢?通常的做法是,发送方将准备好的数据存放在缓存区中,调用API通过系统调用进入内核中。内核服务程序在内核空间分配内存,将数据从发送方缓存区复制到内核缓存区中。接收方读数据时也要提供一块缓存区,内核将数据从内核缓存区拷贝到接收方提供的缓存区中并唤醒接收线程,完成一次数据发送。这种存储-转发机制有两个缺陷:首先是效率低下,需要做两次拷贝:用户空间->内核空间->用户空间。Linux使用copy_from_user()和copy_to_user()实现这两个跨空间拷贝,在此过程中如果使用了高端内存(high memory),这种拷贝需要临时建立/取消页面映射,造成性能损失。其次是接收数据的缓存要由接收方提供,可接收方不知道到底要多大的缓存才够用,只能开辟尽量大的空间或先调用API接收消息头获得消息体大小,再开辟适当的空间接收消息体。两种做法都有不足,不是浪费空间就是浪费时间。

Binder采用一种全新策略:由Binder驱动负责管理数据接收缓存。我们注意到Binder驱动实现了mmap()系统调用,这对字符设备是比较特殊的,因为mmap()通常用在有物理存储介质的文件系统上,而象Binder这样没有物理介质,纯粹用来通信的字符设备没必要支持mmap()。Binder驱动当然不是为了在物理介质和用户空间做映射,而是用来创建数据接收的缓存空间。先看mmap()是如何使用的:

fd = open(“/dev/binder”, O_RDWR);

mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);

这样Binder的接收方就有了一片大小为MAP_SIZE的接收缓存区。mmap()的返回值是内存映射在用户空间的地址,不过这段空间是由驱动管理,用户不必也不能直接访问(映射类型为PROT_READ,只读映射)。

接收缓存区映射好后就可以做为缓存池接收和存放数据了。前面说过,接收数据包的结构为binder_transaction_data,但这只是消息头,真正的有效负荷位于data.buffer所指向的内存中。这片内存不需要接收方提供,恰恰是来自mmap()映射的这片缓存池。在数据从发送方向接收方拷贝时,驱动会根据发送数据包的大小,使用*佳匹配算法从缓存池中找到一块大小合适的空间,将数据从发送缓存区复制过来。要注意的是,存放binder_transaction_data结构本身以及表4中所有消息的内存空间还是得由接收者提供,但这些数据大小固定,数量也不多,不会给接收方造成不便。映射的缓存池要足够大,因为接收方的线程池可能会同时处理多条并发的交互,每条交互都需要从缓存池中获取目的存储区,一旦缓存池耗竭将产生导致无法预期的后果。

有分配必然有释放。接收方在处理完数据包后,就要通知驱动释放data.buffer所指向的内存区。在介绍Binder协议时已经提到,这是由命令BC_FREE_BUFFER完成的。

通过上面介绍可以看到,驱动为接收方分担了*为繁琐的任务:分配/释放大小不等,难以预测的有效负荷缓存区,而接收方只需要提供缓存来存放大小固定,*大空间可以预测的消息头即可。在效率上,由于mmap()分配的内存是映射在接收方用户空间里的,所有总体效果就相当于对有效负荷数据做了一次从发送方用户空间到接收方用户空间的直接数据拷贝,省去了内核中暂存这个步骤,提升了一倍的性能。顺便再提一点,Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。

7 Binder 接收线程管理

Binder通信实际上是位于不同进程中的线程之间的通信。假如进程S是Server端,提供Binder实体,线程T1从Client进程C1中通过Binder的引用向进程S发送请求。S为了处理这个请求需要启动线程T2,而此时线程T1处于接收返回数据的等待状态。T2处理完请求就会将处理结果返回给T1,T1被唤醒得到处理结果。在这过程中,T2仿佛T1在进程S中的代理,代表T1执行远程任务,而给T1的感觉就是象穿越到S中执行一段代码又回到了C1。为了使这种穿越更加真实,驱动会将T1的一些属性赋给T2,特别是T1的优先级nice,这样T2会使用和T1类似的时间完成任务。很多资料会用‘线程迁移’来形容这种现象,容易让人产生误解。一来线程根本不可能在进程之间跳来跳去,二来T2除了和T1优先级一样,其它没有相同之处,包括身份,打开文件,栈大小,信号处理,私有数据等。

对于Server进程S,可能会有许多Client同时发起请求,为了提高效率往往开辟线程池并发处理收到的请求。怎样使用线程池实现并发处理呢?这和具体的IPC机制有关。拿socket举例,Server端的socket设置为侦听模式,有一个专门的线程使用该socket侦听来自Client的连接请求,即阻塞在accept()上。这个socket就象一只会生蛋的鸡,一旦收到来自Client的请求就会生一个蛋 – 创建新socket并从accept()返回。侦听线程从线程池中启动一个工作线程并将刚下的蛋交给该线程。后续业务处理就由该线程完成并通过这个单与Client实现交互。

可是对于Binder来说,既没有侦听模式也不会下蛋,怎样管理线程池呢?一种简单的做法是,不管三七二十一,先创建一堆线程,每个线程都用BINDER_WRITE_READ命令读Binder。这些线程会阻塞在驱动为该Binder设置的等待队列上,一旦有来自Client的数据驱动会从队列中唤醒一个线程来处理。这样做简单直观,省去了线程池,但一开始就创建一堆线程有点浪费资源。于是Binder协议引入了专门命令或消息帮助用户管理线程池,包括:

· INDER_SET_MAX_THREADS

· BC_REGISTER_LOOP

· BC_ENTER_LOOP

· BC_EXIT_LOOP

· BR_SPAWN_LOOPER

首先要管理线程池就要知道池子有多大,应用程序通过INDER_SET_MAX_THREADS告诉驱动*多可以创建几个线程。以后每个线程在创建,进入主循环,退出主循环时都要分别使用BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP告知驱动,以便驱动收集和记录当前线程池的状态。每当驱动接收完数据包返回读Binder的线程时,都要检查一下是不是已经没有闲置线程了。如果是,而且线程总数不会超出线程池*大线程数,就会在当前读出的数据包后面再追加一条BR_SPAWN_LOOPER消息,告诉用户线程即将不够用了,请再启动一些,否则下一个请求可能不能及时响应。新线程一启动又会通过BC_xxx_LOOP告知驱动更新状态。这样只要线程没有耗尽,总是有空闲线程在等待队列中随时待命,及时处理请求。

关于工作线程的启动,Binder驱动还做了一点小小的优化。当进程P1的线程T1向进程P2发送请求时,驱动会先查看一下线程T1是否也正在处理来自P2某个线程请求但尚未完成(没有发送回复)。这种情况通常发生在两个进程都有Binder实体并互相对发时请求时。假如驱动在进程P2中发现了这样的线程,比如说T2,就会要求T2来处理T1的这次请求。因为T2既然向T1发送了请求尚未得到返回包,说明T2肯定(或将会)阻塞在读取返回包的状态。这时候可以让T2顺便做点事情,总比等在那里闲着好。而且如果T2不是线程池中的线程还可以为线程池分担部分工作,减少线程池使用率。

8 数据包接收队列与(线程)等待队列管理

通常数据传输的接收端有两个队列:数据包接收队列和(线程)等待队列,用以缓解供需矛盾。当超市里的进货(数据包)太多,货物会堆积在仓库里;购物的人(线程)太多,会排队等待在收银台,道理是一样的。在驱动中,每个进程有一个全局的接收队列,也叫to-do队列,存放不是发往特定线程的数据包;相应地有一个全局等待队列,所有等待从全局接收队列里收数据的线程在该队列里排队。每个线程有自己私有的to-do队列,存放发送给该线程的数据包;相应的每个线程都有各自私有等待队列,专门用于本线程等待接收自己to-do队列里的数据。虽然名叫队列,其实线程私有等待队列中*多只有一个线程,即它自己。

由于发送时没有特别标记,驱动怎么判断哪些数据包该送入全局to-do队列,哪些数据包该送入特定线程的to-do队列呢?这里有两条规则。规则1:Client发给Server的请求数据包都提交到Server进程的全局to-do队列。不过有个特例,就是上节谈到的Binder对工作线程启动的优化。经过优化,来自T1的请求不是提交给P2的全局to-do队列,而是送入了T2的私有to-do队列。规则2:对同步请求的返回数据包(由BC_REPLY发送的包)都发送到发起请求的线程的私有to-do队列中。如上面的例子,如果进程P1的线程T1发给进程P2的线程T2的是同步请求,那么T2返回的数据包将送进T1的私有to-do队列而不会提交到P1的全局to-do队列。

数据包进入接收队列的潜规则也就决定了线程进入等待队列的潜规则,即一个线程只要不接收返回数据包则应该在全局等待队列中等待新任务,否则就应该在其私有等待队列中等待Server的返回数据。还是上面的例子,T1在向T2发送同步请求后就必须等待在它私有等待队列中,而不是在P1的全局等待队列中排队,否则将得不到T2的返回的数据包。

这些潜规则是驱动对Binder通信双方施加的限制条件,体现在应用程序上就是同步请求交互过程中的线程一致性:1) Client端,等待返回包的线程必须是发送请求的线程,而不能由一个线程发送请求包,另一个线程等待接收包,否则将收不到返回包;2) Server端,发送对应返回数据包的线程必须是收到请求数据包的线程,否则返回的数据包将无法送交发送请求的线程。这是因为返回数据包的目的Binder不是用户指定的,而是驱动记录在收到请求数据包的线程里,如果发送返回包的线程不是收到请求包的线程驱动将无从知晓返回包将送往何处。

接下来探讨一下Binder驱动是如何递交同步交互和异步交互的。我们知道,同步交互和异步交互的区别是同步交互的请求端(client)在发出请求数据包后须要等待应答端(Server)的返回数据包,而异步交互的发送端发出请求数据包后交互即结束。对于这两种交互的请求数据包,驱动可以不管三七二十一,统统丢到接收端的to-do队列中一个个处理。但驱动并没有这样做,而是对异步交互做了限流,令其为同步交互让路,具体做法是:对于某个Binder实体,只要有一个异步交互没有处理完毕,例如正在被某个线程处理或还在任意一条to-do队列中排队,那么接下来发给该实体的异步交互包将不再投递到to-do队列中,而是阻塞在驱动为该实体开辟的异步交互接收队列(Binder节点的async_todo域)中,但这期间同步交互依旧不受限制直接进入to-do队列获得处理。一直到该异步交互处理完毕下一个异步交互方可以脱离异步交互队列进入to-do队列中。之所以要这么做是因为同步交互的请求端需要等待返回包,必须迅速处理完毕以免影响请求端的响应速度,而异步交互属于‘发射后不管’,稍微延时一点不会阻塞其它线程。所以用专门队列将过多的异步交互暂存起来,以免突发大量异步交互挤占Server端的处理能力或耗尽线程池里的线程,进而阻塞同步交互。

9 总结

Binder使用Client-Server通信方式,安全性好,简单高效,再加上其面向对象的设计思想,独特的接收缓存管理和线程池管理方式,成为Android进程间通信的中流砥柱。

图文详解 Android Binder跨进程通信机制 原理

图文详解 Android Binder跨进程通信机制 原理

目录

%title插图%num
目录


1. Binder到底是什么?

  • 中文即 粘合剂,意思为粘合了两个不同的进程
  • 网上有很多对Binder的定义,但都说不清楚:Binder是跨进程通信方式、它实现了IBinder接口,是连接 ServiceManager的桥梁blabla,估计大家都看晕了,没法很好的理解
  • 我认为:对于Binder的定义,在不同场景下其定义不同

%title插图%num
定义

在本文的讲解中,按照 大角度 -> 小角度 去分析Binder,即:

  • 先从 机制、模型的角度 去分析 整个Binder跨进程通信机制的模型

    其中,会详细分析模型组成中的 Binder驱动

  • 再 从源码实现角度,分析 Binder在 Android中的具体实现

从而全方位地介绍 Binder,希望你们会喜欢。


2. 知识储备

在讲解Binder前,我们先了解一些基础知识

2.1 进程空间分配

  • 一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来
  • 二者区别:
    1. 进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
    2. 进程间,内核空间的数据可共享,所以内核空间 = 可共享空间
  • 进程内 用户 与 内核 进行交互 称为系统调用

%title插图%num
示意图

2.2 进程隔离

为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的

2.3 跨进程通信( IPC )

  • 隔离后,由于某些需求,进程间 需要合作 / 交互
  • 跨进程间通信的原理
    1. 先通过 进程间 的内核空间进行 数据交互
    2. 再通过 进程内 的用户空间 & 内核空间进行 数据交互,从而实现 进程间的用户空间 的数据交互

%title插图%num
示意图

Binder,就是充当 连接 两个进程(内核空间)的通道。


3. Binder 跨进程通信机制 模型

3.1 模型原理

Binder 跨进程通信机制 模型 基于 Client - Server 模式,模型原理图如下:

相信我,一张图就能解决问题

%title插图%num
示意图

3.2 额外说明

说明1:Client进程、Server进程 & Service Manager 进程之间的交互都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互 **

原因:

  1. Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互
  2. Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互

所以,原理图可表示为以下:

虚线表示并非直接交互

%title插图%num
示意图

说明2: Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)

所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程 并 显式使用上述3个步骤,*终借助 Android的基本架构功能就可完成进程间通信

%title插图%num
示意图

说明3:Binder请求的线程管理
  • Server进程会创建很多线程来处理Binder请求
  • 管理Binder模型的线程是采用Binder驱动的线程池,并由Binder驱动自身进行管理

    而不是由Server进程来管理的

  • 一个进程的Binder线程数默认*大是16,超过的请求会被阻塞等待空闲的Binder线程。

    所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程同时工作


  • 至此,我相信大家对Binder 跨进程通信机制 模型 已经有了一个非常清晰的定性认识
  • 下面,我将通过一个实例,分析Binder跨进程通信机制 模型在 Android中的具体代码实现方式

    即分析 上述步骤在Android中具体是用代码如何实现的


4. Binder机制 在Android中的具体实现原理

  • Binder机制在 Android中的实现主要依靠 Binder类,其实现了IBinder 接口

    下面会详细说明

  • 实例说明:Client进程 需要调用 Server进程的加法函数(将整数a和b相加)

    即:

    1. Client进程 需要传两个整数给 Server进程
    2. Server进程 需要把相加后的结果 返回给Client进程
  • 具体步骤
    下面,我会根据Binder 跨进程通信机制 模型的步骤进行分析

步骤1:注册服务

  • 过程描述
    Server进程 通过Binder驱动 向 Service Manager进程 注册服务
  • 代码实现
    Server进程 创建 一个 Binder 对象

    1. Binder 实体是 Server进程 在 Binder 驱动中的存在形式
    2. 该对象保存 Server 和 ServiceManager 的信息(保存在内核空间中)
    3. Binder 驱动通过 内核空间的Binder 实体 找到用户空间的Server对象
  • 代码分析
    Binder binder = new Stub();
    // 步骤1:创建Binder对象 ->>分析1

    // 步骤2:创建 IInterface 接口类 的匿名类
    // 创建前,需要预先定义 继承了IInterface 接口的接口 -->分析3
    IInterface plus = new IPlus(){

          // 确定Client进程需要调用的方法
          public int add(int a,int b) {
               return a+b;
         }

          // 实现IInterface接口中唯一的方法
          public IBinder asBinder(){ 
                return null ;
           }
};
          // 步骤3
          binder.attachInterface(plus,"add two int");
         // 1. 将(add two int,plus)作为(key,value)对存入到Binder对象中的一个Map<String,IInterface>对象中
         // 2. 之后,Binder对象 可根据add two int通过queryLocalIInterface()获得对应IInterface对象(即plus)的引用,可依靠该引用完成对请求方法的调用
        // 分析完毕,跳出


<-- 分析1:Stub类 -->
    public class Stub extends Binder {
    // 继承自Binder类 ->>分析2

          // 复写onTransact()
          @Override
          boolean onTransact(int code, Parcel data, Parcel reply, int flags){
          // 具体逻辑等到步骤3再具体讲解,此处先跳过
          switch (code) { 
                case Stub.add: { 

                       data.enforceInterface("add two int"); 

                       int  arg0  = data.readInt();
                       int  arg1  = data.readInt();

                       int  result = this.queryLocalIInterface("add two int") .add( arg0,  arg1); 

                        reply.writeInt(result); 

                        return true; 
                  }
           } 
      return super.onTransact(code, data, reply, flags); 

}
// 回到上面的步骤1,继续看步骤2

<-- 分析2:Binder 类 -->
 public class Binder implement IBinder{
    // Binder机制在Android中的实现主要依靠的是Binder类,其实现了IBinder接口
    // IBinder接口:定义了远程操作对象的基本接口,代表了一种跨进程传输的能力
    // 系统会为每个实现了IBinder接口的对象提供跨进程传输能力
    // 即Binder类对象具备了跨进程传输的能力

        void attachInterface(IInterface plus, String descriptor);
        // 作用:
          // 1. 将(descriptor,plus)作为(key,value)对存入到Binder对象中的一个Map<String,IInterface>对象中
          // 2. 之后,Binder对象 可根据descriptor通过queryLocalIInterface()获得对应IInterface对象(即plus)的引用,可依靠该引用完成对请求方法的调用

        IInterface queryLocalInterface(Stringdescriptor) ;
        // 作用:根据 参数 descriptor 查找相应的IInterface对象(即plus引用)

        boolean onTransact(int code, Parcel data, Parcel reply, int flags);
        // 定义:继承自IBinder接口的
        // 作用:执行Client进程所请求的目标方法(子类需要复写)
        // 参数说明:
        // code:Client进程请求方法标识符。即Server进程根据该标识确定所请求的目标方法
        // data:目标方法的参数。(Client进程传进来的,此处就是整数a和b)
        // reply:目标方法执行后的结果(返回给Client进程)
         // 注:运行在Server进程的Binder线程池中;当Client进程发起远程请求时,远程请求会要求系统底层执行回调该方法

        final class BinderProxy implements IBinder {
         // 即Server进程创建的Binder对象的代理对象类
         // 该类属于Binder的内部类
        }
        // 回到分析1原处
}

<-- 分析3:IInterface接口实现类 -->

 public interface IPlus extends IInterface {
          // 继承自IInterface接口->>分析4
          // 定义需要实现的接口方法,即Client进程需要调用的方法
         public int add(int a,int b);
// 返回步骤2
}

<-- 分析4:IInterface接口类 -->
// 进程间通信定义的通用接口
// 通过定义接口,然后再服务端实现接口、客户端调用接口,就可实现跨进程通信。
public interface IInterface
{
    // 只有一个方法:返回当前接口关联的 Binder 对象。
    public IBinder asBinder();
}
  // 回到分析3原处

注册服务后,Binder驱动持有 Server进程创建的Binder实体

步骤2:获取服务

  • Client进程 使用 某个 service前(此处是 相加函数),须 通过Binder驱动 向 ServiceManager进程 获取相应的Service信息
  • 具体代码实现过程如下:

%title插图%num
示意图

此时,Client进程与 Server进程已经建立了连接

步骤3:使用服务

Client进程 根据获取到的 Service信息(Binder代理对象),通过Binder驱动 建立与 该Service所在Server进程通信的链路,并开始使用服务

  • 过程描述
    1. Client进程 将参数(整数a和b)发送到Server进程
    2. Server进程 根据Client进程要求调用 目标方法(即加法函数)
    3. Server进程 将目标方法的结果(即加法后的结果)返回给Client进程
  • 代码实现过程

步骤1: Client进程 将参数(整数a和b)发送到Server进程

// 1. Client进程 将需要传送的数据写入到Parcel对象中
// data = 数据 = 目标方法的参数(Client进程传进来的,此处就是整数a和b) + IInterface接口对象的标识符descriptor
  android.os.Parcel data = android.os.Parcel.obtain();
  data.writeInt(a); 
  data.writeInt(b); 

  data.writeInterfaceToken("add two int");;
  // 方法对象标识符让Server进程在Binder对象中根据"add two int"通过queryLocalIInterface()查找相应的IInterface对象(即Server创建的plus),Client进程需要调用的相加方法就在该对象中

  android.os.Parcel reply = android.os.Parcel.obtain();
  // reply:目标方法执行后的结果(此处是相加后的结果)

// 2. 通过 调用代理对象的transact() 将 上述数据发送到Binder驱动
  binderproxy.transact(Stub.add, data, reply, 0)
  // 参数说明:
    // 1. Stub.add:目标方法的标识符(Client进程 和 Server进程 自身约定,可为任意)
    // 2. data :上述的Parcel对象
    // 3. reply:返回结果
    // 0:可不管

// 注:在发送数据后,Client进程的该线程会暂时被挂起
// 所以,若Server进程执行的耗时操作,请不要使用主线程,以防止ANR


// 3. Binder驱动根据 代理对象 找到对应的真身Binder对象所在的Server 进程(系统自动执行)
// 4. Binder驱动把 数据 发送到Server 进程中,并通知Server 进程执行解包(系统自动执行)

步骤2:Server进程根据Client进要求 调用 目标方法(即加法函数)

// 1. 收到Binder驱动通知后,Server 进程通过回调Binder对象onTransact()进行数据解包 & 调用目标方法
  public class Stub extends Binder {

          // 复写onTransact()
          @Override
          boolean onTransact(int code, Parcel data, Parcel reply, int flags){
          // code即在transact()中约定的目标方法的标识符

          switch (code) { 
                case Stub.add: { 
                  // a. 解包Parcel中的数据
                       data.enforceInterface("add two int"); 
                        // a1. 解析目标方法对象的标识符

                       int  arg0  = data.readInt();
                       int  arg1  = data.readInt();
                       // a2. 获得目标方法的参数

                       // b. 根据"add two int"通过queryLocalIInterface()获取相应的IInterface对象(即Server创建的plus)的引用,通过该对象引用调用方法
                       int  result = this.queryLocalIInterface("add two int") .add( arg0,  arg1); 

                        // c. 将计算结果写入到reply
                        reply.writeInt(result); 

                        return true; 
                  }
           } 
      return super.onTransact(code, data, reply, flags); 
      // 2. 将结算结果返回 到Binder驱动

步骤3:Server进程 将目标方法的结果(即加法后的结果)返回给Client进程

  // 1. Binder驱动根据 代理对象 沿原路 将结果返回 并通知Client进程获取返回结果
  // 2. 通过代理对象 接收结果(之前被挂起的线程被唤醒)

    binderproxy.transact(Stub.ADD, data, reply, 0);
    reply.readException();;
    result = reply.readInt();
          }
}
  • 总结
    下面,我用一个原理图 & 流程图来总结步骤3的内容

%title插图%num
原理图%title插图%num
流程图


5. 优点

对比 Linux (Android基于Linux)上的其他进程通信方式(管道/消息队列/共享内存/信号量/Socket),Binder 机制的优点有:

  • 高效
    1. Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次
    2. 通过驱动在内核空间拷贝数据,不需要额外的同步处理
  • 安全性高
    Binder 机制为每个进程分配了 UID/PID 来作为鉴别身份的标示,并且在 Binder 通信时会根据 UID/PID 进行有效性检测

    1. 传统的进程通信方式对于通信双方的身份并没有做出严格的验证
    2. 如,Socket通信 ip地址是客户端手动填入,容易出现伪造
  • 使用简单
    1. 采用Client/Server 架构
    2. 实现 面向对象 的调用方式,即在使用Binder时就和调用一个本地对象实例一样

6. 总结

  • 本文主要详细讲解 跨进程通信模型 Binder机制 ,总结如下:

%title插图%num
定义%title插图%num
原理图

Android Binder IPC分析

1.binder通信概述

binder通信是一种client-server的通信结构,
1.从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;
2.实际上,代理接口中定义的方法与server中定义的方法是一一对应的;
3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;
4.代理接口将该Parcel发送给内核中的binder driver.
5.server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;
6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。

 

%title插图%num

2.service manager

Service Manager是一个linux级的进程,顾名思义,就是service的管理器。这里的service是什么概念呢?这里的service的概念和init过程中init.rc中的service是不同,init.rc中的service是都是linux进程,但是这里的service它并不一定是一个进程,也就是说可能一个或多个service属于同一个linux进程。在这篇文章中不加特殊说明均指android native端的service。

任何service在被使用之前,均要向SM(Service Manager)注册,同时客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle返回给client,handle是每个service的唯一标识符。

SM的入口函数在service_manager.c中,下面是SM的代码部分
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)) {
LOGE(“cannot become context manager (%s)/n”, strerror(errno));
return -1;
}

svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}

这个进程的主要工作如下:
1.初始化binder,打开/dev/binder设备;在内存中为binder映射128K字节空间;
2.指定SM对应的代理binder的handle为0,当client尝试与SM通信时,需要创建一个handle为0的代理binder,这里的代理binder其实就是*节中描述的那个代理接口;

3.通知binder driver(BD)使SM成为BD的context manager;
4.维护一个死循环,在这个死循环中,不停地去读内核中binder driver,查看是否有可读的内容;即是否有对service的操作要求, 如果有,则调用svcmgr_handler回调来处理请求的操作。

5.SM维护了一个svclist列表来存储service的信息。

%title插图%num

这里需要声明一下,当service在向SM注册时,该service就是一个client,而SM则作为了server。而某个进程需要与service通信时,此时这个进程为client,service才作为server。因此service不一定为server,有时它也是作为client存在的。

由于下面几节会介绍一些与binder通信相关的几个概念,所以将SM的功能介绍放在了后面的部分来讲。

应用和service之间的通信会涉及到2次binder通信。

1.应用向SM查询service是否存在,如果存在获得该service的代理binder,此为一次binder通信;
2.应用通过代理binder调用service的方法,此为第二次binder通信。

3.ProcessState

ProcessState是以单例模式设计的。每个进程在使用binder机制通信时,均需要维护一个ProcessState实例来描述当前进程在binder通信时的binder状态。
ProcessState有如下2个主要功能:
1.创建一个thread,该线程负责与内核中的binder模块进行通信,称该线程为Pool thread;
2.为指定的handle创建一个BpBinder对象,并管理该进程中所有的BpBinder对象。

3.1 Pool thread

在Binder IPC中,所有进程均会启动一个thread来负责与BD来直接通信,也就是不停的读写BD,这个线程的实现主体是一个IPCThreadState对象,下面会介绍这个类型。

下面是 Pool thread的启动方式:

ProcessState::self()->startThreadPool();

3.2 BpBinder获取

BpBinder主要功能是负责client向BD发送调用请求的数据。它是client端binder通信的核心对象,通过调用transact函数向BD发送调用请求的数据,它的构造函数如下:

BpBinder(int32_t handle);
通过BpBinder的构造函数发现,BpBinder会将当前通信中server的handle记录下来,当有数据发送时,会通知BD数据的发送目标。

ProcessState通过如下方式来获取BpBinder对象:

ProcessState::self()->getContextObject(handle);

在这个过程中,ProcessState会维护一个BpBinder的vector mHandleToObject,每当ProcessState创建一个BpBinder的实例时,回去查询mHandleToObject,如果对应的handle已经有binder指针,那么不再创建,否则创建binder并插入到mHandleToObject中。
ProcessState创建的BpBinder实例,一般情况下会作为参数构建一个client端的代理接口,这个代理接口的形式为BpINTERFACE,例如在与SM通信时,client会创建一个代理接口BpServiceManager.

4.IPCThreadState

IPCThreadState也是以单例模式设计的。由于每个进程只维护了一个ProcessState实例,同时ProcessState只启动一个Pool thread,也就是说每一个进程只会启动一个Pool thread,因此每个进程则只需要一个IPCThreadState即可。
Pool thread的实际内容则为:
IPCThreadState::self()->joinThreadPool();

 

ProcessState中有2个Parcel成员,mIn和mOut,Pool thread会不停的查询BD中是否有数据可读,如果有将其读出并保存到mIn,同时不停的检查mOut是否有数据需要向BD发送,如果有,则将其内容写入到BD中,总而言之,从BD中读出的数据保存到mIn,待写入到BD中的数据保存在了mOut中。

ProcessState中生成的BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据,这样的话这个binder IPC过程的client端的调用请求的发送过程就明了了。

 

IPCThreadState有两个重要的函数,talkWithDriver函数负责从BD读写数据,executeCommand函数负责解析并执行mIn中的数据。

%title插图%num

5.主要基类

5.1基类IInterface

为server端提供接口,它的子类声明了service能够实现的所有的方法;

5.2基类IBinder
BBinder与BpBinder均为IBinder的子类,因此可以看出IBinder定义了binder IPC的通信协议,BBinder与BpBinder在这个协议框架内进行的收和发操作,构建了基本的binder IPC机制。
5.3基类BpRefBase
client端在查询SM获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例。

 

 

6.两个接口类

6.1 BpINTERFACE

如果client想要使用binder IPC来通信,那么首先会从SM出查询并获得server端service的BpBinder,在client端,这个对象被认为是server端的远程代理。为了能够使client能够想本地调用一样调用一个远程server,server端需要向client提供一个接口,client在在这个接口的基础上创建一个BpINTERFACE,使用这个对象,client的应用能够想本地调用一样直接调用server端的方法。而不用去关心具体的binder IPC实现。
下面看一下BpINTERFACE的原型:
class BpINTERFACE : public BpInterface<IINTERFACE>

顺着继承关系再往上看
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase

BpINTERFACE分别继承自INTERFACE,和BpRefBase;
● BpINTERFACE既实现了service中各方法的本地操作,将每个方法的参数以Parcel的形式发送给BD。
例如BpServiceManager的
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
● 同时又将BpBinder作为了自己的成员来管理,将BpBinder存储在mRemote中,BpServiceManager通过调用BpRefBase的remote()来获得BpBinder指针。

 

6.2 BnINTERFACE

在定义android native端的service时,每个service均继承自BnINTERFACE(INTERFACE为service name)。BnINTERFACE类型定义了一个onTransact函数,这个函数负责解包收到的Parcel并执行client端的请求的方法。

顺着BnINTERFACE的继承关系再往上看,
class BnINTERFACE: public BnInterface<IINTERFACE>

IINTERFACE为client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口类,这个共同接口类的目的就是保证service方法在C-S两端的一致性。

再往上看
class BnInterface : public INTERFACE, public BBinder

同时我们发现了BBinder类型,这个类型又是干什么用的呢?既然每个service均可视为一个binder,那么真正的server端的binder的操作及状态的维护就是通过继承自BBinder来实现的。可见BBinder是service作为binder的本质所在。

那么BBinder与BpBinder的区别又是什么呢?

其实它们的区别很简单,BpBinder是client端创建的用于消息发送的代理,而BBinder是server端用于接收消息的通道。查看各自的代码就会发现,虽然两个类型均有transact的方法,但是两者的作用不同,BpBinder的transact方法是向IPCThreadState实例发送消息,通知其有消息要发送给BD;而BBinder则是当IPCThreadState实例收到BD消息时,通过BBinder的transact的方法将其传递给它的子类BnSERVICE的onTransact函数执行server端的操作。

 

7. Parcel

Parcel是binder IPC中的*基本的通信单元,它存储C-S间函数调用的参数.但是Parcel只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储。

简单的Parcel读写不再介绍,下面着重介绍一下2个函数

 

7.1 writeStrongBinder

当client需要将一个binder向server发送时,可以调用此函数。例如
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}

看一下writeStrongBinder的实体
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}

接着往里看flatten_binder
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;

obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
LOGE(“null proxy”);
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}

return finish_flatten_binder(binder, obj, out);
}

还是拿addService为例,它的参数为一个BnINTERFACE类型指针,BnINTERFACE又继承自BBinder,
BBinder* BBinder::localBinder()
{
return this;
}
所以写入到Parcel的binder类型为BINDER_TYPE_BINDER,同时你在阅读SM的代码时会发现如果SM收到的service的binder类型不为BINDER_TYPE_HANDLE时,SM将不会将此service添加到svclist,但是很显然每个service的添加都是成功的,addService在开始传递的binder类型为BINDER_TYPE_BINDER,SM收到的binder类型为BINDER_TYPE_HANDLE,那么这个过程当中究竟发生了什么?
为了搞明白这个问题,花费我很多的事件,*终发现了问题的所在,原来在BD中做了如下操作(drivers/staging/android/Binder.c):

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
……………………………………

if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc;
……………………………………
}

 

阅读完addService的代码,你会发现SM只是保存了service binder的handle和service的name,那么当client需要和某个service通信了,如何获得service的binder呢?看下一个函数

7.2 readStrongBinder

当server端收到client的调用请求之后,如果需要返回一个binder时,可以向BD发送这个binder,当IPCThreadState实例收到这个返回的Parcel时,client可以通过这个函数将这个被server返回的binder读出。

sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}

往里查看unflatten_binder

status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);

if (flat) {
switch (flat->type) {
case BINDER_TYPE_BINDER:
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}

发现如果server返回的binder类型为BINDER_TYPE_BINDER的话,也就是返回一个binder引用的话,直接获取这个binder;如果server返回的binder类型为BINDER_TYPE_HANDLE时,也就是server返回的仅仅是binder的handle,那么需要重新创建一个BpBinder返回给client。

有上面的代码可以看出,SM保存的service的binder仅仅是一个handle,而client则是通过向SM获得这个handle,从而重新构建代理binder与server通信。

这里顺带提一下一种特殊的情况,binder通信的双方即可作为client,也可以作为server.也就是说此时的binder通信是一个半双工的通信。那么在这种情况下,操作的过程会比单工的情况复杂,但是基本的原理是一样的,有兴趣可以分析一下MediaPlayer和MediaPlayerService的例子。

 

8. 经典桥段分析

main_ mediaserver.cpp
int main(int argc, char** argv)
{

//创建进程mediaserver的ProcessState实例
sp<ProcessState> proc(ProcessState::self());

//获得SM的BpServiceManager
sp<IServiceManager> sm = defaultServiceManager();
LOGI(“ServiceManager: %p”, sm.get());

//添加mediaserver中支持的service。
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();

//启动ProcessState的pool thread
ProcessState::self()->startThreadPool();

//这一步有重复之嫌,加不加无关紧要。
IPCThreadState::self()->joinThreadPool();
}

 

9. Java 层的binder机制

了解了native通信机制后,再去分析JAVA层的binder机制,就会很好理解了。它只是对native的binder做了一个封装。这一部分基本上没有太复杂的过程,这里不再赘述了。

Android Binder机制的详解

1.Binder机制简介
Android Binder是源于Palm的OpenBinder,它为android设备跨进程访问而设计。Binder机制从表现上看可以实现java 和native层的对象实例可以从一个进程访问到另一个进程,从而透明的实现跨进程的调用。调用者感觉不到自己的请求是在另一个进程中完成并返回结果的。在android系统中使用Binder机制*多的是系统服务,这些服务一般运行在特定的进程中。服务的使用者成为Client,Client一般是应用程序,是跨进程访问的请求发起者。运行行在特定进程中的服务被成为server,这些server用于接受client端发起的请求并处理具体相关业务并返回结果。

Android系统中的Binder机制涉及java和c++层两部分,使java和native层都可以实现Binder的client、server逻辑并通过系统的/dev/binder虚拟设备提供的跨进程能力发起请求和接受请求处理业务返回结果。其中java层的Binder是对C++层Binder机制的上层封装,通过JNI的方式调用到C++的实现逻辑中。

我将通过分别分析C++层的实现和java的封装两部分分析Binder机制。

2.C++层Binder的实现
我通过分析MediaServer的实现逻辑通过阅读源码了解一下Binder的实现逻辑.MediaServer执行程序的入口是main()方法:

…/frameworks/av/media/mediaserver/main_mediaserver.cpp
int main(int argc __unused, char** argv)
{
…………………

//获取一个ProcessState对象,同时通过pen_driver()打开/dev/binder的虚拟设备,通过mmap()为binder分配一块空间用于通信读写,通过
//iotrl()为binder指定线程数
sp<ProcessState> proc(ProcessState::self());
//获取一个IServerManager对象,用于服务注册
sp<IServiceManager> sm = defaultServiceManager();
ALOGI(“ServiceManager: %p”, sm.get());
//初始化音频系统AudioFlinger
AudioFlinger::instantiate();
//多媒体服务初始化
MediaPlayerService::instantiate();
ResourceManagerService::instantiate();
//cameraServer 相机服务
CameraService::instantiate();
//音频系统AudioPolicy服务
AudioPolicyService::instantiate();
SoundTriggerHwService::instantiate();
RadioService::instantiate();
registerExtensions();
//创建线程池?
ProcessState::self()->startThreadPool();
//加入线程池?
IPCThreadState::self()->joinThreadPool();

………………..
}

1.1ProcessState的作用
从main_mediaServer.cpp的main()方法中可以看到 sp proc(ProcessState::self())获取一个ProcessState对象,先看一下ProcessState::self()函数都做了什么:

sp<ProcessState> ProcessState::self()
{
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) {
return gProcess;
}
//创建一个新的ProcessState对象
gProcess = new ProcessState;
return gProcess;
}

实现很简单,self()方法就是一个简单的单例调用new 创建了一个ProcessState对象。那看一下ProcessState的构造方法都做了些什么:

…/frameworks/native/libs/binder/ProcessState.cpp

ProcessState::ProcessState()
: mDriverFD(open_driver()) //打开Binder
, mVMStart(MAP_FAILED) //映射内存的的起始地址
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
{
if (mDriverFD >= 0) {
#if !defined(HAVE_WIN32_IPC)
//为Binder分配一块内存用于存取数据
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
// *sigh*
ALOGE(“Using /dev/binder failed: unable to mmap transaction memory.\n”);
close(mDriverFD);
mDriverFD = -1;
}
#else
mDriverFD = -1;
#endif
}

LOG_ALWAYS_FATAL_IF(mDriverFD < 0, “Binder driver could not be opened. Terminating.”);
}

在ProcessState的构造函数中首先调用了open_driver()方法初始化了一个ProcessState的变量mDriverFD,这个open_driver()方法很重要,它打开/dev/binder这个虚拟设备为binder通信做准备。在打开binder设备后又调用系统函数mmap() 为打开的binder设备在内存中映射了一块内存空间并将内存地址返回给变量mVMStart。

那先看一下open_driver()是怎么样打开binder驱动设备的。

…/frameworks/native/libs/binder/ProcessState.cpp
static int open_driver()
{
//打开/dev/binder虚拟设备
int fd = open(“/dev/binder”, O_RDWR);

“““““““““““`
//通过ioctl()告诉驱动*多支持的线程数 15
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
return fd;
}

在open_driver()中直接调用了linux系统函数open()打开了/dev/binder设备并为启动的Binder虚拟设备通过ioctl()[系统函数]为binder 设备指定了*大线程数;

分析到这里基本上就是main_mediaserver–>main()函数中sp proc(ProcessState::self())这句代码做的事情。总结起来主要做了两件事情:

open_driver()打开/dev/binder虚拟设备驱动
为打开的Binder虚拟设备分配内存映射空间
那下边继续回到main_mediaserver的main()方法中继续分析,看在获取到ProcessState后的sp sm =defaultServiceManager()干了什么。

defaultServerManager
defaultServerManager()函数会返回一个实现了IServerManager接口的对象,通过这个对象我们可以和另外一个进程的ServerManager进行通信.先看一下defaultServerManager()方法都干了点什么:

…/frameworks/native/libs/binder/IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;

{
AutoMutex _l(gDefaultServiceManagerLock);
while (gDefaultServiceManager == NULL) {
//gDefaultServiceManager对象创建的地方
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
if (gDefaultServiceManager == NULL)
sleep(1);
}
}

return gDefaultServiceManager;
}

这个方法在IServerManager.cpp中定义的,这个方法又是一个单例来获取一个IServerManger对象。内容很简单但是当我看到interface_cast(…)后我感觉一点都不好,但是它是很重要的不过暂时先不管它。先看看ProcessState::self()->getContextObject(NULL)方法都干了点什么:

…/frameworks/native/libs/binder/ProcessState.cpp

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
//好吧,这里直接调用了getStrongProxyForHande()方法。
//但是传入的参数 0很重要
return getStrongProxyForHandle(0);
}

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;

AutoMutex _l(mLock);
//资源查找,找不到会创建一个新的对象
handle_entry* e = lookupHandleLocked(handle);

if (e != NULL) {
IBinder* b = e->binder;
//新创建的binder 一定为空
if (b == NULL || !e->refs->attemptIncWeak(this)) {
if (handle == 0) {

························

Parcel data;
//这里是一次IPC的binder通信过程,暂时不分析
//我们现在分析的是getStrongProxyForHander()方法返回的是什么
status_t status = IPCThreadState::self()->transact(
0, IBinder::PING_TRANSACTION, data, NULL, 0);
if (status == DEAD_OBJECT)
return NULL;
}
//创建一个BpBinder对象
b = new BpBinder(handle);
e->binder = b; //填充
if (b) e->refs = b->getWeakRefs();
result = b;

···············
}

return result;
}

在getStrongProxyForHandle()函数中首先调用了lookupHandleLocked(0)方法获取一个handle_entry 类型的结构指针。lookupHandleLocked()就是在一个Vector中查找对象找不到就创建一个,不重要先忽略。然后这里创建了一个BpBinder对象,根据名字看应该是和Binder机制有关的我们先可以一下BpBinder的构造方法:

…/frameworks/native/libs/binder/BpBinder.cpp

//BpBinder构造函数
BpBinder::BpBinder(int32_t handle)
: mHandle(handle) //handle 为 0
, mAlive(1)
, mObitsSent(0)
, mObituaries(NULL)
{
“““““““`
//另外一个重要对象?
IPCThreadState::self()->incWeakHandle(handle);
}

在BpBinder的构造函数中出现了一个新对象IPCThreadState,对象名字竟然以IPC应该是和Binder的跨进程通信又关系的。

…/frameworks/native/libs/binder/IPCThreadState.cpp

IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS) {
restart:
const pthread_key_t k = gTLS;
//TLS = thread location storage 线程本地空间 是线程独有空间
//getSpecific()和setSpecific()可以操作这部分空间
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if (st) return st;
//返回一个创建的IPCThreadState对象
return new IPCThreadState;
}

if (gShutdown) return NULL;

““““
goto restart;
}

当我*次看到这个方法时还是有点震惊的,我虽然没有学习过C++但是大一那会儿学习过C语言。当时老师就告诉我们尽量少用goto这个逻辑跳转。这里竟然用了。

这个方法中IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k)是从线程的TLS中获取一个IPCThreadState对象指针,*次调用市肯定是不存在这个对象的所以会new 一个IPCThreadState对象。

TLS是操作系统为每个线程都单独分配的一块内存空间,当前线程独占其他线程无法访问。所以在多线程下对它的访问的线程安全的。

既然这里调用了IPCThreadState的构造函数我们就看看它长什么样子:

…/frameworks/native/libs/binder/IPCThreadState.cpp

IPCThreadState::IPCThreadState()
: mProcess(ProcessState::self()), //获取上边我们创建的ProcessState对象
mMyThreadId(gettid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
//在构造函数中将自己设置到TLS中
pthread_setspecific(gTLS, this);
clearCaller();
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}

IPCThreadState的构造函数很简单就是创建自己后将自己设置到当前线程的TLS中,下次就可以直接从当前线程的TLS中获取了。

我们分析了这么多其实都是gDefaultServiceManager = interface_cast(ProcessState::self()->getContextObject(NULL))的getContextObject(null)方法开始分析。好吧,要不是不是回到看一下我都已经不知道自己现在在分析什么了。那现在我们知道了getContextObject()返回的是一个BpBinder对象。那我们再看看interface_cast()设个什么东西:

…/frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}

原来interface_cast()是一个定义在IInterface.h头文件中的模范方法,那我们现在根据我们上边的调用翻译一下这个模版方法现在的样子:

inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj) {
return IServiceManager::asInterface(obj);
}

实话说分析到这里我有点懵了不知道怎么分析了. 既然模版方法中调用了IServiceManager::asInterface()方法我们就先看看IServiceManager接口的内容:

…/frameworks/native/include/binder/IServiceManager.h

class IServiceManager : public IInterface
{
public:
//这个鸿定义在IInterface.h中,他主要做了如下工作
//1.定义了一个描述字符串
//2.定义了一个asInterface函数
//3.定义了一个getInterfaceDescriptor函数用于返回上边定义的描述字符串
//4.定义了构造函数和析构函数
DECLARE_META_INTERFACE(ServiceManager);

virtual sp<IBinder> getService( const String16& name) const = 0;

virtual sp<IBinder> checkService( const String16& name) const = 0;

virtual status_t addService( const String16& name,
const sp<IBinder>& service,
bool allowIsolated = false) = 0;

virtual Vector<String16> listServices() = 0;

enum {
GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
CHECK_SERVICE_TRANSACTION,
ADD_SERVICE_TRANSACTION,
LIST_SERVICES_TRANSACTION,
};
};

我们都知道Android系统中的系统服务像AMS、WMS等都是有ServiceManager统一管理的。这里的IServiceManager.h就是定义了他的功能接口。在这个接口定义中我们看到一个宏DECLARE_META_INTERFACE(ServiceManager)的使用,那我们先分析一下这个宏定义是什么样子:

#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();

同样我还是先翻译一下这个宏在当前使用场景的样子:

static const android::String16 descriptor;
static android::sp<IServiceManager> asInterface(const android::sp<IBinder>& obj);
virtual const android::String16 getInterfaceDescriptor() const;
IServiceManager();
virtual ~IServiceManager();

从对DECLARE_META_INTERFACE宏的翻译可以看出这是属性和方法的声明;

通过上边的对那个IServiceManager.h和对DECLARE_META_INTERFACE的翻译我们已经知道了IServiceManager接口定义的样子我们下面看一下他的具体实现,但是具体实现内容比较多我抽取其中重要部分分析一下:

…/frameworks/native/libs/binder/IServiceManager.cpp

…………省略………….

//IMPLEMENT_META_INTERFACE这个宏是定义在IInterface.h文件中的
//1.定义常量字符串android.os.IServiceManager
//2.实现getInterfaceDescriptor()函数
//3.实现asInterface()函数,并返回一个BpServiceManager对象
IMPLEMENT_META_INTERFACE(ServiceManager, “android.os.IServiceManager”);

status_t BnServiceManager::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//printf(“ServiceManager received: “); data.print();
switch(code) {
………..继续省略…………..
}
}

在IServiceManager的实现中真正让我感兴趣的这个IMPLEMENT_META_INTERFACE 宏的使用。同样我们找找这个宏的定义并将这个宏翻译一下:

宏定义在IInterface.h文件中
#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() { } \

感觉当前这个宏的使用场景翻译一下:

const android::String16 IServiceManager::descriptor(“android.os.IServiceManager”);
const android::String16& IServiceManager::getInterfaceDescriptor() const {
return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(const android::sp<android::IBinder>& obj) {
android::sp<IBinder> intr;
if(obj != null) {
//这里也很重要
intr = static_cast<IServiceManager>(obj->queryLocalInterface(IServicManager:;descriptor).get());
if(intr == NULL) {
//重点看这里
intr = new BpServieceManager(obj);
}
}
return intr;
//构造函数我就不翻译了。这翻译真要命
}

现在我们已经翻译完了***IMPLEMENT_META_INTERFACE***宏的内容现在将翻译后内容替换到IServiceManager.cpp中看看

…/frameworks/native/libs/binder/IServiceManager.cpp

…………省略………….

//IMPLEMENT_META_INTERFACE这个宏是定义在IInterface.h文件中的
//1.定义常量字符串android.os.IServiceManager
//2.实现getInterfaceDescriptor()函数
//3.实现asInterface()函数,并返回一个BpServiceManager对象
<!–IMPLEMENT_META_INTERFACE(ServiceManager, “android.os.IServiceManager”);–>

const android::String16 IServiceManager::descriptor(“android.os.IServiceManager”);
const android::String16& IServiceManager::getInterfaceDescriptor() const {
return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(const android::sp<android::IBinder>& obj) {
android::sp<IBinder> intr;
if(obj != null) {
//这里也很重要
intr = static_cast<IServiceManager>(obj->queryLocalInterface(IServicManager:;descriptor).get());
if(intr == NULL) {
//重点看这里
intr = new BpServieceManager(obj);
}
}
return intr;
//构造函数我就不翻译了。这翻译真要命

status_t BnServiceManager::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//printf(“ServiceManager received: “); data.print();
switch(code) {
………..继续省略…………..
}
}

这就是翻译后的IServiceManager.cpp的样子我们重点看一下asInterface()方法, 这里这个方法竟然返回了一个BpServiceManager对象。还记得我们先变分析中哪里调用了这个asInterface()方法吗?没错就是我们在interface_cast()模版方法调用,现在我们知道在当前分析场景下interface_cast()返回的是一个***BpServiceManager***类型的对象。那么问题现在有来了,还记得interface_cast()方法是在哪里调用的吗?没错就是defaultServiceManager()方法中调用的。

现在终于将defaultServiceManager()函数分析完了,*终这个方法会返回一个BpServiceManager类型的对象(累死人了,分析了这么多终于知道了这个方法返回了什么。要疯了,特别想爆粗口)。那现在我是特别想知道BpServiceManager到底是个什么东西。但是现在不着急,defautServiceManager()方法引发这么多的联动分析我还是先回味一下defaultServiceManager()函数都干了些啥:

首先调用了ProcessState::getContextObject(null)函数,但是这个函数什么都没有干就直接调用了自己的getStrongProxyForHandle(0)函数。在getStrongProxyForHandle()函数中创建了一个BpBinder对象。
在BpBinder构造函数中又引出了一个重要的类IPCThreadState类。在这里调用了IPCThreadState::self();
在IPCThreadState::self()函数中首先从线程的TLS中获取IPCThreadState对象。到那时在首次点用时TLS中并未存储,所以会调用它的构造方法创建一个IPCThreadState对象并将创建的对象保存到TLS中。以后就可以直接从TLS中获取。
interface_cast()模版方法中调用了IServiceManager的asInterface()函数。通过我们对模版方法、DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏的分析翻译知道*终asInterface()返回了一个BpServiceManager对象。
在整个defaultServiceManager()函数中涉及到BpBinder、ProcessState、IPCThreadState、BpServiceManager等新的类。我们有必要分析一下目前他们的关系如何。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8YrAU0q2-1593588562795)(https://thumbnail0.baidupcs.com/thumbnail/bb7c4d396ec292dc71350f5750f7c4d5?fid=2586003311-250528-448513258459521&time=1546952400&rt=sh&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-HxxYlrpgwgJNMNKaDwPEh57%2BXOk%3D&expires=8h&chkv=0&chkbd=0&chkpc=&dp-logid=183401465543768111&dp-callid=0&size=c710_u400&quality=100&vuk=-&ft=video)]

现在我们可以根据类图关系再来分析一下他们。首先看一下BpServiceManager:

class BpServiceManager : public BpInterface<IServiceManager>
{
//impl参数是IBinder类型,实际上就是上边创建的BpBinder
public BpServiceManager(const sp<IBinder>& impl)
//调用了父类的构造方法,参数impl 是 BpBinder
: BpInterface<IServiceManager>(impl)
{
}
}

BpServiceManager继承自BpInterface。现在看一下BpInterface:

模版类
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);

protected:
virtual IBinder* onAsBinder();
};

—————————-
模版方法
template<typename INTERFACE>
//remote参数是BpBinder
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
//调用父类的构造方法
: BpRefBase(remote)
{
}

继续看一下BpRefBase的实现:

//mRemote*终等于BpBinder(0)对象
BpRefBase::BpRefBase(const sp<IBinder>& o)
//重点在这里,Bpbinder本传递给了mRemote.Binder的通信需要通过mRemote来完成。
: mRemote(o.get()), mRefs(NULL), mState(0)
{
extendObjectLifetime(OBJECT_LIFETIME_WEAK);

if (mRemote) {
mRemote->incStrong(this); // Removed on first IncStrong().
mRefs = mRemote->createWeak(this); // Held for our entire lifetime.
}
}

现在我们知道BpServiceManager实现了IServiceManager接口同时持有了BpBinder,BpBinder可以用于进程的通信。此时BpServiceManager是ServiceManager客户端的代理对象,BpBinder是Binder通信机制的Client端代表。

2. MediaPlayerService addService()
我们继续回到MediaServer的main()函数中继续分析。看看MediaPlayerService::instantiate()实现:

void MediaPlayerService::instantiate() {
//通过上边的分析我们已经知道defaultServiceManager()返回的是一个BpServiceManager对象。在这里调用了他的addService()方法。
defaultServiceManager()->addService(
String16(“media.player”), new MediaPlayerService());
}

在instantiate()函数中调用了BpServiceManager的addService():

…/frameworks/native/libs/binder/IServiceManager.cpp的内嵌类BpServiceManager

virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated)
{
Parcel data, reply;
//
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
//remote()函数返回的是mRemote,就是BpRefBase中的mRemote,即BpBinder对象。这里调用了BpBinder的transact()方法。
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}

从BpServiceManager的addService()方法可以看出*终将调用转移到了BpBinder中。由此我们知道BpBinder才是代表client进行跨进程通信的代表。下面我们继续分析一下BpBinder;

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) {
//BpBinder将通信委托给了IPCThreadState的transcat()方法
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}

return DEAD_OBJECT;
}

看到这里还是有点震惊的,我本来认为BpBinder已经是实现通信的客户端的代表了。现在发现它竟然将通信委托给IPCThreadState来完成。原来BpBinder还是个壳子。继续看一下IPCThreadState::transact():

status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err = data.errorCheck();

““““““““““““““`

if (err == NO_ERROR) {
LOG_ONEWAY(“>>>> SEND from pid %d uid %d %s”, getpid(), getuid(),
(flags & TF_ONE_WAY) == 0 ? “READ REPLY” : “ONE WAY”);
//发送Binder通信消息
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}

if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}

if ((flags & TF_ONE_WAY) == 0) {
#if 0
if (code == 4) { // relayout
ALOGI(“>>>>>> CALLING transaction 4”);
} else {
ALOGI(“>>>>>> CALLING transaction %d”, code);
}
#endif
if (reply) {
//等待通讯结果
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}

““““““““““““
} else {
err = waitForResponse(NULL, NULL);
}

return err;
}

在IPCThreadState的transact()方法中有两个重要的方法调用, err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL)和err = waitForResponse(reply)。我们先来分析一下writeTransactionData()的实现:

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; //binder_transaction_data 是binder通信的数据结构

tr.target.ptr = 0; /* Don’t pass uninitialized stack data to a remote process */
tr.target.handle = handle; //现在handle是0;0代表ServiceManager。
tr.code = code;
tr.flags = binderFlags;
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();
} else if (statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else {
return (mLastError = err);
}
//mOut的类型是Pracel.它是准备发送给Binder的Server端。
mOut.writeInt32(cmd);
//将Binder通信的数据写入到mOut中
mOut.write(&tr, sizeof(tr));

return NO_ERROR;
}

从writeTransactionData()方法的分析发现里边主要做了数据准备并将数据序列化到Pracel中。

我们继续分析分析waitForResponse(reply)函数:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
//循环处理
while (1) {
//重点talkWithDriver()
if ((err=talkWithDriver()) < NO_ERROR) break;
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;

cmd = (uint32_t)mIn.readInt32();

IF_LOG_COMMANDS() {
alog << “Processing waitForResponse Command: ”
<< getReturnString(cmd) << endl;
}

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;

case BR_ACQUIRE_RESULT:
{
ALOG_ASSERT(acquireResult != NULL, “Unexpected brACQUIRE_RESULT”);
const int32_t result = mIn.readInt32();
if (!acquireResult) continue;
*acquireResult = result ? NO_ERROR : INVALID_OPERATION;
}
goto finish;

case BR_REPLY:
{
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
ALOG_ASSERT(err == NO_ERROR, “Not enough command data for brREPLY”);
if (err != NO_ERROR) goto finish;

if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t),
freeBuffer, this);
} else {
err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
}
} else {
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
continue;
}
}
goto finish;

default:
//重点
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}

BR_开头的case都是Binder Server端的,BC_开头的case是Binder Client端使用。
在IPCThreadState的waitForResponse()函数中重要的方法有talkWithDriver()和case 的default:分支中的executeCommand()函数。现在我们先分析一下talkWithDriver():

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
if (mProcess->mDriverFD <= 0) {
return -EBADF;
}

binder_write_read bwr; //binder通信的数据结构

// Is the read buffer empty?
const bool needRead = mIn.dataPosition() >= mIn.dataSize();

// We don’t want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();

// This is what we’ll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}

IF_LOG_COMMANDS() {
TextOutput::Bundle _b(alog);
if (outAvail != 0) {
alog << “Sending commands to driver: ” << indent;
const void* cmds = (const void*)bwr.write_buffer;
const void* end = ((const uint8_t*)cmds)+bwr.write_size;
alog << HexDump(cmds, bwr.write_size) << endl;
while (cmds < end) cmds = printCommand(alog, cmds);
alog << dedent;
}
alog << “Size of receive buffer: ” << bwr.read_size
<< “, needRead: ” << needRead << “, doReceive: ” << doReceive << endl;
}

// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
//又是一个循环
do {
IF_LOG_COMMANDS() {
alog << “About to read/write, write size = ” << mOut.dataSize() << endl;
}
#if defined(HAVE_ANDROID_OS)
//通过ioctl()进行读写
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
#else
err = INVALID_OPERATION;
#endif
if (mProcess->mDriverFD <= 0) {
err = -EBADF;
}
IF_LOG_COMMANDS() {
alog << “Finished read/write, write size = ” << mOut.dataSize() << endl;
}
} while (err == -EINTR);

IF_LOG_COMMANDS() {
alog << “Our err: ” << (void*)(intptr_t)err << “, write consumed: ”
<< bwr.write_consumed << ” (of ” << mOut.dataSize()
<< “), read consumed: ” << bwr.read_consumed << endl;
}

if (err >= NO_ERROR) {
//清空客户端的Pracel的内存空间
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < mOut.dataSize())
mOut.remove(0, bwr.write_consumed);
else
mOut.setDataSize(0);
}
//将ioctl()从sever端获取的数据写入到mIn中准备发送给client端
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}

““““““
return NO_ERROR;
}

return err;
}

完成Binder通信其实是通过Binder驱动的共享内存来完成不同进程之间的通信,从对talkWithDriver()函数分析来看完成对Binder驱动的共享内存的操作是ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)函数来完成的。ioctl()是一个linux系统API。对ioctl()方法调用是在do{}while()中调用的,由此可见这个ioctl()会多次调用来完成通信。在ioctl()完成后请求结果被放到binder_write_read类型的bwr中,然后将bwr的结果内容写入到Pracel的mIn中为下一步返回客户端做做准备。

我们在回到IPCThreadState::waitForResponse()函数中分析一下另外一个executeCommand(cmd)方法:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;

switch ((uint32_t)cmd) {
case BR_ERROR:
result = mIn.readInt32();
break;

case BR_OK:
break;

case BR_ACQUIRE:
“““““`
break;
case BR_RELEASE:
“““““`
break;

case BR_INCREFS:
““““““`
break;

““““`

case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
ALOG_ASSERT(result == NO_ERROR,
“Not enough command data for brTRANSACTION”);
if (result != NO_ERROR) break;

Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);

const pid_t origPid = mCallingPid;
const uid_t origUid = mCallingUid;
const int32_t origStrictModePolicy = mStrictModePolicy;
const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;

mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
mLastTransactionBinderFlags = tr.flags;

int curPrio = getpriority(PRIO_PROCESS, mMyThreadId);
if (gDisableBackgroundScheduling) {
if (curPrio > ANDROID_PRIORITY_NORMAL) {
setpriority(PRIO_PROCESS, mMyThreadId, ANDROID_PRIORITY_NORMAL);
}
} else {
if (curPrio >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(mMyThreadId, SP_BACKGROUND);
}
}

Parcel reply;
status_t error;

if (tr.target.ptr) {
//BnService从BBinder派生
//这里的B实际上实现了BnServiceXXX的对象
sp<BBinder> b((BBinder*)tr.cookie);
error = b->transact(tr.code, buffer, &reply, tr.flags);

} else {
error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
}

if ((tr.flags & TF_ONE_WAY) == 0) {
LOG_ONEWAY(“Sending reply to %d!”, mCallingPid);
if (error < NO_ERROR) reply.setError(error);
sendReply(reply, 0);
} else {
LOG_ONEWAY(“NOT sending reply to %d!”, mCallingPid);
}

mCallingPid = origPid;
mCallingUid = origUid;
mStrictModePolicy = origStrictModePolicy;
mLastTransactionBinderFlags = origTransactionBinderFlags;

}
break;
·····································
return result;
}

看到executeCommand()函数中那么多的switch分支真不知道从哪里分析,脑子已经懵懵的。还是从BR_TRANSACTION:这个分支看看吧。在这个分支中我们可以看到//BnService从BBinder派生sp b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply, tr.flags);我们又看到了一个BBinder类的对象,有我们从BpBinder是Binder Client的代表可以猜测BBinder可能是Binder的Server端的代表。实际上这里的b是BnSerciceManager对象。那先放下executeCommaond()函数,这里我们先来分析一下BBinder和BnServiceManaer是什么然后在回来分析:

“`/frameworks/native/include/binder/IServiceManager.h
class BnServiceManager : public BnInterface<IServiceManager>
{
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};

开始看张类图直观一些,手画丑的不行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2YzuoKT4-1593588562797)(https://thumbnail0.baidupcs.com/thumbnail/01a427c58cc69e8688aeddecc50fb5c3?fid=2586003311-250528-868147785755165&time=1546952400&rt=sh&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-AvZaBr10el8zdroFSH1cY0BrMYs%3D&expires=8h&chkv=0&chkbd=0&chkpc=&dp-logid=183456904394929954&dp-callid=0&size=c710_u400&quality=100&vuk=-&ft=video)]

BnServiceManager继承自BnInterface,再看一下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,在BnServiceManager继承BnInterface时模版类中的INTERFACE 是 IServiceManager。用于BnInterface继承了INTERFACE,所以BnServicManager继承了ISeviceManager接口。下面看一下BBinder的定义:

“`/frameworks/native/include/binder/Binder.h
class BBinder : public IBinder
{
………….
}

这样以来继承关系已经很明确。再BnServiceManager的接口声明中重新声明了onTransact()函数并在IServiceManager.cpp中实现了它:

status_t BnServiceManager::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//printf(“ServiceManager received: “); data.print();
switch(code) {
case GET_SERVICE_TRANSACTION: {
········
} break;
case CHECK_SERVICE_TRANSACTION: {
········
} break;
case ADD_SERVICE_TRANSACTION: {
CHECK_INTERFACE(IServiceManager, data, reply);
String16 which = data.readString16();
sp<IBinder> b = data.readStrongBinder();
//在这里有调用了addService()完成真正的服务添加。
status_t err = addService(which, b);
reply->writeInt32(err);
return NO_ERROR;
} break;
case LIST_SERVICES_TRANSACTION: {
········
return NO_ERROR;
} break;
default:
//调用父类默认实现
return BBinder::onTransact(code, data, reply, flags);
}
}

我们对BnServiceManager和BBinder的分析先分析到这里,回到executeCommond()方法中再结合sp b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply,tr.flags);调用bnServiceManager的transact()方法。以分析的addService()*终会进入到case ADD_SERVICE_TRANSACTION:分支当中并在这里调用了addService()方法。
***看到这里我是有点模糊的这个分支中的addService()方法的实现在哪里,也没有找到一个类继承自BnServiceManager。***后来网上看别人的分析发现还是有个文件实现了这里的ServiceManager的功能,具体实现在Service_manager.c中实现的。

不太明白为什么不继续继承BnServiceManager来实现具体逻辑呢?

ServiceManager
ServiceManager的实现在Service_manager.c中,先从这个文件的main()函数开始了解一下它干了啥:

int main(int argc, char **argv)
{
struct binder_state *bs;
//打开binder虚拟设备
bs = binder_open(128*1024);
if (!bs) {
ALOGE(“failed to open binder driver\n”);
return -1;
}
//角色转换
if (binder_become_context_manager(bs)) {
ALOGE(“cannot become context manager (%s)\n”, strerror(errno));
return -1;
}

//重点,处理客户端发过来的请求
binder_loop(bs, svcmgr_handler);

return 0;
}

main()函数比较简单主要通过调用了binder_open()函数打开Binder的虚拟设备.先看一下binder_open()都做了些什么:

/打开Binder设备
struct binder_state *binder_open(size_t mapsize)
{
struct binder_state *bs;
struct binder_version vers;

bs = malloc(sizeof(*bs));
if (!bs) {
errno = ENOMEM;
return NULL;
}
//打开虚拟设备,调用Linux系统方法打开文件
bs->fd = open(“/dev/binder”, O_RDWR);
if (bs->fd < 0) {
fprintf(stderr,”binder: cannot open device (%s)\n”,
strerror(errno));
goto fail_open;
}

if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
fprintf(stderr,
“binder: kernel driver version (%d) differs from user space version (%d)\n”,
vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);
goto fail_open;
}

bs->mapsize = mapsize;
//调用mmap()函数给binder驱动做内存映射
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
if (bs->mapped == MAP_FAILED) {
fprintf(stderr,”binder: cannot map device (%s)\n”,
strerror(errno));
goto fail_map;
}

return bs;

fail_map:
close(bs->fd);
fail_open:
free(bs);
return NULL;
}

我在这个方法中又看到了goto的使用。有点兴奋!

在open_binder()函数中打开了Binder驱动,并给起映射了内存空间。我们继续看一下binder_become_context_manager(bs)干了什么:

int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

很简单但是我没有看太懂,主要是对ioctl()这个函数不太懂。大概是给打开的Binder设备指定了映射名称为0;(谁懂给我普及一下)

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般*多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。百度百科

还有一个处理Binder请求的方法binder_loop(bs, svcmgr_handler):

//binder_handler 是个函数指针,func现在是svcmgr_handler
void binder_loop(struct binder_state *bs, binder_handler func)
{
int res;
struct binder_write_read bwr;
uint32_t readbuf[32];

bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;

readbuf[0] = BC_ENTER_LOOPER;
//在binder_write中调用了ioctl函数,调用Binder设备的函数,标志serviceManager进入的Loop 状态。
binder_write(bs, readbuf, sizeof(uint32_t));
//循环不断的
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
//通过ioctl()方法从Binder设备中读取缓冲区,检查是不是又IPC请求。
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
ALOGE(“binder_loop: ioctl failed (%s)\n”, strerror(errno));
break;
}
//接受到请求,然后解析结果 调用func函数
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
if (res == 0) {
ALOGE(“binder_loop: unexpected reply?!\n”);
break;
}
if (res < 0) {
ALOGE(“binder_loop: io error %d %s\n”, res, strerror(errno));
break;
}
}
}

在binder_loop()中让binder设备进入loop状态,通过循环不断通过ioctl()方法获取Binder设备是不是有IPC请求,如果有将获取到的数据交给binder_parse()函数进行解析.

int binder_parse(struct binder_state *bs, struct binder_io *bio,
uintptr_t ptr, size_t size, binder_handler func)
{
……………..数据处理………………………..

case BR_TRANSACTION: {

………………..

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);
//调用了func()函数,有func()函数去做真正的服务管理
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}
ptr += sizeof(*txn);
break;
}
……………………………..
}
}
return r;
}

在binder_prase()方法的binder_handler func参数指向的是svcmgr_handler函数,下面看看这个函数:

…/frameworks/native/cmds/servicemanager/service_manager.c

//binder 通信处理数据的回调函数
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
struct svcinfo *si;
uint16_t *s;
size_t len;
uint32_t handle;
uint32_t strict_policy;
int allow_isolated;

//ALOGI(“target=%p code=%d pid=%d uid=%d\n”,
// (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid);

if (txn->target.ptr != BINDER_SERVICE_MANAGER)
return -1;

if (txn->code == PING_TRANSACTION)
return 0;

strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}

if ((len != (sizeof(svcmgr_id) / 2)) ||
memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
fprintf(stderr,”invalid id %s\n”, str8(s, len));
return -1;
}

if (sehandle && selinux_status_updated() > 0) {
struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
if (tmp_sehandle) {
selabel_close(sehandle);
sehandle = tmp_sehandle;
}
}

switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
……….

case SVC_MGR_ADD_SERVICE:
//注册服务
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
handle = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
//注册服务真正工作的地方
if (do_add_service(bs, s, len, handle, txn->sender_euid,
allow_isolated, txn->sender_pid))
return -1;
break;

case SVC_MGR_LIST_SERVICES: {
……
}
default:
ALOGE(“unknown code %d\n”, txn->code);
return -1;
}

bio_put_uint32(reply, 0);
return 0;
}

在svcmgr_handler()函数中SVC_ADD_SERVICES分支中注册服务,其中注册服务的实际处理交给了do_add_service()方法继续处理。

int do_add_service(struct binder_state *bs,
const uint16_t *s, size_t len,
uint32_t handle, uid_t uid, int allow_isolated,
pid_t spid)
{
struct svcinfo *si;

if (!handle || (len == 0) || (len > 127))
return -1;
//验证UID是否有添加服务的权限。
if (!svc_can_register(s, len, spid)) {
ALOGE(“add_service(‘%s’,%x) uid=%d – PERMISSION DENIED\n”,
str8(s, len), handle, uid);
return -1;
}
//检查服务是不是已经注册
si = find_svc(s, len);
if (si) {
if (si->handle) {
ALOGE(“add_service(‘%s’,%x) uid=%d – ALREADY REGISTERED, OVERRIDE\n”,
str8(s, len), handle, uid);
svcinfo_death(bs, si);
}
si->handle = handle;
} else {
//如果没有注册分配内存
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
//si这个结构体
si->handle = handle;
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
si->name[len] = ‘\0’;
si->death.func = (void*) svcinfo_death;
si->death.ptr = si;
si->allow_isolated = allow_isolated;
si->next = svclist;
//插入链表
svclist = si;
}

binder_acquire(bs, handle);
binder_link_to_death(bs, handle, &si->death);
return 0;
}

//存储注册服务的结构体,是个链表结构
struct svcinfo
{
struct svcinfo *next;
uint32_t handle;
struct binder_death death;
int allow_isolated;
size_t len;
uint16_t name[0];
};

do_add_service()方法通过坚定要注册的服务是否有权限被注册,然后再检查服务是否已经注册了,如果没有被注册则将其插入到链表中。这样我们的服务注册工作已经完成。
C++层的Binder通信的分析基本上分析完了。想抽时间再分析一下Java层对c++层的封装。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速