图解 Binder:ServiceManager

ServiceManager 在 Android 系统中扮演了极其重要的角色,它是所有系统服务的注册中心。许多系统服务(比如 ActivityManagerService、WindowManagerService 等)都会将自己注册到 ServiceManager 中。当其他组件或者应用需要使用这些服务的时候,可以通过 ServiceManager 来查找和获取。

ServiceManager 类

ServiceManager 是一个类,它是单独运行在 ServiceManager 进程里的。它是一个 Binder 实体,它的实现在 Android 系统的 native 层,即 C++ 层,具体的实现文件是service_manager.cpp

ServiceManager 类的部分继承关系如下图:

从图中可以看出,它是一个 BBinder,即 Binder 实体。

ServiceManager 类的实现有几个关键函数:

  • addService():允许服务将自己注册到 ServiceManager 中,以便客户端可以查找它们。一旦注册,服务端的 Binder 引用,就可以被客户端通过 ServiceManager 获取到。

  • getService() 和 checkService():允许客户端查找注册在 ServiceManager 中的服务。客户端可以通过 getService() 获取到服务的 Binder 引用,然后就可以发起 Binder 事务,调用该服务的方法。

addService()

addService() 的实现比较简单,最核心的一句代码是:

mNameToService[name] = Service {
    .binder = binder, // 即服务的 Binder 引用
    .allowIsolated = allowIsolated,
    .dumpPriority = dumpPriority,
    .debugPid = ctx.debugPid,
};

mNameToService 是一个 map,以服务的名称为 key,用来记录服务的 Binder 引用等信息。

using ServiceMap = std::map<std::string, Service>;
ServiceMap mNameToService;

SystemServer 进程启动后,就会启动 AMS、WMS 这些服务。服务启动后,就会通过 Binder 驱动,获取 ServiceManger 的 Binder 引用:

image.png

服务获取到 ServiceManager 的 Binder 引用后,发起 Binder 事务,调用 ServiceManager 的 addService(),注册它自己:

image.png

getService() 和 checkService()

getService()checkService() 都是用来查询服务的,两者都是调用 tryGetService(),只是传参不一样:

Status ServiceManager::getService(const std::string& name, sp<IBinder>* outBinder) {
    *outBinder = tryGetService(name, true);
    return Status::ok();
}

Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
    *outBinder = tryGetService(name, false);
    return Status::ok();
}

tryGetService() 就是利用服务的 name,通过 mNameToService,找到服务的 Binder 引用:

sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
    sp<IBinder> out;
    Service* service = nullptr;
    if (auto it = mNameToService.find(name); it != mNameToService.end()) {
        service = &(it->second);
        out = service->binder;
    }




    if (!out && startIfNotFound) {
        tryStartService(name);
    }


    return out;
}

通过 getService() 或 checkService(),普通的用户进程就可以利用 AMS、WMS 这些服务的名称,通过 ServiceManager 获取到 AMS、WMS 这些服务对应的 Binder 引用:

image.png

懒服务

tryGetService() 的 startIfNotFound 参数为 true 时,可能会调用 tryStartService() 启动懒服务:

void ServiceManager::tryStartService(const std::string& name) {
    std::thread([=] {
        if (!base::SetProperty("ctl.interface_start", "aidl/" + name)) {
            LOG(INFO) << "Tried to start aidl service " << name
                      << " as a lazy service, but was unable to. Usually this happens when a "
                         "service is not installed, but if the service is intended to be used as a "
                         "lazy service, then it may be configured incorrectly.";
        }
    }).detach();
}

懒服务是一种按需加载的服务,即只有当客户端请求服务时,服务才会启动。这种方式可以减少系统启动时间和内存占用。

具体来说,这段代码执行了以下操作:

  • 创建一个新的线程以执行启动服务的操作。这是因为启动服务可能需要一定的时间,如果在主线程中执行可能会阻塞主线程,导致系统响应缓慢。

  • 在新的线程中,通过调用 base::SetProperty(“ctl.interface_start”, “aidl/” + name) 来尝试启动服务。这里的 “ctl.interface_start” 是一个 Android 系统属性,用于控制系统服务的启动。”aidl/” + name 则是要启动的服务的名字。

具体的启动原理是:Android 的 init 进程有一个属性监听器,可以监听系统属性的变化。当 “ctl.interface_start” 这个属性被修改时,init进程会收到通知,然后根据属性值启动对应的服务。

其他资料:动态运行 AIDL 服务

ServiceManager 进程的启动

ServiceManager 进程是一个单独运行的 native 进程,由 init 进程启动,它不属于 Zygote 孵化的进程。

init 进程通过读取 Android 初始化语言写的两个配置文件提供的信息启动 ServiceManager 进程:

servicemanager.rc

service servicemanager /system/bin/servicemanager
    class core animation
    user system
    group system readproc
    ...

init.rc

on init
    ...

    start servicemanager

ServiceManager 进程启动后,会调用 frameworks/native/cmds/servicemanager/main.cpp 的 main()

int main(int argc, char** argv) {

    const char* driver = argc == 2 ? argv[1] : "/dev/binder";
    // 初始化进程所属的 ProcessState 实例
    // 打开并初始化 Binder 驱动
    sp<ProcessState> ps = ProcessState::initWithDriver(driver);
    // 将 Binder 线程池的最大线程数设置为 0
    ps->setThreadPoolMaxThreadCount(0);
    // 限制 ServiceManager 进程只能向 Binder 驱动发送异步事务,即不能发送同步事务
    ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
    // 创建 ServiceManager 的 Binder 实体。
    sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
    // 通过 addService() 注册它自己
    if (!manager->addService("manager", manager, false /*allowIsolated*/,
        IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
        LOG(ERROR) << "Could not self register servicemanager";
    }
    
    // 创建当前线程所属的 IPCThreadState 实例
    IPCThreadState::self()->setTheContextObject(manager);
    // 向 Binder 驱动发送注册消息,成为一个 Context Manager
    ps->becomeContextManager();

    // 创建一个 Looper 实例。Looper 的构造函数里,会调用 epoll_create1() 创建一个 Epoll 实例
    sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
    // 通过  epoll_ctl() 将 Binder 驱动的文件描述符添加到监听项里
    BinderCallback::setupTo(looper);
    ClientCallbackCallback::setupTo(looper, manager);

    // 死循环
    while(true) {
        // 通过 epoll_wait() 监听事件的发生,主要监听 Binder 事务
        looper->pollAll(-1);
    }

    // should not be reached
    return EXIT_FAILURE;
}

上述启动代码最关键的几步是:

  • 打开 Binder 驱动
  • 注册 0 号 Binder 节点
  • epoll 机制监听 Binder 事务

打开 Binder 驱动

打开 Binder 驱动的代码,主要在 ProcessState::initWithDriver() 里,相关调用如下:

ProcessState.cpp

└─initWithDriver()

└──init()

└───ProcessState()

└────open_driver()

└─────open()

└─────ioctl() // 发送 ioctl 命令 BINDER_VERSION,检测 Binder 驱动版本号

└─────ioctl() // 发送 ioctl 命令 BINDER_SET_MAX_THREADS,设置 Binder 线程池的最大线程数

└─────ioctl() // 发送 ioctl 命令 BINDER_ENABLE_ONEWAY_SPAM_DETECTION,启用单向垃圾消息检测

└────mmap()

上面的调用链路,最关键是几个系统调用:

  • open():打开 Binder 驱动设备。最终会调用 Binder 驱动的 binder_open()
  • ioctl():发送各种 ioctl 命令,与 Binder 驱动进行通信。最终会调用 Binder 驱动的 binder_ioctl()
  • mmap():进行 mmap 映射,提供一块虚拟地址空间,用于建立接收其他进程事务消息的缓冲区。最终会调用 Binder 驱动的 binder_mmap()

这几个系统调用,最终都是通过虚拟文件系统,进入到内核层。

1686276821653.png

注册 0 号 Binder 节点

之前在 Binder 事务一文,我们提到了一个问题:Binder 实体需要由一个事务发送出去。但是事务是要由一个 Binder 代理发起的,那么最开始的一个 Binder 代理,它的 Binder 引用又是从哪里来的呢?

这是个先有鸡还是先有蛋的问题。ServiceManager 就是那只最开始的鸡。ServiceManager 在 Binder 驱动中,通常是第一个建立的节点。其他进程,通过 0 号引用,就可以访问它。

ServiceManager 进程初始化时,就会调用 ProcessState::becomeContextManager() ,向 Binder 驱动发送 ioctl 命令 BINDER_SET_CONTEXT_MGR_EXT 或 BINDER_SET_CONTEXT_MGR,将当前进程设置成 Context Manager,即 ServiceManager。

bool ProcessState::becomeContextManager()
{

    flat_binder_object obj {
        .flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
    };

    int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);



    if (result != 0) {
        int unused = 0;
        result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused);
    }

    return result == 0;
}
  • 注:BINDER_SET_CONTEXT_MGR_EXT 和 BINDER_SET_CONTEXT_MGR 区别不大。在 Android 8.0 及更高版本中,新增了 BINDER_SET_CONTEXT_MGR_EXT 命令,允许将 Binder 的 Context Manager 与一个特定的 SELinux 安全策略关联起来,增强系统的安全性。

ioclt() 最终调用到了内核的 binder_ioctl():

binder.c

└─binder_ioctl()

└──binder_ioctl_set_ctx_mgr()

└───binder_new_node()

从调用链路可以看出,最终会为 ServiceManager 建立一个 Binder 节点。另外,binder_ioctl_set_ctx_mgr() 里有几句关键的代码:

new_node = binder_new_node(proc, fbo);
context->binder_context_mgr_node = new_node;

ServiceManager 会记录成 binder_context_mgr_node。

在其他进程想要对 ServiceManger 发起事务,通过 ProcessState::getContextObject() 就可以构建一个 Binder 代理,它持有 0 号引用:

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{

    // 创建一个 BpBinder 实例,引用为 0
    sp<IBinder> context = getStrongProxyForHandle(0);
    return context;
}

当其他进程通过 0 号引用,发起事务时,在 Binder 驱动处理事务的函数 binder_transaction() 里,有几句关键的代码就会执行:

// 如果 Binder 引用不为 0
if (tr->target.handle) {
    ...
} else {
    // Binder 引用为 0,将目标 Binder 节点,设置为 binder_context_mgr_node,即 ServiceManager
    target_node = context->binder_context_mgr_node;
}

Binder 驱动在发现事务数据里的 Binder 引用为 0 时,就会将它的目标 Binder 节点,设置为代表 ServiceManger 的节点。

epoll 机制监听 Binder 事务

与普通的用户进程不同的是,ServiceManager 进程不是通过 Binder 线程池来等待、处理 Binder 事务的,而是在主线程上使用了 epoll 机制监听 Binder 事务。

epoll 是 Linux 中的一种 I/O 复用机制,是 select 和 poll 的替代品,它可以处理大量的并发事件。

epoll 使用一组函数来管理和检测事件:

  • epoll_create(): 创建一个 epoll 文件描述符,该文件描述符会代表一个内核事件表,用来存储需要监听的文件描述符及其相应的事件。

  • epoll_ctl(): 用于向内核事件表中添加、删除或者修改需要监听的文件描述符及其相应的事件。

  • epoll_wait(): 用于等待内核事件表中的事件发生(例如,数据可读、数据可写、连接关闭等)。它有一个 timeout 参数:

    • timeout 为负数时,epoll_wait() 会一直阻塞,直到有一个事件发生。
    • timeout 为 0 时,epoll_wait() 会立即返回,不管是否有事件发生。
    • timeout 为大于 0 的值 x 时,epoll_wait() 会阻塞直到有一个事件发生或者 x 毫秒时间到达。

在使用 select 和 poll 进行 I/O 多路复用时,当监听的文件描述符数量很大时,它们会存在一些性能问题:

  • 每次调用 select 或 poll 都需要遍历所有的文件描述符,检查哪些文件描述符准备就绪,时间复杂度是 O(n),会随文件描述符的数量增长,降低效率。
  • 每次调用 select 或 poll 需要将文件描述符数组,从用户空间拷贝到内核空间。
  • select 和 poll 的通知方式是水平触发,即只要有数据到来就会不断触发通知,直到数据被处理完。这会导致系统资源的浪费。
  • select 能处理的文件描述符的数量受到 FD_SETSIZE 的限制。

epoll 则解决了这些问题:

  • 不随监听的文件描述符数目增长而降低效率。epoll 是基于事件驱动的,当 epoll_ctl() 注册的文件描述符上的事件发生时,才会将该文件描述符添加到一个就绪列表。当我们调用 epoll_wait() ,内核只需要返回就绪列表的文件描述符,不需要遍历所有的文件描述符。
  • 使用 epoll_ctl() 注册的文件描述符,内核会将这些描述符放到一个红黑树上,只需要在调用 epoll_ctl() 注册时进行一次拷贝即可,不需要重复拷贝。
  • epoll 同时支持水平触发(LT)和边缘触发(ET)。边缘触发模式下,当检测到一次事件发生后,只会通知一次,直到下一次事件发生,无论中间是否有数据到达。这样可以减少无用的事件通知,进一步提高效率。
  • epoll 没有描述符数量的限制,仅受限于可用内存。

回顾一下 ServiceManager 进程启动时的 main():

int main(int argc, char** argv) {

    ...

    // 创建一个 Looper 实例。Looper 的构造函数里,会调用 epoll_create1() 创建一个 Epoll 实例
    sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
    // 通过  epoll_ctl() 将 Binder 驱动的文件描述符添加到监听项里
    BinderCallback::setupTo(looper);
    ClientCallbackCallback::setupTo(looper, manager);



    // 死循环
    while(true) {
        // 通过 epoll_wait() 监听事件的发生,主要监听 Binder 事务。epoll_wait() 的 timeout 设置为 -1
        looper->pollAll(-1);
    }

    // should not be reached
    return EXIT_FAILURE;
}

相关调用如下:

main.cpp

└─main()

└──Looper.cpp

└───prepare()

└────Looper()

└─────rebuildEpollLocked()

└──────epoll_create1()

└──BinderCallback.cpp

└───setupTo()

└────IPCThreadState.cpp

└─────setupPolling() // 向缓冲区写入 BC_ENTER_LOOPER 消息;获取 Binder 驱动的文件描述符

└────Looper.cpp

└─────addFd()

└──────addFd()

└───────epoll_ctl()

└──Looper.cpp

└───pollAll()

└────pollOnce()

└─────pollInner()

└──────epoll_wait()

通过 ServiceManager 获取 AMS 的 Binder 引用

在 Activity 的启动流程里,我们能看到下面的一行代码:

final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);

getService() 的返回,就是 AMS 的 Binder 引用。

调用流程图如下:

1686536063265.png

调用链路如下:

ServiceManager.java

└─getService()

└──rawGetService()

└───getIServiceManager() // 获取代表 ServiceManager 的 Binder 引用

└────BinderInternal.java

└─────getContextObject()

└──────android_util_Binder.cpp

└───────android_os_BinderInternal_getContextObject()

└────────ProcessState.cpp

└─────────getContextObject() // 构建一个引用为 0 的 Binder 引用,代表 ServiceManager

└───ServiceManagerProxy.java

└────getService()

└─────IServiceManager.Proxy.java

└──────checkService()

注:IServiceManager.Proxy.java 是 AIDL 自动生成的代码,后续在 AIDL 一章会有类似的内容,这里不做进一步探究。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYo8opoy' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片