参考资料
- module_init解析及内核initcall的初始化顺序 https://www.cnblogs.com/chaozhu/p/6410271.html
- 各种initcall的执行先后顺序 https://blog.csdn.net/fenzhikeji/article/details/6860143
- binder 驱动的操作 https://blog.csdn.net/qq_15893929/article/details/103965668
- Android的IPC机制Binder的各个部分 http://tech.it168.com/a2009/0331/2703/000000270388_all.shtml
- 字符设备驱动-使用alloc_chrdev_region+cdev注册设备驱动 https://blog.csdn.net/weixin_42314225/article/details/81112217
- linux文件系统 - 初始化(一) https://www.cnblogs.com/alantu2018/p/8447303.html
- mount过程分析之五(mount_bdev->fill_super) https://blog.csdn.net/ZR_Lang/article/details/40115013
- VFS四大对象之一 struct super_block https://www.cnblogs.com/linhaostudy/p/7427027.html
- Linux字符设备驱动file_operations https://www.cnblogs.com/chen-farsight/p/6181341.html
简介
接上文,想要了解binder驱动的工作原理,我们从binder驱动加载过程开始:
在android/kernel/msm-4.19/drivers/android/binder.c中,我们可以看到有这么一行:
device_initcall(binder_init);
在Linux内核的启动过程中,一个驱动的注册用module_init调用,即device_initcall,它的initcall 的level为6。
他可以将驱动设备加载进内核中,以供后续使用。
一. Linux内核init call过程
在Android开机流程(一)一文中有阐述Android开机过程。
我们知道系统加电自检后,引导程序执行完毕,内核映像被加载到内存并获得控制权之后,会启动kernel_init。
1.1 android/kernel/msm-4.9/init/main.c
static int __ref kernel_init(void *unused)
{
int ret;
// 初始化kernel参数
kernel_init_freeable()
......
}
1.2 android/kernel/msm-4.9/init/main.c:kernel_init_freeable
static noinline void __init kernel_init_freeable(void)
{
// ......
do_basic_setup();
// ......
}
static void __init do_basic_setup(void)
{
// ......
// 调用各种initcall
do_initcalls();
}
static void __init do_initcalls(void)
{
int level;
// initcall_levels 的大小是9,所以这里level是从0到7的
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
1.3 android/kernel/msm-4.9/init/main.c:do_initcall_level
/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
static void __init do_initcall_level(int level)
{
// ......
// 按顺序执行initcall_levels,其实就是initcall_level_names中的顺序。
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
其实Andorid开机过程中的init.rc中各种init进程和这里类似。
二. binder驱动的初始化
在了解Linux内核各种init call之后, 我们注意到binder.c最后几行代码中有这么一行:
device_initcall(binder_init);
而在android/kernel/msm-4.9/include/linux/init.h中可以看到定义:
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
// HERE!!!
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
所以binder驱动的加载时机是先于Android的init进程的,在Linux内核启动中是以level为6(device)的优先级加载的。
2.1 binder.c:binder_init
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;
// 初始化binder缓冲区,其实就是一个list_lru结构体:binder_alloc_lru
ret = binder_alloc_shrinker_init();
// ......
// 创建binder相关文件
// 在sys/kernel/debug目录下,创建binder目录 - debugfs是一种用户内核调试的虚拟文件系统
binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
// 创建sys/kernel/debug/binder/proc目录
if (binder_debugfs_dir_entry_root)
binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
binder_debugfs_dir_entry_root);
// 创建其余文件
if (binder_debugfs_dir_entry_root) {
debugfs_create_file("state",
0444,
binder_debugfs_dir_entry_root,
NULL,
// 2.1.1 操作函数的定义
&binder_state_fops);
debugfs_create_file("stats",
0444,
binder_debugfs_dir_entry_root,
NULL,
&binder_stats_fops);
debugfs_create_file("transactions",
0444,
binder_debugfs_dir_entry_root,
NULL,
&binder_transactions_fops);
debugfs_create_file("transaction_log",
0444,
binder_debugfs_dir_entry_root,
&binder_transaction_log,
&binder_transaction_log_fops);
debugfs_create_file("failed_transaction_log",
0444,
binder_debugfs_dir_entry_root,
&binder_transaction_log_failed,
&binder_transaction_log_fops);
}
// CONFIG_ANDROID_BINDERFS是编译配置,一般都是定义的,所以这个分支不进入
if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) &&
strcmp(binder_devices_param, "") != 0) {
// ......
}
// 2.2 初始化binder文件系统
ret = init_binderfs();
if (ret)
goto err_init_binder_device_failed;
return ret;
// ......
}
这里主要是创建了sys/kernel/debug/binder目录,以及其子目录或文件:
- proc:记录调用Binder各个进程的内容
- state:记录状态信息,操作函数binder_state_fops
- stats:记录统计信息,操作函数binder_stats_fops
- transactions:记录transaction相关信息,操作函数binder_transactions_fops
- transaction_log:记录transaction日志,操作函数binder_transaction_log_fops
- failed_transaction_log:记录失败的transaction日志信息,操作函数binder_transaction_log_fops
这里比较奇怪的是没有找到创建文件(目录)时的操作函数的定义,比如binder_state_fops。
2.1.1 操作函数的定义
以binder_state_fops为例,其实这些函数的定义是通过宏定义统一实现的。
在文件android/kernel/msm-4.19/drivers/android/binder_internal.h中我们可以看到有如下函数的声明:
int binder_stats_show(struct seq_file *m, void *unused);
DEFINE_SHOW_ATTRIBUTE(binder_stats);
int binder_state_show(struct seq_file *m, void *unused);
DEFINE_SHOW_ATTRIBUTE(binder_state);
int binder_transactions_show(struct seq_file *m, void *unused);
DEFINE_SHOW_ATTRIBUTE(binder_transactions);
int binder_transaction_log_show(struct seq_file *m, void *unused);
DEFINE_SHOW_ATTRIBUTE(binder_transaction_log);
我们看binder_state_show这个函数下面的DEFINE_SHOW_ATTRIBUTE这个宏。
2.1.2 DEFINE_SHOW_ATTRIBUTE
这个宏的申明是在文件:android/kernel/msm-4.19/include/linux/seq_file.h#148
#define DEFINE_SHOW_ATTRIBUTE(__name) \
static int __name ## _open(struct inode *inode, struct file *file) \
{ \
return single_open(file, __name ## _show, inode->i_private); \
} \
\
static const struct file_operations __name ## _fops = { \
.owner = THIS_MODULE, \
.open = __name ## _open, \
.read = seq_read, \
.llseek = seq_lseek, \
.release = single_release, \
}
把这个宏展开就是:
static int __name_open(struct inode *inode, struct file *file)
{
return single_open(file, __name_show, inode->i_private);
}
static const struct file_operations __name_fops = {
.owner = THIS_MODULE,
.open = __name_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
}
所以,当声明函数binder_state_show时,也就通过DEFINE_SHOW_ATTRIBUTE(binder_state)申明了:
- 函数: binder_state_open
- 结构体: binder_state_fops
2.2 init_binderfs 初始化binder文件系统
在文件:android/kernel/msm-4.19/drivers/android/binder_internal.h#init_binderfs
#ifdef CONFIG_ANDROID_BINDERFS
extern int __init init_binderfs(void);
#else
static inline int __init init_binderfs(void)
{
return 0;
}
#endif
这里的CONFIG_ANDROID_BINDERFS就对应内核编译时的配置:android-base.config#20
CONFIG_ANDROID_BINDERFS=y
y就是定义该宏。
这里是引用了外部文件的函数: init_binderfs, 在该头文件的同目录下的文件binderfs.c可以找到对应函数实现:
int __init init_binderfs(void)
{
int ret;
const char *name;
size_t len;
// binder_devices_param是定义在binder.c文件中的:
// char *binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;
// 而CONFIG_ANDROID_BINDER_DEVICES 是编译配置:
// CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
// 所以这里的name就是"binder,hwbinder,vndbinder"
name = binder_devices_param;
// 验证默认的binderfs设备名是否合法有效
// 检验的方式是判断名字中是否含有字符','
for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
// BINDERFS_MAX_NAME = 255
if (len > BINDERFS_MAX_NAME)
return -E2BIG;
name += len;
if (*name == ',')
name++;
}
// 动态分配binder设备编号
ret = alloc_chrdev_region(&binderfs_dev, 0, BINDERFS_MAX_MINOR,
"binder");
if (ret)
return ret;
// 2.2.1 注册binder文件系统
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",
.mount = binderfs_mount,
.kill_sb = binderfs_kill_super,
.fs_flags = FS_USERNS_MOUNT,
};
初始化binder文件系统,三件事:
- 验证binder_devices_param也就是编译配置文件中的:CONFIG_ANDROID_BINDER_DEVICES是否合法
- 分配binder设备编号
- 注册binder文件系统
在这一步之后我们就可以通过 cat proc/devices 查到binder驱动对应的设备号了:
SS9805:/ # cat proc/devices
Character devices:
......
488 binder
......
比如这次我们机器中binder驱动的设备号为488,而且还可以看到这里是注册成字符设备的。
Linux 中的设备有2种类型:字符设备(无缓冲且只能顺序存取)、块设备(有缓冲且可以随机存取)。
在Linux下,一切皆文件,设备也不例外,为了管理这些设备,系统为它们各自都编了号,而每个设备号又分为主设备号和次设备号。主设备号用来区分不同类型的设备,而次设备号用来区分同一类型内的多个设备(及其设备分区)。
2.2.1 register_filesystem
定义在android/kernel/msm-4.19/fs/filesystems.c中:
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock);
// 在file_systems链表中找到合适位置,插入
p = find_filesystem(fs->name, strlen(fs->name));
// 如果不为空,说明已经注册过了,不能重复注册
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
EXPORT_SYMBOL(register_filesystem);
static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
for (p = &file_systems; *p; p = &(*p)->next)
if (strncmp((*p)->name, name, len) == 0 &&
!(*p)->name[len])
break;
return p;
}
主要是将传入的fs插入file_systems链表的末尾,且不能重复注册。
三.binder设备创建及挂载-debugfs挂载
上面的步骤我们分析了binder驱动的初始化,最后会注册binder文件系统。这一过程都是在kernel初始化的device_initcall中完成的。
当kernel启动到一定程度,将文件系统挂载后,自然binder驱动会运作起来:
static struct file_system_type binder_fs_type = {
.name = "binder",
.mount = binderfs_mount, // 文件系统挂载后调用函数
.kill_sb = binderfs_kill_super,
.fs_flags = FS_USERNS_MOUNT,
};
文件系统挂载后,就会调用binderfs_mount函数.
3.1 binderfs.c#binderfs_mount
static struct dentry *binderfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name,
void *data)
{
// 留意这个binderfs_fill_super
return mount_nodev(fs_type, flags, data, binderfs_fill_super);
}
3.2 android/kernel/msm-4.19/fs/super.c#mount_nodev
struct dentry *mount_nodev(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int))
{
int error;
// 获取超级块
// 超级块代表了整个文件系统,超级块是文件系统的控制块,有整个文件系统信息
// 一个文件系统所有的inode都要连接到超级块上,可以说,一个超级块就代表了一个文件系统。
struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);
if (IS_ERR(s))
return ERR_CAST(s);
// 调用传入的函数fill_super
error = fill_super(s, data, flags & SB_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(s);
return ERR_PTR(error);
}
s->s_flags |= SB_ACTIVE;
return dget(s->s_root);
}
EXPORT_SYMBOL(mount_nodev);
这些设计文件系统挂载知识,后续我们在研究一下。
调用mount_nodev后,获取超级块如果一切正常,会调用传入的fill_super函数,这里对应的就是binderfs_fill_super!
3.3 binderfs.c#binderfs_fill_super
static int binderfs_fill_super(struct super_block *sb, void *data, int silent)
{
int ret;
struct binderfs_info *info;
struct inode *inode = NULL;
struct binderfs_device device_info = { { 0 } };
const char *name;
size_t len;
// 设置文件系统中数据块大小,以字节单位为一页(一般为4096)
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
// ......
// kmalloc用于内核空间申请内存,申请的内存位于物理内存映射区域,而且在物理上也是连续的
// 与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
// GFP_KERNEL —— 正常分配内存;
sb->s_fs_info = kzalloc(sizeof(struct binderfs_info), GFP_KERNEL);
// ......
// inode 保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。
// 例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或
// 修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。
inode = new_inode(sb);
// ......
// 此函数在@sb引用的binderfs中创建一个新的binderfs控制设备节点。
ret = binderfs_binder_ctl_create(sb);
if (ret)
return ret;
name = binder_devices_param;
// 遍历并创建所有binder设备,三种:"binder,hwbinder,vndbinder"
// 1. binder
// 2. hwbinder
// 3. vndbinder
for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
strscpy(device_info.name, name, len + 1);
// 3.4 创建对应binder设备
ret = binderfs_binder_device_create(inode, NULL, &device_info);
if (ret)
return ret;
name += len;
if (*name == ',')
name++;
}
if (info->mount_opts.stats_mode == STATS_GLOBAL)
return init_binder_logs(sb);
return 0;
}
3.4 binderfs.c#binderfs_binder_device_create
static int binderfs_binder_device_create(struct inode *ref_inode,
struct binderfs_device __user *userp,
struct binderfs_device *req)
{
int minor, ret;
struct dentry *dentry, *root;
struct binder_device *device;
char *name = NULL;
size_t name_len;
struct inode *inode = NULL;
struct super_block *sb = ref_inode->i_sb;
struct binderfs_info *info = sb->s_fs_info;
// ......
// 给binder_device分配内核内存空间
device = kzalloc(sizeof(*device), GFP_KERNEL);
if (!device)
goto err;
// 创建一个新的inode节点
inode = new_inode(sb);
// ......
// 重点, 这里将加载文件操作集-binder操作
inode->i_fop = &binder_fops;
inode->i_uid = info->root_uid;
inode->i_gid = info->root_gid;
req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */
name_len = strlen(req->name);
// 分配一块长度为name_len + 1的内存(kmalloc+gfp)
// 然后将req->name到(req->name + len)内存的内容copy到新分配的内存中
// 最后后返回该内存的首地址
name = kmemdup(req->name, name_len + 1, GFP_KERNEL);
if (!name)
goto err;
// 初始化binder_device的相关设置
refcount_set(&device->ref, 1);
device->binderfs_inode = inode;
device->context.binder_context_mgr_uid = INVALID_UID;
device->context.name = name;
device->miscdev.name = name;// 分别是binder, hwbinder, vndbinder
device->miscdev.minor = minor;
mutex_init(&device->context.context_mgr_node_lock);
// ......
// 将创建的binder_device保存在inode中
inode->i_private = device;
d_instantiate(dentry, inode);
fsnotify_create(root->d_inode, dentry);
inode_unlock(d_inode(root));
return 0;
// ......
}
创建binder_device的步骤分为:
- 给binder_device分配内核内存空间
- 根据超级块创建一个新的inode节点
- 加载文件操作集-binder_fops 至inode中
- 初始化binder_device的相关设置
- 将创建的binder_device保存在inode中
最重要的是关注步骤3,这里我们还要看看binder_fops的文件操作集具体指向的函数:
// android/kernel/msm-4.19/drivers/android/binder.c
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,
};
哦偶, 终于看到ioctl了,这里先解释下文件操作集的含义及作用:
Linux使用file_operations结构访问驱动程序的函数,这个结构的每一个成员的名字都对应着一个调用。
用户进程利用在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备
驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。
其实驱动程序可以理解为一个被动服务,当有上层调用时,才会进入,内核调用的具体实现后续分析。
比如我们在应用进程中调用ioctl,通过syscall进入内核态,但此时进程上下文还是该应用进程的上下文,对应进程状态为S状态。
所以我们在上一篇文章中 ##1.6 IPCThreadState#talkWithDriver 里面调用ioctl最后是调用了binder_ioctl这个函数!