binder机制(上篇)

问题:binder到底是如何从代理对象找到其对应的binder实体的呢?要回答这个就必须要看Binder驱动如何工作的。

1.Binder设备文件的打开

一个进程在使用Binder机制进行通信之前,必须打开设备文件/dev/binder来获取一个文件描述符,然后才能通过这个文件描述符和Binder驱动进行交互,进程调用open函数打开/dev/binder的时候,binder_open会被调用,实现如下:

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
    . . . . . .
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);

    get_task_struct(current);
    proc->tsk = current;
    . . . . . .
    hlist_add_head(&proc->proc_node, &binder_procs);
    proc->pid = current->group_leader->pid;
    . . . . . .
    filp->private_data = proc;
    . . . . . .
}

来自网络
首先创建一个binder_proc结构体,接下来就进行proc的初始化工作,并将proc加入全局双向链表binder_procs当中,Binder驱动程序将所有打开设备文件/dev/binder的进程都加入到binder_procs当中,因此,通过遍历这个链表就可以知道系统当前有多少进程在使用Binder进程间通信机制。filp->private_data=proc;将新创建的proc保存在当前文件的private_data变量中,参数filep指向一个打开的文件结构体。进程以后调用binder_ioctl与驱动程序交互式,会将struct file结构体传过去,从file结构体的private_data字段就可以得到proc。

1.1 binder_proc介绍

struct binder_proc
{
    struct hlist_node proc_node;
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    int pid;
    . . . . . .
    . . . . . .
};

binder_proc用来描述正在使用binder机制的进程。上面也提到,每当进程打开/dev/binder时,binder驱动就会为其创建binder_proc
结构体。每一个使用Binder进程通信机制的进程都有一个binder线程池,用来出来通信请求,这个Binder线程池是由Binder驱动程序来维护的。成员变量threads是红黑树节点,它以线程ID作为关键字来组织一个进程中的binder线程池,进程可以调用ioctl函数将线程注册到binder驱动程序当中。
一个进程内部包含了一系列的Binder实体对象和Binder引用对象,进程使用三个红黑树描述。nodes组织binder实体对象,以binder实体对象的ptr作为关键字;refs_by_desc和refs_by_node用来组织Binder引用对象,前者以Binder引用对象的desc作为关键字,后者以node作为关键字。
来自网络
有了binder_ref和binder_node知识,我们就可以解释BpBinder到底如何和BBinder联系上的。OK我们已经可以更深入的说明Binder
句柄的作用了,比如进程1的BpBinder发起跨进程调用,向binder驱动传入自己记录的句柄值,binder驱动就会在进程1对应的binder_proc结构的引用树种查找和句柄值相等的binder_ref节点,一旦找到binder_ref节点,就可以通过该节点的node域找到对应的binder_node节点,这个目标binder_node当然是从属进程2的binder_proc啦,不过不要紧,因为binder_ref和binder_node都处于binder驱动的地址空间中,所以是可以用指针直接指向的。目标binder_node节点的cookie域,记录的其实是进程2中BBinder的地址,binder驱动只需把这个值反映给应用层,应用层就可以直接拿到BBinder了。这就是Binder完成精确打击的大体过程。如下图:
这里写图片描述

2. BpBinder和IPCThreadState

从名字上看,IPCThreadState是“和跨进程通信(IPC)相关的线程状态”。那么很显然,一个具有多个线程的进程里应该会有多个IPCThreadState对象了,只不过每个线程只需一个IPCThreadState对象而已。这有点儿“局部单例”的意思。所以,在实际的代码中,IPCThreadState对象是存放在线程的局部存储区(TLS)里的。
IPCThreadState类中含有一个成员变量mProcess,它指向一个ProcessState对象,从名字上看“进程状态”就知道它是进程相关,是进程范围内唯一的。ProcessState负责初始化Binder设备,即打开设备文件/dev/binder,以及将设备文件映射到进程的地址空间。Binder线程中的每个线程都可以通过它来和Binder驱动建立连接。

2.1 BpBinder的transact函数

每当我们利用BpBinder的transact函数发起一次跨进程事物时,其内部都是调用IPCThreadState对象的transact,如下:
status_t BpBinder::transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive)
    {
        status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }

    return DEAD_OBJECT;
}

当然进程中的BpBinder可能被多个线程使用,所以发起传输的IPCThreadState对象可能并不是同一个对象,不过没关系,因为这些ICPThreadState对象可能最终使用同一个ProcessState对象。

2.1.1

调用IPCThreadState的transact

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    . . . . . .
        // 把data数据整理进内部的mOut包中
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    . . . . . .

    if ((flags & TF_ONE_WAY) == 0)
    {
        . . . . . .
        if (reply)
        {
            err = waitForResponse(reply);
        }
        else
        {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        . . . . . .
    }
    else
    {
        err = waitForResponse(NULL, NULL);
    }

    return err;
}

IPCThreadState::transact()会先调用writeTransactionData()函数将data数据整理进内部的mOut包中,这个函数的代码如下:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
                                              int32_t handle, uint32_t code,
                                              const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;

    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    . . . . . .
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    . . . . . .

    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));

    return NO_ERROR;
}

真正完成跨进程事务的是waitForResponse函数,代码如下:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;

    while (1)
    {
        // talkWithDriver()内部会完成跨进程事务
        if ((err = talkWithDriver()) < NO_ERROR)
            break;

        // 事务的回复信息被记录在mIn中,所以需要进一步分析这个回复
        . . . . . .
        cmd = mIn.readInt32();
        . . . . . .
        switch (cmd)
        {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;

        case BR_DEAD_REPLY:
            err = DEAD_OBJECT;
            goto finish;

        case BR_FAILED_REPLY:
            err = FAILED_TRANSACTION;
            goto finish;
        . . . . . .
        . . . . . .
        default:
            // 注意这个executeCommand()噢,它会处理BR_TRANSACTION的。
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }

2.1.2 talkWithDriver

waitForResponse是通过talkWithDriver来和Binder驱动打交道的,最终会调用ioctl函数,因为ioctl函数在传递BINDER_WRITE_READ意义时,使用输入缓冲区和输出缓冲区,所以IPCThreadState有Parcel类型的成员变量,mIn、mOut,将mOut内容发出去,发送后的回复写入mIn。
talkWithDriver代码如下:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    . . . . . .
    binder_write_read bwr;

    . . . . . .
    bwr.write_size = outAvail;
    bwr.write_buffer = (long unsigned int)mOut.data();
    . . . . . .
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (long unsigned int)mIn.data();
    . . . . . .
    . . . . . .
    do
    {
        . . . . . .
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        . . . . . .
    } while (err == -EINTR);

    . . . . . .
    . . . . . .
    return err;
}

mIn、mOut会封装成binder_write_read结构,然后在传给ioctl函数。最关键的当然是ioctl了,此时使用的文件描述符就是ProcessState中记录的mDriverFD,说明是想binder驱动传递语义,至此应用程序通过BpBinder向远端传输的过程就清楚了,数据传送到Binder驱动,就看binder驱动做哪些动作,留到中篇解析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值