我正在开展一个项目,我们在其中向 android 系统添加了一些非标准的安全功能,但我在调整 Binder 时遇到了一些严重的问题。
有没有人对 Binder System 有深入的了解,并且知道 Binder 为什么会“阻塞”一个进程来传输 Parcel,以及接收进程是如何解除阻塞的?
我正在开展一个项目,我们在其中向 android 系统添加了一些非标准的安全功能,但我在调整 Binder 时遇到了一些严重的问题。
有没有人对 Binder System 有深入的了解,并且知道 Binder 为什么会“阻塞”一个进程来传输 Parcel,以及接收进程是如何解除阻塞的?
这是 Android 文档中所说的预期行为:http: //developer.android.com/reference/android/os/IBinder.html
关键的 IBinder API 是由 Binder.onTransact() 匹配的 transact()。这些方法允许您分别发送对 IBinder 对象的调用和接收传入 Binder 对象的调用。此事务 API 是同步的,因此在目标从 Binder.onTransact() 返回之前,对 transact() 的调用不会返回;这是调用本地进程中存在的对象时的预期行为,并且底层进程间通信(IPC)机制确保在跨进程时应用这些相同的语义。
观察通知 API。我们进行了一些调用,以便最终获得对 NotificationManager 对象的引用。有了这个对象,我们调用 notify(...);
117 public void notify(String tag, int id, Notification notification)
118 {
119 int[] idOut = new int[1];
120 INotificationManager service = getService();
121 String pkg = mContext.getPackageName();
122 if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
123 try {
124 service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut);
125 if (id != idOut[0]) {
126 Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
127 }
128 } catch (RemoteException e) {
129 }
130 }
此调用与您的流程同步,它将导致以下调用:
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
38 {
39 switch (code)
40 {
...
46 case TRANSACTION_enqueueNotification:
47 {
48 data.enforceInterface(DESCRIPTOR);
49 java.lang.String _arg0;
50 _arg0 = data.readString();
51 int _arg1;
52 _arg1 = data.readInt();
53 android.app.Notification _arg2;
54 if ((0!=data.readInt())) {
55 _arg2 = android.app.Notification.CREATOR.createFromParcel(data);
56 }
57 else {
58 _arg2 = null;
59 }
60 int[] _arg3;
61 _arg3 = data.createIntArray();
62 this.enqueueNotification(_arg0, _arg1, _arg2, _arg3);
63 reply.writeNoException();
64 reply.writeIntArray(_arg3);
65 return true;
66 }
67 case TRANSACTION_cancelNotification:
68 {
...
169return super.onTransact(code, data, reply, flags);
170}
看到对 this.enqueueNotification 的调用了吗?
public void enqueueNotification(java.lang.String pkg, int id, android.app.Notification notification, int[] idReceived) throws android.os.RemoteException
188{
189android.os.Parcel _data = android.os.Parcel.obtain();
190android.os.Parcel _reply = android.os.Parcel.obtain();
191try {
192_data.writeInterfaceToken(DESCRIPTOR);
193_data.writeString(pkg);
194_data.writeInt(id);
195if ((notification!=null)) {
196_data.writeInt(1);
197notification.writeToParcel(_data, 0);
198}
199else {
200_data.writeInt(0);
201}
202_data.writeIntArray(idReceived);
203mRemote.transact(Stub.TRANSACTION_enqueueNotification, _data, _reply, 0);
204_reply.readException();
205_reply.readIntArray(idReceived);
206}
207finally {
208_reply.recycle();
209_data.recycle();
210}
211}
现在方法(来自 IBinder) mRemote.transact(Stub.TRANSACTION_enqueueNotification, _data, _reply, 0); 会变魔术。根据 Parcel 类的文档:
一个 Parcel 可以包含将在 IPC 另一侧未展平的展平数据(使用此处的各种方法来编写特定类型或通用 Parcelable 接口),以及对将导致另一侧接收的活动 IBinder 对象的引用与 Parcel 中的原始 IBinder 连接的代理 IBinder。
所以一旦对方收到序列化的数据,就会做出相应的响应。所以它因为系统设计而阻塞了调用过程,让开发更加连续,不增加应用程序开发的复杂性和一致性。接收进程没有被阻塞,它是一个活动的 IBinder 对象,它的一个线程将响应请求。现在,如果对象在沉重的开销下工作,它可能会在响应之前阻塞很长时间。因此,如果您打算与忙碌的人交谈,请确保您有一个助手来继续等待回复(也许是另一个线程)。
\o/
阻塞和解除阻塞确实是设计使然 - Binder 是“基于事务的”,但它实际上发生在 IPCThreadState 的低得多的级别,它是每个线程实例保存 Binder 数据:
消息通过 ioctl(2) 操作在低级驱动程序(通常是 /dev/binder,但从 8.0 vndbinder、hwbinder)上发送/接收。这使得调用者可以在一个逻辑操作中发送消息和接收回复,而不是像套接字那样执行两个系统调用(send(2)/receive(2))。
您的应用程序有一个活页夹线程池(由 ProcessState 启动),Android 服务(尤其是 system_server)也是如此。这是为来自驱动程序的传入事务和 BR_* 代码提供服务的内容,这就是 Binder 看似“异步”性质的发生方式 - 池处于休眠状态,但是当传入通知(例如 BR_DEAD_BINDER)到达时,其中一个线程(您可以't control which)在该线程中被转换为 Java 回调(对于死亡通知,onBinderDied)。
这种设计可以轻松享受两全其美——在需要的时候看似异步的操作,在你想要提交“事务”时阻塞发送/接收,这也有助于 AIDL 生成的代码能够获取包裹从池中取出并在完成后回收。