service的注册流程 一

概述

service的注册可以概括为如下几个过程:

  1. 进程启动和ProcessStates的初始化
  2. IBinder 上下文创建
  3. addService 客户端数据构建
  4. binder kernel生成binder_work,唤醒servicemanager的等待队列
  5. servicemanager 循环读取binder_work,处理客户端请求

注:本文涉及的代码段都会删除部分无关紧要的代码,后续不再做解释

1. 进程启动和ProcessState初始化

先附一张ProcessState整体初始化流程图:


ProcessState_seq.png

ProcessState提供了统一的的访问binder 内核的接口,用来管理用户进程到binder内核的通信,它创建了一个线程池,用于管理当前进程访问binder内核的所有线程,进程在启动的时候一般都会把第一个线程作为主线程加入到线程池中。
大多数用户端启动的进程都会在启动的时候初始化ProcessState,代码类似如下:

//main_surfaceflinger.cpp   frameworks\native\services\surfaceflinger
    ProcessState::self()->setThreadPoolMaxThreadCount(4);

    // start the thread pool
    sp<ProcessState> ps(ProcessState::self());
    ps->startThreadPool();

这一段代码摘自surfaceflinger进程的启动main函数,其流程都是调用self()初始化ProcessState,然后调用startThreadPool()把进程作为主线程加入到线程池,至于setThreadPoolMaxThreadCount(4)是设置线程池最大线程数量的,这个默认值是15,各个进程可以自行设置。
上面展示的是native进程的启动调用方式,其实上层android应用进程启动也是一样的,因为应用进程的启动是通过zygote来fork的,最终都会调用到onZygoteInit(),如下:

//app_main.cpp  frameworks\base\cmds\app_process
virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool();
    }

可以看到跟native进程的调用是完全一样的。接下来分两部分分析初始化流程,第一部分是初始化,第二部是线程池的运行。

1.1 初始化

从self()开始:

//ProcessState.cpp  frameworks\native\libs\binder
sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != nullptr) {
        return gProcess;
    }
    gProcess = new ProcessState(kDefaultDriver);
    return gProcess;
}

这里采用了单例模式,gProcess代表ProcessState实例,也就是说一个进程会对应一个ProcessState实例,需要注意的是不同的进程使用不同的空间,所以进程间是不会受单例影响的。
kDefaultDriver:

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

默认就是"/dev/binder",如果vendor定义了ANDROID_VNDK会采用vndbinder。
接下来看构造方法:

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);
...
    }
}

除了一些变量初始化之外主要做了两件事:

  1. 打开binder设备,并把设备描述符赋值给mDriverFD,
static int open_driver(const char *driver)
{
    int fd = open(driver, O_RDWR | O_CLOEXEC);
    if (fd >= 0) {
        int vers = 0;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
    } else {
        ALOGW("Opening '%s' failed: %s\n", driver, strerror(errno));
    }
    return fd;
}

主要功能是首先打开设备"dev/binder/",然后调用ioctl检查binder版本,然后设置默认线程池最大线程数

 #define DEFAULT_MAX_BINDER_THREADS 15
  1. 调用mmap映射内存,并把映射结果起始地址赋值给mVMStart.
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

BINDER_VM_SIZE 映射内存大小

#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)

大小为1M减去两个page size,一个page一般是4K。下面是关于为啥减去两个page的commit comments:

Modify the binder to request 1M - 2 pages instead of 1M. The backing store in the kernel requires a guard page, so 1M allocations fragment memory very badly. Subtracting a couple of pages so that they fit in a power of two allows the kernel to make more efficient use of its virtual address space.

关于mmap大小在binder kernel中也有限制,其最大不能超过4M,请参见:Binder驱动初始化第4.2节
PROT_READ 这个表示映射出的这块内存只有读权限,这就体现了Server和内核的映射,而不是Client和内核的映射,在binder的CS结构中,Server负责只读。这里也能看出每一个用到binder通信的进程都会作为Server的角色来存在。当然,同时也是Client。详见:Binder驱动初始化第4.2节

2.2 启动线程池

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);//true 表示是主线程
    }
}
void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain);
        t->run(name.string());
    }
}

这里主要就是新建一个线程PoolThread,然后调用run启动线程。
另外makeBinderThreadName()是用来命名当前线程名称的,如下:

name.appendFormat("Binder:%d_%X", pid, s);

其命名方式为Binder:<pid>_<index>,下面是通过命令查看到的信息(index是16进制的):

ursa:/ # ps -A -T |grep Binder:29849                                                                                                                                                                       
u0_a14       29849 29865   643 5107944 163636 binder_thread_read  0 S Binder:29849_1
u0_a14       29849 29866   643 5107944 163636 binder_thread_read  0 S Binder:29849_2
u0_a14       29849 29867   643 5107944 163636 binder_thread_read  0 S Binder:29849_3
u0_a14       29849 29940   643 5107944 163636 binder_thread_read  0 S Binder:29849_4 

最后通过调用run启动线程,把name设为线程名称。

PoolThrad

class PoolThread : public Thread
{
public:
    explicit PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    
    const bool mIsMain;
};

这个PoolThread很简单,它就是一个线程,它调用IPCThreadState::self()初始化IPCThreadState,然后调用joinThreadPool(mIsMain)把线程加入到线程池。
接下来分析IPCThreadState。

IPCThreadState
若是ProcessState描述进程,那么IPCThreadState就是用来描述当前进程中的线程,当然是加入线程池的binder线程,而不是其它不相干的线程。所谓binder线程可以简单的理解为与binder进行交互的线程,这个不是进程里面调用进程间通信的线程,而是由内核发起创建的线程,是用来处理进程间调用请求的线程。
在进程启动的时候会创建主线程作为binder线程并加入线程池,而之后的binder线程则是由内核发起创建的线程,在内核处理当前binder请求的时候(意味着当前线程忙),发现如果对应client的binder线程没有超出最大值的时候,会通知client去创建新的线程处理下一个client请求。
进入内核的时候再分析这个线程的创建条件。

//IPCThreadState.cpp    frameworks\native\libs\binder
IPCThreadState::IPCThreadState()
    : mProcess(ProcessState::self()),
      mWorkSource(kUnsetWorkSource),
      mPropagateWorkSource(false),
      mStrictModePolicy(0),
      mLastTransactionBinderFlags(0),
      mCallRestriction(mProcess->mCallRestriction)
{
    pthread_setspecific(gTLS, this);
    clearCaller();
    mIn.setDataCapacity(256);
    mOut.setDataCapacity(256);
    mIPCThreadStateBase = IPCThreadStateBase::self();
}

IPCThreadState初始化主要是构建了两个Parcel变量mIn和mOut,并指定大小为256。mIn用来从binder内核读取数据,mOut用来传递用户数据给binder内核。

joinThreadPool

//IPCThreadState.cpp    frameworks\native\libs\binder
void IPCThreadState::joinThreadPool(bool isMain)
{
//给Parcel mOut写入binder命令,BC_ENTER_LOOPER 标志当前thread进入looper,
//该命令会修改binder内核结构体binder_thread的thread->looper项。
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);

    status_t result;
//无限循环去处理执行command
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();

        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);
//当线程退出的时候把thread->looper再置为BC_EXIT_LOOPER
    mOut.writeInt32(BC_EXIT_LOOPER);
    talkWithDriver(false);
}

所谓线程池其实就是一个无线循环体,不停的读取并处理Binder Cmd。每添加一个线程到线程池,就会启动一个无线循环体来处理Cmd。当循环退出的时候也就标志者对应的线程结束了。

getAndExecuteCommand()
接着看处理命令的函数:

status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;

    result = talkWithDriver();
    if (result >= NO_ERROR) {
        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();

        result = executeCommand(cmd);
    }

    return result;
}

主要是两个部分,一是talkWithDriver()与内核通信,写数据;一是检查写完之后是否有数据需要读取处理,如果有则调用executeCommand(cmd)执行读取到的cmd。


注:这里有一个知识点困扰了很久,talkWithDriver()的调用是没有参数的,而该函数的定义却是有参数的,因为对于C++不是很熟,不知道默认参数的知识点,导致困扰了很久。
关于默认参数: 调用函数时,如果未传递参数的值,则会使用默认值,如果指定了值,则会忽略默认值,使用传递的值。如果实际参数的值留空,则使用这个默认值。

//IPCThreadState.h  frameworks\native\include\binder
status_t            talkWithDriver(bool doReceive=true);

talkWithDriver()
先看talkWithDriver(),分析发送给kernel的事务。

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    binder_write_read bwr;

    // Is the read buffer empty?
//needRead 为true表示当前input buffer即mIn对应的buffer为空,
//false表示buffer不为空
//mIn为空表示可以接收下一个数据。
    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.
//当input buffer不为空(即needRead为false),表示还有数据需要读取,
//所以把outAvail 置为0,标志本次cmd执行不写数据。doreceive默认为true。
    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.
//needRead为true表示可以读取数据,把bwr.read_buffer指向mIn,
//kernel最终会通过binder_thread_read读取数据并写给bwr read,
//届时就可以通过mIn读取相应数据。
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }

    // Return immediately if there is nothing to do.
//read和write size都为0,表示input buffer不为空,还有数据需要读取,
//所以直接return,放弃本次talk with driver。
    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 (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
    } while (err == -EINTR);

    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < mOut.dataSize())
                mOut.remove(0, bwr.write_consumed);
            else {
                mOut.setDataSize(0);
                processPostWriteDerefs();
            }
        }
        if (bwr.read_consumed > 0) {
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        return NO_ERROR;
    }
    return err;
}

归纳一下:

  1. 定义binder_write_read结构体
  2. 检查input buffer即mIn对应的buffer是否还有数据需要读取,如果有,则放弃本次talk with driver
  3. 构建binder_write_read,使得bwr.read和mIn对应起来(此时mIn为空),bwr.write和mOut对应起来,所谓对应就是指向同一块虚拟地址。bwr.read_buffer指向mIn.data(),bwr.write_buffer指向mOut.data()。
  4. 调用ioctl发送BINDER_WRITE_READ给kernel,因为 bwr.read_size = mIn.dataCapacity(),前面提到mIn初始化为256字节,也就是说read_size不为空。所以在调用到kernel的binder_ioctl_write_read的时候会先调用binder_thread_write然后接着调用binder_thread_read。当binder_thread_read执行完毕的时候bwr.read_buffer就存在了数据,接着就可以继续下面的executeCommand了。

这里需要注意,mIn和mOut只要有一个不为空,talkWithDriver就会与kernel进行通信,mIn代表当前thread作为Server从kernel读取Client发来的数据,mOut代表当前thread作为Client向kernel写入发给Server的数据。
这里mIn读取的数据和reply是两个不同的东西,reply是当前thread发给kernel的请求响应结果,函数waitForResponse用来处理reply结果。

executeCommand(cmd)
接着看executeCommand(cmd),处理kernel来的事务。

        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();

        result = executeCommand(cmd);

先判断mIn是否有数据,有,则读出cmd,然后指向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;

    (这里省略一堆BR_XXX cmd)

    case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;
    }
    return result;
}

原理很简单,就是根据不同的cmd来发生不同的故事,不解释了,后续遇到具体cmd了再分析。
这里简单看一下BR_SPAWN_LOOPER,可以看到调用spawnPooledThread(false)并且参数为false,前面分析过spawnPooledThread,它就是负责新建一个thread并且加入thread pool,false代表不是主线程。从这里可以看出binder 线程是kernel根据一定条件发送BR_SPAWN_LOOPER命令给目标进程,使其再新建的线程。

到这里ProcessState的初始化就圆满结束了,简单做个总结:

  1. 每个进程(需要binder通信的)都会初始化一个ProcessState实例,用来与binder内核通信。
  2. ProcessState会打开binder设备并映射一块内存用来读取内核信息,每一个进程既是Client又是Server,C负责写,S负责读。
  3. 创建一个主线程,加入线程池,循环处理cmd。把cmd发给内核,再把内核返回的cmd读取出来处理。
  4. 创建两个Parcel结构mIn和mOut,分别负责S的读和C的写,mOut负责存储C端的cmd供C去写,mIn负责存储内核生成的数据供S读。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容