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
英文地址,請注明出處:http://en.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…