binder的内存管理
Server端在启动之后,对/dev/binder设备调用mmap,内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后Server端的用户空间和内核空间同时进行映射;当Client端发送请求时,请求将数据从Client进程的用户空间拷贝到Server端的内核内存中,因为用户空间和内核空间对该块物理内存进行了同步映射,因此Server端用户空间也可以获得数据了,下面对Server端用户空间与内核空间的同步映射关系进行讲解:
下面为用户空间调用binder_open进行注册过程:
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
struct binder_state *bs;
struct binder_version vers;
bs = malloc(sizeof(*bs));
// 调用内核空间binder_open 生成binder_proc
bs->fd = open(driver, O_RDWR | O_CLOEXEC);
bs->mapsize = mapsize;
// 调用内核空间的binder_mmap 进行内存映射
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
}
mmap的前半部分的流程都是类似的,使用结构体vm_area_struct 来描述一个虚拟内存区域;在构造完毕vm_area_struct之后,如果有fd传入,就会判断当前文件描述符是否有mmap赋值,然后调用对应的内核mmap函数,大致的流程如下:
对于binder来讲,即接下来调用binder_mmap函数,binder_proc中使用binder_alloc做内存管理,binder_mmap初始化过程主要是建立了vm_area_struct(用户空间)与binder_proc->alloc的关联;
- struct binder_alloc的成员变量buffer指向vma->vm_start;
- struct binder_alloc的成员变量vma指向vma;
- struct binder_alloc的buffer_size 赋值为 min(vma->vm_end-vma->vm_start, 4M),所以一个进程用于binder通信最大是4M;
binder_alloc作为当前Server端的binder内存总管,通过struct binder_buffer和struct binder_lru_page对binder使用的内存进行管理;
struct binder_buffer
struct binder_buffer结构体用于对虚拟地址的管理,通过mmap函数之后,当前的进程分配获得一片虚拟内存地址进行binder操作,所以初始的binder_buffer是下面这个样子的
- struct binder_buffer 成员变量user_data指向当前管理的虚拟地址的起始地址,每个binder_buffer会管理一块虚拟内存区域,由user_data指向起始地址;
- struct binder_buffer 成员变量entry 将当前binder_buffer连接到struct binder_alloc的buffers链表上,每一个binder_buffer管理一块区域,根据产生的时间连接;
- struct binder_buffer 成员变量rb_node用来将自己连接到binder_alloc的free_buffers或者是allocated_buffers的链表,free_buffers的key值为buffer_size的大小,allocated_buffers的key值为user_data,即内存的起始地址,同时由成员变量free表示当前binder_buffer管理的虚拟地址是否有在使用;
每个struct binder_buffer的不断分配与归还过程中存在着内存的合并等操作,binder_alloc_new_buf用来从当前的free_buffers中找到一个合适大小的binder_buffer分为两部分,一部分为分配的大小挂在allocated_buffers红黑树上,然后将剩下的binder_buffer重新挂载free_buffers红黑树中。
struct binder_lru_page
上面的struct binder_buffer用来管理虚拟地址中的每块内存,struct binder_lru_page用来管理分配的物理页;
初始时根据当前进程mmap的内存大小,可以计算如果是全部用完,占有的struct page数量;然后根据数量分配struct binder_lru_page的数组,每个struct binder_lru_page用来管理当前index对应的虚拟地址对应的struct page页;初始情况下实际物理页并不分配,对alloc成员变量和lru成员变量进行初始化;
alloc->pages = kcalloc(alloc->buffer_size / PAGE_SIZE,sizeof(alloc->pages[0]),GFP_KERNEL)
因为struc binder_alloc的成员pages指向的是一个binder_lru_page数组,并且每个struct binder_lru_page管理的成员变量是按照顺序排列的,当需要获得虚拟地址空间某个addr对应的page状态时,因此可以通过addr通过运算来获得,如下图代码;则当查看vm_start是否有page时,可以查看alloc->pages[0]处的binder_lru_page状态,需要注意内存对齐;
index = (page_addr - alloc->buffer) / PAGE_SIZE;
struct binder_lru_page *page = &alloc->pages[index]
binder_alloc_new_buf
在描述完毕上面几个结构体的变量之后,就可以对该函数进行解析;
在binder_transaction时,需要将客户端的数据复制的服务端,因此会调用如下函数从服务端的alloc管理的内存中获得一块内存,这块内存依旧由struct binder_buffer来管理;
t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,tr->offsets_size, extra_buffers_size,!reply && (t->flags & TF_ONE_WAY));
binder_alloc_new_buf_locked做以下操作:
-
在alloc->free_list上找到一个最小大于分配size的struct binder_buffer A,然后将剩下的部分赋值给new_buffer,其起始位置在当前A->user_data + size处;然后将其挂在alloc->buffer链表上,这个new buffer管理从A->user_data+size开始的区域;
-
new_buffer管理的是没有使用的地址区域,因此insert_free_buffer链表上;
-
此时struct binder_buffer A->user_data开始到 struct binder_buffer A->user_data + size的区域要被使用了,因此如果其在全局变量binder_freelist的话,需要将对应部分的page拆下来;
-
将struct binder_buffer A从alloc->free_buffers上删除,然后挂载到allocated_buffer链表上;
binder_install_buffer_pages做以下操作: -
通过struct binder_buffer的user_data和data_size获得虚拟地址的起始地址以及结束地址;
-
根据地址获得对应的alloc->pages的索引index以及对应的struct binder_lru_page;
-
调用binder_install_single_page进行映射物理页操作;
-
binder_install_single_page调用alloc_page分配一块物理页;
-
调用vm_insert_page建立vma中addr地址与物理页page的关联;
-
调用binder_set_installed_page使得lru_page->page_ptr指向page;
经过以上步骤,在binder中物理页与用户空间建立了联系;