Android-Framework(六)-开机启动ServiceManager进程

文章内容

本文整理自 Android 6.0(Marshmallow)时期的 servicemanager 源码与配套素材。讨论对象是独立的 servicemanager 进程,以及它自带的 service_manager.c、binder.c 实现。

一、ServiceManager

ServiceManager 开机后完成三件事:binder_open 打开 /dev/binder 并映射 128KB 只读内存(Android 6.0);binder_become_context_manager 向驱动登记上下文管理者身份;binder_loop 进入循环,等待驱动送来请求。第三步收到 BR_TRANSACTION 后,binder_parse 解出事务,svcmgr_handler 再按 txn->code 分发。注册和查询最终都落在一张 svcinfo 链表上。

Android 6.0 中,init 解析 init.rc 后会创建独立的 servicemanager 进程,android.os.IServiceManager 就由这个进程承载。它的特殊之处是 handle 固定为 0。其他进程注册服务时,调用链从 BpServiceManager.addService 进入 BpBinder、IPCThreadState,经 ioctl(BINDER_WRITE_READ) 把事务交给驱动。事务目标为 handle=0,驱动便把它转给 ServiceManager。

addService 调用链总览:服务进程客户端经 Binder 驱动把请求分发给 ServiceManager

主链可以压缩为:BpServiceManager.addService → BpBinder.transact → IPCThreadState.transact → talkWithDriver → ioctl(BINDER_WRITE_READ) → Binder 驱动 → binder_parse → svcmgr_handler → do_add_service。

二、第一步:binder_open 打开驱动

ServiceManager 启动三步:打开驱动、成为上下文管理者、循环等待

main() 的结构不长,三次关键调用分别建立驱动通道、登记身份和进入服务循环。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 347—387 行// 程序入口int main(int argc, char **argv){    // 保存 binder 驱动状态    struct binder_state *bs;    // 指定 binder 设备节点    char *driver = "/dev/binder";     // 第一步:打开 binder 驱动,映射 128KB 只读内存    bs = binder_open(driver, 128 * 1024);    // 检查驱动是否打开成功    if (!bs) {        // 打开失败直接退出        ALOGE("failed to open binder driver: %s\n", driver);        return -1;    }     // 第二步:向驱动声明“我是上下文管理者”,之后 handle=0 就能找到本进程    if (binder_become_context_manager(bs)) {        // 登记失败时记录原因        ALOGE("cannot become context manager (%s)\n", strerror(errno));        return -1;    }     // SELinux 相关初始化:注册 audit/log 回调、获取 sehandle 与服务上下文    selinux_enabled = is_selinux_enabled();    // 获取服务上下文查询句柄    sehandle = selinux_android_service_context_handle();    // 打开 SELinux 状态监听    selinux_status_open(true);    // 只在 SELinux 启用时检查上下文    if (selinux_enabled > 0) {        // 检查服务上下文句柄        if (sehandle == NULL) {            // 缺少句柄时记录错误            ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");            // 中止进程            abort();        }        // 获取 ServiceManager 自身安全上下文        if (getcon(&service_manager_context) != 0) {            // 获取失败时记录错误            ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");            // 中止进程            abort();        }    }    // 声明 SELinux 回调联合体    union selinux_callback cb;    // 绑定审计回调    cb.func_audit = audit_callback;    // 注册审计回调    selinux_set_callback(SELINUX_CB_AUDIT, cb);     // 第三步:进入死循环,等待并处理客户端请求    binder_loop(bs, svcmgr_handler);    // 正常路径理论上不会走到这里    return 0;}

输入是 binder 设备路径和 128KB 映射大小。前两步成功后,进程已经持有 binder_state,并被驱动识别为上下文管理者;binder_loop 随后接管线程,正常运行期间不会返回。

binder_state 只保存三项:设备文件描述符、映射首地址和映射长度。binder_open 要完成 open、版本校验、mmap,任何一环失败都会回收已经取得的资源。

c
// 代码位置 /frameworks/native/cmds/servicemanager/binder.c 第 89—94 行// 描述用户空间持有的 binder 驱动状态struct binder_state {    // /dev/binder 的文件描述符    int fd;    // mmap 映射的虚拟内存首地址    void *mapped;    // 映射大小(128KB)    size_t mapsize;}; // 代码位置 /frameworks/native/cmds/servicemanager/binder.c 第 96—137 行// 打开驱动并建立接收区映射struct binder_state *binder_open(size_t mapsize){    // 保存状态对象    struct binder_state *bs;    // 保存驱动协议版本    struct binder_version vers;     // 分配并初始化 binder_state 对象    bs = malloc(sizeof(*bs));    // 检查内存分配结果    if (!bs) {        // 记录内存不足        errno = ENOMEM;        // 返回空指针        return NULL;    }     // 打开 binder 驱动设备节点    bs->fd = open("/dev/binder", O_RDWR);    // 检查设备打开结果    if (bs->fd < 0) {        // 输出设备打开错误        fprintf(stderr, "binder: cannot open device (%s)\n", strerror(errno));        // 跳到统一释放路径        goto fail_open;    }     // 获取内核驱动版本,必须与用户空间期望的版本一致    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||        // 比较驱动协议版本        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {        // 输出版本不一致信息        fprintf(stderr,                // 给出内核与用户空间版本                "binder: kernel driver version (%d) differs from user space version (%d)\n",                // 传入两侧版本号                vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);        // 走打开失败清理路径        goto fail_open;    }     // 保存映射长度    bs->mapsize = mapsize;    // 映射一块只读、私有的虚拟内存,binder 数据通过这块内存做单次拷贝    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);    // 检查映射结果    if (bs->mapped == MAP_FAILED) {        // 输出映射错误        fprintf(stderr, "binder: cannot map device (%s)\n", strerror(errno));        // 跳转到映射失败清理路径        goto fail_map;    }     // 返回完整状态对象    return bs; // 映射失败的清理入口fail_map:    // 关闭设备文件    close(bs->fd);// 打开或版本检查失败的清理入口fail_open:    // 释放状态对象    free(bs);    // 返回空指针表示失败    return NULL;}

open("/dev/binder", O_RDWR) 建立用户空间与 Binder 驱动的通道。BINDER_VERSION 随后校验双方协议版本,避免用不匹配的数据结构继续通信。mmap 建立 PROT_READ | MAP_PRIVATE 的 128KB 映射,用户空间从中读取驱动投递的数据。Binder 单次拷贝的具体实现属于驱动侧,本文不把这句标志位推演成驱动源码结论。

三、第二步:成为上下文管理者

打开驱动只说明进程能使用 Binder,还不足以让所有客户端通过 handle=0 找到它。binder_become_context_manager(bs) 向文件描述符发出 BINDER_SET_CONTEXT_MGR ioctl,驱动据此登记当前进程。

这次登记建立了关键约定:上下文管理者对应 handle=0。普通 Binder 引用的 handle 从 1 开始分配,而 0 不表示“没有目标”,它是进入 ServiceManager 的固定入口。因此,客户端创建 BpServiceManager 时使用 handle=0,驱动能够把请求送往已经登记的进程。驱动内部如何保存这项身份、如何按 handle 定位目标,留到驱动源码部分处理。

四、第三步:binder_loop 循环等待

binder_write_read 同时描述写区和读区,包含 write_size、write_consumed、write_buffer、read_size、read_consumed、read_buffer 六个字段。ServiceManager 进入循环前先发送一个 4 字节的 BC_ENTER_LOOPER,通知驱动当前线程将承担循环处理工作。

看 binder_loop,要抓住两个状态:

  • 线程先登记为 looper,然后反复阻塞在 BINDER_WRITE_READ。没有请求时,线程进入睡眠;
  • 请求到达后,驱动唤醒线程并填充读缓冲区。
c
// 代码位置 /frameworks/native/cmds/servicemanager/binder.c 第 372—407 行// 进入 Binder 请求处理循环void binder_loop(struct binder_state *bs, binder_handler func){    // 保存系统调用结果    int res;    // 描述本次读写缓冲区    struct binder_write_read bwr;    // 准备 32 个命令字的读缓冲区    uint32_t readbuf[32];     // 先清空写区    bwr.write_size = 0;    // 清空已消费写入字节数    bwr.write_consumed = 0;    // 清空写缓冲区地址    bwr.write_buffer = 0;     // 写入进入循环线程的命令    readbuf[0] = BC_ENTER_LOOPER;    // 发送 4 字节命令给驱动    binder_write(bs, readbuf, sizeof(uint32_t));     // 永久处理驱动返回的数据    for (;;) {        // 每次循环设置读区大小        bwr.read_size = sizeof(readbuf);        // 重置驱动已填充的字节数        bwr.read_consumed = 0;        // 把读区指向本地缓冲区        bwr.read_buffer = (uintptr_t) readbuf;         // 阻塞等待驱动返回命令        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);        // 检查 ioctl 结果        if (res < 0) {            // 记录循环失败原因            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));            // 退出循环            break;        }         // 解析读到的数据并逐条执行 svcmgr_handler        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);        // 零表示出现意外回复        if (res == 0) {            // 记录意外结果            ALOGE("binder_loop: unexpected reply?\n");            // 退出循环            break;        }        // 负值表示解析错误        if (res < 0) {            // 记录解析错误            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));            // 退出循环            break;        }    }}

函数输入是驱动状态和处理回调 svcmgr_handler。每轮输出体现在两处:驱动更新 read_consumed 并把命令放入 readbuf,解析器处理这些命令并生成回复。客户端写给驱动的是 BC_TRANSACTION,ServiceManager 读到的是驱动下发的 BR_TRANSACTION;二者分属协议的发送方向和返回方向,不能混为同一条命令。

五、binder_parse 把驱动数据解包

读缓冲区不是单个 C 结构体,而是一串“4 字节命令字 + 命令附带数据”。ptr 从 read_buffer 起步,end 等于 read_buffer + read_consumed。循环每读出一条命令就移动指针,因此同一个缓冲区可以连续容纳多条命令。

binder_parse 的关键分支是 BR_TRANSACTION。它把命令后的内存解释成 binder_transaction_data,初始化 reply,包装客户端数据为 msg,调用业务回调,随后用 binder_send_reply 把结果送回驱动。

c
// 代码位置 /frameworks/native/cmds/servicemanager/binder.c 第 204—287 行// 解析驱动写入的命令序列int binder_parse(struct binder_state *bs, struct binder_io *bio,                 // 接收缓冲区地址、长度和业务回调                 uintptr_t ptr, size_t size, binder_handler func){    // 默认返回解析错误    int r = -1;    // 计算待解析区间结束地址    uintptr_t end = ptr + (uintptr_t) size;     // 持续读取缓冲区中的命令    while (ptr < end) {        // 读取当前 4 字节命令字        uint32_t cmd = *(uint32_t *) ptr;        // 跳过命令字        ptr += sizeof(uint32_t);         // 根据驱动命令选择处理方式        switch (cmd) {        // 空操作无需处理        case BR_NOOP:        // 事务写入完成也无需额外处理        case BR_TRANSACTION_COMPLETE:            break;         // 增加弱引用        case BR_INCREFS:        // 增加强引用        case BR_ACQUIRE:        // 释放强引用        case BR_RELEASE:        // 释放弱引用        case BR_DECREFS:            // 引用计数类命令跳过 ptr/cookie 结构            ptr += sizeof(struct binder_ptr_cookie);            break;         // 收到一笔事务        case BR_TRANSACTION: {            // 命令后面是 binder_transaction_data            struct binder_transaction_data *txn =                    // 把当前位置解释为事务结构                    (struct binder_transaction_data *) ptr;            // 检查缓冲区是否容得下完整事务头            if ((end - ptr) < sizeof(*txn)) {                // 记录事务过小错误                ALOGE("parse: txn too small!\n");                // 返回解析失败                return -1;            }            // 打印事务信息            binder_dump_txn(txn);            // 有业务回调时处理事务            if (func) {                // 准备 256 字节回复数据区                unsigned rdata[256 / 4];                // 包装客户端发来的数据                struct binder_io msg;                // 包装要写给客户端的回复                struct binder_io reply;                // 保存业务处理结果                int res;                 // 初始化回复缓冲区                bio_init(&reply, rdata, sizeof(rdata), 4);                // 用事务数据初始化请求视图                bio_init_from_txn(&msg, txn);                // 调用 ServiceManager 业务回调                res = func(bs, txn, &msg, &reply);                // 将业务回复写回驱动                binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);            }            // 跳过事务头            ptr += sizeof(*txn);            // 离开事务分支            break;        }        // 处理未知命令        default:            // 记录未知命令            ALOGE("parse: unknown command %d\n", cmd);            // 返回解析失败            return -1;        }    }    // 返回解析结果    return r;}

binder_io 用 data/data0、offs/offs0、used/capacity 等字段描述数据区和对象偏移区。收到事务时无需复制出一份 msg;bio_init_from_txn 直接让它引用 txn 指向的共享数据。

c
// 代码位置 /frameworks/native/cmds/servicemanager/binder.c 第 409—416 行// 从事务结构初始化 binder_io 读取视图void bio_init_from_txn(struct binder_io *bio, struct binder_transaction_data *txn){    // 直接使用 txn 的 buffer,并记录数据区起点    bio->data = bio->data0 = (char *) (intptr_t) txn->data.ptr.buffer;    // 保存可读取的数据字节数    bio->data_avail = txn->data_size;    // 按 size_t 换算偏移数组元素数    bio->offs_avail = txn->offsets_size / sizeof(size_t);    // 标记这段内存由事务共享    bio->flags = BIO_F_SHARED;}

它的输入是 binder_transaction_data,输出是可按 Parcel 布局读取的 binder_io 视图;底层 buffer 的所有权没有转移。

六、svcmgr_handler 分发服务请求

驱动协议解析完毕,业务入口只剩 svcmgr_handler。它先核对事务目标,再处理 Ping,随后读取 strict policy 头和接口名。接口名必须是 android.os.IServiceManager,否则请求会被拒绝。这相当于 native 侧的 Parcel::enforceInterface()。

svcmgr_handler 按事务码分发:GET/CHECK、ADD、LIST

事务码从 IBinder::FIRST_CALL_TRANSACTION=1 开始递增:GET_SERVICE 为 1,CHECK_SERVICE 为 2,ADD_SERVICE 为 3。LIST_SERVICES 走独立的 list 权限检查。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 244—338 行// 统一处理发给 ServiceManager 的事务int svcmgr_handler(struct binder_state *bs,                   // 接收事务头                   struct binder_transaction_data *txn,                   // 接收请求数据                   struct binder_io *msg,                   // 接收回复缓冲区                   struct binder_io *reply){    // 保存链表节点    struct svcinfo *si;    // 保存 UTF-16 字符串    uint16_t *s;    // 保存字符串长度    size_t len;    // 保存服务 handle    uint32_t handle;    // 保存 strict mode 头    uint32_t strict_policy;    // 保存 isolated 访问标志    int allow_isolated;     // 目标必须是 handle 0 对应的上下文管理者    if (txn->target.ptr != BINDER_SERVICE_MANAGER)        // 目标不符时拒绝        return -1;     // Ping 测试不需要业务数据    if (txn->code == PING_TRANSACTION)        // 直接返回成功        return 0;     // 读取 strict mode 头    strict_policy = bio_get_uint32(msg);    // 读取接口名    s = bio_get_string16(msg, &len);    // 检查接口名是否存在    if (s == NULL) {        // 缺少接口名时拒绝        return -1;    }     // 检查接口名长度与内容    if ((len != (sizeof(svcmgr_id) / 2)) ||        // 比较 android.os.IServiceManager        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {        // 输出错误接口名        fprintf(stderr, "invalid id %s\n", str8(s, len));        // 拒绝发错目标的请求        return -1;    }     // 按事务码分发业务    switch (txn->code) {    // 获取服务    case SVC_MGR_GET_SERVICE:    // 检查服务    case SVC_MGR_CHECK_SERVICE:        // 读取服务名        s = bio_get_string16(msg, &len);        // 检查服务名        if (s == NULL) {            // 缺少服务名时拒绝            return -1;        }        // 按名字与调用方身份查找服务        handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);        // 没有有效 handle 时进入默认回复        if (!handle)            break;        // 将 handle 写为 Binder 引用        bio_put_ref(reply, handle);        // 返回成功        return 0;     // 注册服务    case SVC_MGR_ADD_SERVICE:        // 读取服务名        s = bio_get_string16(msg, &len);        // 检查服务名        if (s == NULL) {            // 缺少服务名时拒绝            return -1;        }        // 读取驱动转换后的服务引用        handle = bio_get_ref(msg);        // 读取 isolated 访问标志        allow_isolated = bio_get_uint32(msg) ? 1 : 0;        // 尝试注册服务        if (do_add_service(bs, s, len, handle, txn->sender_euid,                           // 传入访问标志和发送方进程号                           allow_isolated, txn->sender_pid))            // 注册失败时返回错误            return -1;        // 离开注册分支        break;     // 列出服务    case SVC_MGR_LIST_SERVICES:        // 建立局部作用域        {            // 读取目标序号            uint32_t n = bio_get_uint32(msg);            // 检查 list 权限            if (!svc_can_list(txn->sender_pid)) {                // 记录权限拒绝                ALOGE("list_service() uid=%d - PERMISSION DENIED\n",                      // 传入调用方 uid                      txn->sender_euid);                // 返回权限错误                return -1;            }            // 从链表头开始            si = svclist;            // 移动到第 n 个节点            while ((n-- > 0) && (si = si->next));            // 命中节点时返回名字            if (si) {                // 把 UTF-16 服务名写入回复                bio_put_string16(reply, si->name);                // 返回成功                return 0;            }        }        // 没有对应节点时返回失败        return -1;     // 处理未知事务码    default:        // 记录未知事务码        ALOGE("unknown code %d\n", txn->code);        // 返回失败        return -1;    }     // 没有提前返回的分支默认回复 0    bio_put_uint32(reply, 0);    // 返回成功    return 0;}

handler 的输入是事务头和 msg,输出写入 reply。它自身不保存服务;ADD 分支把状态变化交给 do_add_service,GET/CHECK 分支则从链表取得 handle,并由 bio_put_ref 写回引用。

七、注册服务:do_add_service

ServiceManager 管理服务所需的数据并不多。每个 svcinfo 节点记录名字、handle、死亡回调和 isolated 访问策略,svclist 指向链表头。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 128—135 行// 定义每个已注册服务的管理节点struct svcinfo {    // 指向下一个服务    struct svcinfo *next;    // 保存驱动分配并替换后的 handle    uint32_t handle;    // 保存服务进程死亡回调    struct binder_death death;    // 标记是否允许 isolated 进程访问    int allow_isolated;    // 保存 UTF-16 服务名长度    size_t len;    // 在结构体尾部保存服务名    uint16_t name[0];}; // 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 138 行// 初始化全局服务链表为空struct svcinfo *svclist = NULL;

结构体的输入来自一次成功的 ADD_SERVICE,输出是链表中的长期状态。ServiceManager 没有保留客户端 Parcel 的全部内容,只持有后续查询和死亡处理需要的信息。

svcinfo 链表:ServiceManager 的服务目录

注册前要在 svclist 中按“长度相同且 UTF-16 内容相同”匹配服务名。这段线性查找同时服务于注册覆盖和服务查询。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 140—150 行// 按 UTF-16 名字查找服务节点struct svcinfo *find_svc(const uint16_t *s16, size_t len){    // 保存遍历指针    struct svcinfo *si;     // 从链表头逐个遍历    for (si = svclist; si; si = si->next) {        // 先比较长度,再比较名字内容        if ((len == si->len) &&            // 比较 UTF-16 字节序列            memcmp(s16, si->name, len * sizeof(uint16_t)) == 0) {            // 返回命中的节点            return si;        }    }    // 没有命中时返回空指针    return NULL;}

输入是服务名和长度,输出是节点指针或 NULL;该函数不修改链表。

do_add_service 先拒绝无效 handle、空名字和超过 127 个 UTF-16 单元的名字,再通过 svc_can_register 检查 SELinux add 权限。普通进程没有相应策略授权时,注册会在这里失败。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 194—243 行// 注册服务到全局链表int do_add_service(struct binder_state *bs,                   // 接收服务名与长度                   const uint16_t *s, size_t len,                   // 接收 handle、uid、访问策略和 pid                   uint32_t handle, uid_t uid, int allow_isolated,                   pid_t spid){    // 保存目标服务节点    struct svcinfo *si;     // 校验 handle 和服务名长度    if (!handle || (len == 0) || (len > 127))        // 参数不合法时拒绝        return -1;     // 检查调用方的 SELinux add 权限    if (!svc_can_register(s, len, spid)) {        // 记录权限拒绝        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",              // 传入服务名、handle 和 uid              str8(s, len), handle, uid);        // 返回注册失败        return -1;    }     // 在链表中查找同名服务    si = find_svc(s, len);    // 已存在时覆盖原节点    if (si) {        // 旧节点仍持有有效 handle        if (si->handle) {            // 记录覆盖注册            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",                  // 传入服务名、handle 和 uid                  str8(s, len), handle, uid);            // 释放旧引用并清空旧 handle            svcinfo_death(bs, si);        }        // 保存新的 handle        si->handle = handle;    // 不存在时新建节点    } else {        // 分配结构体和带结尾零的 UTF-16 名字空间        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));        // 检查内存分配结果        if (!si) {            // 记录内存不足            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",                  // 传入服务名、handle 和 uid                  str8(s, len), handle, uid);            // 返回注册失败            return -1;        }        // 保存 handle        si->handle = handle;        // 保存名字长度        si->len = len;        // 拷贝服务名及其结尾字符        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));        // 确保名字以零结尾        si->name[len] = '\0';        // 绑定服务死亡回调        si->death.func = (void *) svcinfo_death;        // 保存 isolated 访问策略        si->allow_isolated = allow_isolated;        // 让新节点指向旧链表头        si->next = svclist;        // 以头插法更新全局链表        svclist = si;    }     // 增加对服务 handle 的引用    binder_acquire(bs, handle);    // 请求驱动发送服务死亡通知    binder_link_to_death(bs, handle, &si->death);    // 返回注册成功    return 0;}

已存在的名字会复用节点:先调用 svcinfo_death 释放旧 handle,再写入新 handle。新名字则分配“结构体 + 名字”的连续内存,填充字段后头插进 svclist。成功路径最后调用 binder_acquire 持有引用,并用 binder_link_to_death 登记死亡通知。

八、服务进程死亡后的状态

注册成功不代表服务会永久存在。服务进程退出后,Binder 驱动把死亡通知送给 ServiceManager,先前登记的 svcinfo_death 因而执行。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 153—161 行// 处理服务所在进程的死亡通知void svcinfo_death(struct binder_state *bs, void *ptr){    // 把回调参数还原为服务节点    struct svcinfo *si = (struct svcinfo *) ptr;     // 记录死亡服务名    ALOGI("service '%s' died\n", str8(si->name, si->len));     // 只处理仍然有效的 handle    if (si->handle) {        // 释放驱动侧引用        binder_release(bs, si->handle);        // 清零 handle,使后续查询失败        si->handle = 0;    }}

输入是对应的 svcinfo 节点,状态变化是释放驱动引用并把 handle 置为 0。节点仍在链表里,但 do_find_service 会把它当成不可用服务。反方向的故障更严重:ServiceManager 自身死亡会触发系统服务进程重启;反复失败达到 init 的处理条件时,系统可能重启。

九、查询服务:do_find_service

查询路径先按名字取节点,然后依次检查 handle、isolated 访问策略和 SELinux find 权限。通过这些条件后,返回值仍然只是一个 handle。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 170—192 行// 按名字查询服务 handleuint32_t do_find_service(struct binder_state *bs, const uint16_t *s, size_t len,                         // 接收调用方 uid 和 pid                         uid_t uid, pid_t spid){    // 在服务链表中按名字查找    struct svcinfo *si = find_svc(s, len);     // 检查节点和 handle 是否有效    if (!si || !si->handle) {        // 查不到时返回 0        return 0;    }     // 服务未允许 isolated 进程访问时继续检查    if (!si->allow_isolated) {        // 从 uid 中取出 appid        uid_t appid = uid % AID_USER;        // 判断 appid 是否位于 isolated 区间        if (appid >= AID_ISOLATED_START && appid < AID_ISOLATED_END) {            // isolated 调用方不能取得该服务            return 0;        }    }     // 检查调用方的 SELinux find 权限    if (!svc_can_find(s, len, spid)) {        // 权限不足时返回 0        return 0;    }     // 返回服务 handle    return si->handle;}

输入包含服务名、uid 和 pid,输出为 handle 或 0。allow_isolated 为假时,appid 落在 AID_ISOLATED_START 到 AID_ISOLATED_END 区间的调用方会被拒绝。权限通过以后,handler 使用 bio_put_ref 创建 BINDER_TYPE_HANDLE 类型的 flat_binder_object 回复客户端。

三类操作使用不同的 SELinux 权限:注册检查 add,查询检查 find,枚举检查 list。按服务名检查的路径会结合 service_contexts 查询安全上下文。

c
// 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 110—114 行// 检查注册服务所需的 add 权限static int svc_can_register(const uint16_t *name, size_t name_len, pid_t spid){    // 指定权限名称为 add    const char *perm = "add";    // 按服务上下文查询并返回布尔结果    return check_mac_perms_from_lookup(spid, perm, str8(name, name_len)) ? 1 : 0;} // 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 116—120 行// 检查枚举服务所需的 list 权限static int svc_can_list(pid_t spid){    // 指定权限名称为 list    const char *perm = "list";    // 按调用方上下文检查并返回布尔结果    return check_mac_perms_from_getcon(spid, perm) ? 1 : 0;} // 代码位置 /frameworks/native/cmds/servicemanager/service_manager.c 第 122—126 行// 检查查询服务所需的 find 权限static int svc_can_find(const uint16_t *name, size_t name_len, pid_t spid){    // 指定权限名称为 find    const char *perm = "find";    // 按服务上下文查询并返回布尔结果    return check_mac_perms_from_lookup(spid, perm, str8(name, name_len)) ? 1 : 0;}

这些函数不改动 svclist。它们以发送方 pid、服务名为输入,返回权限判断结果,把“谁能注册、查询或枚举”从链表操作中分离出来。

十、客户端发出的对象与服务端收到的对象

以注册 media.player 为例,客户端交给驱动的数据包含目标接口名 android.os.IServiceManager、服务名 media.player、服务对象和 allowIsolated=0。服务对象此时是 flat_binder_object,类型为 BINDER_TYPE_BINDER:binder 字段保存的是服务对象的本地弱引用,cookie 保存的是强指针,handle 还是空:它指向客户端进程里的本地 Binder 对象,还没有可供 ServiceManager 使用的远端 handle。

驱动转发事务时会处理 Binder 对象:送到 ServiceManager 一侧的类型变为 BINDER_TYPE_HANDLE,handle 已由驱动分配,cookie 为 0。Android 6.0 这条路径中,普通 handle 从 1 递增。具体怎样维护引用表、选择 handle 数值和改写对象属于 Binder 驱动实现,不能仅凭用户空间代码补全。

addService 数据流:驱动把 BINDER_TYPE_BINDER 改写为 BINDER_TYPE_HANDLE 并分配 handle

于是,ServiceManager 收到 ADD_SERVICE 后无需保存客户端发来的完整数据。它从 msg 中读出名字、handle 和 allow_isolated,写进 svcinfo;查询命中时,再把 handle 作为 Binder 引用返回。svclist 这张单链表就是该版本 ServiceManager 的服务目录,主要状态为 handle/name/len/death/allow_isolated。

十一、总结

ServiceManager 成为 Binder 服务总管,依赖的是一条连续的状态链:

  • 打开驱动并建立映射,登记为 handle=0 的上下文管理者,进入 ioctl 等待循环;
  • 收到事务后,协议解析器把请求交给 handler,handler 再通过 svcinfo 链表完成注册、查询、枚举和死亡处理。

本文由 AgentWorkFlow 生成

觉得不错?给作者点个赞吧

分类:Android系统

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Android-Framework(六)-开机启动ServiceManager进程 · Yinchen 的博客