Android8 binder源码学习分析笔记(四)——ServiceManager启动

前文回顾:

Android8 binder源码学习分析笔记(三):
https://blog.csdn.net/g_i_a_o_giao/article/details/151365630?spm=1001.2014.3001.5502

Android8 binder源码学习分析笔记(二):
https://blog.csdn.net/g_i_a_o_giao/article/details/151221566?spm=1011.2415.3001.5331

Android8 binder源码学习分析笔记(一):

https://blog.csdn.net/g_i_a_o_giao/article/details/151365630?spm=1011.2415.3001.5331

在上一篇文章中,我们探讨了Binder驱动如何建立连接、创建线程池以及处理命令。现在,让我们把目光转向ServiceManager的启动过程,看看Binder在其中扮演的角色。

首先来看frameworks/native/cmds/servicemanager/servicemanager.rc中的配置。此处可以看到可执行文件位于/system/bin/servicemanager。(可以看到当servicemanager服务启动以后,会重启zygote service,证明servicemanager是在zygote之前启动的)。

service servicemanager /system/bin/servicemanagerclass core animationuser systemgroup system readproccriticalonrestart restart healthdonrestart restart zygoteonrestart restart audioserveronrestart restart mediaonrestart restart surfaceflingeronrestart restart inputflingeronrestart restart drmonrestart restart cameraserverwritepid /dev/cpuset/system-background/tasksshutdown critical

执行这个可执行文件,会调用frameworks/native/cmds/servicemanager/service_manager.c的main函数。那么我们来看看这个方法。可以看到主要是调用了binder_open方法来打开binder驱动,然后调用了binder_become_context_manager方法来使得serviceManager成为binder的服务管理器,最后就是调用binder_loop进入循环,处理客户端请求。

int main(int argc, char** argv)
{struct binder_state *bs;  // Binder驱动状态结构体指针union selinux_callback cb; // SELinux回调函数联合体char *driver;             // Binder驱动设备路径// 处理命令行参数:如果提供了参数,使用指定的Binder驱动设备// 否则默认使用"/dev/binder"if (argc > 1) {driver = argv[1];} else {driver = "/dev/binder";}// 打开Binder驱动并初始化Binder状态// 128*1024指定了Binder映射内存的大小(128KB)bs = binder_open(driver, 128*1024);if (!bs) {// 如果打开Binder驱动失败
#ifdef VENDORSERVICEMANAGER// 如果是供应商服务管理器,记录警告并进入无限休眠ALOGW("failed to open binder driver %s\n", driver);while (true) {sleep(UINT_MAX);  // 永久休眠,避免频繁重启}
#else// 如果是系统服务管理器,记录错误并退出ALOGE("failed to open binder driver %s\n", driver);
#endifreturn -1;}// 将自己设置为Binder上下文管理器(服务管理器)// 这是Binder IPC机制中的核心角色,负责管理所有服务注册和查找if (binder_become_context_manager(bs)) {ALOGE("cannot become context manager (%s)\n", strerror(errno));return -1;}// 设置SELinux回调函数// 审计回调:用于SELinux访问决策的审计cb.func_audit = audit_callback;selinux_set_callback(SELINUX_CB_AUDIT, cb);// 日志回调:用于SELinux日志记录cb.func_log = selinux_log_callback;selinux_set_callback(SELINUX_CB_LOG, cb);// 根据编译类型获取相应的SELinux句柄
#ifdef VENDORSERVICEMANAGER// 供应商服务管理器使用供应商服务上下文句柄sehandle = selinux_android_vendor_service_context_handle();
#else// 系统服务管理器使用系统服务上下文句柄sehandle = selinux_android_service_context_handle();
#endifselinux_status_open(true);  // 打开SELinux状态监视// 检查SELinux句柄是否有效if (sehandle == NULL) {ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");abort();  // 如果获取失败,终止进程}// 获取当前进程的安全上下文if (getcon(&service_manager_context) != 0) {ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");abort();  // 如果获取失败,终止进程}// 进入Binder循环,处理来自客户端的请求// bs: Binder状态// svcmgr_handler: 处理服务管理器请求的回调函数binder_loop(bs, svcmgr_handler);return 0;
}

首先来看看binder_open函数。这个方法主要是调用open函数打开binder驱动,然后调用mmap方法映射共享内存(重要)。

/*** 打开Binder驱动并初始化Binder状态* * @param driver: Binder设备路径,如"/dev/binder"* @param mapsize: 要映射的共享内存大小* @return: 成功返回binder_state结构体指针,失败返回NULL*/
struct binder_state *binder_open(const char* driver, size_t mapsize)
{struct binder_state *bs;struct binder_version vers;// 分配binder_state结构体内存bs = malloc(sizeof(*bs));if (!bs) {errno = ENOMEM;  // 设置错误号为内存不足return NULL;}// 1. 打开Binder设备文件// O_RDWR: 读写模式打开// O_CLOEXEC: 执行exec()时自动关闭文件描述符bs->fd = open(driver, O_RDWR | O_CLOEXEC);if (bs->fd < 0) {fprintf(stderr,"binder: cannot open %s (%s)\n",driver, strerror(errno));goto fail_open;  // 跳转到错误处理}// 2. 检查Binder驱动版本是否兼容if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {fprintf(stderr,"binder: kernel驱动版本 (%d) 与用户空间版本 (%d) 不同\n",vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);goto fail_open;  // 版本不匹配,跳转到错误处理}// 3. 映射共享内存 - 这是Binder通信的核心bs->mapsize = mapsize;// mmap参数:// NULL: 由内核选择映射地址// mapsize: 映射区域大小// PROT_READ: 只读保护// MAP_PRIVATE: 私有映射,写时复制// bs->fd: 映射的文件描述符// 0: 偏移量为0bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);if (bs->mapped == MAP_FAILED) {fprintf(stderr,"binder: 无法映射设备 (%s)\n",strerror(errno));goto fail_map;  // 映射失败,跳转到错误处理}return bs;  // 成功返回初始化好的binder_state// 错误处理标签
fail_map:close(bs->fd);  // 关闭文件描述符
fail_open:free(bs);      // 释放分配的内存return NULL;   // 返回NULL表示失败
}

再来看看binder_become_context_manager方法和binder_loop方法。在binder_become_context_manager方法中,根据之前创建的binder_state对象,将serviceManager设置为binder的服务管理器。在binder_loop方法中,创建一个循环来处理客户端的请求,与binder驱动进行通信。有点类似上篇文章提到的joinThreadPool方法。(详情可查看这篇笔记https://blog.csdn.net/g_i_a_o_giao/article/details/151365630?spm=1001.2014.3001.5502)

/*** 将当前进程设置为Binder上下文管理器* * @param bs: binder_state结构体指针* @return: ioctl调用结果,0表示成功,-1表示失败*/
int binder_become_context_manager(struct binder_state *bs)
{// 使用ioctl设置当前进程为Binder上下文管理器// BINDER_SET_CONTEXT_MGR: 特殊的ioctl命令// 0: 参数,在此命令中未使用return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}/*** 进入Binder消息循环,处理传入的Binder请求* * @param bs: binder_state结构体指针* @param func: 处理Binder事务的回调函数*/
void binder_loop(struct binder_state *bs, binder_handler func)
{int res;struct binder_write_read bwr;  // Binder读写结构uint32_t readbuf[32];          // 读取缓冲区// 初始化写操作参数(本次循环没有数据要写)bwr.write_size = 0;bwr.write_consumed = 0;bwr.write_buffer = 0;// 1. 通知Binder驱动本线程进入循环状态readbuf[0] = BC_ENTER_LOOPER;  // 命令码:进入循环binder_write(bs, readbuf, sizeof(uint32_t));// 2. 主循环 - 持续处理Binder请求for (;;) {// 设置读操作参数bwr.read_size = sizeof(readbuf);    // 读取缓冲区大小bwr.read_consumed = 0;              // 已消耗数据初始为0bwr.read_buffer = (uintptr_t) readbuf;  // 读取缓冲区地址// 3. 执行Binder读写操作(主要等待读取)// BINDER_WRITE_READ: 最常用的Binder ioctl命令res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);if (res < 0) {ALOGE("binder_loop: ioctl失败 (%s)\n", strerror(errno));break;  // ioctl失败,退出循环}// 4. 解析并处理接收到的Binder数据res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);if (res == 0) {ALOGE("binder_loop: 收到意外回复?!\n");break;  // 解析结果异常,退出循环}if (res < 0) {ALOGE("binder_loop: IO错误 %d %s\n", res, strerror(errno));break;  // 解析错误,退出循环}}
}

我们继续分析一下这个binder_parse方法。这个方法主要负责解析从Binder驱动接收到的数据并处理相应的Binder命令。首先是读取命令码,然后根据命令码进行不同的处理。如果有回调的函数,则进行处理。

/*** 解析从Binder驱动接收到的数据并处理相应的Binder命令* * @param bs: binder状态结构体指针,包含Binder设备信息* @param bio: binder_io结构体指针,用于处理回复数据(可为NULL)* @param ptr: 要解析的数据缓冲区起始地址* @param size: 数据缓冲区的大小* @param func: 处理Binder事务的回调函数* @return: 1表示成功处理,0表示收到回复,-1表示错误*/
int binder_parse(struct binder_state *bs, struct binder_io *bio,uintptr_t ptr, size_t size, binder_handler func)
{int r = 1;  // 默认返回值为1(继续处理)uintptr_t end = ptr + (uintptr_t) size;  // 计算数据结束位置// 循环处理缓冲区中的所有命令while (ptr < end) {// 1. 读取命令码(32位无符号整数)uint32_t cmd = *(uint32_t *) ptr;ptr += sizeof(uint32_t);  // 移动指针到下一个数据位置#if TRACE  // 调试跟踪fprintf(stderr,"%s:\n", cmd_name(cmd));
#endif// 2. 根据命令码进行不同的处理switch(cmd) {case BR_NOOP:  // 无操作命令break;  // 直接跳过,不做任何处理case BR_TRANSACTION_COMPLETE:  // 事务完成通知break;  // 直接跳过,不做任何处理case BR_INCREFS:   // 增加引用计数case BR_ACQUIRE:   // 获取对象case BR_RELEASE:   // 释放对象case BR_DECREFS:   // 减少引用计数
#if TRACEfprintf(stderr,"  %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void *)));
#endif// 这些命令后跟一个binder_ptr_cookie结构,跳过这个结构ptr += sizeof(struct binder_ptr_cookie);break;case BR_TRANSACTION: {  // 收到事务请求(最重要的命令)// 获取事务数据结构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) {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);// 调用处理函数处理事务res = func(bs, txn, &msg, &reply);// 根据事务标志处理回复if (txn->flags & TF_ONE_WAY) {// 单向调用:不需要回复,直接释放缓冲区binder_free_buffer(bs, txn->data.ptr.buffer);} else {// 需要回复:发送处理结果binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);}}ptr += sizeof(*txn);  // 移动指针跳过事务数据结构break;}case BR_REPLY: {  // 收到事务回复// 获取回复事务数据结构struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;// 检查数据长度是否足够if ((end - ptr) < sizeof(*txn)) {ALOGE("parse: reply too small!\n");return -1;  // 数据不足,返回错误}binder_dump_txn(txn);  // 调试输出回复信息(如果启用)// 如果有提供的bio结构,用回复数据初始化它if (bio) {bio_init_from_txn(bio, txn);bio = 0;  // 置零防止后续重复处理} else {/* todo FREE BUFFER */  // 需要释放缓冲区(TODO注释)}ptr += sizeof(*txn);  // 移动指针跳过回复数据结构r = 0;  // 设置返回值为0(表示收到回复)break;}case BR_DEAD_BINDER: {  // Binder对象死亡通知// 获取死亡通知结构struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr;ptr += sizeof(binder_uintptr_t);  // 移动指针// 调用注册的死亡回调函数death->func(bs, death->ptr);break;}case BR_FAILED_REPLY:  // 回复失败r = -1;  // 设置返回值为错误break;case BR_DEAD_REPLY:    // 对方已死亡的回复r = -1;  // 设置返回值为错误break;default:  // 未知命令ALOGE("parse: OOPS %d\n", cmd);return -1;  // 返回错误}}return r;  // 返回处理结果
}

我们再回到service_manager中,可以看到调用binder_looper时,传递了binder_loop(bs, svcmgr_handler); svcmgr_handler函数。那么我们分析一下该函数。在这个函数中,首先进行了一系列异常处理,最核心的还是根据binder驱动返回的code进行相应的处理。如果传递过来的code是SVC_MGR_ADD_SERVICE,那么就会执行do_add_service方法。传递过来的是SVC_MGR_GET_SERVICE和SVC_MGR_CHECK_SERVICE,那么会执行do_find_service方法。

// 处理Binder事务的核心函数
// bs: Binder状态,包含Binder驱动相关的文件描述符和映射信息
// txn: 事务数据,包含本次事务的详细信息(如发送者PID/UID、事务代码等)
// msg: 输入消息,包含客户端发送的数据
// reply: 输出消息,用于向客户端返回处理结果
int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{struct svcinfo *si;        // 服务信息链表节点uint16_t *s;               // 字符串指针(UTF-16)size_t len;                // 字符串长度uint32_t handle;           // Binder句柄uint32_t strict_policy;    // 严格模式策略标志int allow_isolated;        // 是否允许隔离进程访问// 检查事务目标是否为Service Manager本身// BINDER_SERVICE_MANAGER是Service Manager的固定句柄(0)if (txn->target.ptr != BINDER_SERVICE_MANAGER)return -1;// 处理PING事务(心跳检测)if (txn->code == PING_TRANSACTION)return 0;  // 直接返回0表示成功// 从消息中读取严格模式策略(相当于Parcel::enforceInterface())strict_policy = bio_get_uint32(msg);// 读取接口描述符字符串(应为"android.os.IServiceManager")s = bio_get_string16(msg, &len);if (s == NULL) {return -1;  // 读取失败返回错误}// 验证接口描述符是否正确// svcmgr_id是预定义的"android.os.IServiceManager"的UTF-16表示if ((len != (sizeof(svcmgr_id) / 2)) ||memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {fprintf(stderr,"invalid id %s\n", str8(s, len));return -1;  // 接口描述符不匹配返回错误}// SELinux相关处理:检查状态更新并重新加载策略句柄if (sehandle && selinux_status_updated() > 0) {
#ifdef VENDORSERVICEMANAGER// 供应商服务管理器使用不同的上下文句柄struct selabel_handle *tmp_sehandle = selinux_android_vendor_service_context_handle();
#else// 标准服务管理器使用普通服务上下文句柄struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
#endifif (tmp_sehandle) {selabel_close(sehandle);    // 关闭旧句柄sehandle = tmp_sehandle;    // 更新为新句柄}}// 根据事务代码进行分发处理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(s, len, txn->sender_euid, txn->sender_pid);if (!handle)break;  // 未找到服务,跳出switch// 将找到的服务句柄写入回复bio_put_ref(reply, handle);return 0;   // 返回成功case SVC_MGR_ADD_SERVICE:// 添加服务:从消息中读取服务名称s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}// 读取要注册服务的Binder句柄handle = bio_get_ref(msg);// 读取是否允许隔离进程访问的标志allow_isolated = bio_get_uint32(msg) ? 1 : 0;// 调用do_add_service执行实际添加操作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);// 权限检查:确认调用者有权列出服务if (!svc_can_list(txn->sender_pid, txn->sender_euid)) {ALOGE("list_service() uid=%d - PERMISSION DENIED\n",txn->sender_euid);return -1;  // 权限不足返回错误}// 遍历服务链表找到第n个服务si = svclist;while ((n-- > 0) && si)si = si->next;if (si) {// 将服务名称写入回复bio_put_string16(reply, si->name);return 0;  // 返回成功}return -1;  // 索引超出范围返回错误}default:// 未知事务代码处理ALOGE("unknown code %d\n", txn->code);return -1;}// 默认回复:写入32位0值(表示操作成功但无额外数据)bio_put_uint32(reply, 0);return 0;
}

首先来看看do_add_service方法,在这个方法中,首先就是进行一系列异常处理,然后调用find_svc方法来判断是否已存在同名的服务,如果存在的话则先移除旧的死亡通知,再更新现有服务的句柄。如果不存在的话,则创建新的服务节点。并且注册服务死亡回调。

// 添加服务的具体实现函数
// bs: Binder状态,用于与Binder驱动交互
// s: 服务名称(UTF-16字符串)
// len: 服务名称长度
// handle: 要注册的Binder服务句柄
// uid: 调用者的用户ID
// allow_isolated: 是否允许隔离进程访问该服务
// spid: 调用者的进程ID
int do_add_service(struct binder_state *bs,const uint16_t *s, size_t len,uint32_t handle, uid_t uid, int allow_isolated,pid_t spid)
{struct svcinfo *si;  // 服务信息结构体指针// 参数有效性检查if (!handle || (len == 0) || (len > 127))return -1;  // 句柄无效、服务名为空或超长都返回错误// 权限检查:确认调用者有权注册此服务if (!svc_can_register(s, len, spid, uid)) {ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",str8(s, len), handle, uid);return -1;  // 权限不足返回错误}// 在现有服务列表中查找是否已存在同名服务si = find_svc(s, len);if (si) {// 如果服务已存在且已有有效句柄if (si->handle) {ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",str8(s, len), handle, uid);// 先移除旧的死亡通知svcinfo_death(bs, si);}// 更新现有服务的句柄si->handle = handle;} else {// 服务不存在,创建新的服务节点// 分配内存:基础结构大小 + 服务名称存储空间(包含终止符)si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));if (!si) {ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",str8(s, len), handle, uid);return -1;  // 内存分配失败返回错误}// 初始化服务信息结构体si->handle = handle;      // 设置Binder句柄si->len = len;            // 设置服务名长度memcpy(si->name, s, (len + 1) * sizeof(uint16_t));  // 复制服务名称si->name[len] = '\0';     // 确保字符串终止si->death.func = (void*) svcinfo_death;  // 设置死亡回调函数si->death.ptr = si;       // 设置死亡回调参数si->allow_isolated = allow_isolated;  // 设置隔离进程访问权限si->next = svclist;       // 将新节点插入链表头部svclist = si;             // 更新链表头指针}// 增加Binder句柄的引用计数,防止服务被意外释放binder_acquire(bs, handle);// 注册死亡通知,当服务进程终止时能收到通知binder_link_to_death(bs, handle, &si->death);return 0;  // 返回成功
}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) &&!memcmp(s16, si->name, len * sizeof(uint16_t))) {return si;}}return NULL;
}

我们继续去分析do_find_service方法,在这个方法中,同样是调用了find_svc方法在服务列表中查找指定名称的服务,如果找到了,就进行一系列的检查,通过检查以后返回对应服务的句柄。

// 查找服务的具体实现函数
// s: 要查找的服务名称(UTF-16字符串)
// len: 服务名称长度
// uid: 调用者的用户ID
// spid: 调用者的进程ID
// 返回值: 找到的服务句柄,如果未找到或没有权限访问则返回0
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{// 在服务列表中查找指定名称的服务struct svcinfo *si = find_svc(s, len);// 检查服务是否存在且具有有效句柄if (!si || !si->handle) {return 0;  // 服务不存在或句柄无效,返回0}// 检查隔离进程访问权限if (!si->allow_isolated) {// 如果此服务不允许从隔离进程访问,// 则检查UID是否为隔离进程// 计算应用ID(去除用户ID部分)uid_t appid = uid % AID_USER;// 检查应用ID是否在隔离进程范围内if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {return 0;  // 调用者是隔离进程且服务不允许访问,返回0}}// 权限检查:确认调用者有权查找此服务if (!svc_can_find(s, len, spid, uid)) {return 0;  // 权限不足,返回0}// 所有检查通过,返回找到的服务句柄return si->handle;
}

再回到最初的binder_loop中,执行完func以后,会根据事务的标志判断需不需要返回。不需要的话就会释放掉缓冲区,需要的话就会调用binder_send_reply方法,将事务的处理结果返回。

if (txn->flags & TF_ONE_WAY) {binder_free_buffer(bs, txn->data.ptr.buffer);} else {binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);}// 发送Binder回复的函数
// bs: Binder状态,用于与Binder驱动通信
// reply: 包含回复数据的binder_io结构
// buffer_to_free: 需要释放的缓冲区指针(之前分配的事务缓冲区)
// status: 事务处理状态,非0表示错误状态
void binder_send_reply(struct binder_state *bs,struct binder_io *reply,binder_uintptr_t buffer_to_free,int status)
{// 定义一个打包的数据结构,包含两个Binder命令和事务数据// 使用packed属性确保编译器不会在结构成员之间添加填充字节struct {uint32_t cmd_free;                    // 释放缓冲区的命令binder_uintptr_t buffer;              // 要释放的缓冲区指针uint32_t cmd_reply;                   // 发送回复的命令struct binder_transaction_data txn;   // 事务数据} __attribute__((packed)) data;// 设置释放缓冲区命令data.cmd_free = BC_FREE_BUFFER;           // Binder命令:释放缓冲区data.buffer = buffer_to_free;             // 要释放的缓冲区地址// 设置回复命令data.cmd_reply = BC_REPLY;                // Binder命令:发送回复// 初始化事务数据结构data.txn.target.ptr = 0;                  // 目标对象指针(回复不需要特定目标)data.txn.cookie = 0;                      // 附加数据(通常用于Binder对象)data.txn.code = 0;                        // 事务代码(回复通常为0)// 根据状态码设置不同的回复内容if (status) {// 错误状态:只返回状态码data.txn.flags = TF_STATUS_CODE;      // 设置状态码标志data.txn.data_size = sizeof(int);     // 数据大小为int的大小data.txn.offsets_size = 0;            // 没有Binder对象偏移量data.txn.data.ptr.buffer = (uintptr_t)&status;  // 数据指向状态码data.txn.data.ptr.offsets = 0;        // 没有偏移量数组} else {// 正常状态:返回完整的回复数据data.txn.flags = 0;                   // 清除所有标志data.txn.data_size = reply->data - reply->data0;  // 计算数据大小data.txn.offsets_size = ((char*) reply->offs) - ((char*) reply->offs0);  // 计算偏移量大小data.txn.data.ptr.buffer = (uintptr_t)reply->data0;  // 数据缓冲区起始地址data.txn.data.ptr.offsets = (uintptr_t)reply->offs0; // 偏移量数组起始地址}// 将组合好的数据写入Binder驱动binder_write(bs, &data, sizeof(data));
}

通过源码分析,整个ServiceManager的流程就已经很清晰了:

  • Service Manager启动后调用 binder_loop() 进入等待状态

  • 当客户端想要添加/查找服务时,向Binder驱动发送事务

  • Binder驱动将事务传递给Service Manager进程

  • binder_loop() 收到消息,调用 svcmgr_handler() 处理

  • Service Manager处理完成后,调用 binder_send_reply() 发送回复

  • Binder驱动将回复传递回客户端进程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96808.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96808.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis 大 Key 与热 Key:生产环境的风险与解决方案

&#x1f525; Redis 大 Key 与热 Key&#xff1a;生产环境的风险与解决方案 文章目录&#x1f525; Redis 大 Key 与热 Key&#xff1a;生产环境的风险与解决方案&#x1f9e0; 一、问题定义与识别&#x1f4a1; 什么是大 Key&#xff1f;&#x1f525; 什么是热 Key&#xff…

C++算法题中的输入输出形式(I/O)

本文主要帮助刷leetcode题型快速适应完整带输入输出的题&#xff08;机试、考试、比赛等&#xff09;接收能用cin就用cin 。cin 自动分割单词 的特性&#xff08;cin 读取字符串时会自动跳过空格 / 换行&#xff0c;将连续非空格字符作为一个 “单词”&#xff09;一、单组输入…

【左程云算法09】栈的入门题目-最小栈

目录 栈的入门题目-最小栈 代码演示 视频链接 算法讲解015【入门】栈的入门题目-最小栈 Leecode155 栈的入门题目-最小栈 实现一个getmin方法&#xff08;高效方法&#xff0c;即不用遍历&#xff09;&#xff0c;希望能实现O&#xff08;1&#xff09; 做法&#xff1a…

Grafana与Prometheus实战

&#x1f31f;Grafana的Dashboard的权限管理 创建团队 创建用户 设置团队权限 &#x1f31f;Prometheus启用https及认证功能 自建ca的证书 准备证书目录 mkdir /app/tools/prometheus-2.53.4.linux-amd64/certs cd /app/tools/prometheus-2.53.4.linux-amd64/certs生成ca的…

FPGA交通灯设计报告(源码+管脚约束+实物图+设计报告)

基于FPGA的交通灯设计 摘要 本设计采用FPGA技术实现了一个智能交通灯控制系统。系统以Verilog HDL为设计语言,在FPGA平台上实现了交通灯的自动控制、数码管倒计时显示、紧急情况处理等功能。通过合理的状态机设计和模块化编程,系统具有良好的实时性、可靠性和可扩展性,能够…

技术论文分析分析论文《计算机病毒判定专家系统原理与设计》思考其在游戏中的应用

论文原文的引言主要有两大部分的内容&#xff1a;介绍计算机病毒&#xff0c;明确本文使用的病毒分类方式&#xff1b;分析传统计算机病毒检测存在的弊端。对于计算机病毒的定义&#xff0c;文中给出的定义比较严谨&#xff0c;我自己查了一下现在百度百科的定义&#xff0c;两…

《Unity项目实战:动态加载引发的显存危机全链路排查与重构实践》

从动态光影那流光溢彩、仿佛赋予虚拟世界真实质感的绚丽效果—这得益于Unity引擎强大的HDRP管线对光照路径的精准模拟,到物理引擎驱动的物体碰撞精准到毫厘的物理反馈—依托Unity Physics模块对刚体动力学的毫秒级计算,再到能够依据不同设备性能自动适配的画质表现—通过Unit…

智慧水库综合管理系统平台御控物联网解决方案

一、行业背景与痛点分析水库作为防洪、灌溉、供水、发电及生态保护的核心基础设施&#xff0c;其管理效率直接关系到区域水资源安全与可持续发展。然而&#xff0c;传统水库管理模式存在四大核心痛点&#xff1a;数据孤岛严重&#xff1a;水位、雨量、水质、设备状态等数据分散…

使用nvm安装Node.js18以下报错解决方案——The system cannot find the file specified.

使用 nvm 安装 Node.js 18以下 报错解决方案 在前端开发过程中&#xff0c;常常需要针对不同项目切换 Node.js 版本。nvm&#xff08;Node Version Manager&#xff09;是最常用的工具。但最近在尝试安装 Node.js 14 版本时&#xff0c;遇到了奇怪的错误。 问题描述 使用 nv…

在Excel和WPS表格中快速复制上一行内容

有的时候我们在Excel和WPS表格中想复制上一行对应单元格、连续区域或整行的内容&#xff0c;只需要在当前行拖动鼠标左键选中相关区域&#xff0c;然后按CtrlD键即可将上一行对应位置的内容复制过来——需要注意的是&#xff0c;如果当前行有数据&#xff0c;这些数据会直接被覆…

408学习之c语言(递归与函数)

今天主要学习了递归与函数的相关内容&#xff0c;下面将我今天所学知识与所写代码分享给大家 递归核心要点 递归三要素 基准条件&#xff08;明确终止条件&#xff09; 递归调用&#xff08;逐步分解问题&#xff09; 收敛性&#xff08;确保每次递归都向基准条件靠近&#xff…

swVBA自学笔记016、Solidworks API Help 帮助文档的(三大版块)

目录1. Namespace (命名空间) 版块2. Interface (接口) 版块3. Members (接口成员) 版块4、总结关系5、如果你感觉上面说的过于简单&#xff0c;请往下看!6、示例链接→SOLIDWORKS API Help 20197、需要注意的是&#xff0c;带“I”的对象表示&#xff1a;接口1. Namespace (命…

通俗易懂地讲解JAVA的BIO、NIO、AIO

理解Java的I/O模型&#xff08;BIO、NIO、AIO&#xff09;对于构建高性能网络应用至关重要 &#x1f9e0; 通俗理解&#xff1a;快递站的故事 想象一个快递站&#xff1a; • BIO&#xff1a;就像快递站为每一个包裹都安排一位专员。专员从接到包裹到处理完&#xff08;签收、…

LabVIEW 泵轮检测系统

在汽车行业&#xff0c;泵轮作为液力变矩器关键部件&#xff0c;其质量检测极为重要。传统手工检测泵轮效率低且误差大&#xff0c;为此构建基于 LabVIEW 与西门子硬件结合的泵轮检测系统。 应用场景 聚焦汽车零部件生产车间&#xff0c;对泵轮总成进行出厂前检测。在液力变矩…

2025年8月月赛 T2 T3

一. 七天假日 T2原思路&#xff1a;直接计算左右括号的数量&#xff0c;然后直接输出他们的差改进思路&#xff1a; 用d值记录截止到当前位置&#xff0c;还需要多少个右括号可以满足非法要求cur&#xff1a;截止到当前位置&#xff0c;已经有多少个右括号sum是右括号位置的前缀…

数据结构----栈的顺序存储(顺序栈)

栈的特点&#xff1a;先进后出栈的操作&#xff1a;用数组进行存储&#xff08;1&#xff09;初始化&#xff1a;//栈 typedef struct {int *data;//指针模拟分配数组int top;//栈“顶”指针 }Stack; //初始化 Stack InitStack(){Stack s;//给数组分配空间s.data (int*)malloc…

React Hooks原理深度解析与高级应用模式

React Hooks原理深度解析与高级应用模式 引言 React Hooks自16.8版本引入以来&#xff0c;彻底改变了我们编写React组件的方式。然而&#xff0c;很多开发者仅仅停留在使用层面&#xff0c;对Hooks的实现原理和高级应用模式了解不深。本文将深入探讨Hooks的工作原理、自定义Hoo…

兼职网|基于SpringBoot和Vue的蜗牛兼职网(源码+数据库+文档)

项目介绍 : SpringbootMavenMybatis PlusVue Element UIMysql 开发的前后端分离的蜗牛兼职网&#xff0c;项目分为管理端和用户端和企业端。 项目演示: 基于SpringBoot和Vue的蜗牛兼职网 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可…

TDengine 聚合函数 LEASTSQUARES 用户手册

LEASTSQUARES 函数用户手册 函数定义 LEASTSQUARES(expr, start_val, step_val)功能说明 LEASTSQUARES() 函数对指定列的数据进行最小二乘法线性拟合&#xff0c;返回拟合直线的斜率&#xff08;slope&#xff09;和截距&#xff08;intercept&#xff09;。该函数基于线性回…

Redis最佳实践——安全与稳定性保障之高可用架构详解

全面详解 Java 中 Redis 在电商应用的高可用架构设计一、高可用架构核心模型 1. 多层级高可用体系 #mermaid-svg-anJ3iQ0ymhr025Jn {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-anJ3iQ0ymhr025Jn .error-icon{fil…