Android Binder原理(四)ServiceManager的啓動過程

本文首發於微信公衆號「後廠技術官」

關聯繫列
Android AOSP基礎系列
Android系統啓動系列
應用進程啓動系列
Android深入四大組件系列
Android深入理解Context系列
Android深入理解JNI系列
Android解析WindowManager
Android解析WMS系列
Android解析AMS系列
Android包管理機制系列
Android輸入系統系列

前言

在上一篇文章中,我們以MediaPlayerService爲例,講解了系統服務是如何註冊的(addService),既然有註冊就勢必要有獲取,但是在瞭解獲取服務前,我們最好先了解ServiceManager的啓動過程,這樣更有助於理解系統服務的註冊和獲取的過程。

另外還有一點需要說明的是,要想了解ServiceManager的啓動過程,需要查看Kernel Binder部分的源碼,這部分代碼在內核源碼中,AOSP源碼是不包括內核源碼的,因此需要單獨下載,見Android AOSP基礎(二)AOSP源碼和內核源碼下載這篇文章。

1.ServiceManager的入口函數

ServiceManager是init進程負責啓動的,具體是在解析init.rc配置文件時啓動的,init進程是在系統啓動時啓動的,因此ServiceManager亦是如此,不理解init進程和init.rc的可以看Android系統啓動流程(一)解析init進程啓動過程這篇文章。
rc文件內部由Android初始化語言編寫(Android Init Language)編寫的腳本,它主要包含五種類型語句:Action、Commands、Services、Options和Import。
在Android 7.0中對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的入口函數在service_manager.c中:
frameworks/native/cmds/servicemanager/service_manager.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的三個信息:

struct binder_state
{
    int fd; //binder設備的文件描述符
    void *mapped; //binder設備文件映射到進程的地址空間
    size_t mapsize; //內存映射後,系統分配的地址空間的大小,默認爲128KB
};

main函數主要做了三件事:
1.註釋2處調用binder_open函數用於打開binder設備文件,並申請128k字節大小的內存空間。
2.註釋3處調用binder_become_context_manager函數,將servicemanager註冊成爲Binder機制的上下文管理者。
3.註釋4處調用binder_loop函數,循環等待和處理client端發來的請求。

現在對這三件事分別進行講解。

1.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 < 0) {
        fprintf(stderr,"binder: cannot open %s (%s)\n",
                driver, strerror(errno));
        goto fail_open;
    }
    //獲取Binder的version
    if ((ioctl(bs->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,也就是128K。映射完畢後會將地址空間的起始地址和大小保存在binder_state結構體中的mapped和mapsize變量中。

這裏着重說一下open函數,它會調用Kernel Binder部分的binder_open函數,這部分源碼位於內核源碼中,這裏展示的代碼版本爲goldfish3.4。

用戶態和內核態
臨時插入一個知識點:用戶態和內核態
Intel的X86架構的CPU提供了0到3四個特權級,數字越小,權限越高,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:%d\n",
		     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變量,後面的1.2小節會再次提到這個private_data變量。

1.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 < 2);
	if (ret)
		goto err_unlocked;

	binder_lock(__func__);
	thread = binder_get_thread(proc);//2
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
    ...
	case BINDER_SET_CONTEXT_MGR:
		if (binder_context_mgr_node != NULL) {//3
			printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
			ret = -EBUSY;
			goto err;
		}
		ret = security_binder_set_context_mgr(proc->tsk);
		if (ret < 0)
			goto err;
		if (binder_context_mgr_uid != -1) {//4
			if (binder_context_mgr_uid != current->cred->euid) {//5
				printk(KERN_ERR "binder: BINDER_SET_"
				       "CONTEXT_MGR bad uid %d != %d\n",
				       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,如果查詢到直接返回,如果查詢不到會創建一個新的binder_thread並返回。
註釋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,不等於就goto到err標籤。
如果不滿足註釋4的條件,說明此前沒有進程註冊Binder機制的上下文管理者,就會在註釋6處將當前進程的有效用戶ID賦值給全局變量binder_context_mgr_uid,另外還會在註釋7處調用binder_new_node函數創建一個Binder對象並賦值給全局變量binder_context_mgr_node。

1.3 循環等待和處理client端發來的請求

servicemanager成功註冊成爲Binder機制的上下文管理者後,servicemanager就是Binder機制的“總管”了,它需要在系統運行期間處理client端的請求,由於client端的請求不確定何時發送,因此需要通過無限循環來實現,實現這一需求的函數就是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 %s\n", res, strerror(errno));
            break;
        }
    }
}

註釋1處將BC_ENTER_LOOPER指令通過binder_write函數寫入到Binder驅動中,這樣當前線程(ServiceManager的主線程)就成爲了一個Binder線程,這樣就可以處理進程間的請求了。
在無限循環中不斷的調用註釋2處的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 %08lx\n",
			     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 < 0) {
				bwr.read_consumed = 0;
				if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
					ret = -EFAULT;
				goto err;
			}
		}
	    ...
		binder_debug(BINDER_DEBUG_READ_WRITE,
			     "binder: %d:%d wrote %ld of %ld, read return %ld of %ld\n",
			     proc->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函數,在本系列的第一篇文章Android Binder原理(一)學習Binder前必須要了解的知識點提過。在這裏,它用於將把用戶空間數據ubuf拷貝出來保存到內核數據bwr(binder_write_read結構體)中。
註釋2處,bwr的輸入緩存區有數據時,會調用註釋3處的binder_thread_write函數來處理BC_ENTER_LOOPER協議,其內部會將目標線程的狀態設置爲BINDER_LOOPER_STATE_ENTERED,這樣目標線程就是一個Binder線程。
註釋4處通過copy_to_user函數將內核空間數據bwr拷貝到用戶空間。

2.總結

ServiceManager的啓動過程實際上就是分析ServiceManager的入口函數,在入口函數中主要做了三件事,本篇文章深入到內核源碼來對這三件逐一進行分析,由於涉及的函數比較多,這篇文章只介紹了我們需要掌握的,剩餘大家可以自行閱讀源碼,比如binder_thread_write、copy_to_user函數。

更多的內容請關注我的獨立博客的知識體系:
http://liuwangshu.cn/system/


分享前沿技術、技術資訊、行業祕聞,技術管理,助力10萬+程序員成長爲技術官和架構師。
發佈了243 篇原創文章 · 獲贊 1860 · 訪問量 266萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章