Binder(native层)

http://gityuan.com/2015/10/31/binder-prepare/

1. 注册服务##

  1. 从cpp开始分析(main_mediaserver.cpp)
int main(int argc __unused, char** argv)
{
    ...
    InitializeIcuOrDie();
    //获得ProcessState实例对象
    sp<ProcessState> proc(ProcessState::self());
    //获取ServiceManager实例对象
    sp<IServiceManager> sm = defaultServiceManager();
    AudioFlinger::instantiate();
    //多媒体服务 
    MediaPlayerService::instantiate();
    ResourceManagerService::instantiate();
    CameraService::instantiate();
    AudioPolicyService::instantiate();
    SoundTriggerHwService::instantiate();
    RadioService::instantiate();
    registerExtensions();
    //创建Binder线程,并加入线程池
    ProcessState::self()->startThreadPool();
    //当前线程加入到线程池 
    IPCThreadState::self()->joinThreadPool();
 }

可见主多媒体服务,在main方法中初始化了很多多媒体的服务,我们就挑一个去分析

void MediaPlayerService::instantiate() { 
           defaultServiceManager()->addService( String16("media.player"), new MediaPlayerService()); 
}
virtual status_t addService(const String16& name, const sp<IBinder>& service,
        bool allowIsolated)
{
    Parcel data, reply; //Parcel是数据通信包
    data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());   //写入RPC头信息
    data.writeString16(name);        // name为 "media.player"
    data.writeStrongBinder(service); // MediaPlayerService对象
    data.writeInt32(allowIsolated ? 1 : 0); // allowIsolated= false
    status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); 
    return err == NO_ERROR ? reply.readExceptionCode() : err;
}

很明先的ServiceManager的addService的方法是公用的,最主要的就是调用的
remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply)
remote方法返回的是BpBinder对象(IBinder的子类,和BBinder一样)
client端:BpBinder.transact()来发送事务请求;
server端:BBinder.onTransact()会接收到相应事务。

然后我们去看BpBinder的transact的方法

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

其实最终调用的就是IPCThreadState的transact方法,每一个线程都有一个IPCThreadState,而每一个IPCThreadState都会有mIn和mOut,这两个东西就是把相应的数据写入的Binder中和从Binder中读取。

然后可以在IPCThreadState的transact方法中

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck(); //数据错误检查
    flags |= TF_ACCEPT_FDS;
    ....
    if (err == NO_ERROR) {
        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) {
        //需要等待reply的场景
        if (reply) {
            //等待响应  
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }

    } else {
        //oneway,则不需要等待reply的场景
        err = waitForResponse(NULL, NULL);
    }
    return err;
}

主要方法就是writeTransactionData和waitForResponse这两个方法
前面的是将数据写到binder设备中去,而waitForResponse方法就是等待binder设备的数据回调。
而主要的和binder之间的交互就是定义在waitForResponse方法中的

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

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break; // 【见流程10】
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;

        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;

        case BR_ACQUIRE_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));
                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);  //【见流程11】
            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;
}

主要的就是BR_REPLY
我们看到while(1)就是一个死循环,不断的从mIn中去读response的反馈,,读到BR_REPLY之后,用binder_transaction_data类型去把反馈读出来,我们可以看while(1)里面err=talkWithDriver()这个方法是将数据写到mIn里面去的,也就是真正看到最后,重中之重就是这个跟老司机交流的方法。

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }

    binder_write_read bwr;

    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data();

    if (doReceive && needRead) {
        //接收数据缓冲区信息的填充。如果以后收到数据,就直接填在mIn中了。
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }

    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        //通过ioctl不停的读写操作,跟Binder Driver进行通信
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }

    } while (err == -EINTR);

    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);
        }
        return NO_ERROR;
    }
    return err;
}

最主要的方法就是如下这个循环:
do {
//通过ioctl不停的读写操作,跟Binder Driver进行通信
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
if (mProcess->mDriverFD <= 0) {
err = -EBADF;
}
} while (err == -EINTR);
ioctl方法会不断的去跟binder driver通信,然后将数据写入到binder_write_read中。

再回去看waitforResponse的default选项:
err = executeCommand(cmd);
也就是说除了
case BR_TRANSACTION_COMPLETE:
case BR_DEAD_REPLY:
case BR_FAILED_REPLY:
case BR_ACQUIRE_RESULT:
case BR_REPLY:
这几个类型之外的所有类型都走的是executeCommand方法
主要看BR_TRANSACTION方法

            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            if (result != NO_ERROR) break;

            Parcel buffer;
            buffer.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);

            const pid_t origPid = mCallingPid;
            const uid_t origUid = mCallingUid;
            const int32_t origStrictModePolicy = mStrictModePolicy;
            const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;

            mCallingPid = tr.sender_pid;
            mCallingUid = tr.sender_euid;
            mLastTransactionBinderFlags = tr.flags;

            int curPrio = getpriority(PRIO_PROCESS, mMyThreadId);
            if (gDisableBackgroundScheduling) {
                if (curPrio > ANDROID_PRIORITY_NORMAL) {
                    setpriority(PRIO_PROCESS, mMyThreadId, ANDROID_PRIORITY_NORMAL);
                }
            } else {
                if (curPrio >= ANDROID_PRIORITY_BACKGROUND) {
                    set_sched_policy(mMyThreadId, SP_BACKGROUND);
                }
            }

            Parcel reply;
            status_t error;
            // tr.cookie里存放的是BBinder,此处b是BBinder的实现子类
            if (tr.target.ptr) {
                sp<BBinder> b((BBinder*)tr.cookie);
                error = b->transact(tr.code, buffer, &reply, tr.flags); //

            } else {
                error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
            }

            if ((tr.flags & TF_ONE_WAY) == 0) {
                if (error < NO_ERROR) reply.setError(error);
                sendReply(reply, 0);
            }

            mCallingPid = origPid;
            mCallingUid = origUid;
            mStrictModePolicy = origStrictModePolicy;
            mLastTransactionBinderFlags = origTransactionBinderFlags;

主要的方法就是
sp<BBinder> b((BBinder*)tr.cookie);
error = b->transact(tr.code, buffer, &reply, tr.flags);

status_t BBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    data.setDataPosition(0);

    status_t err = NO_ERROR;
    switch (code) {
        case PING_TRANSACTION:
            reply->writeInt32(pingBinder());
            break;
        default:
            err = onTransact(code, data, reply, flags); 
            break;
    }

    if (reply != NULL) {
        reply->setDataPosition(0);
    }

    return err;
}
status_t BBinder::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/)
{
    switch (code) {
        case INTERFACE_TRANSACTION:
            reply->writeString16(getInterfaceDescriptor());
            return NO_ERROR;

        case DUMP_TRANSACTION: {
            int fd = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
               args.add(data.readString16());
            }
            return dump(fd, args);
        }

        case SYSPROPS_TRANSACTION: {
            report_sysprop_change();
            return NO_ERROR;
        }

        default:
            return UNKNOWN_TRANSACTION;
    }
}
请求流程图

MediaPlayeService发送注册请求给Binder设备(BC_TRANSACTION),Binder收到请求之后,回复BR_TRANSACTION_COMPLETE,然后Binder发送给ServiceManger,BR_TRANSACTION命令告诉他需要注册,然后SericeManager进行注册服务注册,之后返回BC_REPLY给Binder,Binder成功唤醒MediaPlayerService,然后告诉ServiceManger已经成功完成任务了,后者进入睡眠,然后告诉MediaPlayerServiceBR_REPLY,走完整个请求流程。

然后我们来看main方法的最后两行
//创建Binder线程,并加入线程池 ProcessState::self()->startThreadPool();
当前线程创建binder线程,并且加入到binder线程池
//当前线程加入到线程池 IPCThreadState::self()->joinThreadPool();
当前线程自己也加入binder线程池

2. 获取服务##

获取servicemanager之后就不断的循环的去获取名为media.service的binder服务

sp<IServiceManager> sm = defaultServiceManager(); //获取ServiceManager
sp<IBinder> binder;
do {
    //获取名为"media.player"的服务 【见流程2】
    binder = sm->getService(String16("media.player"));
    if (binder != 0) {
        break;
    }
    usleep(500000); // 0.5 s
} while (true);

而getservice方法则是做5次请求,每次间隔1秒,取不到就返回null

virtual sp<IBinder> getService(const String16& name) const
    {
        unsigned n;
        for (n = 0; n < 5; n++){
            sp<IBinder> svc = checkService(name); //【见流程3】
            if (svc != NULL) return svc;
            sleep(1);
        }
        return NULL;
    }

checkservice会去验证服务是否存在

virtual sp<IBinder> checkService( const String16& name) const
{
    Parcel data, reply;
    //写入RPC头
    data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
    //写入服务名
    data.writeString16(name);
    remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply); //【见流程4】
    return reply.readStrongBinder();
}

主要就是transact方法,和之前的注册服务一样的了。

然后我们来看看死亡通知

if (sDeathNotifier == NULL) {
    sDeathNotifier = new DeathNotifier(); //创建死亡通知对象
}

//将死亡通知连接到binder 【见流程14】
binder->linkToDeath(sDeathNotifier);
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);

将死亡通知和binder连接起来,死亡通知主要负责的东西就是Bp端(跟远程端打交道)需要去监听B端(跟本地端打交道)的相关服务的生死情况,在B端的服务线程退出之后,就会发出死亡通知,然后Bp端就会做出服务的清理工作。

如何去获取serviceManager

我们在注册和获取服务的过程中都调用了defaultServiceManager,那么到底是怎么去获得到的呢?

sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    {
        AutoMutex _l(gDefaultServiceManagerLock); //加锁
        while (gDefaultServiceManager == NULL) {
             //【见下文小节二,三,四】
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
            if (gDefaultServiceManager == NULL)
                sleep(1);
        }
    }
    return gDefaultServiceManager;
}

很显然,使用了单例模式去管理serviceManager,对单例进行加锁,然后去获取serviceManger,如果拿不到就停止1秒钟,因为可能存在serviceManager还没有准备好的情况。

然后我们一个一个看,ProcessState::self()方法,之前就看过,直接去获取到ProcessState的单例,每个线程只有一个,那到底做了什么呢?首先是一个很简单的单例

sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        return gProcess;
    }

    //实例化ProcessState 【见小节2.2】
    gProcess = new ProcessState;
    return gProcess;
}

然后我们看一下,new方法到底new出来的是什么东西

ProcessState::ProcessState()
    : mDriverFD(open_driver()) // 打开Binder驱动【见小节2.3】
    , 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) {
        //采用内存映射函数mmap,给binder分配一块虚拟地址空间,用来接收事务
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            close(mDriverFD); //没有足够空间分配给/dev/binder,则关闭驱动
            mDriverFD = -1;
        }
    }
}

打开binder驱动,我们看到由于是单例模式,每一次只会打开binder驱动一次,还有其中有两个参数,我们需要知道一下:
BINDER_VM_SIZE = (110241024) - (4096 *2), binder分配的默认内存大小为1M-8k。
DEFAULT_MAX_BINDER_THREADS = 15,binder默认的最大可并发访问的线程数为16

然后来看getContextObject方法,

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
    return getStrongProxyForHandle(0);  //【见小节3.2】
}

去获取handle为0的IBinder

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);
    //查找handle对应的资源项【见小节3.3】
    handle_entry* e = lookupHandleLocked(handle);

    if (e != NULL) {
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            if (handle == 0) {
                Parcel data;
                //通过ping操作测试binder是否准备就绪
                status_t status = IPCThreadState::self()->transact(
                        0, IBinder::PING_TRANSACTION, data, NULL, 0);
                if (status == DEAD_OBJECT)
                   return NULL;
            }
            //当handle值所对应的IBinder不存在或弱引用无效时,则创建BpBinder对象【见小节3.4】
            b = new BpBinder(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }
    return result;
}

我们看到先去获得handle对应的资源项,然后如果拿不到它的binder的话,就去创建BpBinder,对handle为0的情况还需要去判断binder是否已经准备就绪了,因为handle为0的情况,是要首次去打开binder,首次去初始化serviceManager。然后我们看一下new BpBinder方法是怎么实现的:

BpBinder::BpBinder(int32_t handle)
    : mHandle(handle)
    , mAlive(1)
    , mObitsSent(0)
    , mObituaries(NULL)
{
    extendObjectLifetime(OBJECT_LIFETIME_WEAK); //延长对象的生命时间
    IPCThreadState::self()->incWeakHandle(handle); //handle所对应的binder弱引用 + 1
}

然后interface_cast方法传入的参数其实就是最后的最后,new出来的BpBinder对象。
而interface_cast方法最后主要就是去创建BpServiceManager
其实说的最通俗一点就是首次去获取ServiceManger的时候系统会帮我们打开binder开关,位binder创建内存控件,并为我们新建BpBinder对象,然后将BpBinder包裹起来,生成新的BpServiceManager单例。

ServiceManager使如何启动的

上面我们看到已经获得到了ServiceManager了,那么我们来到main方法中去看启动的时候做了什么

int main(int argc, char **argv)
{
    struct binder_state *bs;
    //打开binder驱动,申请128k大小的内存空间 【见流程1】
    bs = binder_open(128*1024);
    if (!bs) {
        return -1;
    }

    //成为上下文管理者 【见流程2】
    if (binder_become_context_manager(bs)) {
        return -1;
    }

    selinux_enabled = is_selinux_enabled(); //判断selinux权限问题
    sehandle = selinux_android_service_context_handle();
    selinux_status_open(true);

    if (selinux_enabled > 0) {
        if (sehandle == NULL) {  //无法获取sehandle
            abort();
        }

        if (getcon(&service_manager_context) != 0) { //无法获取service_manager上下文
            abort();
        }
    }

    union selinux_callback cb;
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
    cb.func_log = selinux_log_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);

    //进入无限循环,处理client端发来的请求 【见流程5】
    binder_loop(bs, svcmgr_handler);

    return 0;
}

具体的时序图如下


Paste_Image.png

先是打开binder驱动的相关操作,binder_open()

struct binder_state *binder_open(size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;
    //请求了相应size的内存空间
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }

    //通过系统调用陷入内核,打开Binder设备驱动
    bs->fd = open("/dev/binder", O_RDWR);
    if (bs->fd < 0) {
        goto fail_open; // 无法打开binder设备
    }

     //通过系统调用,ioctl获取binder版本信息
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open; //内核空间与用户空间的binder不是同一版本
    }

    bs->mapsize = mapsize;
    //通过系统调用,mmap内存映射
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        goto fail_map; // binder设备内存无法映射
    }

    return bs;

fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}

分别是位binder请求内存空间,然后打开binder设备,再然后去获取binder 的版本信息,并且与binder建立起内存映射联系。
然后去成为上下文的管理者,然后通过binder_loop(),去不断的循环读写。

小结
ServiceManager启动流程:
打开binder驱动,并调用mmap()方法分配128k的内存映射空间:binder_open();
通知binder驱动使其成为守护进程:binder_become_context_manager();
验证selinux权限,判断进程是否有权注册或查看指定服务;
进入循环状态,等待Client端的请求:binder_loop()。

ServiceManger意义:
ServiceManger集中管理系统内的所有服务,通过权限控制进程是否有权注册服务;
ServiceManager能通过字符串名称来查找对应的Service,操作方便;
当Server进程异常退出,只需告知ServiceManager,每个Client通过查询ServiceManager可获取Server进程的情况,降低所有Client进程直接检测会导致负载过重。

Binder的核心方法

我们来一一看一下binder有哪些核心的方法
1.首先是binder的init方法:
主要任务是负责注册misc设备(理解为一种特殊的设备驱动),并且打开binder设备
2.binder的mmap方法:
主要负责在内存虚拟地址空间申请一块和用户虚拟地址一样大小的内存,再申请一个page大小的物理内存,分别映射到内核虚拟空间和用户虚拟空间。说白了就是实现了内核和用户的同步操作,感觉就好像是通过了binder访问了内核服务的样子。
3.binder的ioctl方法:
主要负责在两个进程之间收发IPC的数据和IPC reply的数据
4.binder的ioctl_write_read方法:
首先把用户空间数据拷贝到内核空间bwr;
当bwr写缓存中有数据,则执行binder写操作;当写失败,再将bwr数据写回用户空间,并退出;
当bwr读缓存中有数据,则执行binder读操作;当读失败,再将bwr数据写回用户空间,并退出;
最后把内核数据bwr拷贝到用户空间。
这个很直白,其实最后binder的进程通信很好的被诠释,就是先将用户空间数据拷贝到内核空间,然后进行读写,如果失败则将缓存中的内容写回到用户空间,并且退出。如果成功则将内核中的数据拷贝到用户空间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容