Android Binder 机制初步学习 笔记(二)—— Binder 设备基本操作实现
来源:互联网 发布:林忆软件 编辑:程序博客网 时间:2024/06/07 07:40
- NOTE
- Binder 设备基本操作实现
- Binder 设备初始化
- 1 binder_init
- 2 binder_fops
- Binder 设备文件打开
- 1 binder_open
- Binder 设备文件的内存映射
- 1 binder_mmap
- 2 binder_update_page_range
- 3 binder_buffer_size
- 4 binder_insert_free_buffer
- 5 内存映射示意图
- Binder 内核缓冲区管理
- 1 分配内核缓冲区
- 11 binder_alloc_buf 1st
- 12 binder_alloc_buf 2nd
- 13 binder_alloc_buf 3rd
- 14 binder_alloc_buf 4th
- 15 binder_insert_allocated_buffer
- 2 释放内核缓冲区
- 21 binder_free_buf
- 22 buffer_start_page buffer_end_page
- 23 binder_delete_free_buffer
- 3 查询内核缓冲区
- 31 binder_buffer_lookup
- 1 分配内核缓冲区
- Binder 设备初始化
NOTE
- 源码版本:Android 7.1.2。
- 内核版本:android-goldfish-3.4
- 内核下载:
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
(清华镜像站) - 以下分析思路均来自老罗的《Android 系统源代码情景分析(修订版)》。
Binder 设备基本操作实现
- 从这里开始分析
binder.c
中关于binder
设备操作的实现:init
:初始化。open
:打开设备。mmap
:内存映射。ioctl
:I/O
管理。
- 没有特别说明时,文件位置均为:
kernel/goldfish/drivers/staging/android/binder.c
1. Binder 设备初始化
1.1 binder_init( )
- 调用
create_singlethread_workqueue()
创建一个工作队列。 - 调用
debugfs_create_dir()
创建目录binder
作为根目录。 - 在根目录下创建一个目录
proc
:- 每个使用了
Binder
机制的进程都在此有一个对应的文件。 - 这些文件以进程
ID
命名。 - 通过文件可读取到对应的
Binder
线程池、实体、引用对象以及内核缓冲区等信息。
- 每个使用了
- 调用
misc_register()
创建一个Binder
设备。 - 在根目录下创建五个文件:
state
stats
transactions
transaction_log
failed_transaction_log
- 通过它们读取驱动运行状况,如协议请求次数、日志记录信息等。
static int __init binder_init(void){ int ret; binder_deferred_workqueue = create_singlethread_workqueue("binder"); if (!binder_deferred_workqueue) return -ENOMEM; binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL); if (binder_debugfs_dir_entry_root) binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", binder_debugfs_dir_entry_root); ret = misc_register(&binder_miscdev); if (binder_debugfs_dir_entry_root) { debugfs_create_file("state", S_IRUGO, binder_debugfs_dir_entry_root, NULL, &binder_state_fops); debugfs_create_file("stats", S_IRUGO, binder_debugfs_dir_entry_root, NULL, &binder_stats_fops); debugfs_create_file("transactions", S_IRUGO, binder_debugfs_dir_entry_root, NULL, &binder_transactions_fops); debugfs_create_file("transaction_log", S_IRUGO, binder_debugfs_dir_entry_root, &binder_transaction_log, &binder_transaction_log_fops); debugfs_create_file("failed_transaction_log", S_IRUGO, binder_debugfs_dir_entry_root, &binder_transaction_log_failed, &binder_transaction_log_fops); } return ret;}
1.2 binder_fops
- 全局变量
binder_fops
指定了设备文件的操作方法列表:- 文件打开:
binder_open()
- 内存映射:
binder_mmap()
IO
控制:binder_ioctl()
- 文件打开:
static const struct file_operations binder_fops = { .owner = THIS_MODULE, .poll = binder_poll, .unlocked_ioctl = binder_ioctl, .mmap = binder_mmap, .open = binder_open, .flush = binder_flush, .release = binder_release,};static struct miscdevice binder_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "binder", .fops = &binder_fops};
2. Binder 设备文件打开
- 进程使用
Binder
机制之前,要先打开设备文件以获得一个文件描述符。 - 通过文件描述符,进程可以与驱动交互,从而可使用
Binder
通信。
2.1 binder_open( )
- 创建一个
binder_proc
结构体proc
,并对它进行初始化:- 申请必要的内存空间:
kzalloc()
- 初始化任务控制块:
proc->tsk = current
- 初始化工作项队列:
INIT_LIST_HEAD(&proc->todo)
- 初始化等待队列:
init_waitqueue_head(&proc->wait)
- 设置默认优先级:
proc->default_priority = task_nice(current)
- 创建统计信息:
binder_stats_created()
- 将 proc 加入全局 hash 队列:
hlist_add_head(&proc->proc_node, &binder_procs)
- 设置进程号:
proc->pid = current->group_leader->pid
。 - 初始化死亡通知工作项队列:
INIT_LIST_HEAD(&proc->delivered_death)
- 申请必要的内存空间:
- 将初始化成功的
proc
保存:filp
指向一个打开文件结构体。- 将
proc
保存在其成员private_data
中。 - 进程调用
open
打开设备后,内核会返回一个文件描述符给进程,而这个描述符与filp
指向的结构体是关联在一起的。 - 进程以描述符为参数调用
mmap
或ioctl
与驱动交互时,驱动可通过private_data
获取proc
。
- 读取调试相关的信息:
- 在目标设备上的
/proc/binder/proc
目录下创建以进程ID
为名的只读文件。 - 通过这个文件,我们可以获得相应进程的线程池、实体对象、引用对象、缓冲区等信息。
- 在目标设备上的
static HLIST_HEAD(binder_procs);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 = 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); 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_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;}
3. Binder 设备文件的内存映射
- 打开设备文件后,需要调用
mmap
函数将这个设备文件映射到进程的地址空间,然后才可以使用Binder
进程间通信机制。 - 内存映射目的是给进程分配内核缓冲区,以便进行数据通信。
3.1 binder_mmap( )
- 该函数比较长,根据参考书所述,可以分成两段来分析。
- 第一段:
- 参数
vma
指向结构体vm_area_struct
,用于描述一段虚拟地址空间。 - 变量
area
指向一个结构体vm_struct
,这也是用来描述虚拟地址空间的。 - 它们描述的虚拟地址是连续的,但对应的物理页面可以不连续。
vm_area_struct
与vm_struct
区别:- 在 Linux 内核中,一个进程可占虚拟地址空间为
4G
,其中0G ~ 3G
为用户地址空间,剩下为内核地址空间。 vm_area_struct
:- 描述用户地址空间。
- 空间范围
0G ~ 3G
。
vm_struct
:- 描述内核地址空间。
- 空间范围
(3G + 896M + 8M)~ 4G
。
- 注意到
4G
的空间中有一段空出来的地址:3G ~(3G + 896M)
:映射物理内存的前896M
,它们之间是简单线性关系。(3G + 896M)~(3G + 896M + 8M)
:安全保护区,用于检测非法指针。
- 在 Linux 内核中,一个进程可占虚拟地址空间为
filp
指向一个打开文件结构体:- 成员变量
private_data
指向一个进程结构体binder_proc
。 - 将
private_data
转为proc
,即获取了对应的进程。
- 成员变量
vma
的成员变量vma_start
与vma_end
指定了要映射的用户地址空间范围:- 若范围大于
4M
,则将其截断为4M
(断尾)。 - 从这可以知道,
Binder
驱动最多可以为进程分配4M
内核缓冲区。
- 若范围大于
- 驱动分配的内核缓冲区在用户空间只读:
#define FORBIDDEN_MMAP_FLAGS (VM_WRITE)
- 若检查出指定要映射的用户地址空间可写,则返回。
- 驱动分配的内核缓冲区在用户空间不可拷贝:
VM_DONTCOPY
位置1
,表示不可拷贝。VM_MAYWRITE
位置0
,禁止设置可能会执行写操作标志位的操作。
- 检查
buffer
是否已经指向一块内核缓冲区,若是则出错返回。
- 参数
int ret; struct vm_struct *area; struct binder_proc *proc = filp->private_data; const char *failure_string; struct binder_buffer *buffer; if ((vma->vm_end - vma->vm_start) > SZ_4M) vma->vm_end = vma->vm_start + SZ_4M; ...... ...... 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; }
- 第二段:
- 在进程内核地址空间中分配大小为
vma->vm_end - vma ->vm_start
的空间。 proc->buffer
保存空间起始地址,proc->buffer_size
保存大小。- 指定该空间的打开和关闭函数为
binder_vma_open
与binder_vma_close
。 - 计算缓冲区的用户空间地址与内核空间地址差值
proc->user_buffer_offset
。 - 调用
kzalloc()
创建一个物理页面结构体指针数组(每一页虚拟地址空间都对应有一个物理页面),并将数组地址存放在proc->pages
中。 - 调用
binder_update_page_range()
为虚拟地址空间area
分配物理页面。 - 用一个
binder_buffer
结构的buffer
来描述area
。 - 将
buffer
加入内核缓冲区列表proc->buffers
中。 - 由于
area
对应物理页面空闲,调用binder_insert_free_buffer
将其加入空闲内核缓冲区红黑树proc->free_buffers
中。 - 将进程最大可用异步事务缓冲区大小设置为
proc->buffer_size / 2
。(防止异步事务消耗过多内核缓冲区,影响同步事务)
- 在进程内核地址空间中分配大小为
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); ...... ...... 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; 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); 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(proc->tsk); proc->vma = vma; proc->vma_vm_mm = vma->vm_mm; /*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/ return 0; ...... ......
3.2 binder_update_page_range( )
- 这个函数的作用,是为一段指定的虚拟地址空间分配或释放物理页面。
- 参数解释:
proc
:描述了目标进程。allocate
:值为0
则释放物理界面,否则分配物理页面。start
&&end
:指定操作的内核地址空间的开始、结束地址。vma
:指向要映射的用户地址空间。
- 实现逻辑:
- 判断
vma
是否指向一个空地址:- 空,则从
proc
中获取vma
。并判断目标进程的vma_mm
与当前变量mm
是否匹配,若不匹配则会报错,并把vma
置空。 - 非空,则将变量
mm
置为空,不需要用目标进程中获取对应的vma
。
- 空,则从
- 通过
allocate
值判断目前走的是分配还是释放操作,若为释放操作,则直接跳到free_range
的部分。 - 若为分配操作,则进入第一个
for
循环:- 内核地址
start ~ end
可能包含多个页面,因此需要循环操作依次为每个虚拟地址页面分配一个物理页面。 - 首先从目标进程
proc
的物理页面指针数组中,获得一个与page_addr ~(page_addr + PAGE_SIZE)
对应的物理页面指针。 - 调用
alloc_page()
分配物理页面。 - 分配成功后,对
tmp_area
的相关操作对应着内核地址的映射。注意,给它分配的大小是两倍PAGE_SIZE
,这是因为Linux
内核规定,这个范围内的内核地址都必须在后保留一块空地址空间作为安全保护区,以检测非法指针。 - 对
user_page_addr
的操作则对应着用户地址空间的映射。
- 内核地址
- 若为释放操作,则进入
free_range
中的for
循环:- 同样,在地址区间内可能存在多个页面,因此需要循环。
- 首先获得对应的页面指针。
- 调用
zap_page_range()
解除该物理页面在用户地址的映射。 - 调用
unmap_kernel_range()
解除在内核地址的映射。 - 最后调用
__free_page()
释放本物理页面,并把页面指针置空。
- 判断
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 (vma) mm = NULL; else mm = get_task_mm(proc->tsk); if (mm) { down_write(&mm->mmap_sem); vma = proc->vma; if (vma && mm != proc->vma_vm_mm) { pr_err("binder: %d: vma mm and task mm mismatch\n", proc->pid); vma = NULL; } } 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]; BUG_ON(*page); *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO); ...... 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); ...... user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset; ret = vm_insert_page(vma, user_page_addr, page[0]); ...... } if (mm) { up_write(&mm->mmap_sem); mmput(mm); } 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;}
3.3 binder_buffer_size( )
- 该函数作用是,计算相应内核缓冲区的大小。
- 结构
binder_buffer
所描述的内核缓冲区由两块数据:- 元数据块,描述缓冲区本身。
- 有效数据块,保存真正的事务数据。
- 内核缓冲区大小指的是有效数据块的大小。
- 实现逻辑:
- 若该缓冲区是缓冲区列表
buffers
中最后一个元素:- 有效数据块从成员变量
data
开始,一直到驱动为进程分配的连续内核地址的末尾。 - 先计算连续内核地址的末尾地址,再减去
data
地址,则可得到大小。 - 数据块信息如图 4 所示。
- 有效数据块从成员变量
- 若非最后一个元素:
- 当前缓冲区大小等于下一个缓冲区起始位置减去当前缓冲区
data
地址。 - 数据块信息如图 5 所示。
- 当前缓冲区大小等于下一个缓冲区起始位置减去当前缓冲区
- 若该缓冲区是缓冲区列表
图 4. 位于末尾的内核缓冲区内存布局
图 5. 位于头部或中间的内核缓冲区内存布局
static size_t binder_buffer_size(struct binder_proc *proc, struct binder_buffer *buffer){ 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;}
3.4 binder_insert_free_buffer( )
- 该函数作用是,将一个空闲内核缓冲区加入到进程的空闲缓冲红黑树中。
- 实现逻辑:
- 调用 binder_buffer_size() 计算传入的缓冲区大小。
- 进入 while 循环:
- 在红黑树 proc->free_buffers 中找到 new_buffer 的合适位置。
- 调用 rb_link_node() 连接红黑树节点。
- 调用 rb_insert_color() 将 new_buffer 保存到对应位置。
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); new_buffer_size = binder_buffer_size(proc, new_buffer); binder_debug(BINDER_DEBUG_BUFFER_ALLOC, "binder: %d: add free buffer, size %zd, " "at %p\n", proc->pid, new_buffer_size, 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); if (new_buffer_size < buffer_size) p = &parent->rb_left; else p = &parent->rb_right; } rb_link_node(&new_buffer->rb_node, parent, p); rb_insert_color(&new_buffer->rb_node, &proc->free_buffers);}
3.5 内存映射示意图
Binder
驱动为进程分配的内核缓冲区有两个地址:- 用户空间地址。
- 内核空间地址。
- 它们之间有简单的线性关系,如图 6 所示。
- 当
Binder
驱动需要将一块数据传输给进程时,它可以将数据保存在为进程所分配的一块内核缓冲区中,然后再把这块缓冲区的用户空间地址告诉进程,则进程就可以访问到相应的数据。 - 如此,数据就不需要用内核空间拷贝到用户空间,从而提高数据传输效率。
图 6. Binder 设备文件内存映射示意图
4. Binder 内核缓冲区管理
- 开始时,
Binder
驱动只为进程分配一个页面的物理内存。 - 随着进程的需要,可以分配更多内存,但最多只能分配
4M
。 - 驱动为进程维护一个内核缓冲区池:
- 由于物理内存分配以页面为单位,而进程使用内存时不是以页面为单位。
- 池中每块内存使用
binder_buffer
结构描述。 - 将内存块保存在一个列表中。
- 对于已分配的,和空闲的内存块,分别保存在两个红黑树中。
- 对于这些缓冲区,需要有一定的管理机制(分配、释放、查询)。
4.1 分配内核缓冲区
- 进程使用命令协议
BC_TRANSACTION
或BC_REPLY
传递数据时:- 驱动需要将这些数据从用户空间拷贝到内核空间,再传递给目标进程。
- 此时驱动需要在目标进程的内存池中分出一小块内核空间保存这些数据。
4.1.1 binder_alloc_buf( ) —— 1st
- 该函数实现了缓冲区的分配操作。
- 由于对应的代码比较长,分成几个小段来分析。
- 参数含义:
proc
:目标进程。data_size
:数据缓冲区大小。offset_size
:偏移数组缓冲区大小。is_async
:区别同步事务与异步事务。
- 实现逻辑 —— 第一段:
- 用
ALIGN()
将data_size
与offset_size
对其到一个void
指针大小边界。 - 将对其后的数据相加,得到要分配的内核缓冲区大小
size
。 - 检查
size
值:- 溢出,说明请求分配的缓冲区太大,出错退出函数。
- 无溢出,继续下一步。
- 检查是否用于异步事务:
- 若是,则检查请求分配的空间是否大于目标进程剩余可用于异步事务的空间,如果大于则出错退出。
- 若否,则继续下一步。
- 用
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 *)); if (size < data_size || size < offsets_size) { binder_user_error("binder: %d: got transaction with invalid " "size %zd-%zd\n", proc->pid, data_size, offsets_size); return NULL; } if (is_async && proc->free_async_space < size + sizeof(struct binder_buffer)) { binder_debug(BINDER_DEBUG_BUFFER_ALLOC, "binder: %d: binder_alloc_buf size %zd" "failed, no async space left\n", proc->pid, size); return NULL; }
4.1.2 binder_alloc_buf( ) —— 2nd
- 实现逻辑 —— 第二段:
- 进入
while
循环体:- 采用最佳适配算法在目标进程的空闲缓冲区红黑树中检查有没有最合适的内核缓冲区可用。
- 若有,则保存到
best_fit
中。 - 若无,
best_fit
为NULL
。
- 若没有可用缓冲区,则出错返回。
- 若有可用缓冲区,而这个缓冲区大小并不是刚好合适,而是更大一些。此时
n == NULL
成立,则计算该buffer
的大小,保存在buffer_size
中。
- 进入
while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); BUG_ON(!buffer->free); buffer_size = binder_buffer_size(proc, buffer); if (size < buffer_size) { best_fit = n; n = n->rb_left; } else if (size > buffer_size) n = n->rb_right; else { best_fit = n; break; } } if (best_fit == NULL) { printk(KERN_ERR "binder: %d: binder_alloc_buf size %zd failed, " "no address space\n", proc->pid, size); return NULL; } if (n == NULL) { buffer = rb_entry(best_fit, struct binder_buffer, rb_node); buffer_size = binder_buffer_size(proc, buffer); }
4.1.3 binder_alloc_buf( ) —— 3rd
- 这一段中需要用到的几个宏定义:
- 位置:
kernel/goldfish/arch/arm/include/asm/page.h
- 位置:
/* PAGE_SHIFT determines the page size */#define PAGE_SHIFT 12#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)#define PAGE_MASK (~(PAGE_SIZE-1))
- 这一段中需要用到的几个宏定义:
- 位置:
kernel/goldfish/include/linux/mm.h
- 位置:
/* to align the pointer to the (next) page boundary */#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
- 实现逻辑 —— 第三段:
- 使用宏
PAGE_MASK
计算空闲缓冲区buffer
的结束地址所在页面的起始地址,保存在has_page_addr
中。 - 若在第二段中提到的情况,
n == NULL
成立,此时需要将获得的空闲缓冲区进行裁剪:- 其中一块用来分配。
- 另一块继续留在目标进程的空闲缓冲区红黑树中。
- 注意,若裁剪后的第二块缓冲区小于等于
4
字节时,就不进行裁剪。 - 最终需要分配的内核缓冲区大小保存在
buffer_size
中。
- 使用宏
PAGE_ALIGN
将缓冲区结束地址对其到页面边界,保存在end_page_addr
中。 - 若对齐后的结束地址大于页面起始地址,则将其修正为
has_page_addr
。 - 调用
binder_update_page_range()
分配物理页面。
- 使用宏
has_page_addr = (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK); if (n == NULL) { if (size + sizeof(struct binder_buffer) + 4 >= buffer_size) buffer_size = size; /* no room for other buffers */ else buffer_size = size + sizeof(struct binder_buffer); } 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;
- 在这部分实现中,关于
end_page_addr
与has_page_addr
的关系有三种情况。 - 第一种情况如图 7 所示:
- 空闲内核缓冲区
buffer
的结束地址刚好对齐到页面边界。 - 此时
end_page_addr
必定小于等于has_page_addr
。
- 空闲内核缓冲区
图 7. 情况一,buffer结束地址对齐到页面边界
- 第二种情况如图 8 所示:
buffer
的结束地址没有对齐到页面边界,并且大于要分配的缓冲区的结束地址。- 此时
end_page_addr
依然小于has_page_addr
。
图 8. 情况二,buffer结束地址未对齐到页面边界,且大于要分配的缓冲区结束地址
- 第三中情况如图 9 所示:
buffer
结束地址没有对齐到页面边界,并且小于要分配的缓冲区的结束地址。- 此时
end_page_addr
大于has_page_addr
,需要修正。
图 9. 情况三,buffer结束地址未对齐到页面边界,且小于要分配的缓冲区结束地址
4.1.4 binder_alloc_buf( ) —— 4th
- 实现逻辑 —— 第四段:
- 调用
rb_erase()
将空闲缓冲区从目标进程的相应红黑树中删除。 - 调用
binder_insert_allocated_buffer()
将分配的缓冲区加入到目标进程相应的红黑树中。 - 检查是否有多余的缓冲区,若有,则需要将其封装成一个新的空闲缓冲区
new_buffer
,并添加到目标进程的缓冲区列表,以及空闲红黑树中。 - 最后对新分配的缓冲区
buffer
进行一些初始化操作:- 设置数据缓冲区大小。
- 设置偏移数组缓冲区大小。
- 标记是否应用于异步事务。
- 若用于异步事务,则要减少目标进程中可用于异步事务的内核缓冲区的大小。
- 将
buffer
返回给调用者。
- 调用
rb_erase(best_fit, &proc->free_buffers); buffer->free = 0; binder_insert_allocated_buffer(proc, buffer); if (buffer_size != size) { struct binder_buffer *new_buffer = (void *)buffer->data + size; list_add(&new_buffer->entry, &buffer->entry); new_buffer->free = 1; binder_insert_free_buffer(proc, new_buffer); } binder_debug(BINDER_DEBUG_BUFFER_ALLOC, "binder: %d: binder_alloc_buf size %zd got " "%p\n", proc->pid, size, 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); binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC, "binder: %d: binder_alloc_buf size %zd " "async free %zd\n", proc->pid, size, proc->free_async_space); } return buffer;
4.1.5 binder_insert_allocated_buffer( )
- 实现逻辑:
- 通过
while
循环中的逻辑在已分配页面缓冲区的红黑树proc->allocated_buffers
中找寻一个合适的位置p
。 - 找到位置后,将当前已分配页面的缓冲区
new_buffer
加入到红黑树的相应位置。
- 通过
static void binder_insert_allocated_buffer(struct binder_proc *proc, struct binder_buffer *new_buffer){ struct rb_node **p = &proc->allocated_buffers.rb_node; struct rb_node *parent = NULL; struct binder_buffer *buffer; BUG_ON(new_buffer->free); while (*p) { parent = *p; buffer = rb_entry(parent, struct binder_buffer, rb_node); BUG_ON(buffer->free); if (new_buffer < buffer) p = &parent->rb_left; else if (new_buffer > buffer) p = &parent->rb_right; else BUG(); } rb_link_node(&new_buffer->rb_node, parent, p); rb_insert_color(&new_buffer->rb_node, &proc->allocated_buffers);}
4.2 释放内核缓冲区
- 当进程处理完驱动给它发送的返回协议
BR_TRANSACTION
或BR_REPLY
之后,它就会使用命令协议BC_FREE_BUFFER
通知驱动释放缓冲区。
4.2.1 binder_free_buf( )
- 该函数实现了释放内核缓冲区的操作。
- 实现逻辑:
- 通过
binder_buffer_size()
计算缓冲区大小。 - 通过
ALIGN()
将数据缓冲区大小,偏移数组缓冲区大小分别对齐后相加,保存在size
中。 - 检查要释放的空间是否用于异步事务,若是,则将其占用的大小增加到
proc->free_async_space
中。 - 调用
binder_update_page_range()
释放buffer
用于保存数据的那一部分地址所占用的物理页面。 - 调用
rb_erase()
将buffer
从对应的红黑树proc->allocated_buffers
中删除。 - 若
buffer
不是缓冲区列表proc->buffers
的最后一个元素,且它前后缓冲区也是空闲的,则此时需要将它们合并成一个大的连续的空闲缓冲区。 - 合并的方法是将后一个空闲缓冲区删掉,然后将它占用的地址空间追加到前一个空闲缓冲区中。(如图 10 所示)
- 通过
图 10. 合并两个连续的空闲内核缓冲区
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 *)); ...... ...... 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); 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);}
4.2.2 buffer_start_page( ) && buffer_end_page( )
Binder
驱动以页面大小为单位来分配物理页面,因此在删除空闲缓冲区时,首先需要找到用于描述它的结构体binder_buffer
所在的虚拟页面的地址。- 通过函数
buffer_start_page()
与buffer_end_page()
就可计算出所需的地址。 binder_buffe
r 结构体所在的虚拟地址有两种情况:- 横跨两个虚拟地址页面,此时第一个函数返回第一个页面地址,另一个函数返回第二个页面地址。
- 在同一虚拟地址页面,此时两个函数返回的是同一个页面地址。
- 只分析横跨的情况:
buffer_start_page()
:将结构体binder_buffer
的地址与宏PAGE_MASK
进行按位与操作,即可得到第一个虚拟页面地址。buffer_end_page()
:将binder_buffer
结构体指针加 1,相当于将它前移一个结构体大小,即得到这个结构体的末尾地址。用末尾地址与宏PAGE_MASK
进行按位与操作,则得到第二个虚拟页面地址。- 如图 11 所示,一个计算实例。
图 11. 计算binder_buffer横跨时的两个虚拟地址页面地址
static void *buffer_start_page(struct binder_buffer *buffer){ return (void *)((uintptr_t)buffer & PAGE_MASK);}static void *buffer_end_page(struct binder_buffer *buffer){ return (void *)(((uintptr_t)(buffer + 1) - 1) & PAGE_MASK);}
4.2.3 binder_delete_free_buffer( )
- 该函数作为是删除对应的
binder_buffer
结构体。 - 调用该函数删除缓冲区
buffer
时:- 要保证
buffer
指向的不是目标进程的第一个缓冲区。 - 该缓冲区及它前面一个缓冲区都必须是空闲的。
- 要保证
- 假设
binder_buffer
结构体buffer
、prev
与next
都是横跨两个虚拟页面的。 - 这个函数的实现逻辑输出来有点绕口,一下子虚拟页面,一下又变成物理页面。总觉得这部分直接看代码更好理解。
- 实现逻辑:
prev
与next
初始化为NULL
,它们分别表示buffer
的前一个和后一个缓冲区。- 将
free_page_end
与free_page_start
初始化为1
,这表示需要释放buffer
所横跨的第一个虚拟页面地址与第二个虚拟页面地址所对应的物理页面。 - 检查
buffer
与prev
的关系:- 若
prev
的第二个虚拟地址页面与buffer
的第一个是同一个页面,则将free_page_start
设置为0
,此时表示buffer
所在的第一个虚拟页面对应的物理页面不可释放。 - 在上述情况下,若
prev
的第二个虚拟地址页面与buffer
的第二个也是同一页面,则将free_page_end
设置为0
,此时buffer
所在的第二个虚拟页面对应的物理页面也不可释放。这种情况如图 12 所示。
- 若
- 检查
buffer
与next
的关系:- 若
next
的第一个虚拟页面与buffer
的第二个是同一个页面,则将free_page_end
设置为0
,即不释放buffer
的第二个虚拟页面对应的物理页面。 - 在上述情况下,若
next
的第二个虚拟页面与buffer
的第一个是同一个页面,则将free_page_start
设置为0
,即也不释放buffer
的第一个虚拟页面对应的物理页面。这种情况如图 13 所示。
- 若
- 通过上述检查,调整好
free_page_start
与free_page_end
的值后,就根据这两个值进行相应的释放操作。两个变量的取值与对应的释放操作如图 14 中的表格所示。
图 12. 待删除的buffer是与prev位于同一虚拟地址页面
图 13. 待删除的buffer是与next位于同一虚拟地址页面
图 14. free_page_end与free_page_start的值与物理页面释放的对应关系
static void binder_delete_free_buffer(struct binder_proc *proc, struct binder_buffer *buffer){ struct binder_buffer *prev, *next = NULL; int free_page_end = 1; int free_page_start = 1; BUG_ON(proc->buffers.next == &buffer->entry); prev = list_entry(buffer->entry.prev, struct binder_buffer, entry); BUG_ON(!prev->free); if (buffer_end_page(prev) == buffer_start_page(buffer)) { free_page_start = 0; if (buffer_end_page(prev) == buffer_end_page(buffer)) free_page_end = 0; ...... } if (!list_is_last(&buffer->entry, &proc->buffers)) { next = list_entry(buffer->entry.next, struct binder_buffer, entry); if (buffer_start_page(next) == buffer_end_page(buffer)) { free_page_end = 0; if (buffer_start_page(next) == buffer_start_page(buffer)) free_page_start = 0; ...... } } list_del(&buffer->entry); if (free_page_start || free_page_end) { ...... binder_update_page_range(proc, 0, free_page_start ? buffer_start_page(buffer) : buffer_end_page(buffer), (free_page_end ? buffer_end_page(buffer) : buffer_start_page(buffer)) + PAGE_SIZE, NULL); }}
4.3 查询内核缓冲区
- 进程使用完一个内核缓冲区后:
- 主动使用命令协议
BC_FREE_BUFFER
通知驱动释放缓冲区对应的物理页面。 - 进程只知道缓冲区的用户空间地址。
- 驱动需要知道用于描述该缓冲区的
binder_buffer
结构体,才可以进行释放。 - 为了找到这个结构体,驱动提供了一个查询函数。
- 主动使用命令协议
4.3.1 binder_buffer_lookup( )
- 该函数作用是根据一个用户空间地址来查询一个内核缓冲区。
- 实现逻辑:
kern_ptr
的计算:user_ptr
是用户空间地址,用它减去offsetof(struct binder_buffer, data)
就能得到一个binder_buffer
结构体的用户空间地址。- 接着再减去
proc->user_buffer_offset
即可得到我们所需的内核空间地址。
- 计算出的内核空间地址不一定指向一个有效内核缓冲区:
- 对于进程已分配物理页面的缓冲区,以其内核空间地址为关键字保存在红黑树
proc->allocated_buffers
中。 - 若可在红黑树中找到对应节点,则说明计算得到的
binder_buffer
结构体是指向一个有效内核缓冲区的。
- 对于进程已分配物理页面的缓冲区,以其内核空间地址为关键字保存在红黑树
while
循环体中的逻辑即是寻找这个对应的节点:- 若能找到,则将该节点对应的
binder_buffer
返回给调用者。 - 若不能找到,则返回
NULL
,表示未找到相应缓冲区。
- 若能找到,则将该节点对应的
static struct binder_buffer *binder_buffer_lookup(struct binder_proc *proc, void __user *user_ptr){ struct rb_node *n = proc->allocated_buffers.rb_node; struct binder_buffer *buffer; struct binder_buffer *kern_ptr; kern_ptr = user_ptr - proc->user_buffer_offset - offsetof(struct binder_buffer, data); 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;}
阅读全文
0 0
- Android Binder 机制初步学习 笔记(二)—— Binder 设备基本操作实现
- Android Binder 机制初步学习 笔记(三)—— Binder 进程通讯库简介
- Android Binder 机制初步学习 笔记(四,完结)—— Binder 简单应用示例
- Android Binder 机制初步学习 笔记(一)—— 概述及数据结构介绍
- android binder机制之——(我是binder实例)
- android binder机制之——(创建binder服务)
- android binder机制之——(我是binder实例)
- android binder机制之——(我是binder实例)
- android binder机制之——(创建binder服务)
- binder学习笔记(二)
- Android Binder通信机制学习(二)
- android binder机制实现
- Android Binder机制(二) Binder中的数据结构
- Android Binder机制(二) Binder中的数据结构
- Android Binder 机制学习
- Android Binder机制学习
- android Binder 机制学习
- Binder机制学习笔记-Binder框架
- 深度探讨验证码发展史,账户中心安全科普文
- 第五周项目三—括号的匹配
- maven 私服搭建
- bzoj 1811: [Ioi2005]mea 乱搞
- Java-Collection源码分析(九)——WeakHashMap
- Android Binder 机制初步学习 笔记(二)—— Binder 设备基本操作实现
- FILE文件流操作之fopen、fseek、fread、fclose
- DoubleCheck
- 在IDEA中实战Git
- bzoj1811: [Ioi2005]mea
- 微信运动过气了吗?
- (一)用unicode输出全部的希腊字母
- 「敏捷开发」适合什么样的团队?
- PHP入门篇