主頁 > 移動端開發 > Service Manager的啟動

Service Manager的啟動

2021-10-12 07:32:51 移動端開發

Service Manager的用處

  • 在正式展開敘述之前,我們需要先說明一下為什么要進行Service Manager的啟動和獲取,回到binder-框架認知中的整體框架圖可知:

    在這里插入圖片描述

    客戶端想要和服務端進行通信,首先需要和對應服務端建立連接,那么客戶端是怎么知道,并且完成和服務端進行連接的呢?這就需要用到Service Manager服務了,其實對應客戶端和服務端的連接通信都是通過Service Manager來建立的,之所以Service Manager可以實作相關需求,原因如下:

    1. Service ManagerBinderhandle,固定為0;而這就保證了:不論是Client端還是Service端都可以準確的獲取到Service Manager
    2. Service Manager中記錄了需要通信的server,這樣就可以通過Service Manager去獲取對應的需要通信的server

Service Manager的啟動

Service Manager啟動相關圖示

  • Service Manager啟動方法呼叫圖

    在這里插入圖片描述

  • Service Manager啟動時序圖

    在這里插入圖片描述

Service Manager啟動代碼決議

  • 需要說明以下分析都是基于Android P版本進行分析

1. 啟動Service Manager行程

  • ServiceManager是由init行程通過決議init.rc檔案而創建的,其所對應的可執行程式是ServiceManager,所對應的源檔案是service_manager.c,行程名是servicemanager

    檔案路徑:android/system/core/rootdir/init.rc

    # Start essential services.
    # 啟動servicemanager.rc
    start servicemanager
    start hwservicemanager
    start vndservicemanager
    

    檔案路徑:android/frameworks/native/cmds/servicemanager.rc

    # 啟動service Manager行程
    service servicemanager /system/bin/servicemanager
        class core animation
        user system
        group system readproc
        critical
        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
    

2. 進入 service_manager.c 中的 main()方法

  • 啟動servicemanager的入口就是 service_manager.c 中的main()方法:

    int main(int argc, char** argv)
    {
        struct binder_state *bs;
        union selinux_callback cb;
        char *driver;
    
        if (argc > 1) {
            driver = argv[1];
        } else {
            driver = "/dev/binder";
        }
    
        //獲取SM的binder驅動相關資訊,并在其中建立了記憶體映射,這里open的Binder驅動檔案是SM行程的,所以下面映射的128K的空間的SM的Binder的大小
        bs = binder_open(driver, 128*1024);
        if (!bs) {
    #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);
    #endif
            return -1;
        }
    
        //將該binder注冊成ServiceManager大管家
        if (binder_become_context_manager(bs)) {
            ALOGE("cannot become context manager (%s)\n", strerror(errno));
            return -1;
        }
    
        cb.func_audit = audit_callback;
        selinux_set_callback(SELINUX_CB_AUDIT, cb);
        cb.func_log = selinux_log_callback;
        selinux_set_callback(SELINUX_CB_LOG, cb);
    
    #ifdef VENDORSERVICEMANAGER
        sehandle = selinux_android_vendor_service_context_handle();
    #else
        sehandle = selinux_android_service_context_handle();
    #endif
        selinux_status_open(true);
    
        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 loop回圈
        binder_loop(bs, svcmgr_handler);
    
        return 0;
    }
    

    main() 方法中,關于SM的啟動,主要的動作是三個:

    1. binder_open() : 打開Binder設備檔案
    2. binder_become_context_manager() : 告知Binder驅動當前行程是Binder背景關系管理者
    3. binder_loop() : 進入訊息回圈,等待Client請求;如果沒有Client請求,那么就進入中斷等待狀態,如果有Client請求,就被喚醒,讀取并處理Client請求

    接下來針對這三個方法進行詳細決議,

1. binder_open()方法決議
  • 代碼路徑:android/framework/native/cmds/servicemanager/binder.c

    //此處引數,driver = /dev/binder,mapsize = 128*1024
    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;
        }
    
        //打開dev/binder驅動,并回傳驅動檔案句柄,保存在bs->fd中
        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;
        }
    
        //獲取binder驅動版本號
        if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
            (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
            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;
        //建立SM行程的binder記憶體映射,mapsize為128K,所以此處映射的空間就是128K,所以Service Manager行程的binder空間就只有128K
        bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
        if (bs->mapped == MAP_FAILED) {
            fprintf(stderr,"binder: cannot map device (%s)\n",
                    strerror(errno));
            goto fail_map;
        }
    
        //回傳SM的binder相關資訊,包含binder驅動檔案的句柄、記憶體大小以及binder驅動首地址
        return bs;
    
    fail_map:
        close(bs->fd);
    fail_open:
        free(bs);
        return NULL;
    }
    

    binder_open()方法的用處就是打開“/dev/binder”節點的Binder驅動設備檔案,然后呼叫mmap()方法進行記憶體映射

  • 這里面的open()mmap()最后都是呼叫Binder驅動中的binder_open()binder_mmap(),具體方法決議見binder框架決議(1),此處不多做說明,

2. binder_become_context_manager()方法決議
  • 代碼路徑:android/framework/native/cmds/servicemanager/binder.c

    int binder_become_context_manager(struct binder_state *bs)
    {
        //呼叫到binder驅動層的Binder_ioctl,傳入的引數是BINDER_SET_CONTEXT_MGR
        return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
    }
    

    ioctl()會呼叫到Binder驅動層的binder_ioctl()方法

    • 代碼路徑:android/kernel/drivers/staging/android/binder.c
    static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    	...
        switch (cmd) {
            ......
            //傳入的引數是BINDER_SET_CONTEXT_MGR,命中此處case
            case BINDER_SET_CONTEXT_MGR:
                ret = binder_ioctl_set_ctx_mgr(filp);
    		   if (ret)
                    goto err;
    		   break;    
                
           .....
        }
        ...
    }
    

    傳入的引數為:BINDER_SET_CONTEXT_MGR,命中對應case后呼叫方法:binder_ioctl_set_ctx_mgr(filp)

    static int binder_ioctl_set_ctx_mgr(struct file *filp,
    				    struct flat_binder_object *fbo)
    {
    	int ret = 0;
    	//filp->private_data中記錄的就是對應行程的proc資訊
    	struct binder_proc *proc = filp->private_data;
    	//拿到對應行程的context物件
    	struct binder_context *context = proc->context;
    	struct binder_node *new_node;
    	//拿到當前的uid
    	kuid_t curr_euid = current_euid();
    
    	mutex_lock(&context->context_mgr_node_lock);
    	//判斷是否已經存在binder_context_mgr_node,如果已經存在,不能再進行二次設定,直接退出,保證只存在一個SM
    	if (context->binder_context_mgr_node) {
    		pr_err("BINDER_SET_CONTEXT_MGR already set\n");
    		ret = -EBUSY;
    		goto out;
    	}
    	//判斷proc->tsk的合法性,如果proc->tsk不合法直接退出
    	ret = security_binder_set_context_mgr(proc->tsk);
    	if (ret < 0)
    		goto out;
    	//判斷binder_context_mgr_uid是否合法,這個也是在判斷是否已經進行過SM的設定了,確保只存在一個SM
    	if (uid_valid(context->binder_context_mgr_uid)) {
    		//如果binder_context_mgr_uid合法,即表示已經進行過SM的設定了,那么就uid和當前行程的uid是否相等
    		if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) {
    			pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
    			       from_kuid(&init_user_ns, curr_euid),
    			       from_kuid(&init_user_ns,
    					 context->binder_context_mgr_uid));
    			ret = -EPERM;
    			goto out;
    		}
    	} else {
    		//如果不存在uid,則將當前執行緒的uid設定進去
    		context->binder_context_mgr_uid = curr_euid;
    	}
    	//根據proc新建一個binder_node物件,fbo此時為null
    	new_node = binder_new_node(proc, fbo);
    	if (!new_node) {
    		ret = -ENOMEM;
    		goto out;
    	}
    	//加鎖,確保執行緒安全
    	binder_node_lock(new_node);
    	//增加相關binder參考計數
    	new_node->local_weak_refs++;
    	new_node->local_strong_refs++;
    	new_node->has_strong_ref = 1;
    	new_node->has_weak_ref = 1;
    	//將新建的binder_node物件使用binder_context_mgr_node保存起來
    	context->binder_context_mgr_node = new_node;
    	binder_node_unlock(new_node);
    	binder_put_node(new_node);
    out:
    	mutex_unlock(&context->context_mgr_node_lock);
    	return ret;
    }
    

    縱觀binder_ioctl_set_ctx_mgr()方法,其實它做的事情就是:在判斷binder_context_mgr_node為空的前提下,通過binder_new_node()方法新建了ServicerManager服務對應的Binder物體,并將其賦值binder_context_mgr_node

    • 其實每個需要進行IPC通信的server都會通過binder_new_node()方法去新建一個Binder物體,而SM和其他服務的Binder物體所不同有兩點:
      1. binder_new_node(proc, fbo) SM的Binder物體在創建時傳入的引數是特殊的
      2. SMBinder物體是保存在binder_context_mgr_node中的,而這就保證了其他的服務都可以通過context->binder_context_mgr_node去獲取SMBinder物體

    接下來我們看一下binder_new_node()方法的代碼細節:

    static struct binder_node *binder_new_node(struct binder_proc *proc,
    					   struct flat_binder_object *fp)
    {
    	struct binder_node *node;
    	//創建一個新的binder_node物件
    	struct binder_node *new_node = kzalloc(sizeof(*node), GFP_KERNEL);
    
    	if (!new_node)
    		return NULL;
    	binder_inner_proc_lock(proc);
    	//傳入代表當前行程的proc資料、新創建的new_node、fp(在創建SM時,fp為null)
    	node = binder_init_node_ilocked(proc, new_node, fp);
    	binder_inner_proc_unlock(proc);
    	if (node != new_node)
    		/*
    		 * The node was already added by another thread
    		 */
    		kfree(new_node);
    
    	return node;
    }
    
    static struct binder_node *binder_init_node_ilocked(
    						struct binder_proc *proc,
    						struct binder_node *new_node,
    						struct flat_binder_object *fp)
    {
    	struct rb_node **p = &proc->nodes.rb_node;
    	struct rb_node *parent = NULL;
    	struct binder_node *node;
    	//通過傳入的fp引數是否為空,決定了ptr和cookie是否為0
    	binder_uintptr_t ptr = fp ? fp->binder : 0;
    	binder_uintptr_t cookie = fp ? fp->cookie : 0;
    	__u32 flags = fp ? fp->flags : 0;
    	s8 priority;
    
    	assert_spin_locked(&proc->inner_lock);
    
    	//在proc->nodes.rb_node這顆紅黑樹中,查找是否存在匹配的binder物體(通過ptr成員來判斷)
    	while (*p) {
    
    		parent = *p;
    		node = rb_entry(parent, struct binder_node, rb_node);
    
    		if (ptr < node->ptr)
    			p = &(*p)->rb_left;
    		else if (ptr > node->ptr)
    			p = &(*p)->rb_right;
    		else {
    			/*
    			 * A matching node is already in
    			 * the rb tree. Abandon the init
    			 * and return it.
    			 */
    			binder_inc_node_tmpref_ilocked(node);
    			return node;
    		}
    	}
    	//如果在紅黑樹中沒有找到對應相同ptr的binder_node物件,那么就將新創建的new_node賦值給node
    	node = new_node;
    	binder_stats_created(BINDER_STAT_NODE);
    	node->tmp_refs++;
    	//將新創建的node放入node->rb_node中
    	rb_link_node(&node->rb_node, parent, p);
    	rb_insert_color(&node->rb_node, &proc->nodes);
    	node->debug_id = atomic_inc_return(&binder_last_id);
    	//將當前行程的proc資訊保存到node->proc中
    	node->proc = proc;
    	//保存下當前行程的ptr、cookie值,在SM中,ptr和cookie都為0,這是SM特殊的地方!
    	node->ptr = ptr;
    	node->cookie = cookie;
    	node->work.type = BINDER_WORK_NODE;
    	priority = flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
    	node->sched_policy = (flags & FLAT_BINDER_FLAG_SCHED_POLICY_MASK) >>
    		FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT;
    	node->min_priority = to_kernel_prio(node->sched_policy, priority);
    	node->accept_fds = !!(flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
    	node->inherit_rt = !!(flags & FLAT_BINDER_FLAG_INHERIT_RT);
    	node->txn_security_ctx = !!(flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX);
    	spin_lock_init(&node->lock);
    	INIT_LIST_HEAD(&node->work.entry);
    	INIT_LIST_HEAD(&node->async_todo);
    	binder_debug(BINDER_DEBUG_INTERNAL_REFS,
    		     "%d:%d node %d u%016llx c%016llx created\n",
    		     proc->pid, current->pid, node->debug_id,
    		     (u64)node->ptr, (u64)node->cookie);
    
    	return node;
    }
    
  • 至此binder_become_context_manager()就分析完畢了,其所謂的將SM設定為大管家,本質上其實就是兩點:

    1. SM的驅動層Binder物體創建是特殊的,binder_new_node(proc, 0, 0)中傳入的引數都是0
    2. SM的驅動層Binder物體用binder_context_mgr_node進行了保存,而這就保證了所有server都可以通過context->binder_context_mgr_node獲取到SMBinder物體
3. binder_loop()方法決議
  • 代碼路徑:android/framework/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;
    
        //放入讀資料為BC_ENTER_LOOPER
        readbuf[0] = BC_ENTER_LOOPER;
        //將資料通過ioctl寫入,
        //這里的作用就是告訴Kernel binder驅動,我(ServiceManager行程)要進入訊息回圈狀態了,請做好相關準備
        binder_write(bs, readbuf, sizeof(uint32_t));
    
        //此處是死回圈
        for (;;) {
            //這里的read資料就是BC_ENTER_LOOPER
            bwr.read_size = sizeof(readbuf);
            bwr.read_consumed = 0;
            bwr.read_buffer = (uintptr_t) readbuf;
    
            //通過ioctl呼叫kernel層的binder_ioctl,這里首次進來,write_buffer是空的,read_buffer為BC_ENTER_LOOPER
            //如果沒有訊息需要處理,同步狀態下,會在這里阻塞
            res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    
            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);
            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;
            }
        }
    }
    

    決議binder_loop()方法可以看到,其中存在死回圈,也就是說,SM啟動后最侄訓一直在binder_loop()方法中回圈獲取訊息,而在ioctl()方法處,如果不再有訊息,則會阻塞,

    接下來進一步決議其中的方法,從binder_write()方法開始看:

    int binder_write(struct binder_state *bs, void *data, size_t len)
    {
        struct binder_write_read bwr;
        int res;
    
        //將傳入的data放到binder_write_read資料結構中的write_buffer中,此處的data為BC_ENTER_LOOPER
        bwr.write_size = len;
        bwr.write_consumed = 0;
        bwr.write_buffer = (uintptr_t) data;
        bwr.read_size = 0;
        bwr.read_consumed = 0;
        bwr.read_buffer = 0;
        //通過ioctl呼叫kernel層的binder_ioctl將資料寫入
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        if (res < 0) {
            fprintf(stderr,"binder_write: ioctl failed (%s)\n",
                    strerror(errno));
        }
        return res;
    }
    

    分析binder_write()發現,最終也是呼叫了ioctl()方法進到了kernel層進行訊息處理,那么接下來簡單分析一下傳入的資料分別為:write_buffer = BC_ENTER_LOOPERread_buffer = BC_ENTER_LOOPER時,在kernelbinder_ioctl()中都做了哪些動作吧

    • 代碼路徑:android/drivers/android/binder.c
    static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
     ....
         switch (cmd) {
          .....
             //傳入的cmd為BINDER_WRITE_READ,命中case
             case BINDER_WRITE_READ:
                 ret = binder_ioctl_write_read(filp, cmd, arg, thread);
                 if (ret)
                     goto err;
                 break;
          ......
                 
         }
    .....
    }
    

    兩次傳下來的cmd都為BINDER_WRITE_READ,命中對應case,呼叫方法:binder_ioctl_write_read(filp, cmd, arg, thread)

    static int binder_ioctl_write_read(struct file *filp,
    				unsigned int cmd, unsigned long arg,
    				struct binder_thread *thread)
    {
    	int ret = 0;
        //之前binder_open()方法打開的對應binder驅動檔案背景關系資訊,就是存在filp->private_data中,此時就是通過其獲取到之前打開的binder背景關系
    	struct binder_proc *proc = filp->private_data;
    	unsigned int size = _IOC_SIZE(cmd);
    	void __user *ubuf = (void __user *)arg;
    	struct binder_write_read bwr;
    
    	if (size != sizeof(struct binder_write_read)) {
    		ret = -EINVAL;
    		goto out;
    	}
        //通過copy_from_user將用戶空間的傳輸資料的封裝bwr,拷貝到內核空間中
    	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
    		ret = -EFAULT;
    		goto out;
    	}
    	binder_debug(BINDER_DEBUG_READ_WRITE,
    		     "%d:%d write %lld at %016llx, read %lld at %016llx\n",
    		     proc->pid, thread->pid,
    		     (u64)bwr.write_size, (u64)bwr.write_buffer,
    		     (u64)bwr.read_size, (u64)bwr.read_buffer);
    
        //第一次進入時,write_size>0,存放的資料是BC_ENTER_LOOPER,命中if;第二次進入時,write_size=0,不滿足條件
    	if (bwr.write_size > 0) {
    		ret = binder_thread_write(proc, thread,
    					  bwr.write_buffer,
    					  bwr.write_size,
    					  &bwr.write_consumed);
    		trace_binder_write_done(ret);
    		if (ret < 0) {
    			bwr.read_consumed = 0;
    			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
    				ret = -EFAULT;
    			goto out;
    		}
    	}
        //第一次進入時read_size=0,不滿足條件;第二次進入時,read_size>0,存放的資料是BC_ENTER_LOOPER,命中if
    	if (bwr.read_size > 0) {
    		ret = binder_thread_read(proc, thread, bwr.read_buffer,
    					 bwr.read_size,
    					 &bwr.read_consumed,
    					 filp->f_flags & O_NONBLOCK);
    		trace_binder_read_done(ret);
            //如果todo佇列不為空,那么就喚醒處于中斷狀態的wait等待執行緒
    		if (!list_empty(&proc->todo))
    			wake_up_interruptible(&proc->wait);
    		if (ret < 0) {
    			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
    				ret = -EFAULT;
    			goto out;
    		}
    	}
    	binder_debug(BINDER_DEBUG_READ_WRITE,
    		     "%d:%d wrote %lld of %lld, read return %lld of %lld\n",
    		     proc->pid, thread->pid,
    		     (u64)bwr.write_consumed, (u64)bwr.write_size,
    		     (u64)bwr.read_consumed, (u64)bwr.read_size);
        //讀寫完畢后,將內核空間的資料的封裝bwr,拷貝回用戶空間
    	if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
    		ret = -EFAULT;
    		goto out;
    	}
    out:
    	return ret;
    }
    

    接下來我們逐一分析 binder_thread_write()binder_thread_read()方法

    先看一下 binder_thread_write()

    static int binder_thread_write(struct binder_proc *proc,
    			struct binder_thread *thread,
    			binder_uintptr_t binder_buffer, size_t size,
    			binder_size_t *consumed)
    {
    	uint32_t cmd;
    	struct binder_context *context = proc->context;
    	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
    	void __user *ptr = buffer + *consumed;
    	void __user *end = buffer + size;
    
        //回圈獲取buffer中的資料
    	while (ptr < end && thread->return_error == BR_OK) {
    		if (get_user(cmd, (uint32_t __user *)ptr))
    			return -EFAULT;
    		ptr += sizeof(uint32_t);
    		trace_binder_command(cmd);
    		if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
    			binder_stats.bc[_IOC_NR(cmd)]++;
    			proc->stats.bc[_IOC_NR(cmd)]++;
    			thread->stats.bc[_IOC_NR(cmd)]++;
    		}
    		switch (cmd) {
             //此時buffer中的資料是BC_ENTER_LOOPER,命中case
    		case BC_ENTER_LOOPER:
    			binder_debug(BINDER_DEBUG_THREADS,
    				     "%d:%d BC_ENTER_LOOPER\n",
    				     proc->pid, thread->pid);
    			if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
    				thread->looper |= BINDER_LOOPER_STATE_INVALID;
    				binder_user_error("%d:%d ERROR: BC_ENTER_LOOPER called after BC_REGISTER_LOOPER\n",
    					proc->pid, thread->pid);
    			}
                 //更新looper的值,表明現在進入了loop狀態
    			thread->looper |= BINDER_LOOPER_STATE_ENTERED;
    			break;
    		......
    	}
    	return 0;
    }
    

    到這里,第一個寫入的BC_ENTER_LOOPER就處理完成了,其實就是置了標志位,表明當下進入了loop狀態,

    接下來,我們看一下binder_thread_read()中,對BC_ENTER_LOOPER的處理:

    static int binder_thread_read(struct binder_proc *proc,
    			      struct binder_thread *thread,
    			      binder_uintptr_t binder_buffer, size_t size,
    			      binder_size_t *consumed, int non_block)
    {
    	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
    	void __user *ptr = buffer + *consumed;
    	void __user *end = buffer + size;
    
    	int ret = 0;
    	int wait_for_proc_work;
    
    	//第一次進來consumed=0成立,命中if,向用戶空間寫入訊息:BR_NOOP
    	if (*consumed == 0) {
    		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
    			return -EFAULT;
    		ptr += sizeof(uint32_t);
    	}
    ........
    	//設定標志位,BINDER_LOOPER_STATE_WAITING
    	thread->looper |= BINDER_LOOPER_STATE_WAITING;
    	if (wait_for_proc_work)
    		proc->ready_threads++;
    
    	binder_unlock(__func__);
    
    	trace_binder_wait_for_work(wait_for_proc_work,
    				   !!thread->transaction_stack,
    				   !list_empty(&thread->todo));
    	if (wait_for_proc_work) {
    		//在呼叫binder_thread_write時,我們置入了標志位:BINDER_LOOPER_STATE_ENTERED,所以這里為false
    		if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
    					BINDER_LOOPER_STATE_ENTERED))) {
    			binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n",
    				proc->pid, thread->pid, thread->looper);
    			wait_event_interruptible(binder_user_error_wait,
    						 binder_stop_on_user_error < 2);
    		}
    		//設定優先級,當前執行緒要處理proc的事務,所以設定優先級和proc一樣
    		binder_set_nice(proc->default_priority);
    		//判斷是否是訊息同步處理機制
    		//如果是不阻塞式,即異步的處理方式,通過binder_has_proc_work()讀取訊息,如果沒有訊息,則直接回傳
    		if (non_block) {
    			if (!binder_has_proc_work(proc, thread))
    				ret = -EAGAIN;
    		} else
    			//如果是阻塞式,即同步的處理方式,同樣使用binder_has_proc_work()讀取訊息,但是如果沒有訊息存在,則在此處阻塞
    			ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
    	} else {
    		if (non_block) {
    			if (!binder_has_thread_work(thread))
    				ret = -EAGAIN;
    		} else
    			ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));
    	}
        ......
    }
    

    至此,binder_thread_read()也已分析完成,當待處理佇列中沒有需要處理的事務時,那么就會阻塞(針對SM訊息啟動來說),直到有待處理事務寫入,從而喚醒SMbinder_loop(),繼續獲取事務進行處理

Service Manager啟動總結

  • 通過對service_manager.c檔案中的main()方法的決議,我們可以得知:
    1. 對于ServiceManager行程來說:
      • 通過 binder_open() 方法打開 Binder驅動設備檔案,并且將記憶體映射到ServiceManager的行程空間
      • 通過binder_become_context_manager()設定當前行程為Binder背景關系管理者
      • 通過binder_loop()進入訊息獲取回圈,等待Client的請求
    2. 對于Binder驅動來說:
      • 初始化了ServiceManager對應的行程背景關系環境,即binder_proc變數
      • 將內核虛擬記憶體和用戶虛擬記憶體映射到同一塊物理記憶體中,大小是128K
      • 新建當前執行緒對應的binder_thread物件,并將其添加到行程背景關系資訊binder_proc->threads紅黑樹中
      • 新建ServiceManagerBinder物體,并將該Binder物體用context->binder_context_mgr_node保存
      • 當沒有訊息事務處理時,進入中斷等待狀態,等待其他行程將其喚醒

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/309540.html

標籤:其他

上一篇:Android異步任務與多執行緒

下一篇:進位元組了!3年Android開發的艱辛三面經歷總結!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more