首页 > 学院 > 开发设计 > 正文

binder初探

2019-11-07 22:49:15
字体:
来源:转载
供稿:网友
一、引言

前面android进程的创建流程(http://blog.csdn.net/newhope1106/article/details/54932800)有提到过binder,先来看看这部分的代码。

    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)            throws ZygoteInit.MethodAndArgsCaller {        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");        redirectLogStreams();        commonInit();        nativeZygoteInit();//binder相关        applicationInit(targetSdkVersion, argv, classLoader);    }

当zygote通过fork创建了一个新的进程之后,java层会调用handleChildPRoc方法,然后调用到了zygoteInit方法,接着调用nativeZygoteInit方法,这个是native方法,该方法是在AndroidRuntime.cpp中注册的。
static JNINativeMethod gMethods[] = {    { "nativeFinishInit", "()V",        (void*) com_android_internal_os_RuntimeInit_nativeFinishInit },    { "nativeZygoteInit", "()V",        (void*) com_android_internal_os_RuntimeInit_nativeZygoteInit },    { "nativeSetExitWithoutCleanup", "(Z)V",        (void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },};也就是会调用com_android_internal_os_RuntimeInit_nativeZygoteInit函数,

static void com_android_internal_os_RuntimeInit_nativeZygoteInit(JNIEnv* env, jobject clazz){    gCurRuntime->onZygoteInit();}

调用到app_main.cpp中的onZygoteInit方法
    virtual void onZygoteInit()    {        sp<ProcessState> proc = ProcessState::self();        ALOGV("App process: starting thread pool./n");        proc->startThreadPool();    }这里ProcessState是单例模式创建,一个进程只有一个,
sp<ProcessState> ProcessState::self(){    Mutex::Autolock _l(gProcessMutex);    if (gProcess != NULL) {        return gProcess;    }    gProcess = new ProcessState;    return gProcess;}看看ProcessState构造函数
ProcessState::ProcessState()    : mDriverFD(open_driver())//注意这个调用,open_driver    , mVMStart(MAP_FAILED)    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)    , mExecutingThreadsCount(0)    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)    , mManagesContexts(false)    , mBinderContextCheckFunc(NULL)    , mBinderContextUserData(NULL)    , mThreadPoolStarted(false)    , mThreadPoolSeq(1){    if (mDriverFD >= 0) {        // XXX Ideally, there should be a specific define for whether we        // have mmap (or whether we could possibly have the kernel module        // availabla).#if !defined(HAVE_WIN32_ipC)        // mmap the binder, providing a chunk of virtual address space to receive transactions.        //这里是在binder驱动中创建用户虚拟空间和同大小的内核虚拟空间,分配一个物理空间,并映射到用户空间和内核空间中        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);        if (mVMStart == MAP_FAILED) {            // *sigh*            ALOGE("Using /dev/binder failed: unable to mmap transaction memory./n");            close(mDriverFD);            mDriverFD = -1;        }#else        mDriverFD = -1;#endif    }    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");}上面有一个比较核心的函数open_driver,
static int open_driver(){    int fd = open("/dev/binder", O_RDWR);    if (fd >= 0) {        fcntl(fd, F_SETFD, FD_CLOEXEC);        int vers = 0;        status_t result = ioctl(fd, BINDER_VERSION, &vers);        if (result == -1) {            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));            close(fd);            fd = -1;        }        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {            ALOGE("Binder driver protocol does not match user space protocol!");            close(fd);            fd = -1;        }        size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);        if (result == -1) {            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));        }    } else {        ALOGW("Opening '/dev/binder' failed: %s/n", strerror(errno));    }    return fd;}上面主要是打开binder驱动,然后检查binder版本号,接着设置最大binder线程数,然后调用proc->startThreadPool(),

void ProcessState::startThreadPool(){    AutoMutex _l(mLock);    if (!mThreadPoolStarted) {        mThreadPoolStarted = true;        spawnPooledThread(true);    }}void ProcessState::spawnPooledThread(bool isMain){    if (mThreadPoolStarted) {        String8 name = makeBinderThreadName();        ALOGV("Spawning new pooled thread, name=%s/n", name.string());        sp<Thread> t = new PoolThread(isMain);        t->run(name.string());    }}

这里是创建一个线程PoolThread,调用其run方法,看看这个线程的定义
{public:    PoolThread(bool isMain)        : mIsMain(isMain)    {    }    protected:    virtual bool threadLoop()    {        IPCThreadState::self()->joinThreadPool(mIsMain);        return false;    }        const bool mIsMain;};threadLoop()函数,和java线程中的run方法一样,线程开始之后,会被调用,IPCThreadState是线程单例的,每个线程只有一个,接着调用joinThreadPool函数,然后调用talkWithDriver等等。本篇主要是粗略介绍一下binder几个核心部分。二、binder简单描述1.背景作为andorid系统的核心之一,binder被选择成为android特有的IPC方式,主要是基于其安全性和性能两个方面,安全性是因为本身实现了身份验证,性能是因为其只需要一次拷贝即可。2.过程描述

上述是binder工作的基本原理图,Service Manager在开机的时候,通过service命令启动,向binder驱动申请成为上下文管理者,Binder作为C/S结构,服务端要被客户端知道,首先需要向Service Manager进行注册,Service Manager会把服务端的名称和句柄保存在链表当中,但是保存之前,需要对其权限进行检查,才可以进行注册,客户端通过服务端名称向Service Manager查找服务端,Service Manager通过名称找到服务端,并返回给客户端,于是客户端可以和服务端进行通信了,这里讲解都非常粗糙,很多细节并没有将清楚,后续会详细讲解,这里所有的通信都离不开binder驱动,首先来了解一下binder驱动,涉及代码非常少,因为了解binder驱动首先需要知道binder驱动用到的数据结构,这里先不详细说明,把整个过程过一遍。三、binder驱动binder驱动在内核空间中,作为内核模块加载进来的,首先binder驱动把自己注册成为misc设备,文件目录会创建/dev/binder节点,然后提供了操作binder设备方法,这里初始化调用是通过binder_init函数调用的。
static int __init binder_init(void){    ...    // 注册misc设备    ret = misc_register(&binder_miscdev);    ...    return ret;}static struct miscdevice binder_miscdev = {    .minor = MISC_DYNAMIC_MINOR, //次设备号 动态分配    .name = "binder",     //设备名    .fops = &binder_fops  //设备的文件操作结构,这是file_Operations结构};static const struct file_operations binder_fops = {    .owner = THIS_MODULE,    .poll = binder_poll,    .unlocked_ioctl = binder_ioctl,    .compat_ioctl = binder_ioctl,    .mmap = binder_mmap,    .open = binder_open,    .flush = binder_flush,    .release = binder_release,};上面提供了一些方法,主要介绍核心的方法,binder_open、binder_mmap、binder_ioctl方法,这些都是用户空间要调用的。1.用户空间和内核空间的调用是通过系统调用实现,从而陷入内核态2.binder_openbinder_open主要作用是为每个进程创建一个binder_proc对象,并把它加入到binder_procs哈希链表当中去。然后会初始化TODO列表和wait队列等等
static int binder_open(struct inode *nodp, struct file *filp){    struct binder_proc *proc; // binder进程     proc = kzalloc(sizeof(*proc), GFP_KERNEL); // 为binder_proc结构体在分配kernel内存空间    if (proc == NULL)        return -ENOMEM;    get_task_struct(current);    proc->tsk = current;   //将当前线程的task保存到binder进程的tsk    INIT_LIST_HEAD(&proc->todo); //初始化todo列表    init_waitqueue_head(&proc->wait); //初始化wait队列    proc->default_priority = task_nice(current);  //将当前进程的nice值转换为进程优先级    binder_lock(__func__);   //同步锁,因为binder支持多线程访问    binder_stats_created(BINDER_STAT_PROC); //BINDER_PROC对象创建数加1    hlist_add_head(&proc->proc_node, &binder_procs); //将proc_node节点添加到binder_procs为表头的队列    proc->pid = current->group_leader->pid;    INIT_LIST_HEAD(&proc->delivered_death);    filp->private_data = proc;       //file文件指针的private_data变量指向binder_proc数据    binder_unlock(__func__); //释放同步锁    return 0;}

3.binder_mmapbinder_mmap主要功能是根据用户空间调用分配一块和用户虚拟空间大小一致的内核空间(不大于4M),并映射到同一块物理空间(一次拷贝的秘密),binder_proc中会记录内核虚拟空间的起始地址和内核空间到进程虚拟地址的偏移地址,这样保证内核空间buffer和用户空间buffer同步,然后把分配的虚拟内存加入到binder_proc的buffer中去4.binder_ioctl这个是用得最多的核心方法了,binder_ioctl的作用是负责进程间收发IPC数据和IPC reply数据
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){    int ret;    struct binder_proc *proc = filp->private_data;    struct binder_thread *thread;  // binder线程    unsigned int size = _IOC_SIZE(cmd);    void __user *ubuf = (void __user *)arg;    //进入休眠状态,直到中断唤醒    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);    if (ret)        goto err_unlocked;    binder_lock(__func__);    //获取binder_thread    thread = binder_get_thread(proc);    if (thread == NULL) {        ret = -ENOMEM;        goto err;    }    switch (cmd) {    case BINDER_WRITE_READ:  //进行binder的读写操作        ret = binder_ioctl_write_read(filp, cmd, arg, thread);         if (ret)            goto err;        break;    case BINDER_SET_MAX_THREADS: //设置binder最大支持的线程数        if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {            ret = -EINVAL;            goto err;        }        break;    case BINDER_SET_CONTEXT_MGR: //成为binder的上下文管理者,也就是ServiceManager成为守护进程        ret = binder_ioctl_set_ctx_mgr(filp);        if (ret)            goto err;        break;    case BINDER_THREAD_EXIT:   //当binder线程退出,释放binder线程        binder_free_thread(proc, thread);        thread = NULL;        break;    case BINDER_VERSION: {  //获取binder的版本号        struct binder_version __user *ver = ubuf;        if (size != sizeof(struct binder_version)) {            ret = -EINVAL;            goto err;        }        if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,                 &ver->protocol_version)) {            ret = -EINVAL;            goto err;        }        break;    }    default:        ret = -EINVAL;        goto err;    }    ret = 0;err:    if (thread)        thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;    binder_unlock(__func__);    wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);err_unlocked:    trace_binder_ioctl_done(ret);    return ret;}首先获取binder_proc中对应的binder_thread,如果不存在,则创建一个新的binder_thread并做初始化操作,添加到binder_proc中,上面命令中比较重要的是BINDER_SET_CONTEXT_MGR和BINDER_WRITE_READ(1)BINDER_SET_CONTEXT_MGR上面提到Service Manager向binder驱动申请成为上下文管理者就是调用的这个命令,它会创建一个全局的binder_proc节点binder_context_mgr_node,句柄handle为0,服务端和客户端都可以通过handle为0直接获取到SM(2)BINDER_WRITE_READ这个命令是数据接收和发送的核心,涉及到两个重要的函数,binder_thread_write和binder_thread_read。(a)binder_thread_write解析命令,处理请求,拷贝用户数据到内核空间,调用binder_transact,根据handle找到binder_ref,根据binder_ref找到目标binder_node,也就找到了binder_proc,创建binder_transaction节点插入到目标binder_proc的TODO链表并唤醒目标进程(b)binder_thread_read处理响应优先处理binder_thread的TODO链表,否则处理binder_proc的TODO链表中的内容,将命令写入用户态,binder_transaction_data也拷贝到用户态,这里只记录了数据的地址,并不是数据的拷贝,移除binder_transaction节点,binder_transaction_data有一个cookie成员变量记录binder实体的地址,对应的也就是BBinder,因为用户态就是根据这个BBinder来调用transact来处理的,通过这个cookie就可以明确是哪个binder实体了,如果不携带ONEWAY信息,就需要发送结果,客户端也会阻塞等待四、Service ManagerService Manager作为开机启动的服务,运行在用户空间,自己实现了和binder驱动交互的功能1.通过binder_open申请128k内存空间2.发送binder_become_context_manager命令向binder驱动申请成为上下文管理者3.binder_loop无限循环,调用ioctl获取客户端(客户端和服务端都是SM的客户端)数据,并解析处理4.核心函数是svcmgr_handler,处理SVC_MGR_ADD_SERVICE命令注册服务,处理SVC_MGR_GET_SERVICE命令获取服务五、服务端和客户端1.服务端向Service Manager注册,binder驱动会创建一个binder_node节点,分配一个最小未分配handle,并和服务名一起传给Service Manager,Service Manager会将服务名和handle保存在链表当中,binder驱动也会在Service Manager的binder_proc节点当中创建一个binder_ref指向这个binder_node,handle指向这个binder_ref2.客户端客户端通过服务名向Service Manager获取对应的服务,Service Manager通过服务名找到handle,然后binder驱动通过handle找到binder_ref,又通过binder_ref找到binder_node,并且在客户端进程创建binder_ref指向这个服务的binder_node,同时也会创建一个最小未分配handle以上仅仅只是对binder的几个核心的部分作简单描述,后续文章将详细讲解。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表