binder驱动实际上没有对应的硬件,但是它也实现了mmap函数,他可不是用来映射物理介质到用户空间的,而是帮助实现binder驱动完成一次数据转移的功能。下面先看看这个mmap如何使用,通常,在进程打开/dev/binder节点之后,会调用mmap函数:
fd = open(“/dev/binder”, O_RDWR);
mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
这样Binder的接收方就有了一片大小为MAP_SIZE的接收缓存区。mmap()的返回值是内存映射在用户空间的地址,不过这段空间是由binder驱动管理,用户不必也不能直接访问(映射类型为PROT_READ,只读映射)。接收缓存区映射好后就可以做为缓存池接收和存放数据了。
下面是结构体binder_proc中和缓冲区管理相关的域:
struct binder_proc {
...
void *buffer; // binder_mmap()
ptrdiff_t user_buffer_offset; // binder_mmap()
struct list_head buffers; // 所有binder_buffer的链表
struct rb_root free_buffers;
struct rb_root allocated_buffers;
size_t free_async_space;
struct page **pages; // binder_mmap()
size_t buffer_size; // binder_mmap()
uint32_t buffer_free;
...
}
基本的buffer结构体:
struct binder_buffer {
struct list_head entry; /* free and allocated entries by addesss */
struct rb_node rb_node; /* free entry by size or allocated entry by address */
unsigned free:1; // 是否空闲的标志
unsigned allow_user_free:1;
unsigned async_transaction:1;
unsigned debug_id:29;
struct binder_transaction *transaction;
struct binder_node *target_node;
size_t data_size;
size_t offsets_size;
uint8_t data[0]; // ………………………..
};
一、binder_mmap()
/* vm_area_struct是task_struct中记录进程在用户空间中的一段虚拟地址空间,binder的mmap函数和其他的通常的mmap不同,这里会在内核空间中申请保留一段虚拟地址空间来和进程用户空间中的那段虚拟空间相对应,后面会在需要的时候分配物理内存,binder驱动负责管理这片映射的区域。*/
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
struct vm_struct *area;
struct binder_proc *proc = filp->private_data; // 当前进程的binder_proc结构体
const char *failure_string;
struct binder_buffer *buffer; // binder驱动用来存储一次单边传输数据的基本结构体
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M; //这段虚拟空间不得大于4MB
… // debug
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) { // 只能被映射成只读
// #define FORBIDDEN_MMAP_FLAGS (VM_WRITE)
ret = -EPERM;
failure_string = "bad vm_flags";
goto err_bad_arg;
}
vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
// VM_DONTCOPY:fork的时候不拷贝这段vma; VM_MAYWRITE: 尝试写操作
if (proc->buffer) { // binder_proc.buffer中保存映射之后的内核虚拟地址
ret = -EBUSY;
failure_string = "already mapped"; // 已经mmap过了
goto err_already_mapped;
}
// get_vm_area() - 在内核中申请并保留一块连续的内核虚拟内存空间
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
if (area == NULL) {
… // 出错处理
}
proc->buffer = area->addr; // 内核虚拟内存空间的起始地址
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
/* 被映射的区域的起始地址在用户空间和内核空间之间的偏移,可以为负数。这个偏移值很重要。*/
…
/* 分配存放struct page结构体指针的指针数组内存空间,主要用来保存指向申请的物理页struct page的指针。*/
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
/* 最大申请4MB的虚拟地址空间,所以这里的struct page结构体指针数组,数组最大长度是1024,占内存最大为4MB。*/
if (proc->pages == NULL) {
… // 出错处理
}
proc->buffer_size = vma->vm_end - vma->vm_start;
// 内核分配的那段虚拟地址空间大小
vma->vm_ops = &binder_vm_ops; // 这个操作集的open和close实际上没做什么
vma->vm_private_data = proc;// 保存binder_proc作为私有数据
/* binder_update_page_range(),这里它是在分配物理页并映射到刚才保留的虚拟内存空间上。当然根据参数,它也可以释放物理页面。在这里,Binder使用 alloc_page分配页面,使用map_vm_area为分配的物理内存做映射,使用vm_insert_page把分配的物理页插入到用户进程的当前vma区域。*/
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; // 准备用binder_buffer来格式化这片内核虚拟地址空间
INIT_LIST_HEAD(&proc->buffers); // 初始化链表头
list_add(&buffer->entry, &proc->buffers);/* 此刻刚刚分配了内核虚拟地址空间,也刚刚分配了1页的物理内存,这个时候将这一页的物理内存看做是一个binder_buffer结构体,插入到binder_proc.buffers链表中。*/
buffer->free = 1; // 表明是空闲的binder_buffer
binder_insert_free_buffer(proc, buffer); // 该函数详解见后
// 以binder_buffer的size为索引将自身挂入当前进程红黑树free_buffers中
proc->free_async_space = proc->buffer_size / 2; // 空闲的异步传输空间
barrier();
proc->files = get_files_struct(current);
proc->vma = vma;
…
return 0;
…
return ret;
}
/****************** binder_update_page_range() ********************/
static int binder_update_page_range(struct binder_proc *proc, int allocate,
void *start, void *end,
struct vm_area_struct *vma)
{
void *page_addr;
unsigned long user_page_addr;
struct vm_struct tmp_area;
struct page **page;
struct mm_struct *mm;
…
if (end <= start)
return 0;
if (vma)
mm = NULL;
else
mm = get_task_mm(proc->tsk);
if (mm) {
down_write(&mm->mmap_sem);
vma = proc->vma;
}
if (allocate == 0) // 释放特定范围的内存
goto free_range;
…
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;
struct page **page_array_ptr;
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; // 二级指针
// 得到当前page_addr在binder_proc.pages中的数组项地址
BUG_ON(*page); // 异常检测
*page = alloc_page(GFP_KERNEL | __GFP_ZERO);
// 分配一个物理清0页,返回struct page结构体指针
if (*page == NULL) {
… // 出错处理
}
tmp_area.addr = page_addr;
tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
page_array_ptr = page;
ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
// 将刚刚分配的物理页映射到内核虚拟地址page_addr上。
if (ret) {
… // 出错处理
}
// 内核虚拟地址段中地址page_addr在用户空间虚拟地址段中对应的虚拟地址
user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
ret = vm_insert_page(vma, user_page_addr, page[0]);
// 将刚刚分配的物理页插入到上面计算出的用户空间对应的虚拟地址上。
if (ret) {
… // 出错处理
}
}
…
return 0;
free_range: // 释放物理内存
for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) {
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
if (vma)
zap_page_range(vma, (uintptr_t)page_addr +
proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
__free_page(*page);
*page = NULL;
err_alloc_page_failed:
;
}
err_no_vma:
if (mm) {
up_write(&mm->mmap_sem);
mmput(mm);
}
return -ENOMEM;
}
/****************** binder_update_page_range() ********************/
/****************** binder_insert_free_buffer () ********************/
// 以binder_buffer的size为索引将自身挂入当前进程红黑树free_buffers中。
static void binder_insert_free_buffer(struct binder_proc *proc, struct binder_buffer *new_buffer)
{
struct rb_node **p = &proc->free_buffers.rb_node;
struct rb_node *parent = NULL;
struct binder_buffer *buffer;
size_t buffer_size;
size_t new_buffer_size;
BUG_ON(!new_buffer->free); // 异常检测,当前binder_buffer应该是空闲的才对。
new_buffer_size = binder_buffer_size(proc, new_buffer);
…
while (*p) {
parent = *p;
buffer = rb_entry(parent, struct binder_buffer, rb_node);
BUG_ON(!buffer->free);
buffer_size = binder_buffer_size(proc, buffer);
// 红黑树上某一个binder_buffer的data size
if (new_buffer_size < buffer_size)
p = &parent->rb_left;
else
p = &parent->rb_right;
// 查找插入红黑树free_buffers中的最佳位置
}
rb_link_node(&new_buffer->rb_node, parent, p);
rb_insert_color(&new_buffer->rb_node, &proc->free_buffers);
// 将最新的binder_buffer插入红黑树free_buffers
}
/****************** binder_insert_free_buffer () ********************/
/****************** binder_buffer_size () ********************/
static size_t binder_buffer_size(struct binder_proc *proc, struct binder_buffer *buffer)
{
/* 如果这个binder_buffer是在binder_proc.buffers链表的最后,也就是最新加入的,那么这个buffer的size是最大的,因为也把未分配的虚拟空间大小算上了。*/
if (list_is_last(&buffer->entry, &proc->buffers))
return proc->buffer + proc->buffer_size - (void *)buffer->data;
else
return (size_t)list_entry(buffer->entry.next,
struct binder_buffer, entry) - (size_t)buffer->data;
/* 否则就用下一个binder_bufffer的首地址减去这个binder_buffer的data首地址。*/
}
/****************** binder_buffer_size () ********************/
mmap函数实际上在为用户空间分配了一段虚拟地址空间vma之后,以这个为参数传递给binder_mmap()函数,这个函数中首先开辟一段内核的虚拟地址空间,保存在binder_proc.buffer域,初始化一些其他域之后,就分配一页物理内存,将其先映射到内核虚拟地址段,随后将这页物理页插入到用户空间的虚拟地址段中去。最后将整个内核虚拟空间看做一个binder_buffer插入到以buffer大小为索引的红黑树free_buffers上。
二、binder_alloc_buf()
在binder交互的发送侧调用ioctl的时候最终会调用到函数binder_transaction()这个函数,这个函数在拷贝数据之前会申请一个binder_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;
}
static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc,
size_t data_size,
size_t offsets_size, int is_async)
{
struct rb_node *n = proc->free_buffers.rb_node;
struct binder_buffer *buffer;
size_t buffer_size;
struct rb_node *best_fit = NULL;
void *has_page_addr;
void *end_page_addr;
size_t size;
…
size = ALIGN(data_size, sizeof(void *)) +ALIGN(offsets_size, sizeof(void *));
// 需求空间4字节对齐
…
/* 如果是异步传输,同时异步传输的空间不够用将会报错。为什么后面还要求多出
sizeof(struct binder_buffer)的长度,因为需要为下一个binder_buffer留出物理内存空间*/
if (is_async && proc->free_async_space < size + sizeof(struct binder_buffer)) {
…
return NULL;
}
while (n) {
buffer = rb_entry(n, struct binder_buffer, rb_node);
BUG_ON(!buffer->free); // 非空闲的binder_buffer挂错了树: free_buffers
buffer_size = binder_buffer_size(proc, buffer); // 见第一节中注释
if (size < buffer_size) {
best_fit = n;
n = n->rb_left; // 找到和要求大小最合适的binder_buffer地址
} else if (size > buffer_size)
n = n->rb_right; // 找到和要求大小最合适的binder_buffer地址
else {
best_fit = n; // 找到和要求大小相等的binder_buffer地址
break;
}
}
/* 在红黑树free_buffers上寻找大小最合适的binder_buffer,这里会出现几种情况:
1. 最极端的情况,free_buffers树上已经存在的binder_buffer中,除了最右侧这个包含大量虚拟地址空间的binder_buffer合适之外,其余binder_buffer的data大小都比需求的小。此时best_fit等于最右侧的binder_buffer,n = NULL。(和这种情况类似,binder_mmap刚刚执行完之后到来的第一个数据传输时,调用该函数后的情形,就是这个红黑树上除了这个包含大量虚拟空间的binder_buffer之外,没有其他的binder_buffer。)
2. 常见的情况,所有空闲binder_buffer之中,存在一个binder_buferr,它的data的大小最接近需求的大小(肯定是大于)。根据这个相差的字节数,又存在两种情况:2.1 差值 >= sizeof(struct binder_buffer) + 4;2.2差值 < sizeof(struct binder_buffer) +4,后面的代码会根据这两种情况做不同的处理。为什么要多4个字节?是这样的,binder_buffer结构体最后的域data[0],在用sizeof计算这个结构体占多少内存时本身就没有计算进去,而多4个字节呢,是考虑到BC_x或者BR_x协议有不带参数的命令,就一个命令正好占4字节,刚刚好也够一次单边传输之用了。这种情形下best_fit为最data大小最接近需求大小的binder_buffer,而n还是为NULL。
3. 常见的情况,恰好找到一个data大小和需求空间大小相等的binder_buffer。这种请best_fit就等于这个binder_buffer,而n不为NULL,等于这个binder_buffer的rb_node指针。
*/
if (best_fit == NULL) { // no address space
… // 由上面三种情况,可以看出,如果空间足够,这个best_fit就不可能为NULL。
}
if (n == NULL) {
buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
buffer_size = binder_buffer_size(proc, buffer);
} /* 前2种情况下,n都为NULL, 这里取出best_fit对应的binder_buffer,并且计算
出这个binder_buffer的data空间。对于第一总情况,这个buffer_size是比较大的,其中包含了没有做映射的空间;而对于第二种情况,这个buffer_size是data所占的实际物理内存大小。*/
…
has_page_addr = (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK);
/* 地址向减小方向页对齐。这里有两种情况:1. (uintptr_t)buffer->data + buffer_size已经翻页;2. (uintptr_t)buffer->data + buffer_size 还在buffer->data同一页。*/
if (n == NULL) {/* 第一、二种情况存在 */
if (size + sizeof(struct binder_buffer) + 4 >= buffer_size) // 情况2.2
buffer_size = size; /* no room for other buffers */
else
buffer_size = size + sizeof(struct binder_buffer);// 情况2.1和第一种情况
}
end_page_addr =(void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size);
if (end_page_addr > has_page_addr)
end_page_addr = has_page_addr;
if (binder_update_page_range(proc, 1,
(void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL))
return NULL;
/* binder_update_page_range()在上面三种情况下,所做动作也不同:
第一种情况:end_page_addr远小于has_page_addr,这时end_page_addr没变,还是PAGE_ALIGN(buffer->data+ size+ sizeof(struct binder_buffer)),只是这个buffer->data+ size+ sizeof(struct binder_buffer)值还会不会和buffer->data处于同一个页面,如果是,那么函数binder_update_page_range()中的第3、4个参数就是相等的,这个函数就不会分配物理内存页;如果不是处于同一物理页内,也就是翻过页了,具体翻几页都行,这个时候第3个参数表示的是即将分配的第一个物理页的页地址,而end_page_addr表示最后一个需求的物理页的页地址。
第2.1种情况:((uintptr_t)buffer->data + buffer_size) & PAGE_MASK,如果加上buffer_size没有翻页,那么这个式子的结果是buffer->data所在页的页地址;如果是翻了n页了,那么这个式子的结果是buffer->data所在页的后n页的页地址。如果没有翻页的情况下,PAGE_ALIGN((uintptr_t)buffer->data + buffer_size)会是buffer->data所在页的下一页,这样经过上面的if条件的限制,这个函数的第3、4个参数就是一样的了,那么这个函数就不会为其多分配物理页。
如果在翻页了的情况下,最极端的情况就是buffer->data+ size+ sizeof(struct binder_buffer)和前面计算has_page_addr的buffer->data + buffer_size处于同一页内,这个时候算出来的end_page_addr就比has_page_addr大1,不过经过那个if的限定,最终调用函数binder_update_page_range()分配的也是翻过的那n个物理页。其余不极端的情况都是很好理解的。
情况2.2和2.1的情形类似,可以自行分析。
第三中情况就更加好理解了,因为是找了大小和需求恰好相等的binder_buffer,这个时候算出来的end_page_addr正好比has_page_addr大1,由if条件限定,所以binder_update_page_range()函数不会分配物理空间,因为这是之前用过的,物理页已经分配好了。
*/
rb_erase(best_fit, &proc->free_buffers);// 从空闲树上摘除这个binder_buffer
buffer->free = 0; // 清除空闲标志。
binder_insert_allocated_buffer(proc, buffer); //挂入已分配的红黑树上: allocated_buffers
if (buffer_size != size) { //情况2.1和第一种情况,生成一新的binder_buffer挂入空闲树
struct binder_buffer *new_buffer = (void *)buffer->data + size;
list_add(&new_buffer->entry, &buffer->entry); // 挂入proc-> buffers链表
new_buffer->free = 1;
binder_insert_free_buffer(proc, new_buffer); // 将这个新的binder_buffer挂入空闲树
}
…
// 对刚刚分配的binder_buffer相应域赋值
buffer->data_size = data_size;
buffer->offsets_size = offsets_size;
buffer->async_transaction = is_async;
if (is_async) { // 如果是异步传输,异步空间相应减小
proc->free_async_space -= size + sizeof(struct binder_buffer);
…
}
return buffer; // 返回新分配的binder_buffer结构体
}
三、 BC_FREE_BUFFER
上层应用程序使用完binder_buffer之后,可以给驱动发送命令BC_FREE_BUFFER来释
放掉这这个binder_buffer,以供下次使用。
case BC_FREE_BUFFER: {
// BC_FREE_BUFFER = _IOW('c', 3, int),
// cmd | data_ptr(data_ptr指向在read操作中接收到的binder_buffer的data 区域)
void __user *data_ptr;
struct binder_buffer *buffer;
if (get_user(data_ptr, (void * __user *)ptr))
return -EFAULT;
ptr += sizeof(void *);
buffer = binder_buffer_lookup(proc, data_ptr);// 取出binder_buffer结构体指针
if (buffer == NULL) { // 判断binder_buffer对象是否为NULL
…
}
if (!buffer->allow_user_free) {
/* 判断buffer对象是否允许用户释放, binder_thread_read在使用完之后会将这里设置成1。*/
…
break;
}
…
if (buffer->transaction) {
// 如果为active,那么强制将buffer->transaction置成NULL
buffer->transaction->buffer = NULL;
buffer->transaction = NULL;
}
// 当前释放的binder_buffer如果是异步传输所用,并且目标binder_node存在.
if (buffer->async_transaction && buffer->target_node) {
BUG_ON(!buffer->target_node->has_async_transaction);
// 该成员表明该节点在to-do队列中有异步交互尚未完成。
if (list_empty(&buffer->target_node->async_todo))//无异步任务等待了
buffer->target_node->has_async_transaction = 0;
else
list_move_tail(buffer->target_node->async_todo.next, &thread->todo);
// 否则,将下一个异步任务移入当前task的私有todo队列中执行。
}
binder_transaction_buffer_release(proc, buffer, NULL);
/* 该函数将对使用buffer这个binder_buffer的通讯中出现的flat_binder_object
结构体对应的binder_node或者binder_ref减少一个引用计数。*/
binder_free_buf(proc, buffer);
/* 真正的释放buffer这个binder_buffer内存空间的函数,解析见后 */
break;
}
/*****************************************/
static struct binder_buffer *binder_buffer_lookup(struct binder_proc *proc,
void __user *user_ptr)
{
struct rb_node *n = proc->allocated_buffers.rb_node;
// 对于已经分配的buffer空间,以内存地址为索引加入红黑树allocated_buffers中。
struct binder_buffer *buffer;
struct binder_buffer *kern_ptr;
kern_ptr = user_ptr - proc->user_buffer_offset - offsetof(struct binder_buffer, data);
/* 进程ioctl传下来的指针并不是binder_buffer的地址,而直接是binder_buffer.data的地址。user_buffer_offset 用户空间和内核空间,被映射区域起始地址之间的偏移。*/
while (n) {
buffer = rb_entry(n, struct binder_buffer, rb_node);
BUG_ON(buffer->free);
if (kern_ptr < buffer)
n = n->rb_left;
else if (kern_ptr > buffer)
n = n->rb_right;
else
return buffer;
}
return NULL;
}
/*****************************************/
static void binder_free_buf(struct binder_proc *proc, struct binder_buffer *buffer)
{
size_t size, buffer_size;
buffer_size = binder_buffer_size(proc, buffer);
size = ALIGN(buffer->data_size, sizeof(void *)) + ALIGN(buffer->offsets_size, sizeof(void *));
BUG_ON(buffer->free);
BUG_ON(size > buffer_size);
BUG_ON(buffer->transaction != NULL);
BUG_ON((void *)buffer < proc->buffer);
BUG_ON((void *)buffer > proc->buffer + proc->buffer_size);
if (buffer->async_transaction) {
proc->free_async_space += size + sizeof(struct binder_buffer);
…
}
binder_update_page_range(proc, 0,
(void *)PAGE_ALIGN((uintptr_t)buffer->data),
(void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
NULL); // 第二个参数是0,表示是释放内存空间。
rb_erase(&buffer->rb_node, &proc->allocated_buffers);
buffer->free = 1;
if (!list_is_last(&buffer->entry, &proc->buffers)) {
struct binder_buffer *next = list_entry(buffer->entry.next, struct binder_buffer, entry);
if (next->free) {
rb_erase(&next->rb_node, &proc->free_buffers);
binder_delete_free_buffer(proc, next);
}
}
if (proc->buffers.next != &buffer->entry) {
struct binder_buffer *prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);
if (prev->free) {
binder_delete_free_buffer(proc, buffer);
rb_erase(&prev->rb_node, &proc->free_buffers);
buffer = prev;
}
}
binder_insert_free_buffer(proc, buffer);
}
// 后面的这个函数只是粗略地看了下,其中细节比较复杂,没时间看了,先这样吧!不过到这里,大致明白了binder_buffer的分配和回收过程。至于细节,以后有时间慢慢研究了!
参考文献:
http://blog.csdn.net/universus/archive/2011/02/27/6211589.aspx
Android Binder设计与实现 – 设计篇