当前位置:首页 > 技术 > 正文内容

Android Binder通信机制中Service Manager的初始化与节点绑定流程

访客 技术 2026年9月28日 13

Service Manager 核心职责与启动链路

Binder 通信架构由 Client、Server 以及承担全局协调角色的 Service Manager 共同构成。Manager 的启动实质上是向驱动层宣告自身"上下文管理者"身份的过程,随后接管所有服务的注册查询请求。该生命周期可抽象为三个关键阶段:打开设备文件并映射共享内存、通过控制指令提交管理器标识、进入阻塞式事件循环监听事务。

用户态实现逻辑

传输通道建立与角色声明

应用层通过标准文件接口打开 /dev/binder,驱动据此完成进程控制块分配与虚拟内存映射。获取句柄后,进程需执行单次 ioctl 调用,构造描述符对象并将 BINDER_SET_CONTEXT_MGR_EXT 命令下发至内核,从而确立全局单例地位。

int svc_mgr_main(int argc, char *argv[]) {
    struct binder_transport *ctx = NULL;
    const char *device_path = "/dev/binder";

    ctx = init_binder_device(device_path, SHARED_MEM_SIZE);
    if (!ctx) return -1;

    if (claim_context_manager_role(ctx)) {
        perror("Failed to register as context manager");
        cleanup_binder(ctx);
        return -1;
    }

    enter_event_dispatch_loop(ctx, handle_service_requests);
    return 0;
}

长轮询与事务分发

Manager 启动后立即进入无限循环,依托双向缓冲交互模型与内核同步数据。每次迭代均触发 ioctl 执行读操作,若内核存在待处理指令则直接填充预设缓冲区,随后交由解析器路由至具体业务函数。底层协议采用定长头结构,指令与负载交替排列。当捕获到事务投递标志时,解析器剥离头部元数据,组装消息体并回调上层处理例程。针对单向或双向调用,分别执行缓冲回收或响应回传操作。

void enter_event_dispatch_loop(struct binder_transport *ctx, message_router handler) {
    for (;;) {
        struct comm_io_req io_request;
        uint8_t recv_buffer[PAGE_SIZE] __aligned(4);

        io_request.read_len = sizeof(recv_buffer);
        io_request.read_ptr = (uintptr_t)recv_buffer;
        io_request.write_len = 0;

        int status = ioctl(ctx->fd, BINDER_IO_CONTROL, &io_request);
        if (status < 0) break;

        int consumed = parse_and_dispatch(ctx, (uint32_t *)recv_buffer, io_request.read_consumed, handler);
        if (consumed <= 0) break;
    }
}

static int parse_and_dispatch(struct binder_transport *ctx, const uint32_t *stream, size_t len, message_router cb) {
    const uint32_t *ptr = stream;
    const uint32_t *end = stream + len / sizeof(uint32_t);

    while (ptr < end) {
        uint32_t opcode = *ptr++;
        switch (opcode) {
            case BR_TRANSACTION:
            case BR_TRANSACTION_SECURE: {
                union binder_xfer_data txn_bundle;
                memset(&txn_bundle, 0, sizeof(txn_bundle));

                if (opcode == BR_TRANSACTION_SECURE) {
                    memcpy(&txn_bundle.sec_ctx, ptr, sizeof(struct binder_sec_ctx));
                    ptr += sizeof(struct binder_sec_ctx) / sizeof(uint32_t);
                } else {
                    memcpy(&txn_bundle.basic, ptr, sizeof(struct binder_basic_txn));
                    ptr += sizeof(struct binder_basic_txn) / sizeof(uint32_t);
                }

                struct binder_msg_io msg_in, reply_out;
                bio_initialize_write(&reply_out, TEMP_REPLY_BUF, BUF_SIZE);
                bio_import_from_transaction(&msg_in, 
                    opcode == BR_TRANSACTION_SECURE ? &txn_bundle.sec_ctx.t : &txn_bundle.basic.t);

                int result = cb(ctx, &txn_bundle.basic.t, &msg_in, &reply_out);
                
                if (!(txn_bundle.basic.t.flags & TF_ONE_WAY)) {
                    send_response_to_client(ctx, &reply_out, txn_bundle.basic.t.data.ptr.buffer, result);
                } else {
                    release_kernel_buffer(ctx, txn_bundle.basic.t.data.ptr.buffer);
                }
                break;
            }
            default: break;
        }
    }
    return ptr - stream;
}

内核态调度与状态机管理

Binder 在 Linux 设备模型中被归类为杂项字符设备。驱动初始化阶段仅完成主设备号注册与基础上下文分配,实际运行时资源按需动态生成。

进程与线程上下文构建

每次文件打开操作均触发 binder_proc 实例化。该结构体封装了当前进程的通信状态、锁队列及内存引用追踪表,并通过 filp->private_data 与 VFS 层绑定,确保后续系统调用可直接定位进程控制块。同一进程内首次访问时创建 binder_proc,后续访问时创建或复用线程控制块 binder_thread,后者挂载于进程的 Red-Black 树中以 PID 为键进行索引。

上下文管理者注册校验

当内核接收 BINDER_SET_CONTEXT_MGR_EXT 指令时,执行严格的单例与权限校验。首先检查全局 binder_context 是否已存在管理器节点,若已存在则直接拒绝。接着验证发起进程的 Effective UID 是否符合预设安全策略。校验通过后,驱动分配 binder_node 对象并将其关联到共享上下文中。该节点充当全局寻址锚点,负责维护弱引用计数与安全传输标志位,最终指针写入 context->manager_ref 完成拓扑绑定。

static int assign_global_manager(struct file *fp, struct flat_obj_desc *user_desc) {
    struct binder_context *sys_ctx;
    struct binder_proc *proc = get_proc_from_file(fp);
    struct binder_node *mgmt_node = NULL;
    kuid_t process_euid = current_cred_euid();
    int op_status = 0;

    sys_ctx = proc->context_handle;
    mutex_lock(&sys_ctx->manager_lock);

    if (sys_ctx->active_manager_node != NULL) {
        op_status = -EBUSY;
        goto unlock_exit;
    }

    op_status = check_security_policy(process_euid);
    if (op_status) goto unlock_exit;

    if (is_uid_valid(sys_ctx->designated_uid)) {
        if (!uid_match(sys_ctx->designated_uid, process_euid)) {
            op_status = -EPERM;
            goto unlock_exit;
        }
    } else {
        assign_uid(sys_ctx, process_euid);
    }

    mgmt_node = create_binder_node(proc, user_desc);
    if (!mgmt_node) {
        op_status = -ENOMEM;
        goto unlock_exit;
    }

    acquire_node_references(mgmt_node, STRONG | WEAK);
    sys_ctx->active_manager_node = mgmt_node;
    release_node_reference(mgmt_node);

unlock_exit:
    mutex_unlock(&sys_ctx->manager_lock);
    return op_status;
}

双向缓冲交互与轮询就绪标记

BINDER_WRITE_READ 是用户态与内核态通信的核心桥梁。该命令采用零拷贝设计理念,内核仅记录用户空间缓冲区的虚拟地址指针,避免冗余的数据搬运。驱动程序遍历该指针指向的指令流,逐条解析操作码。当识别到 BC_ENTER_LOOPER(主线程)或 BC_REGISTER_LOOPER(工作线程)时,会在对应 binder_thread 的状态掩码中注入 LOOPER_ACTIVE_FLAG,表明该执行路径已具备持续消费事务的能力,内核据此允许外部投递跨进程调用请求。

事务回传阶段遵循相同的流水线机制。内核优先检查当前线程的工作队列 todo_list 及所属进程的挂起任务。若无待处理项,线程切换至可中断睡眠状态;一旦有远程 Client 发起调用,对应的 binder_work 将被唤醒并序列化至用户态缓冲区。返回给应用层的内存布局严格对齐,首部存放控制命令字,其后紧跟原始事务载荷,形成紧凑的命令-数据交替数组,便于上层快速枚举消费。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。