热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android10 Binder原理概述深入解析_Android

这篇文章主要为大家介绍了Android10 Binder原理概述深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多

IPC工具介绍

Binder作为Android 众多的IPC通讯手段之一,在Framework的数据传输中起到极为关键的作用。为什么Google需要重新创造Binder这么一个IPC工具,使用linux默认提供的Pipe、Socket、共享内存、信号、消息队列等IPC工具不行吗?

答案是 这些传统的linux IPC工具有一部分android也在使用,只是在某些场合下它们无法满足需求,所以才创造了Binder这么一个工具。 为了更好地向各位读者解释为什么需要Binder,我们先来简单地认识一下linux传统的IPC工具,让大家对它们的优势和劣势有一个更为直观的认识。

Pipe

管道是一种最基本的IPC工具,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。它有如下特质:


  • 其本质是一个伪文件(实为内核缓冲区)

  • 由两个文件描述符引用,一个表示读端,一个表示写端。

  • 规定数据从管道的写端流入管道,从读端流出,一般只能单向通信,双向通信需建立两个管道。

  • 只能用于父子、兄弟进程(有共同祖先)间通信。

  • 数据一旦被读走,便不在管道中存在,不可反复读取。 因此,管道的局限性表现得非常明显,它并不适合一对多的方式建立通讯(尽管技术上能够实现),原因在于第5条,管道中的数据无法反复读取。类似的还有FIFO(命名管道),它在管道的基础上做了升级,摆脱了第4条的共同祖先的限制,但仍要面临一对多通讯的困境。

framework中有没有使用Pipe进行通讯?答案是有,但是用的很少,相比之下用的更多的是FIFO!!各位读者如果感兴趣的话,可以在源码中搜一下 TransferPipe这个类,在其中可以找到Pipe的痕迹。

Sign

信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。linux系统已经预置了一部分信号标识,它们都有着特殊的含义,部分信号如下所示:


  • SIGHUP:本信号在用户终端结束时发出,通常是在终端的控制进程结束时,通知同一会话期内的各个作业,这时他们与控制终端不在关联。比如,登录Linux时,系统会自动分配给登录用户一个控制终端,在这个终端运行的所有程序,包括前台和后台进程组,一般都属于同一个会话。当用户退出时,所有进程组都将收到该信号,这个信号的默认操作是终止进程。此外对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。

  • SIGINT:程序终止信号。当用户按下CRTL+C时通知前台进程组终止进程。

  • SIGQUIT:Ctrl+\控制,进程收到该信号退出时会产生core文件,类似于程序错误信号。

  • SIGILL:执行了非法指令。通常是因为可执行文件本身出现错误,或者数据段、堆栈溢出时也有可能产生这个信号。

  • SIGTRAP:由断点指令或其他陷进指令产生,由调试器使用。

  • SIGABRT:调用abort函数产生,将会使程序非正常结束。

  • SIGBUS:非法地址。包括内存地址对齐出错。比如访问一个4个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法地址的非法访问触发。

  • SIGFPE:发生致命的算术运算错误。

  • SIGKILL:用来立即结束程序的运行。不能被捕捉、阻塞或忽略,只能执行默认动作。

信号只能起到对进程的通知作用,它无法发送复杂的数据类型,不适合用于进程间的数据交换。

信号在整个framework中也扮演了极为重要的角色,各位读者可以通过搜索sigemptysetsigaddset等关键字,在源码中找到它们的身影。

message queue

消息队列,Unix的通信机制之一,可以理解为是一个存放消息(数据)容器。将消息写入消息队列,然后再从消息队列中取消息,一般来说是先进先出的顺序。消息队列本质上是位于内核空间的链表,链表的每个节点都是一条消息。每一条消息都有自己的消息类型,消息类型用整数来表示,而且必须大于 0。每种类型的消息都被对应的链表所维护。

其中数字 1 表示类型为 1 的消息,数字2、3、4 类似。彩色块表示消息数据,它们被挂在对应类型的链表上。

消息队列的缺陷在于:容量受到系统限制;消息队列的发送方与接收方没有强关联性,容易造成发送方往消息队列中存放了消息,没有接收方来取消息或接收方没有及时取消息的问题,消息的及时性无法保障。

目前在Android 10的非内核源码范围内,没有发现使用消息队列。

shared memory

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

共享内存利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点。但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,因此,这些进程之间的读写操作的同步问题操作系统无法实现。必须由各进程利用其他同步工具解决,开发上手难度较高,容易出错,且存在数据安全隐患。

目前在Android 10的非内核源码范围内,没有发现使用共享内存。

Socket

Socket这个不需要特别介绍了,不管是做C++开发还是Java开发,都会涉及到套接字编程。相对其他的IPC方式,Socket是最适合做一对多这种通讯需求的。它的问题在于数据需要经过两次拷贝,通讯效率相对低下。这个问题在电脑等设备上都不是什么特别大的问题,但考虑到Android搭载的移动设备,尤其是早期的移动设备,这个问题就很致命了。

framework中当然也存在Socket的使用痕迹,比如 system/core/init/init.cpp这个文件中就采用epoll机制,实现init进程与其子进程的通讯。

Android更看重的是效率和一对多通讯的问题,无法采用传统的IPC工具实现,所以只能考虑自己另起炉灶。除此之外,传统的IPC无法获得对方进程的PID\UID,从而无法鉴别对象的身份,从而会使Android系统的安全性无法得到保证(ps:无法获得对方进程的身份指的是Linux默认没有提供获取通讯进程的身份的接口,并不是说采用传统IPC没有办法实现这样的安全管控需求,只是谷歌在综合考虑了上述所有的因素的情况下,在共享内存的基础上做了一套新的解决方案)。

这里给各位读者留个思考题,有兴趣的读者可以自己动手去实验一下:

在一对多通讯的场景下,Binder的传输效率一定会比Socket高吗?(提示:Socket包括BIO、NIO、NIO2、epoll等,请不要局限在BIO的通讯方式)

AIDL

AIDL 是 Android interface definition Language 的英文缩写, 意思Android 接口定义语言,它与Binder有着千丝万缕的联系。

AIDL是谷歌使用Java 编程语言的语法定义的专门服务于Binder IPC通讯的脚本语言,推出的根本原因是为了避免Binder通讯中大量模板代码的书写。AIDL脚本会在编译期间,由Android SDK 工具生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 generated/ 目录中。

我们来看一个简单的aidl文件:

packageackage com.example.commonservice;
// Declare any non-default types here with import statements
interface ITtsService {
void showTts(in String uid,in int textId,in boolean toPlayTts,in int type);
boolean isShowing();
}

它生成的java文件如下所示:

/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.commonservice;
// Declare any non-default types here with import statements
public interface ITtsService extends android.os.IInterface
{
/** Default implementation for ITtsService. */
public static class Default implements com.example.commonservice.ITtsService
{
@Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException
{
}
@Override public boolean isShowing() throws android.os.RemoteException
{
return false;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
{
private static final java.lang.String DESCRIPTOR = "com.example.commonservice.ITtsService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.commonservice.ITtsService interface,
* generating a proxy if needed.
*/
public static com.example.commonservice.ITtsService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.commonservice.ITtsService))) {
return ((com.example.commonservice.ITtsService)iin);
}
return new com.example.commonservice.ITtsService.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_showTts:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
boolean _arg2;
_arg2 = (0!=data.readInt());
int _arg3;
_arg3 = data.readInt();
this.showTts(_arg0, _arg1, _arg2, _arg3);
reply.writeNoException();
return true;
}
case TRANSACTION_isShowing:
{
data.enforceInterface(descriptor);
boolean _result = this.isShowing();
reply.writeNoException();
reply.writeInt(((_result)?(1):(0)));
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.commonservice.ITtsService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) 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.writeString(uid);
_data.writeInt(textId);
_data.writeInt(((toPlayTts)?(1):(0)));
_data.writeInt(type);
boolean _status = mRemote.transact(Stub.TRANSACTION_showTts, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().showTts(uid, textId, toPlayTts, type);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public boolean isShowing() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_isShowing, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().isShowing();
}
_reply.readException();
_result = (0!=_reply.readInt());
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.example.commonservice.ITtsService sDefaultImpl;
}
static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.example.commonservice.ITtsService impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.commonservice.ITtsService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
public boolean isShowing() throws android.os.RemoteException;
}

虽然是简短的一个aidl文件,但生成的模板代码却极为复杂,为了整理清楚这段代码的结构,笔者先隐藏其部分内容:

public interface ITtsService extends android.os.IInterface
{
/** Default implementation for ITtsService. */
public static class Default implements com.example.commonservice.ITtsService
{
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService
{
private static class Proxy implements com.example.commonservice.ITtsService
{
}
static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException;
public boolean isShowing() throws android.os.RemoteException;
}

可以看到,代码的结构如同套娃一样,一层接一层。各位读者不妨思考一下,如果不这样套娃,把ITtsService里的DefaultStub类移到外面来,这样做可不可以?

答案是,可以的,不过类的命名方式可能要稍微做一下修改,如ITtsService_DefalutITtsService_Stub,以便于引用上的区分。当然,代码的结构不是重点,虽然谷歌的方式可读性会差一点,但开发人员不需要直接和这些源码打交道,也不是不可以接受。

Binder不一定都是跨进程通讯,同样也支持同进程通讯,比如 Activity绑定Service,通过Binder实现数据传输。在同一进程通讯的情况下,Stub类身兼两职,因其implements了ITtsService,它可以作为客户端的调用方;同时,它也是服务端的实现方。而在跨进程通讯的情况下,则由Proxy来担任客户端的调用方。

至于Default这个类的作用,暂时不明,无法找到相关的资料得知为什么谷歌要生成这么一个类。

在此,AIDL的介绍先告一段落,其中更多的细节将放到后续的文章中再做补充。

HIDL

HIDL的生命周期及其短暂,它从Android 8引入,然后在Android 10 立马被 Stable AIDL 所取代,虽然没啥存在感,但还是简单地提及一下吧。

HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是指用于在可以独立编译的代码库之间进行通信的系统。

HIDL 旨在用于进程间通信 (IPC)。进程之间的通信采用 Binder 机制。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。

更多HIDL相关的资料,可以参考 source.android.google.cn/docs/core/a…


推荐阅读
  • intellij idea的安装与使用(保姆级教程)
    intellijidea的安装与使用(保姆级教程)IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(gi ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • STM32与FPGA的对比及学习建议
    本文对比了野火STM32F103指南针板和Xilinx的PYNQ-Z2板(ZYNQ-7020),介绍了野火STM32F103指南针板的学习资料和讲解视频的详细程度,建议初学者学习野火的资料。同时,介绍了STM32开发所用的Keil程序和C指针的重要性。对于ZYNQ-7020的开发,提到了其自带的Linux、Ubuntu18.4系统以及使用SD卡烧入镜像的方法。 ... [详细]
author-avatar
天堂寨旅游2013_668
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有