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
:
vm_struct
:
- 描述内核地址空间。
- 空间范围
(3G + 896M + 8M)~ 4G
。
- 注意到
4G
的空间中有一段空出来的地址:
3G ~(3G + 896M)
:映射物理内存的前 896M
,它们之间是简单线性关系。
(3G + 896M)~(3G + 896M + 8M)
:安全保护区,用于检测非法指针。
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