Binder机制在Android中的地位举足轻重,是用于通信的机制,我们需要掌握的很多原理都和Binder有关。其中系统服务的获取过程也与Binder有关。获取系统服务前需要了解ServiceManager的启动过程,这样更有助于理解系统服务的注册过程和获取过程。

本文选自《Android进阶指北》一书,将主要介绍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

 1service servicemanager /system/bin/servicemanager 2    class core animation 3    user system  //1 4    group system readproc 5    critical //2 6    onrestart restart healthd   7    onrestart restart zygote 8    onrestart restart audioserver 9    onrestart restart media10    onrestart restart surfaceflinger11    onrestart restart inputflinger12    onrestart restart drm13    onrestart restart cameraserver14    onrestart restart keystore15    onrestart restart gatekeeperd16    writepid /dev/cpuset/system-background/tasks17    shutdown critical18}

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

 1int main(int argc, char** argv) 2{ 3    struct binder_state *bs;//1 4    union selinux_callback cb; 5    char *driver; 6 7    if (argc > 1) { 8        driver = argv[1]; 9    } else {10        driver = "/dev/binder";11    }12    bs = binder_open(driver, 128*1024);//213    ...14    if (binder_become_context_manager(bs)) {//315        ALOGE("cannot become context manager (%s)", strerror(errno));16        return -1;17    }18    ...19    if (getcon(&service_manager_context) != 0) {20        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.");21        abort();22    }23    binder_loop(bs, svcmgr_handler);//42425    return 0;26}

以上代码中,注释1处的binder_state结构体用来存储Binder的3个信息,如下所示。

1struct binder_state2{3    int fd;             //binder设备的文件描述符4    void *mapped;         //binder设备文件映射到进程的地址空间5    size_t mapsize;     //内存映射后,系统分配的地址空间的大小,默认为128KB6}

main函数主要有以下3个作用。

  • 注释2处调用了binderopen函数,用于打开binder设备文件,并申请了128KB大小的内存空间。
  • 注释3处调用了binder_become_context_manager函数,将servicemanager注册成为Binder机制的上下文管理者。
  • 注释4处调用了binder_loop函数,循环等待和处理客户端发来的请求。

下面对这3个作用分别进行讲解。

1. 打开binder设备文件

binder_open函数用于打开binder设备文件,并将它映射到进程的地址空间,代码如下所示。

frameworks/native/cmds/servicemanager/binder.c

 1struct binder_state *binder_open(const char* driver, size_t mapsize) 2{ 3    struct binder_state *bs; 4    struct binder_version vers; 5 6    bs = malloc(sizeof(*bs)); 7    if (!bs) { 8        errno = ENOMEM; 9        return NULL;10    }1112    bs->fd = open(driver, O_RDWR | O_CLOEXEC);//113    if (bs->fd fd, BINDER_VERSION, &vers) == -1) ||20        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {//221        fprintf(stderr,22                "binder: kernel driver version (%d) differs from user space version (%d)",23                vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);24        goto fail_open;25    }2627    bs->mapsize = mapsize;28    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);//329    if (bs->mapped == MAP_FAILED) {30        fprintf(stderr,"binder: cannot map device (%s)",31                strerror(errno));32        goto fail_map;33    }34    return bs;3536fail_map:37    close(bs->fd);38fail_open:39    free(bs);40    return NULL;41}

以上代码中,注释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

 1static int binder_open(struct inode *nodp, struct file *filp) 2{//代表Binder进程 3    struct binder_proc *proc;//1 4    binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d", 5             current->group_leader->pid, current->pid); 6//分配内存空间 7    proc = kzalloc(sizeof(*proc), GFP_KERNEL);//2 8    if (proc == NULL) 9        return -ENOMEM;10    get_task_struct(current);11    proc->tsk = current;12    INIT_LIST_HEAD(&proc->todo);13    init_waitqueue_head(&proc->wait);14    proc->default_priority = task_nice(current);15//binder同步锁16    binder_lock(__func__);1718    binder_stats_created(BINDER_STAT_PROC);19    hlist_add_head(&proc->proc_node, &binder_procs);20    proc->pid = current->group_leader->pid;21    INIT_LIST_HEAD(&proc->delivered_death);22    filp->private_data = proc;//323//binder同步锁释放24    binder_unlock(__func__);25    ...26    return 0;27}

以上代码中,注释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

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

ioctl函数会调用Binder驱动的binder_ioctl函数,binder_ioctl函数的代码比较多,这里截取了BINDER_SET_CONTEXT_MGR命令处理部分,代码如下所示。

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

 1static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 2{ 3    int ret; 4    struct binder_proc *proc = filp->private_data; //1 5    struct binder_thread *thread; 6    unsigned int size = _IOC_SIZE(cmd); 7    void __user *ubuf = (void __user *)arg; 8    trace_binder_ioctl(cmd, arg); 910    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error tsk);30        if (ret cred->euid) {//534                printk(KERN_ERR "binder: BINDER_SET_"35                       "CONTEXT_MGR bad uid %d != %d",36                       current->cred->euid,37                       binder_context_mgr_uid);38                ret = -EPERM;39                goto err;40            }41        } else42            binder_context_mgr_uid = current->cred->euid;//643        binder_context_mgr_node = binder_new_node(proc, NULL, NULL);//744        if (binder_context_mgr_node == NULL) {45            ret = -ENOMEM;46            goto err;47        }48        binder_context_mgr_node->local_weak_refs++;49        binder_context_mgr_node->local_strong_refs++;50        binder_context_mgr_node->has_strong_ref = 1;51        binder_context_mgr_node->has_weak_ref = 1;52        break;53 ...54err_unlocked:55    trace_binder_ioctl_done(ret);56    return ret;57}

以上代码中,注释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

 1void binder_loop(struct binder_state *bs, binder_handler func) 2{ 3    int res; 4    struct binder_write_read bwr; 5    uint32_t readbuf[32]; 6 7    bwr.write_size = 0; 8    bwr.write_consumed = 0; 9    bwr.write_buffer = 0;1011    readbuf[0] = BC_ENTER_LOOPER;12    binder_write(bs, readbuf, sizeof(uint32_t));//11314    for (;;) {15        bwr.read_size = sizeof(readbuf);16        bwr.read_consumed = 0;17        bwr.read_buffer = (uintptr_t) readbuf;1819        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//22021        if (res 

以上代码中,注释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

 1int binder_write(struct binder_state *bs, void *data, size_t len) 2{ 3    struct binder_write_read bwr;//1 4    int res; 5 6    bwr.write_size = len; 7    bwr.write_consumed = 0; 8    bwr.write_buffer = (uintptr_t) data;//2 9    bwr.read_size = 0;10    bwr.read_consumed = 0;11    bwr.read_buffer = 0;12    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//313    if (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

 1static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 2{    3    ... 4    void __user *ubuf = (void __user *)arg; 5    ... 6    switch (cmd) { 7    case BINDER_WRITE_READ: { 8        struct binder_write_read bwr; 9        if (size != sizeof(struct binder_write_read)) {10            ret = -EINVAL;11            goto err;12        }13        if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {//114            ret = -EFAULT;15            goto err;16        }17        binder_debug(BINDER_DEBUG_READ_WRITE,18                 "binder: %d:%d write %ld at %08lx, read %ld at %08lx",19                 proc->pid, thread->pid, bwr.write_size, bwr.write_buffer,20                 bwr.read_size, bwr.read_buffer);2122        if (bwr.write_size > 0) {//223            ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);//324            trace_binder_write_done(ret);25            if (ret pid, thread->pid, bwr.write_consumed, bwr.write_size,36                 bwr.read_consumed, bwr.read_size);37        if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {//438            ret = -EFAULT;39            goto err;40        }41        break;42    }43   ...44    return ret;45}

以上代码中,注释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进阶三部曲 专门为应用开发进阶和面试而打造

android获取指针空间大小_Android进阶:Binder那么弱怎么面大厂?相关推荐

  1. android获取指针空间大小_腾讯笔试题:浅谈计算机中cpu位数和指针

    来一个腾讯笔试题 在刷题的时候看到了腾讯笔试题的这个问题 long a = (long)(((int *) 0) + 4);printf("%ld ",a); 请问输出 a 的值是 ...

  2. Android 进程 缓存服务,Android获取应用程序大小和缓存的实例代码

    info package com.qin.appsize; import android.content.Intent; import android.graphics.drawable.Drawab ...

  3. android 获取sd卡大小,Android 获取SD卡容量

    SD卡作为手机的扩展存储设备,在手机中充当硬盘角色,可以让我们手机存放更多的数据以及多媒体等大体积文件.因此查看SD卡的内存就跟我们查看硬盘的剩余空间一样,是我们经常操作的一件事,那么在Android ...

  4. Android 获取内存/存储空间大小

    Android 内存可用空间/总空间大小获取 import android.app.ActivityManager; ... public class memInfo {// 获得可用的内存publi ...

  5. 获取磁盘空间大小计算成看k,m,G

    2019独角兽企业重金招聘Python工程师标准>>> /**      * 获取硬盘空间      */     private static long getSdcSpace() ...

  6. android获取sd的大小,Android实现获取SD卡总容量,可用大小,机身内存总容量及可用大小的方法...

    本文实例讲述了Android实现获取SD卡总容量,可用大小,机身内存总容量及可用大小的方法.分享给大家供大家参考,具体如下: 可能有的同学不知道系统已经提供了获取获取SD卡总容量,可用大小,机身内存总 ...

  7. Android 获取手机内存大小

    背景 项目中图片拍照功能是调用系统相机,由于某些手机内存不足,经常调用系统相机后,项目app进程被系统杀掉了,所以采取的措施是:调用系统相机前先判断手机可用内存大小,当可用内存低于500M时,提示用户 ...

  8. Android Sdcard 可用空间大小

    2019独角兽企业重金招聘Python工程师标准>>> /*** 获取sdcard使用情况*/private void setSdcardInfo(){if(Environment. ...

  9. android获取整体存储空间大小,Android 获取剩余存储空间

    有时我们开Android项目开发时会用到文件存储或上传文件的一些操作,那么我们前提是要获取到该存储设备的大小,以方便于与我们需要操作的文件的大小做比较,如果操作的文件大小小于存储空间,那么就可以继续操 ...

最新文章

  1. 阿里二面:Redis 中的 AOF 文件太大了怎么办?
  2. java的输出流包括_【转】输入/输出流 - 深入理解Java中的流 (Stream)
  3. 第八章 Health Check
  4. 快速了解 UML 类图
  5. linux 删除mysql_MySQL— Linux下解压包方式安装
  6. vb.net label 不要自动换行_自动驾驶小车——(四)数据采集
  7. Nginx中break和last的区别
  8. 天平应什么放置_电子天平,你不得不知道的那些事!
  9. 城市轨道交通运营票务管理论文_城市轨道交通运营管理浅析
  10. vue下载导出Excel案例
  11. 2018java面试(1)- 自我介绍和项目介绍
  12. 分享几款免费的web打印控件
  13. (译)如何制作一个类似tiny wings的游戏:第二部分(完) - 子龙山人 ...
  14. 新项目六之集成新版友盟统计
  15. matlab热应力计算,不同温度下热应力的计算 - 仿真模拟 - 小木虫 - 学术 科研 互动社区...
  16. mysql中sql语句日期比较,mysql sql语句中 日期函数的使用
  17. 无UI自动安装Python解释器
  18. 如何发掘各种暴利的赚钱项目,如何知道别人在干什么赚钱
  19. “旁观者”给阿里未来发展“把的脉”
  20. 2021年安全生产模拟考试(全国特种作业操作证焊工作业-钎焊模拟考试题库二)安考星

热门文章

  1. powerdesigner连接mysql,并导出其数据模型的方法
  2. pytorch DistributedDataParallel提示未参与loss计算的变量错误
  3. Deep learning 学习开篇
  4. NLP之路-查看获取文本语料库
  5. 用phpcms如何将静态页面制作成企业网站(中)
  6. js 跳转传递汉字参数
  7. VC中char,TCHAR,WCHAR总结
  8. window php composer,Window下安装php composer
  9. 10天基于STM32F401RET6智能锁项目实战第1天(环境搭建和新建工程)
  10. 退出页面 数据保留_IOS保留数据换appid教程(副号暂时无法解锁登录看这里)