原贴地址: http://hi.baidu.com/leo_han/blog/item/664c712701fd1121c995590b.html
这是关于 USB Mass Storage 的架构图。
上图关系为:
? StorageManager 为 Client , MountService 是 Server ,通过 AIDL 进行进程间通信。
? MountService 是一个 Android Service ,由 systemserver 启动。
? Volume Daemon ( Vold )是一个 Native Service ,有 Init.c 读取 init.rc 后启动。
? MountService 和 Vold 之间通过 Socket 通信。
? NativeDaemonConnector 帮助 MountService 取得 Vold 的 socket ,建立通信。
? Vold 通过 NetLink 读取 Kernel 的 uevent.
? NetLinkManager 帮助 Vold 建立与 kernel 间的通信
本文算是开个张,好久没有写文章了,忙碌一个月,谢谢东西,总结一下。
注:对于上图,我准备分以下几个方面阐述:
1. 《AIDL 之Jave Framework 层实现》
2. 《AIDL 之 Native Framework 层实现》
3. 《AIDL 之Kernel 层实现:Binder 机制》
4. 《Vold 通信详解》
5. 《Vold Framework 层分析》
IPC 概述
IPC 是一系列面向多线程间数据交换的技术集合。这里所说的多线程间的数据交换,包括进程内线程间的数据交换和进程间的线程数据交换。如果进程运行在不同的 PC 上,需要网络建立连接完成进程间通信。(这种情况极其复杂)。 IPC 技术的方法被分为: 消息发送 、 同步 、 内存共享 和 远程过程调用 ( RPC )这几方面功能。( WIKI )个人理解
IPC :进程间通信, 就是在不同进程之间传播或交换信息。进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区 。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件 。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息 ,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。( 百度百科 )
两种解释, WIKI 侧重定义;百度百科侧重应用。进程的实体是线程,进程间的通信说到根本就是线程间的通信。这包括以下几个应用场合
l 同一计算机,进程内的多线程通信。(同步、互斥、锁等技术和概念)这点我们一般不做 IPC ,仅仅按多线程技术处理。
l 同一计算机,进程间的通信。
l 不同计算机,进程间的通信。
RPC :远程过程调用。在计算机科学领域, RPC 被认为是 IPC 的一种。 RPC 可以在 PC 内或者通过网络在 PC 间进行 function call 。程序员可以将一些核心的 API 集中到一个程序中,通过 RPC ,其他需要该核心 API 的程序可以直接调用,提高代码的共享程度。(WIKI )
RPC 是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 协议 假定某些传输协议 的存在,如 TCP 或 UDP ,为通信程序之间携带信息数据。在 OSI 网络通信模型中, RPC 跨越了传输层 和应用层。 RPC 使得开发包括网络分布式多程序在内的应用程序 更加容易。 RPC 采用客户机 / 服务器模式 。 请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保 持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端 调用过程接收答复信息,获得进程结果,然后调用执行继续进行。(百度百科 )
Android 中的 Service
在 《 Android 核心分析 8 : Android 启动过程详解》 一文中提到了两个概念, Android Service 和 Native Service 。刚刚接触过 Android 程序开发的朋友极易把他们与 Android Application 四大组件之一的 Service 弄混淆。
Android 中的 Service 大概有如下三种(据我所知):
? Android Application Service : 在 Android AP 开发中常常用到的一种概念。从最直白的视角来看,就是剥离了界面的 Activity ,它们在很多 Android 的概念方面比较接近,都是封装有一个完整的功能逻辑实现,只不过 Service 不抛头露脸,只是默默无声的做坚实的后盾。概括说,这一组件是利用 Framework 中的 AIDL ,采用拿来主义实现 RPC 其更详尽的原理请参看 《深入 Android 【三】 -组件入门》 。
? Native Service : 在 Native Framework 这一层中提供支持的一种服务。系统启动阶段解析 Init.rc 过程中启动 Native Service (包括各种用户空间的 Daemon ),即在 Init 第一阶段启动。
? Android Service : 在 Java Framework 这一层为系统提供支持的一种服务,又称 Java Service 。由 SystemServer 启动。即在 Init 第二阶段启动。
本节中关注是 Native service 和 Android Service 。下面介绍他们的启动过程
在 system/core/init 中 init.c 读取 init.rc 等系列配置文件:
启动 Native Service :
启动 Zygote , Zygote 启动 sytemserver 建立 Android service :
SystemServer 启动 Android Service :
Android 中 Service 架构
所谓Android 中Service 架构指的是Android Service 、Native Service 是如何联系起来,以及如何与底层进行通信。
在android 中,各种××××Manager 为AP 提供支持,管理系统的运行。××××Manager 充当一个Client ,××××ManagerService 充当Server 为××××Manager 提供支持。简单,经典的C/S 架构。这里的各种××××ManagerService 就是指Android Service ,都在Java Framework 空间,且都在systemserver 进程中。Native Service 通过socket 或者直接JNI ,Android Service 提供支持。Native Service 在Native Framework (C/C++ 空间)中。以上有所元素全部在android 用户空间中。
至于Native Service 与Kernel 的通信主要有Function call 、poll 、select 、NetLink 等几种
采用 Service 架构方式是比较标准的做法,即图上蓝色线的部份;红色线的部份为非 Service 架构式的做法。 Service 在 Android 框架里的角色是「存取底层硬件」,往上层的话,可以和应用程序沟通。因此,采用标准的 Service 做法,好处是在数据存取( data communication )的处理较为系统化。这方面, Android 提供了标准的处理架构。图上的「 core libraries 」即是 Service 程序代码的实现,也就是, Android 应用程序透过 JNI ( Dalvik )来到 Service 这一层,再透过 Service 加载 *.so 文件;而非标准做法则是让应用程序直接透过 JNI 来加载 *.so 文件。
红色的过程,因为不使用 Service 架构,因此「框架整合」的工作量比较小,甚致大部份的实现都不需要改动框架本身。这样的做法,就有点像是「跳过 framework 」的方式;相对的,此时应用程序开发者需要考虑的设计议题就比较多。
未来的 Android 发展趋势,应会以第二种做法为主,即 Manager API 直接与 Native Service 沟通,以达到更好的效能表现。
Android AIDL 简介
通常每个应用程序都在它自己的进程内运行,但有时需要在进程间传递对象,你可以通过应用程序 UI 的方式写个运行在一个不同的进程中的 service 。在 Android 平台中,一个进程通常不能访问其他进程中的内存区域。所以,他们需要把对象拆分成操作系统能理解的简单形式,以便伪装成对象跨越边界访问。编写这种伪装代码相当的枯燥乏味,好在我们提供了 AIDL 工具可以来做这件事。
AIDL(Android 接口描述语言 ) 是一个 IDL 语言,它可以生成一段代码,使在一个 Android 设备上运行的两个进程使用内部通信进程进行交互。如果你需要在一个进程中 ( 例如 : 在一个 Activity 中 ) 访问另一个进程中 ( 例如 : 一个 Service) 某个对象的方法 , 你就可以使用 AIDL 来生成这样的代码来伪装传递各种参数。
AIDL IPC 的机制是基于接口的,和 COM 或 Corba 类似,但它是轻量级的。它使用代理类在客户端和实现层间传递值。
AIDL 原理之 Framewok 层实现
在看是本章前,强烈建议大家花一点点时间。依次阅读以下文章:
这些和后续章节本位我进来研究的一个课题,本不想分开,无赖百度空间的字数限制,只能人为将完整的一篇文章,分割为几个部分。
本节中我们只需要关注 Java Framework 中 StorageManager 和 MountService 。
在开始本章之前,先向大家介绍 COM 的一个概念 ---------Proxy/Stub 结构(代理 / 存根结构)
打个比方,你到自动取款机上去取款;你就是客户,取款机就是你的代理;你不会在乎钱具体放在那里,你只想看到足够或更多的钱从出口出来(这就是 com 的 透明性)。你同银行之间的操作完全是取款机代理实现。你的取款请求通过取款机,传到另一头,银行的服务器,他也没有必要知道你在哪儿取钱,他所关心的是你 的身份,和你取款多少。当他确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出 卡后,操作完成。取款机不是直接同服务器连接的,他们之间还有一个 “ 存根 ” ,取款机与存根通信,服务器与存根通信。从某种意义上说存根就是服务器的代理。(参考 COM 代理与存根 )
AIDLFramework 层的架构,如下图:
换而言之, Android 就是在传统的 C/S 架构中加入了一层,实现 IPC 。图中表明, AIDL 类似 COM 的 Proxy/Stub 架构。不过是现在 android 自己的序列化类 Pacel 。
以 USB Mass Storage 架构举例。看看到底什么是 AIDL 的 framework 层的实现。编写好 aidl 文件,运用 aidl 工具生成 IMountService.java 文件,如下所示(删除部分内容,方便举例)
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: frameworks/base/core/java/android/os/storage/IMountService.aidl
*/
package android.os.storage;
/** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
* In particular, the ordering of the methods below must match the
* _TRANSACTION enum in IMountService.cpp
* @hide - Applications should use android.os.storage.StorageManager to access
* storage functions.
*/
public interface IMountService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.os.storage.IMountService
{
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
。。。。。。。。。。。
case TRANSACTION_registerListener:
{
data.enforceInterface(DESCRIPTOR);
android.os.storage.IMountServiceListener _arg0;
_arg0 = android.os.storage.IMountServiceListener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
。。。。。。。。。。。
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements android.os.storage.IMountService
{
。。。。。。。。。。。。。。。。
/**
* Registers an IMountServiceListener for receiving async
* notifications.
*/
public void registerListener(android.os.storage.IMountServiceListener listener) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
。。。。。。。。。。。。。。。。
}
可以看到 IMountService 中的 Stub 继承 Binder 和 IMountService
看过源代码,我们不难发现上图中对应的角色:
Client ------ StorageManager
Proxy ------ IMountServie.Stub.Proxy
Parcel ------ 对象序列化类,数据只有继承 Parcelable 才能进行 RPC
Stub ------ IMountService.Stub
Server ------ MountService
当 StorageManager 调用 MountService 方法时,例如调用registerListener ,步骤如下:
? 进入 IMountServie.Stub.Proxy 找到对应的方法registerListener 。
? IMountServie.Stub.Proxy registerListener 利用 Parcel 将函数调用的序列化为 android 理解的结构
? 调用 onTransact 函数, onTransact 根据参数,找到对应 registerListener Switch-case 语句执行
? 数据通过 Binder 机制进行写操作,客户端调用阻塞,等待服务端 reply
? 服务端处理完 request 返回
? 客户端取回数据
AIDL 使用
使用 AIDL 实现一个 IPC 有下列步骤 :
l 创建你的 AIDL 文件 - 这个文件定义一个接口 (YourInterface.aidl) ,该接口定义了可供客户端访问的方法和属性。
l 添加 AIDL 文件到你的 makefile 中 -(Eclipse plugin 可以帮你管理 ) 。 Android 包括编译器, AIDL 调用 , 这些都能在 tools/directory 中找到。
l 实现接口方法 -AIDL 编译器从你的 AIDL 接口中使用 JAVA 编程语言来创建一个接口。这个接口有一个名为 Stub 的内部抽象类,它继承接口 ( 并实现供 IPC 调用的所必需的几个附加方法 ) 。你必须创建一个类来实现该接口。
l 向客户端开放接口 - 如果你写个 service ,你应该扩展该 Service 并重载 getBinder() 方法来返回一个实现上述接口的类的实例。
Step1 创建一个 AIDL 文件
AIDL 语法简单,你可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。这些参数和返回值可以是任何类型,甚至是其他的 AIDL 生成的接口。然而,值得重视的是你必须导入所有的 non-bult-in 类型,即使他们已经作为接口在其他包里定义了。下面是些 AIDL 支持的数据类型 :
简单 Java 编程语言类型 (int,boolean 等 ) - 不需要 import 声明。
下面类之一 ( 不需要 import 声明 )
l String
l List - List 中的所有元素都必须是可支持的类型中的一个,包括其他 AIDL 生成接口和 parcelables 。 List 可以作为泛型类来灵活使用 ( 比如 List<String>) 。而实际的接受方的类则总是 ArrayList, 尽管该方法将被生成来使用 List 接口。
l Map - Map 中的所有元素都必须是可支持的类型中的一个,包括其他 AIDL 生成接口和 parcelables 。泛型化的 Maps( 比如 :Map<Stirng,Integer>) 不被支持。而实际的接受方的类则总是 HashMap ,尽管该方法将被生成去使用 Map 接口。
l CharSequence - CharSequence 的作用是可以被 TextView 和其他 Widget 对象使用。
其他的 AIDL 生成接口通过引用方式进行传递。所以 import 声明是必须的。封装协议实现的自定义的类是值传递的方式。所以 import 声明也是必须的。
下面是基本的 AIDL 语法 :
实现接口,扩展 YourInterface.Stub ,并实现方法成员。 ( 你可以创建一个 aidl 文件并实现 stub 方法而不用绑定 -Android 创建过程在 java 文件之前会处理 aidl 文件 ) 。
这里有个例子,它实现了一个调用 IRemoteService 的接口,并使用匿名实例公开一个简单的方法 gerPid():
实现接口时有几个原则 :
l 抛出的异常不要返回给调用者。
l an "Application is Not Responding" dialog). Try to call them in a separate thread.
l IPC 调用是同步的。如果你知道一个 IPC 服务需要超过几毫秒的时间才能完成地话,你应该避免在 Activity/View 线程中调用。
l 因为它会挂起应用程序 (Android 可能会显示 " 应用程序没有响应 " 对话框 ) 。试着在一个独立的线程中调用。
Step3 向客户端公开接口
现在你已完成了接口的实现,你需要向客户端公开该实现。这就是我们所熟悉的 " 发布服务 " 。发布一个 Service ,然后继承 Service 并实现 getBinder() 返回一个实现的类的实例。下面是个 Service 的代码片断,该 Service 向客户端公了 IRemoteService 接口。
Step4 使用 parcelables 进行参数的值传递
注意:如果你现在使用 Eclipse 插件, Parcelables 并不能工作。你会看到以下的错误信息 :
l 仅声明 parcelables 的 .aidl 文件不需要写进 makefile
l aidl 只能生成接口代码,而不是 parcelables 。
这是个众所周知的局限。 Parcelables 仍然可以被 ant build 的 xml 文件或自定义的 build 系统所使用。你应该在 Eclipse 项目中添加一个工作区,该工作区可以为所有的接口手动运行 aidl 工具。下面的步骤 5 说明为何 Eclipse 不该尝试编译这些 aidl 文件。
这是个众所周知的局限。 Parcelables 仍然可以被 ant build 的 xml 文件或自定义的 build 系统所使用。你应该在 Eclipse 项目中添加一个工作区,该工作区可以为所有的接口手动运行 aidl 工具。下面的步骤 5 说明为何 Eclipse 不该尝试编译这些 aidl 文件。
如果你有类需要通过 AIDL 接口从一个进程发送到另一个,你必须确保类代码可以被 IPC 接收端所使用。通常这意味着一开始你就要和 service 进行通讯。
让类支持 parcelable 协议 , 有五点需要注意
l 让类实现 Parcelable 接口。
l 实现 public void writeToParcel(Parcel out) ,该方法可以将当前对象的状态写入 parcel.
l 实现 public void readFromParcel(Parcel in) ,该方法可以在 parcel 中读出值到对象中 .
l 向类中添加一个静态成员 , 名为 CREATOR 。该对象实现了 Parcelable.Creator 接口 .
l 向 parcelable 类中添加一个 .aidl 文件,以便 AIDl 工具可以找到。但不要向 build 中添加该文件。该文件的用法类似于 C 中的头文件 . 你不需要为 parcelable 编译 aidl 文件,就像你不会编译个 .h 文件一样。
AIDL 将使用代码中生成的这些方法和成员来伪装或解读对象。
下面的例子说明了 Rect 类如何实现了 Parcelable 协议 .
Rect 类中的伪装是相当简单的。仔细看看 Parcel 中的其他方法,你会看到其他各种值你都可以写进 Parcel.
注意:不要忽视从其他进程接收数据时的安全性考虑。在本例中, rect 将从 parcel 中读四个数字,而你的工作则是确保这些都在可接受的值得范围内而不管调用者想要干什么。 Android 中的安全和访问许可中有更多关于如何确保应用程序安全的信息。
注意:不要忽视从其他进程接收数据时的安全性考虑。在本例中, rect 将从 parcel 中读四个数字,而你的工作则是确保这些都在可接受的值得范围内而不管调用者想要干什么。 Android 中的安全和访问许可中有更多关于如何确保应用程序安全的信息。
调用一个 IPC 方法
本系列中应经阐述了×××× Manager 和×××× ManagerService 之间的 C/S 关系。他们之间的电泳也已经说明。其实在 Android 中遍布拿来主义, Android 四大组件 Service IPC 也是采用的 AIDL 。下面以一个 Activity 启动一个 Service 为例,讲解。
l 调用类调用远程接口的步骤 :
l 声明一个接口类型的变量,该接口类型在 .aidl 文件中定义。
l 实现 ServiceConnection 。
l 调用 ApplicationContext.bindService(), 并在 ServiceConnection 实现中进行传递 .
l 在 ServiceConnection.onServiceConnected() 实现中,你会接收一个 IBinder 实例 ( 被调用的 Service). 调用 YourInterfaceName.Stub.asInterface((IBinder)service) 将参数转换为 YourInterface 类型。
l 调用接口中定义的方法。你总会捕捉到 DeadObjectException 异常,该异常在连接断开时被抛出。它只会被远程方法抛出。
l 断开连接,调用接口实例中的 ApplicationContext.unbindService()
调用 IPC 服务需要注意几点 :
l 对象在进程间被引用计数
l 匿名对象可以通过方法参数发送。
在 frameworks/base/core/java/android/os/storage/StorageManager.java 中