再学安卓 - binder之驱动加载

89 阅读19分钟

11_0.png

本文分析基于Android14(aosp分支android-14.0.0_r28)(内核分支common-android14-6.1)

前言

随着对Binder的探索逐步深入,我们总是在某处边界停下来,比如:hdr.type到了Client那边就会变成BINDER_TYPE_HANDLE?ServiceManager的handle值是0?binder_node和binder_ref到底代表什么?零零总总的这些问题,答案总是来自于一句结论:Binder驱动做了xxx。这样的知识存在断点,总是觉得不够通透,因此,从本篇开始,我们就穿过这层边界,到内核中看看驱动是怎么完成那些偷梁换柱的操作。

为了构建完整的Binder驱动知识体系,本篇先介绍驱动加载(initcall)以及调用原理(syscall),若您已掌握Linux相关内核知识,则可以跳过本篇,查看下一篇关于binder驱动重点函数的介绍。

initcall机制

initcall机制广泛用于内核模块、子系统或设备驱动的初始化。相比多个模块、子系统在启动代码中添加自己的init函数,initcall更加灵活解耦。我们以binder驱动的初始化为例进行简要介绍,若想要了解更全面的initcall内容,请查阅其他资料。

// common/drivers/android/binder.c

static int __init binder_init(void) {
    ... ...
}
device_initcall(binder_init);

在binder驱动源码中,我们会看到初始化函数binder_init指针被放到了一个宏定义(device_initcall)中。该宏定义如下:

// common/include/linux/init.h

#define device_initcall(fn)		__define_initcall(fn, 6)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

... ...

// 中间还经历了几个宏定义的包装,最终来到以下宏定义中
#define ____define_initcall(fn, __unused, __name, __sec)  \
        static initcall_t __name                          \
        __used                                            \
        __attribute__((__section__(__sec))) = fn;

其中的id是指initcall的level等级,等级数值越小,优先级越高,内核越先调用该initcall。binder驱动的initcall等级为6。fn则为binder_init。最终宏定义被展开为一个赋值语句:

11_1.png

依次解释一下:

  • 修饰符和变量类型:static和initcall_t就是我们熟悉的变量修饰符和类型,initcall_t相当于无参且返回值为int的函数指针类型的别名。
  • 变量名称:KBUILD_MODNAME、COUNTER、LINE均为编译器插入字段。其中COUNTER为自动计数值,预处理器在预处理过程中遇到宏__COUNTER__就+1,第一次使用时返回 0,第二次返回 1,依此类推。
  • 编译器属性(宏):我们这里看到的都是宏,但他们都会展开成为编译器属性字段,例如:#define __used __attribute__((__used__)),顾名思义这些属性都是写给编译器看的。 __used:即使当前变量没有被任何代码引用也需要保留,保证不被编译器优化掉。 __attribute__((__section__(<section_name>))):指定当前变量在ELF文件中的存放位置(指定的section中)。当前我们讨论的ELF文件其实就是vmlinux,即Linux内核编译成的可执行文件。
  • 函数指针:即需要被执行的初始化函数指针。

对于section不了解的伙伴可以看看ELF文件格式的相关资料,推荐查阅《程序员的自我修养--链接、装载与库》第三章。

绕了一大圈,总结一下,宏xxx_initcall(我们这里是device_initcall)就是指定参数中的函数指针放到vmlinux文件的某个section中,当内核程序启动初始化时,就会按照优先级依次取出section中的若干initcall函数指针,执行相应函数,以达到初始化各个模块、子系统或驱动的目的。

接下来,我们回到第5篇init进程的kernel_init函数中,之前我们关注的是此函数启动init进程的部分,现在我们看看启动init之前,内核是如何执行这些initcall函数的。

// kernel/common/init/main.c

#define __initdata	__section(".init.data")
// initcall_entry_t:initcall_t的别名
// initcall_levels数组元素:指向level第一个初始化函数指针的指针
// __initcall_end:section的结束地址
static initcall_entry_t *initcall_levels[] __initdata = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

static int __ref kernel_init(void *unused) {
	... ...
	kernel_init_freeable();
	... ...
}

// kernel_init函数经过一连串的间接调用,来到do_initcalls
static void __init do_initcalls(void) {
	... ...
	// 遍历所有level,每次循环处理一个level
	// 由于数组最后一个元素表示结束地址不指向初始化函数,因此循环结束条件是数组size-1
	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
		... ...
		do_initcall_level(level, command_line);
	}
	... ...
}

static void __init do_initcall_level(int level, char *command_line) {
	initcall_entry_t *fn;
	... ...
	// 由于level之间首尾相连,所以可以通过fn自增且小于下一个level开始地址来遍历整个level
	// initcall_from_entry函数将fn转换为初始化函数的实际地址
	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(initcall_from_entry(fn));
}

// 执行初始化函数
int __init_or_module do_one_initcall(initcall_t fn) {
	... ...
	do_trace_initcall_start(fn);
	ret = fn();
	do_trace_initcall_finish(fn, ret);
	... ...
	return ret;
}

binder_init函数

// common/drivers/android/binder.c

static int __init binder_init(void) {
    int ret;
	char *device_name, *device_tmp;
	struct binder_device *device;
	struct hlist_node *tmp;
	char *device_names = NULL;
	const struct binder_debugfs_entry *db_entry;

	// 创建并注册内存回收器,当系统内存紧张时会调用回收器的回调函数执行清理内存的逻辑
	// 创建成功返回0,否则结束binder初始化返回错误码
	ret = binder_alloc_shrinker_init();
	if (ret)
		return ret;

	... ...

	// ① 在DebugFS文件系统中创建binder信息目录
	binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);

	// ② 创建binder信息输出的相关文件,例如:state、stats、transactions等
	binder_for_each_debugfs_entry(db_entry)
		debugfs_create_file(db_entry->name,
					db_entry->mode,
					binder_debugfs_dir_entry_root,
					db_entry->data,
					db_entry->fops);

	// ③ 创建binder/proc目录,目录下以pid为名称的文件输出各个进程相关的binder信息
	binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
						binder_debugfs_dir_entry_root);

	... ...
	// ④ 初始化binder设备
	while ((device_name = strsep(&device_tmp, ","))) {
		ret = init_binder_device(device_name);
		if (ret)
			goto err_init_binder_device_failed;
	}

	// ⑤ 初始化binderfs文件系统
	ret = init_binderfs();
	... ...
	return ret;
}

① DebugFS是Linux内核提供的便于开发调试内核程序的内存文件系统,开发人员可以通过用户空间进程(比如:shell)很方便的观察内核程序向这个文件系统中写入的信息和日志。当前步骤就是在DebugFS文件系统下创建了binder目录,存放binder相关的信息和日志。函数debugfs_create_dir返回dentry类型指针,dentry是代表Linux目录项的结构体,目录项可以是文件、目录、符号链接。

DebugFS和Ext2、Ext3等磁盘文件系统类似,但DebugFS仅存在于内存中,它管理的对象也仅是内存中的数据。若想要深入理解Linux文件系统及相关结构,推荐查阅《深入理解Linux内核》第12章-虚拟文件系统。

② binder_for_each_debugfs_entry(db_entry)看起来就非常奇怪,像是一个函数却没有分号结尾,点进去一看,原来它是一个宏,定义如下:

// common/drivers/android/binder_internal.h

#define binder_for_each_debugfs_entry(entry)	\
	for ((entry) = binder_debugfs_entries;	\
	     (entry)->name;			\
	     (entry)++)

宏展开是for循环的头,完整展开如下:

// common/drivers/android/binder.c

for ((entry) = binder_debugfs_entries; (entry)->name; (entry)++)
	debugfs_create_file(db_entry->name,
					db_entry->mode,
					binder_debugfs_dir_entry_root, // 在上一步创建的binder目录中创建文件
					db_entry->data,
					db_entry->fops);

// binder_debugfs_entries数组是存放binder目录下需要创建的文件
const struct binder_debugfs_entry binder_debugfs_entries[] = {
	{
		.name = "state",
		.mode = 0444,
		.fops = &state_fops, // 设置了该文件标准操作对应的函数,比如:open、read等
		.data = NULL,
	},
	{
		.name = "stats",
		... ...
	},
	{
		.name = "transactions",
		... ...
	},
	{
		.name = "transaction_log",
		... ...
	},
	{
		.name = "failed_transaction_log",
		... ...
	},
	{} // 最后增加一个空字段,作为结束判断符
};

③ 创建binder/proc目录,这个目录中会以进程为单位创建文件,方便我们从进程的角度,观察binder的相关信息。

我们来粗略的看看DebugFS中的binder目录下这些文件记录了什么内容,可能我们现在暂时还不能完全理解里面所有数据的意思,但等我们阅读驱动源码之后肯定会有更全面的认识。

Android模拟器系统默认没有挂载DebugFS文件系统,需要root权限手动执行命令挂载:mount -t debugfs debugfs /sys/kernel/debug

stats文件:binder整体情况以及各个进程中binder线程、事务等统计信息。

11_2.png

state文件:各个进程的thread、node、ref等状态信息

11_3.png

binder/proc目录:以各个进程pid为文件名,文件内容跟state的信息类似。

④ 初始化3个binder设备。

为什么初始化3个Binder设备?请参见附录的介绍。

// common/drivers/android/binder.c

static int __init binder_init(void) {
	... ...
	device_tmp = device_names;
	// device_tmp为字符串指针,指向的字符串为“bidner,hwbidner,vndbinder”
	// 以逗号拆分字符串,循环初始化三个bidner设备
	while ((device_name = strsep(&device_tmp, ","))) {
		ret = init_binder_device(device_name);
		if (ret)
			goto err_init_binder_device_failed;
	}
	... ...
}

static int __init init_binder_device(const char *name) {
	int ret;
	struct binder_device *binder_device;

	// 为binder设备结构体分配内存
	binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
	if (!binder_device)
		return -ENOMEM;

	// 设置binder设备文件操作函数
	// binder_fops就是file_operations类型结构体
	// 用户空间针对设备文件的操作最终都会调用这里的函数进行处理
	// 具体有哪些函数,请参见本段代码末尾
	binder_device->miscdev.fops = &binder_fops;
	... ...

	// 初始化引用计数
	refcount_set(&binder_device->ref, 1);
	... ...

	// 注册binder为misc类型的设备
	ret = misc_register(&binder_device->miscdev);
	if (ret < 0) {
		kfree(binder_device);
		return ret;
	}

	// 将新设备添加到全局哈希链表binder_devices的头部
	hlist_add_head(&binder_device->hlist, &binder_devices);

	return ret;
}

const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = compat_ptr_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};

⑤ 初始化binderfs文件系统。

// common/drivers/android/binderfs.c

int __init init_binderfs(void) {
	int ret;
	const char *name;
	size_t len;

	... ...

	// 注册文件系统
	// 静态变量binder_fs_type为file_system_type类型的结构体
	// 若无异常,在register_filesystem函数的最后,binder_fs_type将被加入到内核文件系统链表的最后
	ret = register_filesystem(&binder_fs_type);
	if (ret) {
		unregister_chrdev_region(binderfs_dev, BINDERFS_MAX_MINOR);
		return ret;
	}

	return ret;
}

static struct file_system_type binder_fs_type = {
	.name			= "binder",  //文件系统名称
	.init_fs_context	= binderfs_init_fs_context,  //初始化文件系统上下文函数指针(记住这个重要的函数指针)
	.parameters		= binderfs_fs_parameters,  //文件系统参数
	.kill_sb		= binderfs_kill_super,  //卸载文件系统的函数指针
	.fs_flags		= FS_USERNS_MOUNT,  //文件系统标识
};

初始化文件系统之后,在init进程执行rc脚本阶段,会将文件系统挂载到/dev/binderfs目录下并在/dev目录下创建binder设备文件的符号链接。

// system/core/rootdir/init.rc

# Mount binderfs
mkdir /dev/binderfs
mount binder binder /dev/binderfs stats=global
chmod 0755 /dev/binderfs

symlink /dev/binderfs/binder /dev/binder
symlink /dev/binderfs/hwbinder /dev/hwbinder
symlink /dev/binderfs/vndbinder /dev/vndbinder

chmod 0666 /dev/binderfs/hwbinder
chmod 0666 /dev/binderfs/binder
chmod 0666 /dev/binderfs/vndbinder

我们可以通过命令查看binder文件系统的挂载情况:

emulator64_x86_64:/ # mount | grep binder
binder on /dev/binderfs type binder (rw,relatime,max=1048576,stats=global)

emulator64_x86_64:/ # ls -l /dev | grep binder
lrwxrwxrwx  1 root        root              20 2025-01-10 03:24 binder -> /dev/binderfs/binder
drwxr-xr-x  4 root        root               0 2025-01-10 03:24 binderfs
lrwxrwxrwx  1 root        root              22 2025-01-10 03:24 hwbinder -> /dev/binderfs/hwbinder
lrwxrwxrwx  1 root        root              23 2025-01-10 03:24 vndbinder -> /dev/binderfs/vndbinder

这里会有一个疑点,binder文件系统初始化之后驱动初始化函数就结束了,并没有创建相应的binder设备文件。但我们执行ls命令时能看到/dev/binderfs目录下有binder设备文件。那么这些设备文件是什么时候创建的呢?文件创建一定是在文件系统初始化之后,同时需要文件存在才能创建符号链接,那么文件的创建一定是在驱动初始化函数和rc脚本创建符号链接之间。我们很自然的锁定了mount那一步。

Binderfs的挂载

mount虽然是shell命令,它最终还是会触发系统调用do_mount完成挂载。

// common/fs/namespace.c

long do_mount(const char *dev_name, const char __user *dir_name,
		const char *type_page, unsigned long flags, void *data_page) {
	struct path path;
	int ret;

	// 解析、检查传入的路径字符串
	ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);
	if (ret)
		return ret;
	// 开始mount
	ret = path_mount(dev_name, &path, type_page, flags, data_page);
	path_put(&path);
	return ret;
}

int path_mount(const char *dev_name, struct path *path,
		const char *type_page, unsigned long flags, void *data_page) {
	... ...
	return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
			    data_page);
}

static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
			int mnt_flags, const char *name, void *data) {
	struct file_system_type *type;
	struct fs_context *fc;
	... ...
	// 还记得前面提到的binder_fs_type吗?这个函数就是从内核文件系统链表中将它找出来
	type = get_fs_type(fstype);
	... ...
	// ① 为文件系统创建一个上下文实例,会给fs_context赋值一些后续流程会使用的函数指针
	fc = fs_context_for_mount(type, sb_flags);
	... ...
	if (!err)
		// ② 完成文件系统的超级块、根目录的inode、dentry等数据结构的创建
		err = vfs_get_tree(fc);
	if (!err)
		// 必需的基础数据结构在上一步已完成创建,这个函数开始执行挂载动作
		err = do_new_mount_fc(fc, path, mnt_flags);
	... ...
	return err;
}

① 关于fs_context_for_mount函数的分析

// common/fs/fs_context.c

struct fs_context *fs_context_for_mount(struct file_system_type *fs_type,
					unsigned int sb_flags) {
	return alloc_fs_context(fs_type, NULL, sb_flags, 0, FS_CONTEXT_FOR_MOUNT);
}

static struct fs_context *alloc_fs_context(struct file_system_type *fs_type,
				      struct dentry *reference,
				      unsigned int sb_flags,
				      unsigned int sb_flags_mask,
				      enum fs_context_purpose purpose) {
	// 声明了一个函数指针,此函数返回值为int,入参为fs_context类型的指针
	int (*init_fs_context)(struct fs_context *);
	struct fs_context *fc;
	int ret = -ENOMEM;

	// 为fs_context申请内存
	fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL_ACCOUNT);
	... ...
	fc->purpose	= purpose;
	... ...
	// 将file_system_type存放到fs_context的字段中
	fc->fs_type	= get_filesystem(fs_type);
	... ... 
	fc->log.prefix	= fs_type->name;
	... ...
	// 取出fc->fs_type存放的init_fs_context函数指针并调用
	// 这里的fs_type->init_fs_context就等于binder_fs_type->binderfs_init_fs_context
	init_fs_context = fc->fs_type->init_fs_context;
	... ...
	ret = init_fs_context(fc);
	... ...
	return fc;
}

// common/drivers/android/binderfs.c
static int binderfs_init_fs_context(struct fs_context *fc) {
	... ...
	fc->ops = &binderfs_fs_context_ops;
	return 0;
}

// binderfs_init_fs_context将fc->ops初始化为binderfs_fs_context_ops
// 保存与文件系统上下文相关的函数指针
static const struct fs_context_operations binderfs_fs_context_ops = {
	.free		= binderfs_fs_context_free,
	.get_tree	= binderfs_fs_context_get_tree,
	.parse_param	= binderfs_fs_context_parse_param,
	.reconfigure	= binderfs_fs_context_reconfigure,
};

② 关于vfs_get_tree函数的分析

// common/fs/super.c

int vfs_get_tree(struct fs_context *fc) {
	struct super_block *sb;
	int error;
	... ...
	// 根据之前的分析得知,fc->ops->get_tree等于binderfs_fs_context_ops->binderfs_fs_context_get_tree
	error = fc->ops->get_tree(fc);
	if (error < 0)
		return error;
	... ..
	... ...
	return 0;
}

// common/drivers/android/binderfs.c
static int binderfs_fs_context_get_tree(struct fs_context *fc) {
	// get_tree_nodev函数创建文件系统的超级块super_block
	// 同时,在创建过程中会调用binderfs_fill_super函数来填充超级块
	return get_tree_nodev(fc, binderfs_fill_super);
}

// 此函数中有很多初始化操作,但我们最关心的还是还是设备文件的创建
static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc) {
	... ...
	// binder_devices_param为“bidner,hwbidner,vndbinder”
	// for循环依次调用binderfs_binder_device_create
	name = binder_devices_param;
	for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
		strscpy(device_info.name, name, len + 1);
		ret = binderfs_binder_device_create(inode, NULL, &device_info);
		if (ret)
			return ret;
		name += len;
		if (*name == ',')
			name++;
	}
	... ..
	return 0;
}

// 创建设备文件,同时创建对应的inode、dentry
static int binderfs_binder_device_create(struct inode *ref_inode,
					 struct binderfs_device __user *userp,
					 struct binderfs_device *req) {
	... ...
	device = kzalloc(sizeof(*device), GFP_KERNEL);
	if (!device)
		goto err;

	inode = new_inode(sb);
	if (!inode)
		goto err;
	... ...
	// 注意:binder_fops,它保存着操作binder设备文件的函数指针
	// 针对操作设备文件的系统调用最终都会映射到这些函数指针对应的函数
	inode->i_fop = &binder_fops;
	inode->i_uid = info->root_uid;
	inode->i_gid = info->root_gid;
	... ...
	device->binderfs_inode = inode;
	... ...
	return 0;
}

对于binder设备文件的疑问,引出了binderfs挂载的流程分析,最终我们在mount流程中发现了设备文件的创建过程,并且也证实了设备文件对应的inode中保存了文件操作函数指针。这对于我们接下来binder驱动函数的分析至关重要。当然由于我们的关注点始终在binder驱动相关内容上,因此本小节对Linux文件系统、VFS及挂载操作的介绍非常有限,推荐大家查阅其他专著查漏补缺。

一次系统调用binder_open

在上一篇介绍ServiceManager的内容中,我们得知ServiceManager在启动时会创建ProcessState实例,而在ProcessState的构造函数中,会打开binder设备文件,我们来复习一下这段代码:

// frameworks/native/libs/binder/ProcessState.cpp

ProcessState::ProcessState(const char* driver)
      : mDriverName(String8(driver)),
        mDriverFD(-1),
        mVMStart(MAP_FAILED),
        ... ...
        mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
        mCurrentThreads(0),
        mKernelStartedThreads(0),
        ... ...
        mThreadPoolStarted(false),
        mThreadPoolSeq(1),
        mCallRestriction(CallRestriction::NONE) {
    // 打开Binder设备文件
    base::Result<int> opened = open_driver(driver);
	... ...
}

static base::Result<int> open_driver(const char* driver) {
	// 通过系统调用open打开设备文件
    int fd = open(driver, O_RDWR | O_CLOEXEC);
    ... ...
    return fd;
}

open函数的实现在bionic目录下的cpp,第4篇我们曾经介绍过bionic目录下存放的是符合POSIX标准的库,包含许多系统调用函数的实现或宏定义。

// bionic/libc/bionic/open.cpp

int open(const char* pathname, int flags, ...) {
  ... ...
  // FDTRACK_CREATE是跟踪文件描述符创建的宏,用于调试和性能分析,可以暂时跳过
  return FDTRACK_CREATE(__openat(AT_FDCWD, pathname, force_O_LARGEFILE(flags), mode));
}

__openat函数的实现对应汇编代码,而相应的汇编代码由gensyscalls.py根据SYSCALLS.TXT的描述自动生成。

// bionic/libc/SYSCALLS.TXT

# This file is used to automatically generate bionic's system call stubs.
# This file is processed by a python script named gensyscalls.py, run via
# genrules in Android.bp.

# all表示需要脚本针对所有支持的CPU架构生成对应的汇编代码
int __openat:openat(int, const char*, int, mode_t) all

汇编代码将生成到out目录对应的CPU架构目录下。这里展示的是arm64架构下的汇编代码。其他CPU架构的汇编代码略有不同,但它们的目的都一样,就是完成系统调用open,打开目标文件。

// out/soong/.intermediates/bionic/libc/syscalls-arm64/gen/syscalls-arm64.S

ENTRY(__openat)
    mov x8, __NR_openat
    svc #0

    cmn x0, #(MAX_ERRNO + 1)
    cneg x0, x0, hi
    b.hi __set_errno_internal

    ret
END(__openat)

以上汇编代码我们关注最重要的前两句:mov x8, __NR_openat和svc #0。

// __NR_openat为系统调用号,内核将所有系统调用都编号放入系统调用表中,方便查询调用
// 此条语句是将__NR_openat加载到x8寄存器中
mov x8, __NR_openat

// svc指令将触发软中断,当前进程将切换到内核态由内核从x8寄存器中取出调用号
// 然后查找系统调用表对应的函数执行
svc #0

__NR_openat在arm64架构下为56,不同CPU架构系统调用号不一样,请根据实际情况查阅不同的头文件。

// bionic/libc/kernel/uapi/asm-generic/unistd.h
#define __NR_openat 56

在内核中,也针对系统调用号有所定义。

// common/include/uapi/asm-generic/unistd.h

#define __NR_openat 56
__SYSCALL(__NR_openat, sys_openat)

宏__SYSCALL的定义如下:

// common/arch/arm64/kernel/sys.c

#undef __SYSCALL
#define __SYSCALL(nr, sym)	asmlinkage long __arm64_##sym(const struct pt_regs *);

展开得到:asmlinkage long __arm64_sys_openat(const struct pt_regs *);

__arm64_sys_openat会被宏SYSCALL_DEFINE4定义。

由于该宏展开过程比较复杂,且与主旨关系不大,我们直接跳过,感兴趣的可以查阅common/include/linux/syscalls.h和common/arch/arm64/include/asm/syscall_wrapper.h

// common/fs/open.c

SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags,
		umode_t, mode) {
	if (force_o_largefile())
		flags |= O_LARGEFILE;
	return do_sys_open(dfd, filename, flags, mode);
}

do_sys_open将开启打开文件的标准流程,这部分流程推荐大家查阅其他专著,我们重点关注流程的最后,之前设置的binder_fops是如何被取出执行的,算是对之前初始化设备文件的闭环。

// common/fs/open.c

static int do_dentry_open(struct file *f,
			  struct inode *inode,
			  int (*open)(struct inode *, struct file *)) {
	... ...
	// 入参inode对应着需要打开的文件,我们这个场景下就是/dev/binder
	f->f_inode = inode;
	... ...

	// 从inode中取出文件操作函数指针,即之前存入的binder_fops
	f->f_op = fops_get(inode->i_fop);
	... ...
	
	/* normally all 3 are set; ->open() can clear them if needed */
	f->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
	if (!open)
		// 若传入的open函数指针为空,那么取inode中保存的open函数指针
		// 由此可以看出,do_dentry_open调用方可以指定open函数来覆盖默认行为
		// 我们这个场景下open传入是null,因此会调用binder_open
		open = f->f_op->open;
	if (open) {
		error = open(inode, f);
		if (error)
			goto cleanup_all;
	}
	... ...
}

通过以上的流程分析,我们还原了一次系统调用真实的模样,最终抵达我们的终点binder_open。后续我们分析其他系统调用时将不再关注调用过程,直接分析binder驱动函数本身。

附录

三个Binder设备(bidner,hwbidner,vndbinder)

当年Android 8发布时带来了一个重大的变革 - Project Treble,此计划将硬件供应商代码从system.img中剥离出来,放到verndor.img中,以实现Android系统代码与底软代码的解耦,从而方便两者的独立升级。解耦之后,部分底软代码逻辑被放入独立的进程中,称之为hal进程。这就涉及到进程间通信,为了将解耦进行到底,通信交互及管理也必须区分一下,形成了三个Binder设备(bidner,hwbidner,vndbinder)以及三个用户空间大总管(servicemanager,hwservicemanager,vndservicemanager)。

  • 系统进程之间通信使用binder体系
  • hal与系统进程之间通信使用hw体系
  • hal与hal进程之间通信使用vnd体系 三种体系的运行原理一样,甚至代码都几乎一样。因此,在初始化阶段也就需要创建三个binder设备。