圖解 | 不得錯過的Binder淺析(二)

本文主要分析ServiceManager系統(tǒng)服務(wù)管理進程對binder的管理流程。

大綱:

  • 揭開Binder面紗
  • Binder的管理
    • 1 打開binder驅(qū)動
    • 2 成為系統(tǒng)唯一的上下文
    • 3 進入binder循環(huán)
    • 4 系統(tǒng)服務(wù)的注冊和獲取
  • 總結(jié)
  • 參考資料

本文約3.7k字,閱讀大約15分鐘。

Android源碼基于8.0。

揭開Binder面紗

Binder跟鍵盤、顯示器一樣屬于一種外設(shè)(沒有實體的外設(shè))。由于外設(shè)種類繁多,操作系統(tǒng)如Linux抽象出文件視圖來方便用戶使用外設(shè)。即對用戶來說,通過讀寫外設(shè)文件,讓操作系統(tǒng)將指令發(fā)送給外設(shè)控制器,來實現(xiàn)對外設(shè)的操作。

image

在Linux中,各種外設(shè)文件放在/dev目錄下:

image

不過這些文件并不是像Windows上的那些外設(shè)驅(qū)動程序,而是提供給用戶去訪問外設(shè)的一個端口(就跟文件訪問一樣),如:

  • /dev/console:系統(tǒng)控制臺
  • /dev/mem:物理內(nèi)存的全鏡像。可以用來直接存取物理內(nèi)存。
  • /dev/kmem:內(nèi)核看到的虛擬內(nèi)存的全鏡像。可以用來訪問內(nèi)核中的內(nèi)容。
  • /dev/tty0:虛擬終端
  • ...

Linux抽象出文件視圖,為用戶提供統(tǒng)一接口,一段簡單的操作外設(shè)的程序如下:

//打開 /dev 下的外設(shè)文件
int fd = open(“/dev/xxx”);
for (int i = 0; i < 10; i++) {
    //進行讀寫操作
    write(fd,i,sizeof(int));
}
//關(guān)閉文件
close(fd);

用戶讀寫外設(shè)文件,Linux會通過外設(shè)文件找到外設(shè)控制器的地址、內(nèi)容格式等信息,向他發(fā)送合適的指令來操作外設(shè)。

現(xiàn)在我們通過adb shell進入Android設(shè)備,看下他的/dev目錄長啥樣:

image

可以看到有binder,標(biāo)黃部分的3個分別是binder、hwbinder、vndbinder,我們只關(guān)注binder就行了。

從「一圖摸清Android應(yīng)用進程的啟動」一文可知,在應(yīng)用程序啟動binder線程池時,ProcessState.cpp有這么一段代碼,

//ProcessState.cpp

sp<ProcessState> ProcessState::self(){
    //傳入 binder 外設(shè)文件路徑
    gProcess = new ProcessState("/dev/binder");
    return gProcess;
}

//ProcessState構(gòu)造函數(shù)
ProcessState::ProcessState(const char *driver)
    //路徑賦給 mDriverName
    : mDriverName(String8(driver))
        //1. 打開 binder 驅(qū)動
        , mDriverFD(open_driver(driver))
        ,//...
{
    //2. 映射內(nèi)存
    mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}

我們看下打開binder驅(qū)動的open_driver函數(shù),

//ProcessState.cpp

static int open_driver(const char *driver){
    //打開外設(shè)文件 /dev/binder
    int fd = open(driver, O_RDWR | O_CLOEXEC);
    int vers = 0;
    //獲取 binder 版本進行檢查
    status_t result = ioctl(fd, BINDER_VERSION, &vers);
    size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
    //設(shè)置 binder 最大線程數(shù)為 15
    result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
    //返回 int 類型的 fd 給 mDriverFD
    return fd;
}

看起來是不是跟Linux操作外設(shè)的那段程序很像,只不過這里的讀寫操作由write換成了ioctl,

在計算機中,ioctl(input/output control)是一個專用于設(shè)備輸入輸出操作的系統(tǒng)調(diào)用,該調(diào)用傳入一個跟設(shè)備有關(guān)的請求碼,系統(tǒng)調(diào)用的功能完全取決于請求碼。舉個例子,CD-ROM驅(qū)動程序可以彈出光驅(qū),它就提供了一個對應(yīng)的Ioctl請求碼。設(shè)備無關(guān)的請求碼則提供了內(nèi)核調(diào)用權(quán)限。ioctl這名字第一次出現(xiàn)在Unix第七版中,他在很多類unix系統(tǒng)(比如Linux、Mac OSX等)都有提供,不過不同系統(tǒng)的請求碼對應(yīng)的設(shè)備有所不同。

-- 引用自百科 ioctl

可見ioctl是一個可以控制設(shè)備I/O通道的系統(tǒng)調(diào)用,通過它用戶空間可以跟設(shè)備驅(qū)動溝通。

至于為什么要有ioctl,主要是為非標(biāo)準(zhǔn)設(shè)備考慮的(如binder就是一種非標(biāo)準(zhǔn)外設(shè)),詳見百科 ioctl 背景。

ioctl函數(shù)如下:

int ioctl(int fd, ind cmd, …);

第一個參數(shù)fd是文件描述符,如binder外設(shè)文件;

第二個參數(shù)cmd則是控制命令,如指令BINDER_SET_MAX_THREADS是“設(shè)置線程數(shù)”,最后的省略號則是各指令所需的參數(shù),如maxThreads表示最大線程數(shù)為 15。

指令BINDER_SET_MAX_THREADS的定義如下:

#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)

_IOW是一個宏,Linux內(nèi)核提供了一些宏來方便用戶定義指令(傳入各種參數(shù)進行包裝):

// nr為序號,datatype 為數(shù)據(jù)類型,如 int
_IO(type, nr ) //沒有參數(shù)的命令
_IOR(type, nr, datatype) //從驅(qū)動中讀數(shù)據(jù)
_IOW(type, nr, datatype) //寫數(shù)據(jù)到驅(qū)動
_IOWR(type,nr, datatype) //雙向傳送

名字很好理解,就是 io read write的縮寫。

對binder的了解暫且到這,只需知道他是一個外設(shè),以文件形式通過ioctl來操作就行了。

Binder的管理

從「一圖摸清Android系統(tǒng)服務(wù)」一文可知,init進程會啟動運行在獨立進程的ServiceManager服務(wù)來統(tǒng)一管理系統(tǒng)服務(wù)的注冊和獲取。

image

ServiceManager的入口函數(shù)即service_manager.c的main函數(shù)中,

//frameworks/native/cmds/servicemanager/service_manager.c

int main(int argc, char** argv){
    char *driver = "/dev/binder";
    //1. 打開 binder 驅(qū)動
    struct binder_state *bs = binder_open(driver, 128*1024);
    //2. 讓自己成為整個系統(tǒng)唯一的上下文管理器,
    //   這樣其他進程就能找到 ServiceManager 來注冊服務(wù)了
    binder_become_context_manager(bs);
    //3. 進入binder循環(huán),等待系統(tǒng)服務(wù)的注冊和查找請求
    binder_loop(bs, svcmgr_handler);
}

下面分析這3個步驟。

1 打開binder驅(qū)動

128 * 1024即128kb是mapsize,表示把binder驅(qū)動文件的128kb映射到內(nèi)存空間,而在「一圖摸清Android應(yīng)用進程的啟動」一文可知應(yīng)用進程使用的mapsize大小為BINDER_VM_SIZE1MB-8kb,可見兩者的大小是不同的,

//ProcessState.cpp
//一次Binder通信最大可以傳輸?shù)拇笮∈?1MB-4KB*2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
//映射內(nèi)存
mmap(..., BINDER_VM_SIZE, ...);

回到ServiceManager,看binder_open()的內(nèi)部實現(xiàn)binder.c

//frameworks/native/cmds/servicemanager/binder.c

struct binder_state *binder_open(const char* driver, size_t mapsize){
    struct binder_state *bs;
    //分配空間
    bs = malloc(sizeof(*bs));
    //打開 binder 驅(qū)動,得到int類型的文件描述符 fd
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    //記錄傳入的 128kb
    bs->mapsize = mapsize;
    //映射內(nèi)存,記錄內(nèi)存映射區(qū)的指針
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    return bs;
}

mmap可以將一個文件或者其它對象映射進內(nèi)存,函數(shù)原型:

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

各參數(shù)如下:

  • start:映射區(qū)的開始地址,傳 NULL 表示由系統(tǒng)決定映射區(qū)的起始地址
  • length:映射區(qū)的長度,傳 128kb
  • prot:期望的內(nèi)存保護標(biāo)志,傳 PROT_READ 只讀
  • flags:指定映射對象的類型,映射選項和映射頁是否可以共享。傳 MAP_PRIVATE 建立一個寫入時拷貝的私有映射,內(nèi)存區(qū)域的寫入不會影響到原文件
  • fd:有效的文件描述符,一般是由open()函數(shù)返回
  • offset:被映射對象內(nèi)容的起點,傳 0
  • return:成功執(zhí)行時,mmap()返回被映射區(qū)的指針

mmap會根據(jù)入?yún)inder驅(qū)動文件的一部分映射到內(nèi)存空間,然后返回該內(nèi)存空間的指針。

最后binder_open()返回的bs結(jié)構(gòu)體如下:

//frameworks/native/cmds/servicemanager/binder.c

struct binder_state{
    // binder 驅(qū)動文件描述符
    int fd;
    //由 mmap 得到的內(nèi)存映射區(qū)的指針
    void *mapped;
    // 128kb
    size_t mapsize;
};

2 成為系統(tǒng)唯一的上下文

ServiceManager讓自己成為整個系統(tǒng)唯一的上下文管理器,這樣其他進程就能找到ServiceManager來注冊服務(wù)了,

//frameworks/native/cmds/servicemanager/binder.c

int binder_become_context_manager(struct binder_state *bs){
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

可見就是前邊提到的ioctl調(diào)用,向binder驅(qū)動發(fā)送一個指令“我ServiceManager已成為全局上下文管理器”。

binder驅(qū)動層代碼暫不跟進,我們只需知道:

一般情況下,應(yīng)用層的每個binder實體都會在binder驅(qū)動層對應(yīng)一個binder_node節(jié)點,然而ServiceManager的binder_context_mgr_node比較特殊,它沒有對應(yīng)的應(yīng)用層binder實體。在整個系統(tǒng)里,它是如此特殊,以至于系統(tǒng)規(guī)定,任何應(yīng)用都必須使用句柄0來跨進程地訪問它。

-- 引用自 博客 - 紅茶一杯話Binder

3 進入binder循環(huán)

進入binder循環(huán),等待系統(tǒng)服務(wù)的注冊和查找請求,

//frameworks/native/cmds/servicemanager/binder.c

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    //readbuf 用于跟 binder 驅(qū)動互傳數(shù)據(jù)
    uint32_t readbuf[32];
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    //指令:binder 開始循環(huán)
    readbuf[0] = BC_ENTER_LOOPER;
    //向 binder 發(fā)送該指令
    //內(nèi)部會執(zhí)行 ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) { //進入循環(huán)
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        //向 binder 發(fā)送 讀寫指令
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        //解析從 binder 讀來的數(shù)據(jù),交給傳入的函數(shù) svcmgr_handler 處理
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
    }
}

其中binder_parse解析邏輯如下:

//frameworks/native/cmds/servicemanager/binder.c

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) {
        //不斷從 readbuf 讀取 binder 回傳的指令
        uint32_t cmd = *(uint32_t *) ptr;
        //每讀完一條,進行偏移
        ptr += sizeof(uint32_t);
        switch(cmd) { //處理各種指令
            case BR_TRANSACTION_COMPLETE:
                break;
            case BR_TRANSACTION: 
                //轉(zhuǎn)交給傳入的 svcmgr_handler 函數(shù)處理
                res = func(bs, txn, &msg, &reply);
                //...
                break;
            case BR_REPLY: 
                //...
                break;
                //...
        }
    }
    return r;
}

然后看到傳入的處理函數(shù)svcmgr_handler,

//frameworks/native/cmds/servicemanager/service_manager.c

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply){
    //...省略數(shù)據(jù)包裝和解析的邏輯
    switch(txn->code) {
        case SVC_MGR_GET_SERVICE:
        case SVC_MGR_CHECK_SERVICE:
            //查找系統(tǒng)服務(wù)
            handle = do_find_service(...);
            bio_put_ref(reply, handle);
        case SVC_MGR_ADD_SERVICE:
            //添加系統(tǒng)服務(wù)
            do_add_service(...);
    }
}

svcmgr_handler函數(shù)會根據(jù)不同的語義碼code來執(zhí)行相應(yīng)邏輯,如查找系統(tǒng)服務(wù)的do_find_service、添加系統(tǒng)服務(wù)的do_add_service。

至此,可以看出ServiceManager的binder啟動流程:

image

4 系統(tǒng)服務(wù)的注冊和獲取

下面簡要分析一下系統(tǒng)服務(wù)的注冊和獲取,在「一圖摸清Android系統(tǒng)服務(wù)」一文已對上層邏輯進行介紹,這里直接看do_add_servicedo_find_service兩個方法。

1.添加系統(tǒng)服務(wù)do_add_service。

struct svcinfo *svclist 以鏈表的形式記錄了所有的系統(tǒng)服務(wù),其結(jié)構(gòu)體如下:

//frameworks/native/cmds/servicemanager/service_manager.c

struct svcinfo{
    //下一個服務(wù)
    struct svcinfo *next;
    //服務(wù)的 binder 句柄值
    uint32_t handle;
    struct binder_death death;
    int allow_isolated;
    size_t len;
    //服務(wù)注冊時取的名字
    uint16_t name[0];
};

然后看到do_add_service,

//frameworks/native/cmds/servicemanager/service_manager.c

struct svcinfo *svclist; //鏈表

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){
    if (!svc_can_register(s, len, spid, uid)) {
        //判斷是否可以注冊系統(tǒng)服務(wù)
        //只有 root 進程、SystemServer 進程、有在 allowed[] 數(shù)組中聲明的進程可以
        return -1;
    }
    //分配空間給新的節(jié)點
    struct svcinfo *si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
    //記錄服務(wù)的 binder 句柄值
    si->handle = handle;
    si->len = len;
    //記錄服務(wù)注冊時取的名字
    memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
    //追加一個結(jié)束符
    si->name[len] = '\0';
    //...還有各種賦值

    //新節(jié)點的 next 指向鏈表
    si->next = svclist;
    //鏈表的頭插法
    svclist = si;
}

如下,

image

至于系統(tǒng)服務(wù)int類型的binder句柄值handle怎么來的,是由binder驅(qū)動層為我們分配,然后包裝成特定的數(shù)據(jù)結(jié)構(gòu)回傳給我們的。

可見ServiceManager鏈表svclist管理各系統(tǒng)服務(wù)的binder句柄,結(jié)構(gòu)體是svcinfo

而對應(yīng)到binder驅(qū)動層,則是用鏈表binder_procs管理的,結(jié)構(gòu)體是binder_proc,在/drivers/android/binder.c中,

//drivers/android/binder.c

//鏈表頭結(jié)點
static HLIST_HEAD(binder_procs);
//結(jié)構(gòu)體
struct binder_proc {
    //鏈表普通節(jié)點,由他的 next 和 pprev 串起鏈表
    struct hlist_node proc_node;

    //4棵紅黑樹,rb = red black
    //記錄執(zhí)行傳輸動作的線程信息 binder_thread
    struct rb_root threads;
    //記錄 binder 實體 binder_node
    struct rb_root nodes;
    //記錄 binder 代理 binder_ref
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
};

這里用了HLIST_HEAD和hlist_node來串起鏈表,binder驅(qū)動層的代碼暫不展開,感興趣可以閱讀「紅茶一杯話Binder傳輸機制篇」。

2.查找系統(tǒng)服務(wù)do_find_service

//frameworks/native/cmds/servicemanager/service_manager.c

uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid){
    //遍歷鏈表找到節(jié)點
    struct svcinfo *si = find_svc(s, len);
    //返回 binder 句柄值
    return si->handle;
}

struct svcinfo *find_svc(const uint16_t *s16, size_t len){
    struct svcinfo *si;
    //遍歷鏈表找到節(jié)點
    for (si = svclist; si; si = si->next) {
        if ((len == si->len) &&
            !memcmp(s16, si->name, len * sizeof(uint16_t))) {
            return si;
        }
    }
    return NULL;
}

綜上,查找系統(tǒng)服務(wù)的do_find_service和添加系統(tǒng)服務(wù)的do_add_service如下圖:

image

總結(jié)

ServiceManager作為管理系統(tǒng)服務(wù)的進程,經(jīng)過打開binder驅(qū)動、注冊成為系統(tǒng)唯一的上下文、進入binder循環(huán)3個核心步驟,便開始支持系統(tǒng)服務(wù)的注冊和獲取。系統(tǒng)服務(wù)的注冊和獲取過程基于binder機制實現(xiàn)IPC通信,binder的本質(zhì)就是一個外設(shè),以文件形式通過ioctl系統(tǒng)調(diào)用來操作

留下2個疑問繼續(xù)探討:

  1. binder句柄的遠程轉(zhuǎn)本地
  2. one way異步模式和他的串行調(diào)用(async_todo)、同步模式的并行調(diào)用

系列文章:

補充

  • 系統(tǒng)服務(wù)由ServiceManager進程管理,但用戶自定義的Service組件,bindService時的onServiceConnected回調(diào)拿到的IBinder句柄,是由SystemServer進程的AMS管理的,后面再開篇分析了。

    這兩個進程容易搞混,再貼出來鞏固一下...

    image

參考資料


更多性感文章,關(guān)注原創(chuàng)技術(shù)公眾號:哈利迪ei

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Framework和Binder的內(nèi)容挺深的,本文還是站在應(yīng)用層開發(fā)者的角度來建立基本認知,能在遇到問題的時候有思...
    哈利迪ei閱讀 348評論 0 0
  • 前言 Binder是安卓中實現(xiàn)IPC(進程間通信的)常用手段,四大組件之間的跨進程通信也是利用Binder實現(xiàn)的,...
    賊厲害閱讀 11,458評論 1 14
  • title: 深入理解android-jni,binder,zygote,amsdate: 2020-02-25 ...
    劉佳闊閱讀 1,907評論 0 1
  • 漸變的面目拼圖要我怎么拼? 我是疲乏了還是投降了? 不是不允許自己墜落, 我沒有滴水不進的保護膜。 就是害怕變得面...
    悶熱當(dāng)乘涼閱讀 4,469評論 0 13
  • 感覺自己有點神經(jīng)衰弱,總是覺得手機響了;屋外有人走過;每次媽媽不聲不響的進房間突然跟我說話,我都會被嚇得半死!一整...
    章魚的擁抱閱讀 2,364評論 4 5

友情鏈接更多精彩內(nèi)容