相關文章鏈接:
1. Android FrameWork - 學習啓動篇
2. Android FrameWork - 開機啓動 Init 進程
3. Android 開發者需要知道的 Linux 知識
4. 從 Linux 內核的角度來看 Binder 驅動
5. JNI 基礎 - Android 共享內存的序列化過程
6. Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
7. Android Binder 驅動 - Media 服務的添加過程
8. Android Binder 驅動 - 啓動 ServiceManager 進程
9. Android Binder 驅動 - 內核驅動層源碼分析
相關源碼文件:
/drivers/android/binder.c
/drivers/staging/android/binder.c
在《Android Binder 驅動 - Media 服務的添加過程》 與《Android Binder 驅動 - 啓動 ServiceManager 進程》 中遺留幾個驅動層的方法 binder_open、binder_mmap 和 binder_ioctl,本文我們從內核驅動層來做一次徹底的分析。
1. binder_init
用戶態的程序調用 Kernel 層方法需要陷入內核態,進行系統調用(syscall),打開 Binder 驅動方法的調用鏈爲: open-> __open() -> binder_open()。 open() 爲用戶空間的方法,__open() 便是系統調用中相應的處理方法,內部通過查找會找到對應的內核 binder 驅動的 binder_open() 方法。但在驅動啓動時首先會調用驅動的 xxx_init 方法。
static int __init binder_init(void)
{
int ret;
//創建名爲binder的工作隊列
binder_deferred_workqueue = create_singlethread_workqueue("binder");
...
// 基於 misc_class 構造一個設備,將 miscdevice 結構掛載到 misc_list 列表上,並初始化與 linux 設備模型相關的結構
ret = misc_register(&binder_miscdev);
return ret;
}
static struct miscdevice binder_miscdev = {
// 次設備號 動態分配
.minor = MISC_DYNAMIC_MINOR,
// 設備名
.name = "binder",
// 設備的文件操作結構,這是 file_operations 結構
.fops = &binder_fops
};
// 相對應的一些操作
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,
};
2. binder_open
static int binder_open(struct inode *nodp, struct file *filp)
{
// 當前 binder 進程結構體
struct binder_proc *proc;
// 在內核開闢連續空間,大小不能超過 128K,默認初始化值爲 0
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
// 將當前線程的 task 保存到 binder 進程的 tsk
proc->tsk = current;
// 初始化 todo 列表
INIT_LIST_HEAD(&proc->todo);
// 初始化 wait 隊列
init_waitqueue_head(&proc->wait);
// 將當前進程的 nice 值轉換爲進程優先級
proc->default_priority = task_nice(current);
// 同步鎖,因爲 binder 支持多線程訪問
binder_lock(__func__);
// BINDER_PROC 對象創建數加1
binder_stats_created(BINDER_STAT_PROC);
// 將 proc_node 節點添加到 binder_procs 爲表頭的隊列
hlist_add_head(&proc->proc_node, &binder_procs);
proc->pid = current->group_leader->pid;
// 初始化已分發的死亡通知列表
INIT_LIST_HEAD(&proc->delivered_death);
// file 文件指針的 private_data 變量指向 binder_proc 數據
filp->private_data = proc;
// 釋放同步鎖
binder_unlock(__func__);
return 0;
}
struct binder_proc {
struct hlist_node proc_node;
// threads 樹 保存 binder_proc 進程內用於處理用戶請求的線程
struct rb_root threads;
// nodes樹 保存 binder_proc 進程內的 Binder 實體;
struct rb_root nodes;
// 進程內的 Binder 引用,即引用的其它進程的 Binder 實體,以句柄作 key 值來組織
struct rb_root refs_by_desc;
// 進程內的 Binder 引用,即引用的其它進程的 Binder 實體,以地址作 key 值來組織
struct rb_root refs_by_node;
// 當前進程 id
int pid;
struct vm_area_struct *vma;
struct mm_struct *vma_vm_mm;
// 將當前線程的 task_struct
struct task_struct *tsk;
struct files_struct *files;
struct hlist_node deferred_work_node;
int deferred_work;
// 指向內核虛擬空間的地址
void *buffer;
// 用戶虛擬地址空間與內核虛擬地址空間的偏移量
ptrdiff_t user_buffer_offset;
struct list_head buffers;
struct rb_root free_buffers;
struct rb_root allocated_buffers;
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;
};
創建 binder_proc 對象,並把當前進程等信息保存到 binder_proc 對象,該對象管理 IPC 所需的各種信息並擁有其他結構體的根結構體;再把 binder_proc 對象保存到文件指針 filp,以及把 binder_proc 加入到全局鏈表 binder_procs。
3. binder_mmap
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
// 內核虛擬空間
struct vm_struct *area;
// 從 filp 中獲取之前打開保存的
struct binder_proc *proc = filp->private_data;
const char *failure_string;
struct binder_buffer *buffer;
if (proc->tsk != current)
return -EINVAL;
// 保證映射內存大小不超過 4M
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
// 同步鎖
mutex_lock(&binder_mmap_lock);
// 採用 IOREMAP 方式,分配一個連續的內核虛擬空間,與進程虛擬空間大小一致
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);
...
// 分配物理頁的指針數組,數組大小爲 vma 的等效 page 個數;
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;
//分配物理頁面,同時映射到內核空間和進程空間,先分配1個物理頁
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;
}
// binder_buffer 對象 指向 proc 的 buffer 地址
buffer = proc->buffer;
// 創建進程的 buffers 鏈表頭
INIT_LIST_HEAD(&proc->buffers);
// 將 binder_buffer 地址 加入到所屬進程的 buffers 隊列
list_add(&buffer->entry, &proc->buffers);
buffer->free = 1;
// 將空閒 buffer 放入 proc->free_buffers 中
binder_insert_free_buffer(proc, buffer);
// 異步可用空間大小爲 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;
return 0;
...// 錯誤flags跳轉處,free釋放內存之類的操作
return ret;
}
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 page **page;
// 內存結構體
struct mm_struct *mm;
if (vma)
// binder_mmap 過程 vma 不爲空,其他情況都爲空
mm = NULL;
else
// 獲取 mm 結構體,從 tsk 中獲取
mm = get_task_mm(proc->tsk);
if (mm) {
// 獲取 mm_struct 的寫信號量
down_write(&mm->mmap_sem);
vma = proc->vma;
}
// 此處 allocate 爲 1,代表分配過程。如果爲 0 則代表釋放過程
if (allocate == 0)
goto free_range;
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
// 分配一個 page 的物理內存
*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
// 物理空間映射到虛擬內核空間
ret = map_kernel_range_noflush((unsigned long)page_addr,
PAGE_SIZE, PAGE_KERNEL, page);
// 用戶空間地址 = 內核空間地址 + 偏移量
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);
// 減少 mm->mm_users 計數
mmput(mm);
}
return 0;
//釋放內存的流程
free_range:
...
return -ENOMEM;
}
上面這些代碼大部分都是 linux 內核方面的知識了,我們來簡單解釋下:task_struct 代表的是進程或者線程管理的進程控制結構體,mm_struct 是 task_struct 結構體中虛擬地址管理的結構體,vm_area_struct 代表的是虛擬用戶空間映射管理的結構體,vm_struct 代表的是內核空間管理的結構體。alloc_page 方法的作用是分配一個物理內存,map_kernel_range_noflush 方法的作用是將物理空間映射到虛擬內核空間,vm_insert_page 方法的作用是將物理空間映射到虛擬用戶空間。binder_mmap 的主要作用就是開闢一塊連續的內核空間,並且開闢一個物理頁的地址空間,同時映射到用戶空間和內核空間。
4. binder_ioctl
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread; // binder線程
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
// 進入休眠狀態,直到中斷喚醒
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
goto err_unlocked;
binder_lock(__func__);
// 獲取 binder_thread
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
switch (cmd) {
// 進行 binder 的讀寫操作
case BINDER_WRITE_READ:
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
// 設置 binder 最大支持的線程數
case BINDER_SET_MAX_THREADS:
if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
ret = -EINVAL;
goto err;
}
break;
// 成爲 binder 的上下文管理者,也就是 ServiceManager 成爲守護進程
case BINDER_SET_CONTEXT_MGR:
ret = binder_ioctl_set_ctx_mgr(filp);
if (ret)
goto err;
break;
// 當 binder 線程退出,釋放 binder 線程
case BINDER_THREAD_EXIT:
binder_free_thread(proc, thread);
thread = NULL;
break;
// 獲取 binder 的版本號
case BINDER_VERSION: {
struct binder_version __user *ver = ubuf;
if (size != sizeof(struct binder_version)) {
ret = -EINVAL;
goto err;
}
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
&ver->protocol_version)) {
ret = -EINVAL;
goto err;
}
break;
}
default:
ret = -EINVAL;
goto err;
}
ret = 0;
err:
if (thread)
thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
binder_unlock(__func__);
wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
err_unlocked:
trace_binder_ioctl_done(ret);
return ret;
}
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
struct binder_thread *thread = NULL;
struct rb_node *parent = NULL;
struct rb_node **p = &proc->threads.rb_node;
// 根據當前進程的 pid,從 binder_proc 中查找相應的 binder_thread
while (*p) {
parent = *p;
thread = rb_entry(parent, struct binder_thread, rb_node);
if (current->pid < thread->pid)
p = &(*p)->rb_left;
else if (current->pid > thread->pid)
p = &(*p)->rb_right;
else
break;
}
if (*p == NULL) {
// 新建 binder_thread 結構體
thread = kzalloc(sizeof(*thread), GFP_KERNEL);
if (thread == NULL)
return NULL;
binder_stats_created(BINDER_STAT_THREAD);
thread->proc = proc;
// 保存當前進程(線程)的 pid
thread->pid = current->pid;
init_waitqueue_head(&thread->wait);
// 初始化線程的 todo 隊列
INIT_LIST_HEAD(&thread->todo);
// 把線程加入 proc->threads
rb_link_node(&thread->rb_node, parent, p);
rb_insert_color(&thread->rb_node, &proc->threads);
thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
thread->return_error = BR_OK;
thread->return_error2 = BR_OK;
}
return thread;
}
static int binder_ioctl_write_read(struct file *filp,
unsigned int cmd, unsigned long arg,
struct binder_thread *thread)
{
int ret = 0;
struct binder_proc *proc = filp->private_data;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto out;
}
// 把用戶空間數據 ubuf 拷貝到 bwr
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
// 當寫緩存中有數據,則執行 binder 寫操作
if (bwr.write_size > 0) {
ret = binder_thread_write(proc, thread,
bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
trace_binder_write_done(ret);
// 當寫失敗,再將 bwr 數據寫回用戶空間,並返回
if (ret < 0) {
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
// 當讀緩存中有數據,則執行 binder 讀操作
if (bwr.read_size > 0) {
ret = binder_thread_read(proc, thread,
bwr.read_buffer, bwr.read_size, &bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
trace_binder_read_done(ret);
// 喚醒等待狀態的線程
if (!list_empty(&proc->todo))
wake_up_interruptible(&proc->wait);
// 當讀失敗,再將bwr數據寫回用戶空間,並返回
if (ret < 0) {
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto out;
}
}
// 將內核數據 bwr 拷貝到用戶空間 ubuf
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto out;
}
out:
return ret;
}
通過以上分析,ioctl 命令有 BINDER_WRITE_READ (binder 讀寫交互)、BINDER_SET_CONTEXT_MGR(servicemanager進程成爲上下文管理者)、BINDER_SET_MAX_THREADS(設置最大線程數)、BINDER_VERSION(獲取 binder 版本)。有兩個核心複雜方法 binder_thread_write 和 binder_thread_read,由於這裏面的代碼邏輯較爲複雜,因此到後面我們帶着線索再去具體分析。
視頻地址:https://pan.baidu.com/s/1j_wgzITcgABVbThvO0VBPA
視頻密碼:jj4b