Categories
程式開發

Binder那麼弱怎麼面大廠?


Binder機制在Android中的地位舉足輕重,是用於通信的機制,我們需要掌握的很多原理都和Binder有關。其中系統服務的獲取過程也與Binder有關。獲取系統服務前需要了解ServiceManager的啟動過程,這樣更有助於理解系統服務的註冊過程和獲取過程。本文主要介紹ServiceManager的啟動過程。

有一點需要說明,如果想要了解ServiceManager的啟動過程,就需要查看Kernel Binder部分的源碼。這部分代碼在內核源碼中,AOSP源碼是不包括內核源碼的,因此需要單獨下載。

ServiceManager是init進程負責啟動的,具體是在解析init.rc配置文件時啟動的,而init進程是在系統啟動時啟動的,因此ServiceManager亦是如此,不理解init進程和init.rc配置文件的同學可以看看《Android進階解密》一書第2章。

rc文件內部由Android初始化語言編寫(Android Init Language)的腳本,主要包含5種類型的語句:Action、Commands、Services、Options和Import。

從Android 7.0開始,對init.rc配置文件進行了拆分,每個init.rc配置文件服務一個rc文件。 ServiceManager的啟動腳本在servicemanager.rc中,代碼如下所示。

frameworks/native/cmds/servicemanager/servicemanager.rc

service servicemanager /system/bin/servicemanager

    class core animation

    user system  //1

    group system readproc

    critical //2

    onrestart restart healthd  

    onrestart restart zygote

    onrestart restart audioserver

    onrestart restart media

    onrestart restart surfaceflinger

    onrestart restart inputflinger

    onrestart restart drm

    onrestart restart cameraserver

    onrestart restart keystore

    onrestart restart gatekeeperd

    writepid /dev/cpuset/system-background/tasks

    shutdown critical

service用於通知init進程創建名為servicemanager的進程,這個servicemanager進程執行程序的路徑為/system/bin/servicemanager。

以上代碼中,註釋1處的關鍵字user說明servicemanager是以用戶system的身份運行的,註釋2處的critical說明servicemanager是系統中的關鍵服務,關鍵服務是不會退出的,如果退出了,系統就會重啟。當系統重啟時,會啟動用onrestart關鍵字修飾的進程,比如zygote、media、surfaceflinger等。

servicemanager的入口函數在servicemanager.c中,如下所示。

frameworks/native/cmds/servicemanager/servicemanager.c

int main(int argc, char** argv)

{

    struct binder_state *bs;//1

    union selinux_callback cb;

    char *driver;

 

    if (argc > 1) {

        driver = argv[1];

    } else {

        driver = "/dev/binder";

    }

    bs = binder_open(driver, 128*1024);//2

    ...

    if (binder_become_context_manager(bs)) {//3

        ALOGE("cannot become context manager (%s)n", strerror(errno));

        return -1;

    }

    ...

    if (getcon(&service_manager_context) != 0) {

        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.n");

        abort();

    }

    binder_loop(bs, svcmgr_handler);//4

 

    return 0;

}

以上代碼中,註釋1處的binder_state結構體用來存儲Binder的3個信息,如下所示。

struct binder_state

{

    int fd; //binder设备的文件描述符

    void *mapped; //binder设备文件映射到进程的地址空间

    size_t mapsize; //内存映射后,系统分配的地址空间的大小,默认为128KB

};

main函數主要有以下3個作用。

(1)註釋2處調用了binderopen函數,用於打開binder設備文件,併申請了128KB大小的內存空間

(2)註釋3處調用了binder_become_context_manager函數,將servicemanager註冊成為Binder機制的上下文管理者。

(3)註釋4處調用了binder_loop函數,循環等待和處理客戶端發來的請求。

下面對這3個作用分別進行講解。

1. 打開binder設備文件

binder_open函數用於打開binder設備文件,並將它映射到進程的地址空間,代碼如下所示。

frameworks/native/cmds/servicemanager/binder.c

struct binder_state *binder_open(const char* driver, size_t mapsize)

{

    struct binder_state *bs;

    struct binder_version vers;

 

    bs = malloc(sizeof(*bs));

    if (!bs) {

        errno = ENOMEM;

        return NULL;

    }

 

    bs->fd = open(driver, O_RDWR | O_CLOEXEC);//1

    if (bs->fd fd, BINDER_VERSION, &vers) == -1) ||

        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {//2

        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;

    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);//3

    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;

}

以上代碼中,註釋1處打開了binder設備文件。註釋2處的ioctl函數用於獲取Binder的版本,如果獲取不到或者內核空間和用戶空間的Binder不是同一個版本就會直接goto到fail_open標籤,釋放Binder的內存空間。註釋3處調用了mmap函數進行內存映射,通俗來說就是將binder設備文件映射到進程的地址空間,地址空間的大小為mapsize,也就是128KB。映射完成後,會將地址空間的起始地址和大小保存binderstate結構體的mapped變量和mapsize變量中。

這裡著重講一下open函數,它會調用Kernel Binder部分的binder_open函數,這部分源碼位於內核源碼中,這裡展示的代碼版本為goldfish 3.4。

Intel X86架構的CPU提供了0~3共4個特權級,特權級的數字越小,權限越高,Linux操作系統中主要採用了0和3兩個特權級,分別對應的是內核態與用戶態。用戶態的特權級別低,因此進程在用戶態下不經過系統調用是無法主動訪問內核空間中的數據的,這樣用戶無法隨意進入所有進程共享的內核空間,起到了保護的作用。接下來介紹用戶態和內核態。

若一個進程在執行用戶自己的代碼時處於用戶態,比如open函數,它運行在用戶空間,當前的進程就處於用戶態。當一個進程因為系統調用進入內核代碼中執行時就處於內核態,比如open函數通過系統調用(_open函數)查找到了open函數在Kernel Binder對應的函數為binder_open,這時binder_open函數運行在內核空間,當前的進程由用戶態切換到內核態,代碼如下所示。

kernel/goldfish/drivers/staging/android/binder.c

static int binder_open(struct inode *nodp, struct file *filp)

{//代表Binder进程

struct binder_proc *proc;//1

binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%dn",

     current->group_leader->pid, current->pid);

//分配内存空间

proc = kzalloc(sizeof(*proc), GFP_KERNEL);//2

if (proc == NULL)

return -ENOMEM;

get_task_struct(current);

proc->tsk = current;

INIT_LIST_HEAD(&proc->todo);

init_waitqueue_head(&proc->wait);

proc->default_priority = task_nice(current);

//binder同步锁

binder_lock(__func__);

 

binder_stats_created(BINDER_STAT_PROC);

hlist_add_head(&proc->proc_node, &binder_procs);

proc->pid = current->group_leader->pid;

INIT_LIST_HEAD(&proc->delivered_death);

filp->private_data = proc;//3

//binder同步锁释放

binder_unlock(__func__);

...

return 0;

}

以上代碼中,註釋1處的binder_proc結構體代表Binder進程,用於管理Binder的各種信息。註釋2處用於為binder_proc分配內存空間。註釋3處將binder_proc賦值給file指針的private_data變量,下面還會再次提到這個private_data變量。

2. 註冊成為Binder機制的上下文管理者

binder_become_context_manager函數用於將servicemanager註冊成為Binder機制的上下文管理者,這個管理者在整個系統中只有一個,代碼如下所示。

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函數會調用Binder驅動的binder_ioctl函數,binder_ioctl函數的代碼比較多,這裡截取了BINDER_SET_CONTEXT_MGR命令處理部分,代碼如下所示。

kernel/goldfish/drivers/staging/android/binder.c

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

int ret;

struct binder_proc *proc = filp->private_data; //1

struct binder_thread *thread;

unsigned int size = _IOC_SIZE(cmd);

void __user *ubuf = (void __user *)arg;

trace_binder_ioctl(cmd, arg);

 

ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error tsk);

if (ret cred->euid) {//5

printk(KERN_ERR "binder: BINDER_SET_"

       "CONTEXT_MGR bad uid %d != %dn",

       current->cred->euid,

       binder_context_mgr_uid);

ret = -EPERM;

goto err;

}

} else

binder_context_mgr_uid = current->cred->euid;//6

binder_context_mgr_node = binder_new_node(proc, NULL, NULL);//7

if (binder_context_mgr_node == NULL) {

ret = -ENOMEM;

goto err;

}

binder_context_mgr_node->local_weak_refs++;

binder_context_mgr_node->local_strong_refs++;

binder_context_mgr_node->has_strong_ref = 1;

binder_context_mgr_node->has_weak_ref = 1;

break;

 ...

err_unlocked:

trace_binder_ioctl_done(ret);

return ret;

}

以上代碼中,註釋1處將file指針中的private_data變量賦值給binder_proc,這個private_data變量在binder_open函數中講過,是一個binder_proc結構體。

註釋2處的binder_get_thread函數用於獲取binder_thread,binder_thread結構體指的是Binder線程,binder_get_thread函數內部會從傳入的參數binder_proc中查找binder_thread,如果查詢到則直接返回,如果查詢不到則會創建一個新的binderthread並返回。

註釋3處的全局變量binder_context_mgr_node代表Binder機制的上下文管理者對應的一個Binder對象,如果它不為NULL,說明此前自身已經被註冊為Binder的上下文管理者了,Binder的上下文管理者是不能重複註冊的,因此會goto到err標籤。

註釋4處的全局變量binder_context_mgr_uid代表註冊了Binder機制上下文管理者的進程的有效用戶ID,如果它的值不為-1,說明此前已經有進程註冊Binder的上下文管理者了,因此在註釋5處判斷當前進程的有效用戶ID是否等於binder_context_mgr_uid,如果不等於binder_context_mgr_uid就goto到err標籤。

如果不滿足註釋4處的條件,說明此前沒有進程註冊Binder機制的上下文管理者,這時就會在註釋6處將當前進程的有效用戶ID賦值給全局變量binder_context_mgr_uid,另外還會在註釋7處調用binder_new_node函數創建一個Binder對象並賦值給全局變量binder_context_mgr_node。

3. 循環等待和處理客戶端發來的請求

servicemanager成功註冊成為Binder機制的上下文管理者後,servicemanager就是Binder機制的“總管”了,它需要在系統運行期間處理客戶端的請求,由於客戶端的請求不確定何時發送,因此需要通過無限循環來實現,實現這一需求的函數就是binder_loop,代碼如下所示。

frameworks/native/cmds/servicemanager/binder.c

void binder_loop(struct binder_state *bs, binder_handler func)

{

    int res;

    struct binder_write_read bwr;

    uint32_t readbuf[32];

 

    bwr.write_size = 0;

    bwr.write_consumed = 0;

    bwr.write_buffer = 0;

 

    readbuf[0] = BC_ENTER_LOOPER;

    binder_write(bs, readbuf, sizeof(uint32_t));//1

 

    for (;;) {

        bwr.read_size = sizeof(readbuf);

        bwr.read_consumed = 0;

        bwr.read_buffer = (uintptr_t) readbuf;

 

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//2

 

        if (res < 0) {             ALOGE("binder_loop: ioctl failed (%s)n", strerror(errno));             break;         }           res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);//3         if (res == 0) {             ALOGE("binder_loop: unexpected reply?!n");             break;         }         if (res < 0) {             ALOGE("binder_loop: io error %d %sn", res, strerror(errno));             break;         }     } }

以上代碼中,註釋1處將BC_ENTER_LOOPER命令通過binder_write函數寫入Binder驅動中,這樣當前線程(ServiceManager的主線程)就成為一個Binder線程,這樣就可以處理進程間的請求了。

在無限循環中不斷調用註釋2處的ioctl函數,ioctl函數使用BINDER_WRITE_READ指令查詢Binder驅動中是否有新的請求,如果有新的請求就交給註釋3處的binder_parse函數處理。如果沒有新的請求,當前線程就會在Binder驅動中睡眠,等待新的進程間請求。

由於binder_write函數的調用鏈中涉及了內核空間和用戶空間的交互,因此這裡著重講解這部分內容,代碼如下所示。

frameworks/native/cmds/servicemanager/binder.c

int binder_write(struct binder_state *bs, void *data, size_t len)

{

    struct binder_write_read bwr;//1

    int res;

 

    bwr.write_size = len;

    bwr.write_consumed = 0;

    bwr.write_buffer = (uintptr_t) data;//2

    bwr.read_size = 0;

    bwr.read_consumed = 0;

    bwr.read_buffer = 0;

    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//3

    if (res < 0) {         fprintf(stderr,"binder_write: ioctl failed (%s)n",                 strerror(errno));     }     return res; }

以上代碼中,註釋1處定義了binder_write_read結構體,接下來的代碼對bwr進行賦值,其中需要注意的是,註釋2處data的值為BC_ENTER_LOOPER。註釋3處的ioctl函數會將bwr中的數據發送給binder驅動,我們已經知道了ioctl函數在Kernel Binder中對應的函數為binder_ioctl,此前分析過這個函數,這裡截取BINDER_WRITE_READ命令處理部分,代碼如下所示。

kernel/goldfish/drivers/staging/android/binder.c

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{   

    ...

    void __user *ubuf = (void __user *)arg;

    ...

switch (cmd) {

case BINDER_WRITE_READ: {

struct binder_write_read bwr;

if (size != sizeof(struct binder_write_read)) {

ret = -EINVAL;

goto err;

}

if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {//1

ret = -EFAULT;

goto err;

}

binder_debug(BINDER_DEBUG_READ_WRITE,

     "binder: %d:%d write %ld at %08lx, read %ld at %08lxn",

     proc->pid, thread->pid, bwr.write_size, bwr.write_buffer,

     bwr.read_size, bwr.read_buffer);

 

if (bwr.write_size > 0) {//2

ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);//3

trace_binder_write_done(ret);

if (ret pid, thread->pid, bwr.write_consumed, bwr.write_size,

     bwr.read_consumed, bwr.read_size);

if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {//4

ret = -EFAULT;

goto err;

}

break;

}

   ...

return ret;

}

以上代碼中,註釋1處的copy_from_user函數用於將用戶空間數據ubuf複製出來並保存到內核數據bwr(binder_write_read結構體)中。

註釋2處的bwr的輸入緩存區有數據時,會調用註釋3處的binder_thread_write函數來處理BC_ENTER_LOOPER協議,其內部會將目標線程的狀態設置為BINDER_LOOPER_ STATE_ENTERED,這樣目標線程就是一個Binder線程。

註釋4處通過copyto_user函數將內核空間數據bwr複製到用戶空間。

本文節選自《Android進階指北》一書的5.4節,作者劉望舒,電子工業出版社出版。

第5章主要介紹Native Binder原理,掌握這一章的內容可以幫助讀者們深入理解Java Binder的原理。

Binder那麼弱怎麼面大廠? 1