Binder IPC的理解&系统Server调用过程分析(Android Q)

概述


Android作为一个移动端的操作系统,就必须提供一种可靠的跨进程通信方案。

我们来想想,移动端有哪些特性,IPC跨进程通信需要满足哪些特性?

高效

首先,移动端资源比较有限,尤其是在早期的时候,硬件设备跟PC相比差距很大,这就要求Android系统在设计时,尤其要注意资源的使用效率,所以,IPC首先要满足 ”高效” 这一点。

安全性

其次,在Android系统上,运行着各种应用,有系统自带的,也有开发者提交的,这里面不乏各种恶意应用。所以Android系统在设计上必须要保证每个App的 “安全性”,为此,Android系统基于Linux内核,也同时继承了Linux的各种安全模型。例如,Android中分为各种用户组,用户组里面运行着各种进程,每个用户组/用户都会分别赋予不同的权限,并且每个进程都会运行在独立的沙箱中,这样保证了进程间的隔离,大大提高了系统的安全性。为了进一步增强了系统的安全性,在Android 4.4之后,Android系统加入了SELinux强制访问控制策略。那么进程之间的IPC通信,同样要保障其安全性!如何来保障呢?Binder的设计实现,恰好满足了这一点。

Binder是什么?

Android系统是基于Linux内核开发的,那么它自然就继承了Linux在IPC使用上的方式,例如,传统的管道(Pipe)、信号(Signal)和跟踪(Trace)、命令管道(Named Pipe)、报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)、插口(Socket)等。但是Android系统并没有使用这些同学方式,具体的原因我们在上文中已经分析过了。

那么,Binder是什么?Binder是一套IPC通信方案,但Binder的方案并不是Android原创的,它是基于OpenBinder来实现的。从英文字面上意思看,Binder具有粘结剂的意思,那么它把什么东西粘结在一起呢?

Android系统中,IPC通信包括哪几个部分呢?

  • 内核层:Binder驱动
  • 用户空间:ServiceManager
  • 用户空间:Client
  • 用户空间:Server

Android系统IPC通信的四大组成部分包括了,Binder驱动提供了IPC的核心能力、ServiceManager提供了辅助管理功能、Client是调用端、Server服务提供者,Binder把这四大部件“粘合”在一起,共同为Android系统提供了IPC通信能力。

Binder特性有哪些?

Binder满足了移动端IPC通信对效率和安全性的要求。具体它有哪些特点以及实现原理是怎么样的呢?

简单、高效

首先我们简单来看下其他的IPC方案。

  • Socket作为一款通用接口,主要用于跨网络的Client端和Server端的低速通信,其传输效率低,开销大,不满足Android对性能和传输效率的要求。
  • 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,整个过程经过了2次内存拷贝过程,效率较低。
  • 共享内存的方式,虽然无需拷贝,但实现和控制复杂,难以使用。
对比IPC方式数据拷贝次数:
IPC类型 数据拷贝次数(次)
各种IPC方式数据拷贝次数 2
共享内存 0
共享内存 1

安全性

Android作为移动端的操作系统,是一个打的开放式平台,任何人或组织都可以发布自己的App,并且运行在Android系统上,而用户同样希望自己安装的App,不会对手机和其他App带来一些安全问题,例如数据隐私、访问限制、以及硬件相关的数据网络、耗电等问题。传统IPC的安全性完全依赖上层协议,并且无法获得访问进程的可靠UID、PID等信息,无法对用户身份进行鉴别,不能满足Android对IPC通信安全的要求。

Binder基于Client-Server的通信模式,同时支持非匿名和匿名两种通信方式,增加了各种安全校验机制,在低层保障了系统对安全性的要求。

Binder特性的实现分析

传统IPC方案通信原理

数据传递过程:
  1. 首先,发送方进程通过系统调用,将要发送的数据存拷贝到内核缓存区中。
  2. 然后,接收方开辟一段内存空间,内核通过系统调用将内核缓存区中的数据拷贝到接收方的内存缓存区中,即完成了数据的传递过程。
缺点
  1. 数据需要进行2次拷贝过程,第1次是从发送方用户空间拷贝到内核缓存区,第2次是从内核缓存区拷贝到接收方用户空间,效率低。
  2. 接收方进程并不知道事先要分配多大的空间来接收数据,可能存在空间上的浪费。

Binder的通信原理

Binder是如何只用1次内存拷贝就实现数据的传递呢?

Android是基于Linux内核实现的,而Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。物理内存通过映射到用户空间的虚拟地址中,操作系统通过虚拟内存来操作物理内存。在Linux中,虚拟空间映射可以通过mmap()来实现。

Binder实现1次内存拷贝的秘密就是:它借助了虚拟内存映射机制,在数据接收方和内核空间的数据缓存区做了内存映射。这样一来,从发送方发送的数据,拷贝到内核空间时,直接映射拷贝到了接收方的内存空间中了,也就实现了一次拷贝就完成了数据从发送方、到内核、再到接收方的数据传输过程。

Binder通信过程解析


我们已经回答了Binder是什么,为什么Android会使用Binder等问题,接下来我们来看下Binder的源码实现。

Binder的组成可以分为Binder驱动、ServiceManager、Client端、Server端四个组成部分,这四个部分分别代表了各自的角色,发挥着不同的作用。

类比网络通信,Client代表了普通用户,它通过ServiceManager(相当于DNS服务)来查询系统服务的地址,然后经过Binder驱动(相当于路由器),在内核层实现了IPC跨进程通信的能力,把消息传递到指定的Service端,访问Service端提供的服务。我们日常开发过程中接触较多的部分是Client和Server这两个,但是实现IPC的核心能力的其实是Binder驱动和ServiceManager服务。

Client端

用户端要访问Server端进程提供的服务,第一步要做什么呢?

没错,就是拿到Server端的地址,然后发起对该服务的请求。

地址怎么拿到呢?

我们以最常用的一个系统服务ActivityManagerService(AMS)为例来分析获取AMS的过程。

获取AMS过程解析

获取ActivityManager

回想一下我们怎么获取AMS的?啊,对了,是在Activity(Service中)直接调用getSystemService()方法,传递一个字符串常亮即可。

ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE) activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);//ACTIVITY_SERVICE在Context中定义,值为字符串”activity"

不过这里返回的是一个ActivityManager啊,不是我们想要的AMS啊,这是什么原因?

ActivityManager是AMS在应用层的一个工具类,代理提供一些AMS服务接口的访问,所以在使用时,我们通常获得的是ActivityManager对象。

getSystemService方法实现

getSystemService方法的实现实在ContextImpl类中:

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
调用了SystemServiceRegistry.getSystemService方法

android.app.SystemServiceRegistry.java

    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new ArrayMap<String, ServiceFetcher<?>>();
            
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

这里的ServiceFetcher是一个接口,只有一个方法getService。

SYSTEM_SERVICE_FETCHERS是一个Map,存储了系统所有的服务对象。它的赋值是在SystemServiceRegistry静态代码块中:

    static {
        ……
        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});
        ……
            
     }
     
     private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

我们在这里看到了ActivityManager的创建及注册过程。Map中存储的是AMS的在Client的代理工具类ActivityManger。

那么我们如何通过ActivityManager来访问AMS呢?

AMS的获取

我们随意找一个AMS提供的服务接口,例如isUserRunning:

    public boolean isUserRunning(int userId) {
        try {
            return getService().isUserRunning(userId, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }
    
    private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

逻辑解析

  1. isUserRunning调用了getService()方法。
  2. getService()返回IActivityManagerSingleton.get(),也就是一个IActivityManager对象。
  3. 进一步看,IActivityManager对象是通过ServiceManager.getService获取一个IBinder对象,然后作为参数传递给IActivityManager.Stub.asInterface方法,该方法的返回值又是什么呢?没错!它就是我们想得到的AMS对象。
小结

我们来总结一下获取AMS的过程:

  1. ActivityManager是AMS在应用层的一个工具类,代理提供一些AMS服务接口的访问,所以在使用时,我们通常获得的是ActivityManager对象。
  2. Client端通过ContextImpl的getSystemService(ACTIVITY_SERVICE)来获取AMS。
  3. SystemServiceRegistry负责注册和管理系统服务。
  4. SYSTEM_SERVICE_FETCHERS是一个Map,存储了ActivityManager对象。
  5. 当使用AMS服务时,ActivityManager对象会调用它的getService方法来进行调用转发。
  6. 最终通过IActivityManager.Stub.asInterface(b)来获取真正的服务对象IActivityManager(也就是AMS在Java层的实例)。

Service端

Service端想要提供服务,首先要把服务注册到ServiceManager中,以方便管理和查询。我们继续以AMS为例进行分析。

SystemServer的概述

SystemServer是Zygote进程fork的第一个进程,它是Android系统的核心之一,大多数的服务都运行在这个进程中。应用层想要访问设备的资源,都要通过SystemServer进程来代理访问。

SystemServer进程在Zygote中被创建,最后会调用caller.run来运行SystemServer的main方法。它会注册系统的大多数的服务包括了BootstrapService、CoreService和OtherService三种类型的服务,我们的AMS属于BootstrapService类型服务。

这里我们对SystemServer的创建过程不做过多的分析,之后会单独写一章来介绍,这里简单介绍清楚即可。

SystemServer的main方法

    public static void main(String[] args) {
        new SystemServer().run();
    }
    
    private void run() {
        // Start services.
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices(); //AMS注册位置
            startCoreServices();
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }
    }

继续看startBootstrapServices方法

    private void startBootstrapServices() {
    
        ActivityTaskManagerService atm = mSystemServiceManager.startService(
                ActivityTaskManagerService.Lifecycle.class).getService();
        mActivityManagerService = ActivityManagerService.Lifecycle.startService(
                mSystemServiceManager, atm);
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        mActivityManagerService.setInstaller(installer);
        mActivityManagerService.setSystemProcess();
        

这里调用了ActivityManagerService的setSystemProcess方法,我们继续。

ActivityManagerService的setSystemProcess方法

    public void setSystemProcess() {
        try {
            ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
            ……
    }

这里调用了ServiceManager.addService方法来向系统注册服务,注册之后,系统运行的其他进程就可以访问AMS服务了。

小结

本节分析了AMS的注册过程:

  1. Service端想要提供服务,首先要把服务注册到ServiceManager中,以方便管理和查询。
  2. AMS的注册是在SystemServer的创建过程中进行的。
  3. SystemServer是Zygote进程fork的第一个进程,它是Android系统的核心之一,大多数的服务都运行在这个进程中。
  4. AMS属于BootstrapService类型服务。
  5. 经过一系列调用,最终调用了ServiceManager.addService方法来向系统注册服务,注册之后,系统运行的其他进程就可以访问AMS服务了。

ServiceManager

ServiceManager在整个Binder IPC通信中,相当于网络访问中的DNS,它负责把服务注册到自己的内部列表中,当Client访问某个服务时,它通过查询操作,把特定的服务返回给调用者。

那么,现在就有一个问题了,ServiceManger在Android系统中,也是一个服务,要想访问它,也是要通过Binder IPC通信的,那么我们如何来访问ServiceManager的呢?

Android系统非常巧妙的实现了ServiceManger的访问,系统预留了固定的句柄0,来代表ServiceManager服务,客户端通过0,就能访问到ServiceManager了。

ServiceManager服务的注册和获取实现细节都在native层,我们来简单分析获取ServiceManager的过程。

ProcessState

ProcessState是负责打开Binder驱动,并做mmap映射,IPCThreadState是负责与Binder驱动进行具体的命令交互。一进程只有一个ProcessState实例,且只有在ProcessState对象建立时才打开Binder设备以及做内存映射。

我们首先来看它的构造函数:

native/libs/binder/ProcessState.cpp

ProcessState::ProcessState(const char *driver)
    : mDriverName(String8(driver))
    , mDriverFD(open_driver(driver))
    , mVMStart(MAP_FAILED)
    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
    , mExecutingThreadsCount(0)
    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
    , mStarvationStartTimeMs(0)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(nullptr)
    , mBinderContextUserData(nullptr)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
    , mCallRestriction(CallRestriction::NONE)
{
    if (mDriverFD >= 0) {
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
            close(mDriverFD);
            mDriverFD = -1;
            mDriverName.clear();
        }
    }

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

根据参数传递的binder驱动文件地址,打开驱动,并mmap进内存中。

参数定义:

#ifdef __ANDROID_VNDK__
const char* kDefaultDriver = "/dev/vndbinder";
#else
const char* kDefaultDriver = "/dev/binder";
#endif
  • DEFAULT_MAX_BINDER_THREADS

我们注意这个变量,定义了Binder服务端进程所允许的最大线程数量,这里的默认值是15,也就是一个服务最多能有15个线程同时提供服务,如果同一时间,访问该服务的Client端超过了15个,系统就会抛出错误。

ServiceManager的获取

当我们需要通过ServiceManager获取某个服务时,就会调用IServiceManager.cpp中的defaultServiceManager方法:

sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != nullptr) return gDefaultServiceManager;

    {
        AutoMutex _l(gDefaultServiceManagerLock);
        while (gDefaultServiceManager == nullptr) {
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(nullptr));
            if (gDefaultServiceManager == nullptr)
                sleep(1);
        }
    }

    return gDefaultServiceManager;
}

该方法调用了ProcessState::self()->getContextObject(nullptr))获取ServiceManager服务。

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
    return getStrongProxyForHandle(0);
}

这里,我们可以通过0句柄来获取ServiceManager服务了。

Binder驱动

Binder驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。Binder驱动是运行在内核态的,数据在使用Binder驱动传输时,需要在内核内存空间与用户内存空间进行拷贝操作。

当2个进程C和S进行跨进程通信时,C传递给SM(ServiceManager)一个S的名称,SM查找已注册的列表,从列表中解析出S在客户端C中的Binder引用,C就可以使用这个Binder引用进行通信了。Binder引用会把数据转化成可序列化的对象,经过Binder驱动程序,把数据拷贝到S进程中,然后再反序列化,最终获得C传递过来的数据,并调用S端的本地方法来实现服务的调用。

每个需要通过Binder通信的进程都需要打开/dev/binder驱动一次。Binder驱动的启动是在ProcessState中进行的,并做了mmap映射。在mmap调用之后,内核会调用驱动,为进程创建内存缓冲区(只读,防止用户空间对其修改),用于接收数据。这块内存缓冲区对应了2个虚拟内存地址空间,一个是内核的虚拟空间,另一个是进程用户的虚拟空间。

在Server端进程角度来看,内核与用户内存空间,通过mmap映射到了同一块物理内存上了。Client端发送数据时,只是将数据从Client的用户内存空间,复制到了内核内存空间,由于Server端和内核使用了内存映射,对应了同一块物理内存,所以经过一次拷贝过程,Server端就可以获取到数据了。

我们在Java层使用的各种Binder对象,实际上只是native层的一个代理。各种服务的实体对象,都是在native层声明的,并且都需要注册到Binder驱动中。Binder驱动同时维护了各个Client中的引用于Binder实体之间的映射关系。

总结

  1. Android IPC方式使用了Binder IPC方案。
  2. Binder具有高效、安全等优点。
  3. Binder在执行IPC数据传递过程中只执行了1次数据拷贝。
  4. Binder机制包括了4个组成部分:Client、Server、ServiceManager、Binder驱动。
  5. ServiceManager维护了系统所有的服务索引,负责注册和查找已注册服务。
  6. ServiceManager在系统中的引用id是0,系统通过0即可访问到ServiceManager服务了。
  7. Service服务默认最多只能提供15个线程供Client提供访问支持。
  8. Binder驱动的启动是在ProcessState中进行的,并做了mmap映射。
  9. Binder驱动运行在内核态中。
  10. Binder驱动是实现IPC通信的核心。
原创文章 20 获赞 2 访问量 5101

猜你喜欢

转载自blog.csdn.net/u011578734/article/details/105787981
今日推荐