查看原文
其他

Binder这么弱还赶来面头条?

刘望舒 刘望舒 2022-06-29
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 media
10    onrestart restart surfaceflinger
11    onrestart restart inputflinger
12    onrestart restart drm
13    onrestart restart cameraserver
14    onrestart restart keystore
15    onrestart restart gatekeeperd
16    writepid /dev/cpuset/system-background/tasks
17    shutdown critical
18}

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);//2
13    ...
14    if (binder_become_context_manager(bs)) {//3
15        ALOGE("cannot become context manager (%s)\n", strerror(errno));
16        return -1;
17    }
18    ...
19    if (getcon(&service_manager_context) != 0) {
20        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
21        abort();
22    }
23    binder_loop(bs, svcmgr_handler);//4
24
25    return 0;
26}

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

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

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    }
11
12    bs->fd = open(driver, O_RDWR | O_CLOEXEC);//1
13    if (bs->fd < 0) {
14        fprintf(stderr,"binder: cannot open %s (%s)\n",
15                driver, strerror(errno));
16        goto fail_open;
17    }
18    //获取Binder的version
19    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
20        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {//2
21        fprintf(stderr,
22                "binder: kernel driver version (%d) differs from user space version (%d)\n",
23                vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);
24        goto fail_open;
25    }
26
27    bs->mapsize = mapsize;
28    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);//3
29    if (bs->mapped == MAP_FAILED) {
30        fprintf(stderr,"binder: cannot map device (%s)\n",
31                strerror(errno));
32        goto fail_map;
33    }
34    return bs;
35
36fail_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\n",
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__);
17
18    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;//3
23//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);
9
10    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
11    if (ret)
12        goto err_unlocked;
13
14    binder_lock(__func__);
15    thread = binder_get_thread(proc);//2
16    if (thread == NULL) {
17        ret = -ENOMEM;
18        goto err;
19    }
20
21    switch (cmd) {
22    ...
23    case BINDER_SET_CONTEXT_MGR:
24        if (binder_context_mgr_node != NULL) {//3
25            printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
26            ret = -EBUSY;
27            goto err;
28        }
29        ret = security_binder_set_context_mgr(proc->tsk);
30        if (ret < 0)
31            goto err;
32        if (binder_context_mgr_uid != -1) {//4
33            if (binder_context_mgr_uid != current->cred->euid) {//5
34                printk(KERN_ERR "binder: BINDER_SET_"
35                       "CONTEXT_MGR bad uid %d != %d\n",
36                       current->cred->euid,
37                       binder_context_mgr_uid);
38                ret = -EPERM;
39                goto err;
40            }
41        } else
42            binder_context_mgr_uid = current->cred->euid;//6
43        binder_context_mgr_node = binder_new_node(proc, NULLNULL);//7
44        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;
10
11    readbuf[0] = BC_ENTER_LOOPER;
12    binder_write(bs, readbuf, sizeof(uint32_t));//1
13
14    for (;;) {
15        bwr.read_size = sizeof(readbuf);
16        bwr.read_consumed = 0;
17        bwr.read_buffer = (uintptr_t) readbuf;
18
19        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//2
20
21        if (res < 0) {
22            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
23            break;
24        }
25
26        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);//3
27        if (res == 0) {
28            ALOGE("binder_loop: unexpected reply?!\n");
29            break;
30        }
31        if (res < 0) {
32            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
33            break;
34        }
35    }
36}

以上代码中,注释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);//3
13    if (res < 0) {
14        fprintf(stderr,"binder_write: ioctl failed (%s)\n",
15                strerror(errno));
16    }
17    return res;
18}

以上代码中,注释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))) {//1
14            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\n",
19                 proc->pid, thread->pid, bwr.write_size, bwr.write_buffer,
20                 bwr.read_size, bwr.read_buffer);
21
22        if (bwr.write_size > 0) {//2
23            ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);//3
24            trace_binder_write_done(ret);
25            if (ret < 0) {
26                bwr.read_consumed = 0;
27                if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
28                    ret = -EFAULT;
29                goto err;
30            }
31        }
32        ...
33        binder_debug(BINDER_DEBUG_READ_WRITE,
34                 "binder: %d:%d wrote %ld of %ld, read return %ld of %ld\n",
35                 proc->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))) {//4
38            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进阶指北》


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存