Parcel 从java到driver

791 阅读18分钟
原文链接: zhuanlan.zhihu.com
之所以想写一篇关于parcel的文章,主要这个东西是贯穿了整个binder通信的实体。最核心的原因是想研究一下binder leak的root cause,为什么有些时候parcel没有被回收就会造成binder内存泄漏,然后在长时间运行过后,binder通讯机制就因为binder的内存泄漏导致无法使用。

parcel看上去很小,但是其内部的东西还是挺复杂的。一开始我对其的理解仅仅是一个用来高密度存放数据的东西,现在想想,其实它算是一种通讯机制。

1.1 java端

parcel的本质还是在native层实现的,所以作为一个壳子,java的Parcel会使用一个long型变量保存底层的native对象的pointer。Parcel在内部维护了一个对象池,使用对象池的目的估计都是为了防止大批量生产和回收对象触发gc造成内存抖动的现象产生,为性能着想。

维护两个对象数组,一个对象数组中的parcel对象的生命周期是由其自身管控,另外一个数组只是作为一个holer持有者而已:


/**
     * Retrieve a new Parcel object from the pool.从对象池中去捞数据
     */
    public static Parcel obtain() {
        final Parcel[] pool = sOwnedPool;//sOwnedPool就是指该parcel持有的对象池,由其自身来管控生命周期
        synchronized (pool) {
            Parcel p;
            for (int i=0; i<POOL_SIZE; i++) {
                p = pool[i];
                if (p != null) {
                    pool[i] = null;
                    if (DEBUG_RECYCLE) {
                        p.mStack = new RuntimeException();
                    }
                    return p;
                }
            }
        }
        return new Parcel(0);
    }
这段obtain的代码看上去还比较奇怪,最后一句new parcel是只有当池子中任何一个对象都没有的情况下才会去new,但是奇怪的地方就是new了之后没有显示地加入池子中(猜测加池子的操作是放在了构造函数中),类似的对象池使用可以参考Message的实现
/**
     * Put a Parcel object back into the pool.  You must not touch
     * the object after this call.归还对象到内存池中,归还对象的时候才是加入到对象池中的时机
     */
    public final void recycle() {
        if (DEBUG_RECYCLE) mStack = null;
        freeBuffer();

        final Parcel[] pool;
        if (mOwnsNativeParcelObject) {//先判断这个java对象是否是拥有这个native对象的,根据是否拥有,分别给出两个不同pool
            pool = sOwnedPool;
        } else {
            mNativePtr = 0;
            pool = sHolderPool;
        }

        synchronized (pool) {
            for (int i=0; i<POOL_SIZE; i++) {
                if (pool[i] == null) {
                    pool[i] = this;
                    return;
                }
            }
        }
    }
接下来转到native对象的构造中去,构造函数的入参是native层的对象的指针:
private Parcel(long nativePtr) {
        if (DEBUG_RECYCLE) {
            mStack = new RuntimeException();
        }
        //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
        init(nativePtr);
    }

如果nativePtr非0,则代表native的parcel已经存在,只需要将native的ptr赋值即可;如果入参为0,即代表需要在native层创建parcel对象

private voidinit(longnativePtr) {
        if (nativePtr != 0) {
            mNativePtr = nativePtr;
            mOwnsNativeParcelObject = false;//这个flag代表此java parcel是去管理native的parcel的生命周期
        } else {
            mNativePtr = nativeCreate();
            mOwnsNativeParcelObject = true;
        }
    }

进入nativeCreate:

static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
    Parcel* parcel = new Parcel();
    return reinterpret_cast<jlong>(parcel);
}

这里就new了一个native的Parcel

java端Parcel总结,java端的主要提供的功能:

1、对象池的管理

2、提供对上层应用的读写接口


1.2 C++端

Parcel的native实现是在frameworks/native/binder下的,其属于binder lib的一部分

由于跟实现相关,所以相对比较复杂,还是从其产生和消灭以及读写操作来认识这个类

其构造和析构如下

Parcel::Parcel()
{
    LOG_ALLOC("Parcel %p: constructing", this);
    initState();//初始化状态就做了一些成员变量的赋值而已
}

Parcel::~Parcel()
{
    freeDataNoInit();
    LOG_ALLOC("Parcel %p: destroyed", this);
}

这里就存在一点点奇怪的地方,就是parcel并没有分配内存的操作,主要就是针对成员变量mData这块可读写的buffer的来源,猜测是一种lazy模式把,需要用到时才去做分配操作

Parcel存在几个成员变量:

uint8_t*            mData;
size_tmDataSize;//size应该是最大使用到的内存的位置
size_tmDataCapacity;//一共分配的内存的边界
mutablesize_tmDataPos;//pos应该是指当前读写的位置。可以任意挪动

那关注一个最简单的writeInt32的操作:

writeInt32-->writeAligned大多数write的操作都会跑到writeAligned这里

status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));

    if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
        *reinterpret_cast<T*>(mData+mDataPos) = val;//赋值copy
        returnfinishWrite(sizeof(val));
    }

    status_terr = growData(sizeof(val));//如果数据buffer不够,就需要grow数据大小
    if (err == NO_ERROR) goto restart_write;
    returnerr;
}

关注这个grow,需要思考的一点是,增大数据buffer如何保证不会踩到其他内存导致错误(就是通过分配新内存,丢弃老内存来做到这一点)

status_tParcel::growData(size_tlen)//传入的参数是需要增加的大小
{
    if (len > INT32_MAX) {
        // don't accept size_t values which may have come from an
        // inadvertent conversion from a negative int.
        returnBAD_VALUE;
    }

    size_t newSize = ((mDataSize+len)*3)/2;//每次内存不够扩张内存大小就按照这种比例来扩大
    return (newSize <= mDataSize)
            ? (status_t) NO_MEMORY
            : continueWrite(newSize);
}

这里的continueWrite是扩展内存的关键

by the way,经过别人的博客的提示,也明白了Parcel的在实际使用中的一个场景:

就是在IPCThreadState的构造中

IPCThreadState::IPCThreadState()
    : mProcess(ProcessState::self()),
      mMyThreadId(gettid()),
      mStrictModePolicy(0),
      mLastTransactionBinderFlags(0)
{
    pthread_setspecific(gTLS, this);
    clearCaller();
    mIn.setDataCapacity(256);//mIn和mOut都是parcel类型,代表了该binder接受线程使用的parcel,调用了设置data的容量
    mOut.setDataCapacity(256);
}

从code可以看出setDataCapacity也会去call到continueWrite。这里continueWrite的入参感觉是总的数据的大小。

status_tParcel::setDataCapacity(size_tsize)
{
    if (size > INT32_MAX) {
        // don't accept size_t values which may have come from an
        // inadvertent conversion from a negative int.
        returnBAD_VALUE;
    }

    if (size > mDataCapacity) return continueWrite(size);
    returnNO_ERROR;
}

那么现在主要关注点就跑到continueWrite函数中,这个函数略长同时很多功能函数都会call到这里,可见其功能的重要性,首先我们需要明白continueWrite的入参是期望的新的parcel的buffer的长度,这个参数的值就比较没有约束了,可大可小,甚至可以设置为0来free掉。

截取部分code,针对第一次分配内存的操作:

// This is the first data.  Easy!
        uint8_t* data = (uint8_t*)malloc(desired);//其实还是通过malloc进行在heap进行分配
        if (!data) {
           mError = NO_MEMORY;
            returnNO_MEMORY;
        }

        if(!(mDataCapacity == 0 && mObjects == NULL
             && mObjectsCapacity == 0)) {
            ALOGE("continueWrite: %zu/%p/%zu/%zu", mDataCapacity, mObjects, mObjectsCapacity, desired);
        }

        LOG_ALLOC("Parcel %p: allocating with %zu capacity", this, desired);
        pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
        gParcelGlobalAllocSize += desired;
        gParcelGlobalAllocCount++;
        pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);

        mData = data;
        mDataSize = mDataPos = 0;//pos和size都先归零
        ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize);
        ALOGV("continueWrite Setting data pos of %p to %zu", this, mDataPos);
        mDataCapacity = desired;

也就说parcel的native部分占用的内存也就是在heap中

--------------------------------在进入driver之前,还需要明白两个使用场景-----------------------

就是主动发数据和被动接数据的大致流程

主动发送:找到BpBinder的transact函数

160 status_t BpBinder::transact(
161     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
162 {
163     // Once a binder has died, it will never come back to life.
164     if (mAlive) {
165         status_t status = IPCThreadState::self()->transact(
166             mHandle, code, data, reply, flags);
167         if (status == DEAD_OBJECT) mAlive = 0;
168         return status;
169     }
170 
171     return DEAD_OBJECT;
172 }

IPCThreadState中本质是调用此接口,BC_TRANSACTION中的bc是binder command的意思,是交给binder driver识别,code并没有被包进parcel中,是IOCTL的参数而已,说明其应该只是给binder driver做判断,并非用来做IPC传输的。

writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL)

其实现:

925 status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
 926     int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
 927 {
 928     binder_transaction_data tr;//重新捏了一个binder_transaction_data的数据,然后把code和parcel的内容解包丢进去
 929 
 930     tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
 931     tr.target.handle = handle;
 932     tr.code = code;
 933     tr.flags = binderFlags;
 934     tr.cookie = 0;
 935     tr.sender_pid = 0;
 936     tr.sender_euid = 0;
 937 
 938     const status_t err = data.errorCheck();
 939     if (err == NO_ERROR) {
 940         tr.data_size = data.ipcDataSize();//parcel有一系列ipc开头的接口用于传输
 941         tr.data.ptr.buffer = data.ipcData();//这里填入的是用户空间中数据的地址
 942         tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
 943         tr.data.ptr.offsets = data.ipcObjects();
 944     } else if (statusBuffer) {
 945         tr.flags |= TF_STATUS_CODE;
 946         *statusBuffer = err;
 947         tr.data_size = sizeof(status_t);
 948         tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
 949         tr.offsets_size = 0;
 950         tr.data.ptr.offsets = 0;
 951     } else {
 952         return (mLastError = err);
 953     }
 954 
 955     mOut.writeInt32(cmd);
//mOut也是一个parcel
 956     mOut.write(&tr, sizeof(tr));
//填入此binder_transaction_data进入parcel
 957 
 958     return NO_ERROR;
 959 }

填入后,等到调用talkwithdriver就能与driver进行通信,通过ioctl做数据通信。

这里传给driver的数据结构就构成下面一种模式:

cmd1 tr1 | cmd2 tr2 | cmd3 tr3 | ......


被动接收:

被动接收就是直接通过talkwithdriver就能将该进程需要接收到的东西,就把mIn填充好了,就是收到的东西了。

--------------------------------------------------------------------------------------------------------------------

1.3 Driver端

driver端需要从talkwithdriver讲起

与driver进行通讯的时候需要有特殊的数据结构:binder_write_read,需要将mIn和mOut都变成此数据结构才能跟driver进行通信,主要是将mIn和mOut的data的地址和长度都交给driver,也就是用户空间的地址。很显然driver会去做用户空间和内核空间的数据拷贝操作。

刨去其它无关的逻辑,talkWithDriver最重要的一句是:

ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)
然后就关注driver的ioctl实现了,总观binder的driver的实现,最核心的函数是binder_transaction(),但是还是先一步步来,这段code会比较好读和理解,我对driver有一定认识,但并非平时的工作内容,所以还是会比较生疏一点,driver主要就是为设备文件的读写提供实现体,比如open read write以及ioctl这种

既然talkWithDriver是通过ioctl来进行的,那首先在driver中寻找ioctl的实现

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

这里函数指针中跟ioctl相关的两个都指向了binder_ioctl,那么binder_ioctl就肯定是我们对binder设备文件进行ioctl操作的实现体了

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
从前面可知这里的cmd就是BINDER_WRITE_READ,函数体中主要是switch case对不同的cmd做处理
switch (cmd) {
	case BINDER_WRITE_READ:
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
binder_ioctl_write_read函数中截取主要逻辑:
{
......
	struct binder_write_read bwr;//新建一个bwr变量,该变量主要用于在内核空间保存用户空间中数据的指针
......
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {//从用户空间将数据copy到内核空间,copy的内容就是需要进行IPC的数据在用户空间中的指针,后续会继续根据这些user space中的指针再继续调用copy操作
		ret = -EFAULT;
		goto out;
	}
......
        if (bwr.write_size > 0) {//有需要往driver中写入的内容
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
......
}

将数据完成了从用户空间到内核空间的转换后,使用binder_thread_write来进行下一步的传递

这里会对user空间传递进来的数据进行解析根据cmd做switch case,如之前的BC_TRANSACTION,这类BC开头的cmd一般都是在driver中做解析(主要要区别开来ioctl的code)

这里考虑一个程序既是一个client同时也作为一个server,那就有可能产生下面的情况:

case BC_TRANSACTION:
case BC_REPLY: {
			struct binder_transaction_data tr;

			if (copy_from_user(&tr, ptr, sizeof(tr)))
				return -EFAULT;
			ptr += sizeof(tr);
			binder_transaction(proc, thread, &tr, cmd == BC_REPLY);//这里就跑到了driver中最重要的函数
			break;

		}

到此再往下trace其实就不是很好看懂了,可以先参考一下binder driver的整体设计结构以及各个击破其中的一些比较难理解的点,来帮助后续的阅读

用一张图总结一下这个传输的流程:

先提几个问题,可能会帮助到增强读code的目的性:

1、binder driver是如何辨别pid的?

2、内核空间中的copy过来的存储是如何分配的?(这个可能会涉及到binder leak相关的东西)

3、rb树是怎么在driver中实现的以及为什么使用rb树?

4、是如何提供debug的相关接口功能的?

5、IPCProcessState为何要用mmap把/dev/binder映射出来?

如果能回答清楚这几个问题,基本上就能对binder有比较彻底的认识了

这里推荐一篇好文,后续我会基于他的基础上做一些总结和提炼

Android Binder机制学习总结(二)-Driver部分

这里看一下binder driver中个人认为最为核心,作为枢纽的一个结构体:

struct binder_proc {
	struct hlist_node proc_node;//用于串联所有的binder proc
	struct rb_root threads;//一个进程中所有的线程
	struct rb_root nodes;
	struct rb_root refs_by_desc;
	struct rb_root refs_by_node;
	int pid;//打开binder文件的进程的pid
	struct vm_area_struct *vma;//针对mmap操作填充的虚拟地址空间
	struct mm_struct *vma_vm_mm;
	struct task_struct *tsk;//该进程的进程描述符
	struct files_struct *files;
	struct hlist_node deferred_work_node;
	int deferred_work;
	void *buffer;//这个成员及下面的一系列是为该进程在内核空间创建的一块用于存储传输数据的buffer的首地址
	ptrdiff_t user_buffer_offset;
        //这里可以看到buffer存在两种组织方式,链表形式和红黑树
	struct list_head buffers;//链表头,用于指示所有buffer的链表
	struct rb_root free_buffers;//集中了空闲buffer的红黑树
	struct rb_root allocated_buffers;//集中了已经被分配了的buffer红黑树
	size_t free_async_space;

	struct page **pages;
	size_t buffer_size;
	uint32_t buffer_free;
	struct list_head todo;//待执行任务的链表头
	wait_queue_head_t wait;
	struct binder_stats stats;
	struct list_head delivered_death;
	int max_threads;//该进程支持的最大线程数量
	int requested_threads;
	int requested_threads_started;
	int ready_threads;
	long default_priority;
	struct dentry *debugfs_entry;
};

为什么这个结构体是核心,原因有二:

1、各个进程需要使用binder作为IPC的时候,在open binder设备文件的时候,都会在内核空间中创建一个这样的数据结构,也就是说,这个binder proc和使用binder作为IPC的进程是一一对应的,作为用户进程在binder driver中的代表

2、在这个结构体中有大量的rb_root(红黑树) hlist_node(哈希表)这类内核的构建数据结构的结构体,也就说明了binder proc是组织binder中各类数据结构的入口,树和链表成员比较多,说明一个binder proc是被串在了很多数据结构中。


明白了binder proc作为核心数据,那么就将其作为突破的点来愉快的阅读code吧

open操作:为一个进程在binder driver中创建一个代表

这里的flip会作为参数传递到之后的所有跟文件io相关的调用中去的,保证所有文件操作都能够那得到该进程对应binder proc结构
static int binder_open(struct inode *nodp, struct file *filp)
{
	struct binder_proc *proc;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",
		     current->group_leader->pid, current->pid);
        //为这个proc分配内存
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	if (proc == NULL)
		return -ENOMEM;
	get_task_struct(current);
	proc->tsk = current;//填充当前进程的进程描述符
	INIT_LIST_HEAD(&proc->todo);
	init_waitqueue_head(&proc->wait);
	proc->default_priority = task_nice(current);//填充优先级

	binder_lock(__func__);

	binder_stats_created(BINDER_STAT_PROC);
        /*外部构建结构,头插法将此binder proc丢进hash表中*/
	hlist_add_head(&proc->proc_node, &binder_procs);
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	filp->private_data = proc;//将binder proc丢进filp的private_data中,借此可以将binder_proc传递给其他的io文件操作,可见其枢纽作用

	binder_unlock(__func__);

	if (binder_debugfs_dir_entry_proc) {
		char strbuf[11];

		snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
		proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,
			binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
	}

	return 0;
}

也就是说,在open的时候,创建一个binder_proc然后初始化其中跟此进程相关的成员变量,然后将其丢进整体的维护各个进程的binder_proc hash表中

这里还存在一个binder driver设计不是很合理的地方,就是光open的话基本还不能用,必须要有mmap操作才会在kernel空间中分配buffer等实际用于存储传输数据的内存

这个在ProcessState的构造函数中就已经说明了:

ProcessState::ProcessState()
     : mDriverFD(open_driver())
......
 {
     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;
         }
     }
......
 }

mmap:mmap的大原则就是将用户的空间映射到内核中文件的地址,这里应该就是将起映射到binder driver创建的一块用户做数据传输使用的内核buffer对应的物理地址上

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
//vma代表user空间中的虚拟内存地址空间
//area代表内核空间中的虚拟内存地址空间
	int ret;
	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;

	if (proc->tsk != current)
		return -EINVAL;
        //只允许申请4M的内存,那么哈哈,要是不释放buffer的话就会耗尽内存,导致binder失效,由于buffer是存在单独的数据结构表述,且由树和链表两种结构在维护,因此只要查查释放buffer的操作就能反推出binder leak的原因了
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE,
		     "binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
		     proc->pid, vma->vm_start, vma->vm_end,
		     (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
		     (unsigned long)pgprot_val(vma->vm_page_prot));

	if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
		ret = -EPERM;
		failure_string = "bad vm_flags";
		goto err_bad_arg;
	}
	vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

	mutex_lock(&binder_mmap_lock);
	if (proc->buffer) {
		ret = -EBUSY;
		failure_string = "already mapped";
		goto err_already_mapped;
	}

	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
	if (area == NULL) {
		ret = -ENOMEM;
		failure_string = "get_vm_area";
		goto err_get_vm_area_failed;
	}
	proc->buffer = area->addr;
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
	mutex_unlock(&binder_mmap_lock);

#ifdef CONFIG_CPU_CACHE_VIPT
	if (cache_is_vipt_aliasing()) {
		while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {
			pr_info("binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);
			vma->vm_start += PAGE_SIZE;
		}
	}
#endif
	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
	if (proc->pages == NULL) {
		ret = -ENOMEM;
		failure_string = "alloc page array";
		goto err_alloc_pages_failed;
	}
	proc->buffer_size = vma->vm_end - vma->vm_start;//内核中分配的内存的数量

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;
/*这个函数将user space和kernel space都映射到同一块物理内存中*/
	if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
		ret = -ENOMEM;
		failure_string = "alloc small buf";
		goto err_alloc_small_buf_failed;
	}
//从这里开始就在填充跟内存相关的结构
	buffer = proc->buffer;
	INIT_LIST_HEAD(&proc->buffers);
        //插入这个链表的是vm_struct的entry
	list_add(&buffer->entry, &proc->buffers);
	buffer->free = 1;
	binder_insert_free_buffer(proc, buffer);
	proc->free_async_space = proc->buffer_size / 2;
	barrier();
	proc->files = get_files_struct(current);
	proc->vma = vma;
	proc->vma_vm_mm = vma->vm_mm;

	/*pr_info("binder_mmap: %d %lx-%lx maps %p\n",
		 proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
	return 0;

err_alloc_small_buf_failed:
	kfree(proc->pages);
	proc->pages = NULL;
err_alloc_pages_failed:
	mutex_lock(&binder_mmap_lock);
	vfree(proc->buffer);
	proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
	mutex_unlock(&binder_mmap_lock);
err_bad_arg:
	pr_err("binder_mmap: %d %lx-%lx %s failed %d\n",
	       proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
	return ret;
}

到这里大致了解了binder在IPC过程中使用的内存的由来了。

但数据结构还肯定存在许多疑问,下面就从一些binder的日常入手,来脑补出binder全部的使用场景,进而更好地理解工作机理

先从servicemanager开始,撇开其他操作

bs = binder_open(128*1024);

struct binder_state *binder_open(size_t mapsize) {
......
bs->fd = open("/dev/binder", O_RDWR);
......
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
......
}

service manager跟其他进程套路都一样

然后执行了一个:

binder_become_context_manager(bs)

int binder_become_context_manager(struct binder_state *bs)
 {
     return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
 }

这里就调用了IOCTL,对应的command是BINDER_SET_CONTEXT_MGR

对应到driver中的处理函数是:

static int binder_ioctl_set_ctx_mgr(struct file *filp)
{
	int ret = 0;
	struct binder_proc *proc = filp->private_data;
	kuid_t curr_euid = current_euid();

	if (binder_context_mgr_node != NULL) {
		pr_err("BINDER_SET_CONTEXT_MGR already set\n");
		ret = -EBUSY;
		goto out;
	}
//检查1:
	ret = security_binder_set_context_mgr(proc->tsk);
	if (ret < 0)
		goto out;
//检查2:
	if (uid_valid(binder_context_mgr_uid)) {
		if (!uid_eq(binder_context_mgr_uid, curr_euid)) {
			pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
			       from_kuid(&init_user_ns, curr_euid),
			       from_kuid(&init_user_ns,
					binder_context_mgr_uid));
			ret = -EPERM;
			goto out;
		}
	} else {
		binder_context_mgr_uid = curr_euid;
	}
//构造一个作为context manager的binder node
//这里就引入了一个新的数据结构binder node,是仅次于binder proc的数据结构

	binder_context_mgr_node = binder_new_node(proc, 0, 0);
	if (binder_context_mgr_node == NULL) {
		ret = -ENOMEM;
		goto out;
}	}
	binder_context_mgr_node->local_weak_refs++;
	binder_context_mgr_node->local_strong_refs++;
	binder_context_mgr_node->has_strong_ref = 1;
	binder_context_mgr_node->has_weak_ref = 1;
out:
	return ret;

在driver中新建一个binder_node都会通过调用binder_new_node调用,这里的binder_node是一个进程可以拥有多个该实体,对应于user space中的一个BBinder实体。举个例子:system server中有ams、pms等各种service,其进程在binder的driver中就应该有多个这样的binder_node存在;那类似的binder_ref就对应于BpBinder,而且binder_ref和binder_node之间肯定存在一定的对应关系

这里的ptr参数就应该是binder的号码:

static struct binder_node *binder_new_node(struct binder_proc *proc,
					   binder_uintptr_t ptr,
					   binder_uintptr_t cookie)
{
	struct rb_node **p = &proc->nodes.rb_node;
	struct rb_node *parent = NULL;
	struct binder_node *node;

	while (*p) {
		parent = *p;
		node = rb_entry(parent, struct binder_node, rb_node);

		if (ptr < node->ptr)
			p = &(*p)->rb_left;
		else if (ptr > node->ptr)
			p = &(*p)->rb_right;
		else
			return NULL;
	}

	node = kzalloc(sizeof(*node), GFP_KERNEL);
	if (node == NULL)
		return NULL;
	binder_stats_created(BINDER_STAT_NODE);
	rb_link_node(&node->rb_node, parent, p);
	rb_insert_color(&node->rb_node, &proc->nodes);
	node->debug_id = ++binder_last_id;
	node->proc = proc;
	node->ptr = ptr;
	node->cookie = cookie;
	node->work.type = BINDER_WORK_NODE;
	INIT_LIST_HEAD(&node->work.entry);
	INIT_LIST_HEAD(&node->async_todo);
	binder_debug(BINDER_DEBUG_INTERNAL_REFS,
		     "%d:%d node %d u%016llx c%016llx created\n",
		     proc->pid, current->pid, node->debug_id,
		     (u64)node->ptr, (u64)node->cookie);
	return node;

}

这里比较头痛的是命令数量有点多,包括Binder driver的ioctl的命令以及Binder driver内部的BC_开头的命令:在继续往下之前,先对这些内容进行总结:

BINDER_WRITE_READ(数据读写,其实就是将用户空间的数据读取进内核空间,然后再将里面的命令+数据提取出来进行操作,一次读写是可以支持多个BC_命令的)

------- BC_INCREFS:增加binder引用计数

------- BC_ACQUIRE:获取binder

------- BC_RELEASE:释放binder

------- BC_DECREFS:减少binder引用计数

------- BC_INCREFS_DONE:增加引用计数完成

------- BC_ACQUIRE_DONE:获取binder完成

------- BC_FREE_BUFFER:释放buffer

------- BC_TRANSACTION:数据传输

------- BC_REPLY:数据返回

剩下还有跟LOOPER和binder death相关的命令

BINDER_SET_MAX_THREADS(设置支持的最大线程数量)

BINDER_SET_CONTEXT_MGR(设置该进程为service manager)

BINDER_THREAD_EXIT(线程退出)

BINDER_VERSION(获取binder的版本)

这样看来的话重心都在于ioctl的BINDER_WRITE_READ以及下面的BC命令

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)

这里有两个主要操作:

1、写数据进binder,这里写进去的命令是BC_TRANSACTION

err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);


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.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
     tr.target.handle = handle;
//命令需要传递的目标
     tr.code = code;//这里的code并非是driver空间的命令,这个应该是业务端的命令代码,比如需要调用server端的哪个功能
     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();
         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);
     }
 
     mOut.writeInt32(cmd);
     mOut.write(&tr, sizeof(tr));
 
     return NO_ERROR;
 }

从这里可以看出在和binder driver打交道的进入到driver空间的数据的形式如下:

这里的mOut是也是一个parcel类型

mOut{|cmd1|binder_transaction_data1|cmd2|binder_transaction_data|cmd3|binder_transaction_data|}

然后binder_transaction_data{target(目标端的binder号)|code(调用服务端的功能号)|data.ptr.buffer(数据的buffer地址)}