分享

Android之binder驱动分析[1]

 mandrave 2012-01-06
理解binder驱动最好有一个上层的概念,也就是对binder怎么用的有所了解。binder解决的是进程间通信,也能共享对象。IPC的过程是通过内核一个模块(或用户空间的守护进程),中转和翻译不同进程需要共享的对象,以取得所有进程似乎处于同一个名字空间的效果。比如A进程如果要使用B进程的服务,B进程首先注册此服务,A进程通过binder获取该服务的hanlde,用这个handle,A进程就可以使用该服务了。你可以把handle理解成地址。A进程使用B进程的服务还意味着二者遵循相同的协议,这个协议反映在代码上,就是二者要实现IBinder接口。

关于binder的IPC机制,这个链接必读,读此文档后,你能建立理解binder驱动所需要的抽象。http://www./~hackbod/openbinder/docs/html/BinderIPCMechanism.html

以下分析Binder驱动中主要的几个操作。

    * Open操作

记录进程的信息,并在/proc目录下生成一个直读文件项。注意,进程信息记录在file结构的private字段,意味着每个进程的这个值是独立的。

把进程信息proc加入全局的proc列表里,这样,binder就可以访问所有参与的进程了。

    * Mmap操作

确定大小,最大4MB。然后获得地址空间,并把此空间的地址记录在进程信息里(buffer)。

分配物理页面,记录下来(pages)。

把此buffer插入到进程信息的buffer列表中。

调用binder_update_page_range,把分配的物理页面和vm空间对应起来。

把此进程的buffer插入到进程信息中(binder_insert_free_buffer(proc, buffer))。

    * Ioctl操作

先调用binder_get_thread()找到这个线程的控制数据结构,找不到就创建一个新的。注意在Linux里,线程和进程是一回事,线程有自己的pid。

BINDER_WRITE_READ

这个是最核心的操作,是所有IPC的基础。实现是binder_thread_write()和binder_thread_read(),先写后读,就是发命令,然后读结果。

看binder_thread_write()函数,这个函数接收的一个参数是用户发来的数据。数据包括写缓冲和读缓冲两部分。写缓冲包括一些book-keeping命令(增加/减小引用计数),最后是一个需要响应的命令(IPC!),写缓冲的命令请看BinderDriverCommandProtocol枚举类型。读缓冲也是先包含一些book-keeping命令,最后要么是写缓冲中最后命令的结果,要么是另个嵌套命令,读缓冲的命令请看BinderDriverReturnProtocol枚举类型。

写方向的命令最重要的是BC_TRANSACTION和BC_REPLY,意思就是启动一个事务,然后获得结果。这两个命令后面跟的数据的数据结构由transaction_flags和binder_transaction_data定义。看一下数据结构的定义:

struct binder_transaction_data {
    union {
        size_t handle;
        void *ptr;
    } target;
    void *cookie;
    unsigned int code;
    unsigned int flags;
    pid_t sender_pid;
    uid_t sender_euid;
    size_t data_size;
    size_t offsets_size;
    union {
        struct {
        const void *buffer;
        const void *offsets;
        } ptr;
        uint8_t buf[8];
    } data;
};

其中的target的handle是要处理此事务的目标对象的句柄,根据此handle驱动可以找到应该由哪个进程处理此事务,并把此任务分发给一个线程,而那个线程也正在执行ioctl的BINDER_WRITE_READ操作,即正在等待一个请求。binder驱动把要做的事放在读缓冲里,返回给这个服务线程,后者执行的操作,如上述,遵循BinderDriverReturnProtocol。命令后面仍然是binder_transaction_data数据。这时的binder_transaction_data和发送方写缓冲的是相对应的。

处理请求的线程把数据交给合适的对象,该对象执行预定操作,然后把返回结果用binder_transaction_data结构封装,以写命令的方式传回给binder驱动,binder驱动把此数据放在一个读缓冲里,返回给正在等待结果的原进程(线程)。这样就完成了一次交易。

target的ptr字段是什么意思?为什么跟handle合用一个空间?ptr正是handle的对应物。对于请求方,使用handle来指出远程对象,对于响应方,使用ptr来寻址要执行需要动作的对象。所以handle和ptr是一个东西的两种表达。Handle和ptr之间的翻译关系正是binder驱动需要维护的。

这个映射关系也是进程间引用对象的基础,对一个对象的引用,在远程,是handle,在本地,是地址。

transaction的处理函数是binder_transaction(),这是个大函数。

这个函数分成两个部分:第一部分解析目标进程和目标线程;第二部分完成事务语义。第一部分又分两个子部分:A)此事务是reply;B)不是。A)情况要从事务栈取得目标线程;B)情况则只取得目标进程,因为此时不知道具体哪一个线程会处理此请求。

第二部分首先分配相应的事务数据结构,基本上是输入事务数据结构的克隆。填入一些已知或现成的数据,例如相关进程pid之类。接着开始处理数据部分的内容。此部分区分要处理的是binder对象传递(BINDER_TYPE_BINDER/BINDER_TYPE_WEAK_BINDER),引用(BINDER_TYPE_HANDLE/BINDER_TYPE_WEAK_HANDLE),还是文件(BINDER_TYPE_FD)。

如果是binder对象,那么相当于是在注册服务,此时将分配节点,生成handle,以备引用;如果是引用,则应该是客户在调用该引用所指向的服务,则找到代表此服务的节点,如果该节点的主人确实是目标进程,则增加该节点的计数;反之,尝试在目标进程上下文中找这个节点。如果是文件,则原handle是个文件描述符,根据此fd找到对应的文件(fget),再在目标进程中分配一fd(target_fd=task_get_unused_fd_flags();task_fd_install()),然后把这个fd赋值给返回的handle。

注意此处BINDER_TYPE_HANDLE与BINDER_TYPE_BINDER和handle与ptr的对应关系。

其它几个ioctl操作都简单了:

BINDER_SET_MAX_THREADS:设置进程最大线程数目,进程据此决定线程池的容量。

BINDER_SET_CONTEXT_MGR:ServiceManager设置自己作为context manager节点,凡为0的handle都是只这个master节点。

BINDER_THREAD_EXIT:删除一个线程的数据结构。

BINDER_VERSION:返回版本信息。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多