Mechanism of Android while Passing File Descriptor through Binder

来源:互联网 发布:知乎周刊第一本 编辑:程序博客网 时间:2024/05/16 11:05

Problem Description:

As we know, the file descriptor is private to each process, and even the same file descriptor does not mean the same opened file among different processes. But there are situations in which programs would pass file descriptors throught binder, in other words, pass fd through different processes, I’m intereted in the mechanism of the pass-thru process. And in addition, it seems there are some extra operations while passing binder objects through binder.

Starting with the real-life case

While preparing the sound record in Android, the application should set the output file for the storage of the sound record. But in Android 7.0, the file open operation is done in Java layer, and then file descriptor is passed to the Native layer, noting that they are within the same process, but after that the Native MediaRecorder would pass the file descriptor to the MediaPlayerService side through binder and it would be stored in StagefrightRecorder in mOutputFd. Here the passage through binder would change the process, so what had happened during the fd transfer.

status_t MediaRecorder::setOutputFile(int fd, int64_t offset, int64_t length){    ...    status_t ret = mMediaRecorder->setOutputFile(fd, offset, length);    ...    return ret;}

Note that mMediaRecorder is a class of sp<IMediaRecorder>, a pointer actually points to its derived class BpMediaRecorder, so the process goes on as follows

// @BpMediaRecorder    status_t setOutputFile(int fd, int64_t offset, int64_t length) {        ALOGV("setOutputFile(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length);        Parcel data, reply;        data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor());        data.writeFileDescriptor(fd);        data.writeInt64(offset);        data.writeInt64(length);        // remote() would return a BpBinder, which is the BpMediaRecorder        remote()->transact(SET_OUTPUT_FILE_FD, data, &reply);        return reply.readInt32();    }// Let's focus on data.writeFileDescriptor(fd)status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership){    flat_binder_object obj;    // tells the binder that the passing object is a fd    obj.type = BINDER_TYPE_FD;    // inform the binder to accept fd while processing    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;    obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */    obj.handle = fd;    obj.cookie = takeOwnership ? 1 : 0;    return writeObject(obj, true);}// Note the declaration of flat_binder_objectstruct flat_binder_object {  __u32 type;  __u32 flags;  union {    binder_uintptr_t binder;    __u32 handle;  };  binder_uintptr_t cookie;};// writing object to the Parcelstatus_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData){    const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;    const bool enoughObjects = mObjectsSize < mObjectsCapacity;    if (enoughData && enoughObjects) {restart_write:        // write the object to the data memory        *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;        // remember if it's a file descriptor        if (val.type == BINDER_TYPE_FD) {            if (!mAllowFds) {                // fail before modifying our object index                return FDS_NOT_ALLOWED;            }            mHasFds = mFdsKnown = true;        }        // Need to write meta-data?        if (nullMetaData || val.binder != 0) {            mObjects[mObjectsSize] = mDataPos;            acquire_object(ProcessState::self(), val, this, &mOpenAshmemSize);            mObjectsSize++;        }        return finishWrite(sizeof(flat_binder_object));    }    // grow data if not enough space    if (!enoughData) {        const status_t err = growData(sizeof(val));        if (err != NO_ERROR) return err;    }    if (!enoughObjects) {        size_t newSize = ((mObjectsSize+2)*3)/2;        if (newSize < mObjectsSize) return NO_MEMORY;   // overflow        binder_size_t* objects = (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t));        if (objects == NULL) return NO_MEMORY;        mObjects = objects;        mObjectsCapacity = newSize;    }    goto restart_write;}//update mDataPos and mDataSize if neededstatus_t Parcel::finishWrite(size_t len){    if (len > INT32_MAX) {        // don't accept size_t values which may have come from an        // inadvertent conversion from a negative int.        return BAD_VALUE;    }    mDataPos += len;    ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);    if (mDataPos > mDataSize) {        mDataSize = mDataPos;    }    return NO_ERROR;}

The above is just a process of bundling the parameters into a Parcel. We shall take notice that, while a Fd is found, obj.type := BINDER_TYPE_FD, and the whole Parcel would set the flags, mHasFds and mFdsKnown, to be true;

Then starts the data transfer process:

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;}

As for IPCThreadState,it is a key class while talking with binder, and it is thread specific.

IPCThreadState::IPCThreadState()    : mProcess(ProcessState::self()),      mMyThreadId(gettid()),      mStrictModePolicy(0),      mLastTransactionBinderFlags(0){    pthread_setspecific(gTLS, this);    clearCaller();    // vital member variables to talk with binder driver    mIn.setDataCapacity(256);    mOut.setDataCapacity(256);}sp<ProcessState> ProcessState::self(){    Mutex::Autolock _l(gProcessMutex);    if (gProcess != NULL) {        return gProcess;    }    gProcess = new ProcessState;    return gProcess;}ProcessState::ProcessState()    // open binder, set binder version, max thread number...    : mDriverFD(open_driver())    , mVMStart(MAP_FAILED)    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)    , mExecutingThreadsCount(0)    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)    , mStarvationStartTimeMs(0)    , mManagesContexts(false)    , mBinderContextCheckFunc(NULL)    , mBinderContextUserData(NULL)    , mThreadPoolStarted(false)    , mThreadPoolSeq(1){    if (mDriverFD >= 0) {        // mmap the binder, providing a chunk of virtual address space to receive transactions.        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;        }    }    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");}

In conclusion, ProcessState is process specific and each process can have no more than ProcessState , during the creation of IPCThreadState,

  1. First create ProcessState , if it does not exist, during the creating, binder driver would be opened and map the file to virtual memory;
  2. Also, set up thread specific storage for IPCThreadState, and set mIn & mOut, which are of the type Parcel
  3. mIn & mOut would alloc 256 bytes to mData, and set mDataSize = mDataPos = 0, mDataCapacity = 256;

Now, let’s go back to transact()

// parameters passed:// code := SET_OUTPUT_FILE_FD; data (just bundled); flags := 0;// status_t IPCThreadState::transact(int32_t handle,                                  uint32_t code, const Parcel& data,                                  Parcel* reply, uint32_t flags){    status_t err = data.errorCheck();    // orignal flags := 0    flags |= TF_ACCEPT_FDS;    IF_LOG_TRANSACTIONS() {        TextOutput::Bundle _b(alog);        alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand "            << handle << " / code " << TypeCode(code) << ": "            << indent << data << dedent << endl;    }    if (err == NO_ERROR) {        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),            (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");        // write the transaction data        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);    }    if (err != NO_ERROR) {        if (reply) reply->setError(err);        return (mLastError = err);    }    if ((flags & TF_ONE_WAY) == 0) {        #if 0        if (code == 4) { // relayout            ALOGI(">>>>>> CALLING transaction 4");        } else {            ALOGI(">>>>>> CALLING transaction %d", code);        }        #endif        if (reply) {            // generally the transact would come here            err = waitForResponse(reply);        } else {            Parcel fakeReply;            err = waitForResponse(&fakeReply);        }        #if 0        if (code == 4) { // relayout            ALOGI("<<<<<< RETURNING transaction 4");        } else {            ALOGI("<<<<<< RETURNING transaction %d", code);        }        #endif        IF_LOG_TRANSACTIONS() {            TextOutput::Bundle _b(alog);            alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "                << handle << ": ";            if (reply) alog << indent << *reply << dedent << endl;            else alog << "(none requested)" << endl;        }    } else {        err = waitForResponse(NULL, NULL);    }    return err;}

Normally, the transaction would undergo 2 steps:

  1. writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
  2. waitForResponse(NULL, NULL);
// cmd := BC_TRANSACTION, binderFlags := TF_ACCEPT_FDS, code := SET_OUTPUT_FILE_FD, statusBuffer := NULLstatus_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;    // transfer Parcel type to binder_transaction_data    tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */    tr.target.handle = handle;    tr.code = code;    tr.flags = binderFlags;    tr.cookie = 0;    tr.sender_pid = 0;    tr.sender_euid = 0;    const status_t err = data.errorCheck();    if (err == NO_ERROR) {        tr.data_size = data.ipcDataSize();        // stores the data in Parcel        tr.data.ptr.buffer = data.ipcData();        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);        tr.data.ptr.offsets = data.ipcObjects();    } else if (statusBuffer) {        tr.flags |= TF_STATUS_CODE;        *statusBuffer = err;        tr.data_size = sizeof(status_t);        tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);        tr.offsets_size = 0;        tr.data.ptr.offsets = 0;    } else {        return (mLastError = err);    }    // write the command as well as binder_transaction_data to mOut    mOut.writeInt32(cmd);    mOut.write(&tr, sizeof(tr));    return NO_ERROR;}

The above code segment transform the data (Parcel) to binder_transaction_data, which would in turn become another object for mOut (Parcel), and a cmd is also wrapped within mOut;

// reply is not NULL, while acquireResult := NULLstatus_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){    uint32_t cmd;    int32_t err;    while (1) {        if ((err=talkWithDriver()) < NO_ERROR) break;        err = mIn.errorCheck();        if (err < NO_ERROR) break;        if (mIn.dataAvail() == 0) continue;        cmd = (uint32_t)mIn.readInt32();        IF_LOG_COMMANDS() {            alog << "Processing waitForResponse Command: "                << getReturnString(cmd) << endl;        }        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;        case BR_ACQUIRE_RESULT:            {                ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT");                const int32_t result = mIn.readInt32();                if (!acquireResult) continue;                *acquireResult = result ? NO_ERROR : INVALID_OPERATION;            }            goto finish;        case BR_REPLY:            {                binder_transaction_data tr;                err = mIn.read(&tr, sizeof(tr));                ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");                if (err != NO_ERROR) goto finish;                if (reply) {                    if ((tr.flags & TF_STATUS_CODE) == 0) {                        reply->ipcSetDataReference(                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),                            tr.data_size,                            reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),                            tr.offsets_size/sizeof(binder_size_t),                            freeBuffer, this);                    } else {                        err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);                        freeBuffer(NULL,                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),                            tr.data_size,                            reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),                            tr.offsets_size/sizeof(binder_size_t), this);                    }                } else {                    freeBuffer(NULL,                        reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),                        tr.data_size,                        reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),                        tr.offsets_size/sizeof(binder_size_t), this);                    continue;                }            }            goto finish;        default:            err = executeCommand(cmd);            if (err != NO_ERROR) goto finish;            break;        }    }finish:    if (err != NO_ERROR) {        if (acquireResult) *acquireResult = err;        if (reply) reply->setError(err);        mLastError = err;    }    return err;}// the key process of communicating with binder driver;// doReceive := truestatus_t IPCThreadState::talkWithDriver(bool doReceive){    if (mProcess->mDriverFD <= 0) {        return -EBADF;    }    binder_write_read bwr;    // in the beginning, mIn.dataPosition == mIn.dataSize()    const bool needRead = mIn.dataPosition() >= mIn.dataSize();    // outAvail := mOut.dataSize    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;    bwr.write_size = outAvail;    bwr.write_buffer = (uintptr_t)mOut.data();    // This is what we'll read.    if (doReceive && needRead) {        bwr.read_size = mIn.dataCapacity();        bwr.read_buffer = (uintptr_t)mIn.data();    } else {        bwr.read_size = 0;        bwr.read_buffer = 0;    }    // Return immediately if there is nothing to do.    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;    bwr.write_consumed = 0;    bwr.read_consumed = 0;    status_t err;    do {        IF_LOG_COMMANDS() {            alog << "About to read/write, write size = " << mOut.dataSize() << endl;        }#if defined(__ANDROID__)        // in fact bwr would pass the mIn & mOut data buffer address, which would be written        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)            err = NO_ERROR;        else            err = -errno;#else        err = INVALID_OPERATION;#endif        if (mProcess->mDriverFD <= 0) {            err = -EBADF;        }        IF_LOG_COMMANDS() {            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;        }    } while (err == -EINTR);    IF_LOG_COMMANDS() {        alog << "Our err: " << (void*)(intptr_t)err << ", write consumed: "            << bwr.write_consumed << " (of " << mOut.dataSize()                        << "), read consumed: " << bwr.read_consumed << endl;    }    if (err >= NO_ERROR) {        if (bwr.write_consumed > 0) {            if (bwr.write_consumed < mOut.dataSize())                mOut.remove(0, bwr.write_consumed);            else                mOut.setDataSize(0);        }        if (bwr.read_consumed > 0) {            mIn.setDataSize(bwr.read_consumed);            mIn.setDataPosition(0);        }        IF_LOG_COMMANDS() {            TextOutput::Bundle _b(alog);            alog << "Remaining data size: " << mOut.dataSize() << endl;            alog << "Received commands from driver: " << indent;            const void* cmds = mIn.data();            const void* end = mIn.data() + mIn.dataSize();            alog << HexDump(cmds, mIn.dataSize()) << endl;            while (cmds < end) cmds = printReturnCommand(alog, cmds);            alog << dedent;        }        return NO_ERROR;    }    return err;}

​ So, right here, after calling ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr), the process would enter kernel space, and let’s examine the kernel source code, which lays under the path: kernel/linux-4.4/drivers/android/binder.c, and we’ll see that the ioctl() is defined as binder_ioctl():

static const struct file_operations binder_fops = {    .owner = THIS_MODULE,    .poll = binder_poll,    // here defines the ioctl    .unlocked_ioctl = binder_ioctl,    .compat_ioctl = binder_ioctl,    .mmap = binder_mmap,    .open = binder_open,    .flush = binder_flush,    .release = binder_release,};// notice the cmd passed was BINDER_WRITE_READ, other switch branches are omitted herestatic 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;    unsigned int size = _IOC_SIZE(cmd);    void __user *ubuf = (void __user *)arg;    /*pr_info("binder_ioctl: %d:%d %x %lx\n",            proc->pid, current->pid, cmd, arg);*/    trace_binder_ioctl(cmd, arg);    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);    if (ret)        goto err_unlocked;    binder_lock(__func__);    thread = binder_get_thread(proc);    if (thread == NULL) {        ret = -ENOMEM;        goto err;    }    switch (cmd) {    case BINDER_WRITE_READ:        ret = binder_ioctl_write_read(filp, cmd, arg, thread);        if (ret)            goto err;        break;        ...    }    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);    if (ret && ret != -ERESTARTSYS)        pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);err_unlocked:    trace_binder_ioctl_done(ret);    return ret;}// here cmd is unfolded and cmd := BC_TRANSACTIOM, other branches omitted;static int binder_thread_write(struct binder_proc *proc,            struct binder_thread *thread,            binder_uintptr_t binder_buffer, size_t size,            binder_size_t *consumed){    uint32_t cmd;    void __user *buffer = (void __user *)(uintptr_t)binder_buffer;    void __user *ptr = buffer + *consumed;    void __user *end = buffer + size;    while (ptr < end && thread->return_error == BR_OK) {        // in fact, the following is just a macro, not a function, so the cmd would be assigned value        // which is BC_TRANSACTION        if (get_user(cmd, (uint32_t __user *)ptr))            return -EFAULT;        ptr += sizeof(uint32_t);        trace_binder_command(cmd);        if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {            binder_stats.bc[_IOC_NR(cmd)]++;            proc->stats.bc[_IOC_NR(cmd)]++;            thread->stats.bc[_IOC_NR(cmd)]++;        }        switch (cmd) {        ...        case BC_TRANSACTION:        case BC_REPLY: {            struct binder_transaction_data tr;            // copy the data from ptr to tr            if (copy_from_user(&tr, ptr, sizeof(tr)))                return -EFAULT;            ptr += sizeof(tr);            binder_transaction(proc, thread, &tr, cmd == BC_REPLY);            break;        }        ...        }        *consumed = ptr - buffer;    }    return 0;}// params: reply := 0 (false)static void binder_transaction(struct binder_proc *proc,                   struct binder_thread *thread,                   struct binder_transaction_data *tr, int reply){    struct binder_transaction *t;    struct binder_work *tcomplete;    binder_size_t *offp, *off_end;    binder_size_t off_min;    // this is the target process, so binder does not aimless copy send the transaction, but would    // specify the target process, and do the transfer    struct binder_proc *target_proc;    struct binder_thread *target_thread = NULL;    struct binder_node *target_node = NULL;    struct list_head *target_list;    wait_queue_head_t *target_wait;    struct binder_transaction *in_reply_to = NULL;    struct binder_transaction_log_entry *e;    uint32_t return_error;    e = binder_transaction_log_add(&binder_transaction_log);    e->call_type = reply ? 2 : !!(tr->flags & TF_ONE_WAY);    e->from_proc = proc->pid;    e->from_thread = thread->pid;    e->target_handle = tr->target.handle;    e->data_size = tr->data_size;    e->offsets_size = tr->offsets_size;    ...     // allocate space for b->buffer;    t->buffer = binder_alloc_buf(target_proc, tr->data_size,        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));    if (t->buffer == NULL) {        return_error = BR_FAILED_REPLY;        goto err_binder_alloc_buf_failed;    }    t->buffer->allow_user_free = 0;    t->buffer->debug_id = t->debug_id;    t->buffer->transaction = t;    t->buffer->target_node = target_node;    trace_binder_transaction_alloc_buf(t->buffer);    if (target_node)        binder_inc_node(target_node, 1, 0, NULL);    offp = (binder_size_t *)(t->buffer->data +                 ALIGN(tr->data_size, sizeof(void *)));    // here the data is copied to t->buffer->data    if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)               tr->data.ptr.buffer, tr->data_size)) {        binder_user_error("%d:%d got transaction with invalid data ptr\n",                proc->pid, thread->pid);        return_error = BR_FAILED_REPLY;        goto err_copy_data_failed;    }    if (copy_from_user(offp, (const void __user *)(uintptr_t)               tr->data.ptr.offsets, tr->offsets_size)) {        binder_user_error("%d:%d got transaction with invalid offsets ptr\n",                proc->pid, thread->pid);        return_error = BR_FAILED_REPLY;        goto err_copy_data_failed;    }    if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) {        binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n",                proc->pid, thread->pid, (u64)tr->offsets_size);        return_error = BR_FAILED_REPLY;        goto err_bad_offset;    }    off_end = (void *)offp + tr->offsets_size;    off_min = 0;    for (; offp < off_end; offp++) {        struct flat_binder_object *fp;        if (*offp > t->buffer->data_size - sizeof(*fp) ||            *offp < off_min ||            t->buffer->data_size < sizeof(*fp) ||            !IS_ALIGNED(*offp, sizeof(u32))) {            binder_user_error("%d:%d got transaction with invalid offset, %lld (min %lld, max %lld)\n",                      proc->pid, thread->pid, (u64)*offp,                      (u64)off_min,                      (u64)(t->buffer->data_size -                      sizeof(*fp)));            return_error = BR_FAILED_REPLY;            goto err_bad_offset;        }        // again the data to be processed is passed to fp        fp = (struct flat_binder_object *)(t->buffer->data + *offp);        off_min = *offp + sizeof(struct flat_binder_object);        // now the fp->type := BINDER_TYPE_FD        switch (fp->type) {        ...        case BINDER_TYPE_FD: {            int target_fd;            struct file *file;            if (reply) {                if (!(in_reply_to->flags & TF_ACCEPT_FDS)) {                    binder_user_error("%d:%d got reply with fd, %d, but target does not allow fds\n",                        proc->pid, thread->pid, fp->handle);                    return_error = BR_FAILED_REPLY;                    goto err_fd_not_allowed;                }            } else if (!target_node->accept_fds) {                binder_user_error("%d:%d got transaction with fd, %d, but target does not allow fds\n",                    proc->pid, thread->pid, fp->handle);                return_error = BR_FAILED_REPLY;                goto err_fd_not_allowed;            }            // get a struct file from the kernel maintained file table with the given fd;            file = fget(fp->handle);            if (file == NULL) {                binder_user_error("%d:%d got transaction with invalid fd, %d\n",                    proc->pid, thread->pid, fp->handle);                return_error = BR_FAILED_REPLY;                goto err_fget_failed;            }            // transfer file from current process to target process;            if (security_binder_transfer_file(proc->tsk, target_proc->tsk, file) < 0) {                fput(file);                return_error = BR_FAILED_REPLY;                goto err_get_unused_fd_failed;            }            // it would allocate a new fd for the struct file in target process            target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);            if (target_fd < 0) {                fput(file);                return_error = BR_FAILED_REPLY;                goto err_get_unused_fd_failed;            }            // install the allocated fd for the target process            task_fd_install(target_proc, target_fd, file);            trace_binder_transaction_fd(t, fp->handle, target_fd);            binder_debug(BINDER_DEBUG_TRANSACTION,                     "        fd %d -> %d\n", fp->handle, target_fd);            /* TODO: fput? */            // the handle now is the fd for the target process, and now the changed struct is t            // for fp is a pointer to the data section of t;            fp->handle = target_fd;        } break;        ...        }    }    if (reply) {        BUG_ON(t->buffer->async_transaction != 0);        binder_pop_transaction(target_thread, in_reply_to);    } else if (!(t->flags & TF_ONE_WAY)) {        BUG_ON(t->buffer->async_transaction != 0);        t->need_reply = 1;        t->from_parent = thread->transaction_stack;        thread->transaction_stack = t;    } else {        BUG_ON(target_node == NULL);        BUG_ON(t->buffer->async_transaction != 1);        if (target_node->has_async_transaction) {            target_list = &target_node->async_todo;            target_wait = NULL;        } else            target_node->has_async_transaction = 1;    }    // the work type now is BINDER_WORK_TRANSACTION    t->work.type = BINDER_WORK_TRANSACTION;    // add the work entry to the target to-do list    list_add_tail(&t->work.entry, target_list);    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;    list_add_tail(&tcomplete->entry, &thread->todo);    if (target_wait)        wake_up_interruptible(target_wait);    return;    ...}

Here comes the core part of transferring, when the function binder_transaction() found the transaction type is a file descriptor, it fetches a struct file from the file table maintained by kernel with the given fd. The following fget() would do the task.

struct file *fget(unsigned int fd){    struct file *file;    struct files_struct *files = current->files;    rcu_read_lock();    file = fcheck_files(files, fd);    if (file) {        /* File object ref couldn't be taken */        if (file->f_mode & FMODE_PATH ||            !atomic_long_inc_not_zero(&file->f_count))            file = NULL;    }    rcu_read_unlock();    return file;}#define rcu_dereference_check_fdtable(files, fdtfd) \    (rcu_dereference_check((fdtfd), \                   lockdep_is_held(&(files)->file_lock) || \                   //increment the file count                   atomic_read(&(files)->count) == 1 || \                   rcu_my_thread_group_empty()))#define files_fdtable(files) \        (rcu_dereference_check_fdtable((files), (files)->fdt))struct file_operations;struct vfsmount;struct dentry;extern void __init files_defer_init(void);static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd){    struct file * file = NULL;    struct fdtable *fdt = files_fdtable(files);    if (fd < fdt->max_fds)        file = rcu_dereference_check_fdtable(files, fdt->fd[fd]);    return file;}

Back to binder_transaction(), a summary of the fd transaction process:

  1. fget() to get the struct file from file table with the fd in current process, and of cource increment the file count;
  2. transfer the struct file to the target process;
  3. allocate and install a fd for the struct file for target process;
  4. finally, put the transferred data, wrapped in ‘t’, to the tail of todo list for target process

And at last, the target process would read from the todo list entry and fetch the file descriptor as normal one.

阅读全文
0 0
原创粉丝点击