这是一系列的 Binder 文章,会从内核层到 Framework 层,再到 Java 层,深入浅出,介绍整个 Binder 的设计。详见《图解 Binder:概述》。
本文基于 Android 内核分支 common-android13-5.15 解析。
一些关键代码的链接,可能会因为源码的变动,发生位置偏移、丢失等现象。可以搜索函数名,重新进行定位。
Linux的内核模块机制允许开发者向内核添加功能。很多功能或者外设驱动都可以编译成模块。Linux系统使用两种方式去加载系统中的模块:动态和静态。Binder驱动是通过静态的方式,在编译期编译到内核中,在内核启动的时候,执行初始化。
Binder驱动初始化
Binder驱动初始化的入口在common/drivers/android/binder.c:
static int __init binder_init(void)
{
...
}
device_initcall(binder_init);
本文主要分两部分:
- binder_init()的解析
- 内核对binder_init()的调用
细说binder_init()
binder_init()是Binder驱动的初始化函数。binder_init()主要做以下操作:
- 创建几个帮助调试的文件和目录
- 注册misc设备
- 注册binder文件系统
static int __init binder_init(void)
{
int ret;
char *device_name, *device_tmp;
struct binder_device *device;
char *device_names = NULL;
//创建目录:/binder
binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
if (binder_debugfs_dir_entry_root) {
const struct binder_debugfs_entry *db_entry;
//创建几个文件:
// /binder/state
// /binder/stats
// /binder/transactions
// /binder/transaction_log
// /binder/failed_transaction_log
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
binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
binder_debugfs_dir_entry_root);
}
if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) &&
strcmp(binder_devices_param, "") != 0) {
//binder_devices_param固定为 binder,hwbinder,vndbinder
device_names = kstrdup(binder_devices_param, GFP_KERNEL);
device_tmp = device_names;
while ((device_name = strsep(&device_tmp, ","))) {
//分别初始化设备binder、hwbinder、vndbinder
ret = init_binder_device(device_name);
}
}
//初始化binder文件系统
ret = init_binderfs();
return ret;
}
- 可以通过查看/sys/kernel/debug/binder查看state、stats、transactions、transaction_log、failed_transaction_log文件,以及一个proc目录。
- /sys/kernel/debug/binder/state:整体以及各个进程的thread/node/ref/buffer的状态信息,如有deadnode也会打印
- /sys/kernel/debug/binder/stats:整体以及各个进程的线程数,事务个数等的统计信息
- /sys/kernel/debug/binder/failed_transaction_log:记录32条最近的传输失败事件
- /sys/kernel/debug/binder/transaction_log:记录32条最近的传输事件
- /sys/kernel/debug/binder/transactions:遍历所有进程的buffer分配情况
- proc目录中都是进程号,观察其中的进程都是注册在驱动的进程,其中包括servicemanager。可以通过命令
cat /sys/kernel/debug/binder/proc/进程号查看对应进程的binder信息。
注册misc设备——init_binder_device()
static int __init init_binder_device(const char *name)
{
int ret;
struct binder_device *binder_device;
binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
//设置misc设备的操作
binder_device->miscdev.fops = &binder_fops;
//设置misc设备的次设备号(动态分配的)
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
//设置misc设备的设备名
binder_device->miscdev.name = name;
binder_device->context.binder_context_mgr_uid = INVALID_UID;
binder_device->context.name = name;
//初始化互斥锁
mutex_init(&binder_device->context.context_mgr_node_lock);
//注册misc设备
ret = misc_register(&binder_device->miscdev);
//将binder设备加入链表(头插法)
hlist_add_head(&binder_device->hlist, &binder_devices);
return ret;
}
- misc设备:Linux内核把无法归类的设备定义为misc设备,譬如看门狗、实时时钟等。Linux内核把所有的misc设备组织在一起,构成一个子系统,进行统一管理。在这个子系统里的所有misc类型的设备共享一个主设备号MISC_MAJOR(10),但它们次设备号不同。
- 结构体binder_device表示一个binder设备节点,记录了该节点相关信息。
binder_device->miscdev:即结构体miscdevice。它表示一个misc设备,记录该设备的名称、次版本号、支持的系统调用操作等。binder_device->miscdev.fops = &binder_fops;
binder_device->miscdev.fops即结构体file_operations。它是把系统调用和驱动程序关联起来的关键结构。这个结构的每一个成员都对应着一个系统调用,Linux系统调用通过调用file_operations中相应的函数指针,接着把控制权转交给函数,从而完成Linux设备驱动程序的工作。
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //对应系统调用read
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //对应系统调用write
__poll_t (*poll) (struct file *, struct poll_table_struct *); //对应系统调用poll
int (*open) (struct inode *, struct file *); //对应系统调用open
...
}
而binder_fops的定义如下:
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,
};
这意味着当binder驱动执行系统调用时,
- 如果是系统调用ioctl(),最终会调用binder_ioctl()
- 如果是系统调用mmap(),最终会调用binder_map()
- 如果是系统调用open(),最终会调用binder_open()
- ...
注册Binder文件系统
Linux将文件系统分为了两层:VFS(虚拟文件系统)、具体文件系统。
VFS(Virtual Filesystem Switch)不是一种实际的文件系统,它只存在于内存中。它是一个内核软件层,是在具体的文件系统之上抽象的一层,用来处理与Posix文件系统相关的所有调用。它屏蔽了底层各种文件系统复杂的调用实现,为各种文件系统提供一个通用的接口。
当通过系统调用 open() 打开 "/dev/binder" 设备文件时,就会沿着 VFS,最后定位到 binder 文件系统,调用其对应的 binder_open() 实现,完成 binder 驱动的打开。其他系统调用,也是类似的。
Binder驱动在使用前,必须:
- 在VFS上注册
- 挂载对应的文件系统
注册
static struct file_system_type binder_fs_type = {
.name = "binder",
.init_fs_context = binderfs_init_fs_context,
.parameters = binderfs_fs_parameters,
.kill_sb = kill_litter_super,
.fs_flags = FS_USERNS_MOUNT,
};
int __init init_binderfs(void)
{
int ret;
ret = register_filesystem(&binder_fs_type);
return ret;
}
- register_filesystem()会向VFS注册binder文件系统。
- 每个注册的文件系统都用一个类型为file_system_type的对象表示。file_system_type主要记录文件系统的类型相关信息,比如名称、上下文初始化函数指针等。
binder_fs_type指明将要挂载的Binder文件系统名为binder。
挂载
Binder文件系统挂载到VFS的时机在init进程启动的时候。相关挂载指令在system/core/rootdir/init.rc中:
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
mount指令的解析函数是do_mount()。
symlink指令的解析函数是do_symlink()。
do_mount()会调用mount()将代表Binder驱动的路径/dev/binderfs挂载到Binder文件系统。
do_symlink()会调用symlink(),为相应的文件路径起别名。比如,/dev/binderfs/binder对应别名就是/dev/binder。这样当我们执行open("/dev/binder", O_RDWR | O_CLOEXEC)时,实际就是打开位于/dev/binderfs/binder的Binder驱动。
何时调用binder_init()?
内核主要是通过initcall机制,在启动init进程的时候,完成对binder_init()的调用。
几个重要的宏定义
回顾一下下面的代码:
static int __init binder_init(void)
{
...
}
device_initcall(binder_init);
这里有几个重要的宏定义:
1) __init
__init的宏定义在common/include/linux/init.h中:
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline __nocfi
__section是GCC的一个编译属性,在GCC中定义如下:
section ("section-name")Normally, the compiler places the code it generates in the
textsection. Sometimes, however, you need additional sections, or you need certain particular functions to appear in special sections. Thesectionattribute specifies that a function lives in a particular section. For example, the declaration:extern void foobar (void) __attribute__ ((section ("bar")));puts the function
foobarin thebarsection. Some file formats do not support arbitrary sections so thesectionattribute is not available on all platforms. If you need to map the entire contents of a module to a particular section, consider using the facilities of the linker instead.
通常,编译器会将生成的代码是放在ELF的.text段中的。不过section属性可以指定一个函数,将其放在一个特定的段中。
所以,所有标识为__init的函数都会放在.init.text这个段内。在这个段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。
所以,binder_init会被放入.init.text这个段内。
2) device_initcall
device_initcall(binder_init)就是将指向binder_init的函数指针,注册在.initcall6.init段里。内核启动时,会调用它,对Binder驱动进行初始化。
device_initcall的宏定义在common/include/linux/init.h中:
typedef int (*initcall_t)(void);
typedef initcall_t initcall_entry_t;
/* Format: <modname>__<counter>_<line>_<fn> */
#define __initcall_id(fn) \
__PASTE(__KBUILD_MODNAME, \
__PASTE(__, \
__PASTE(__COUNTER__, \
__PASTE(_, \
__PASTE(__LINE__, \
__PASTE(_, fn))))))
/* Format: __<prefix>__<iid><id> */
#define __initcall_name(prefix, __iid, id) \
__PASTE(__, \
__PASTE(prefix, \
__PASTE(__, \
__PASTE(__iid, id))))
#define __initcall_section(__sec, __iid) \
#__sec ".init"
#define __initcall_stub(fn, __iid, id) fn
#define ____define_initcall(fn, __unused, __name, __sec) \
static initcall_t __name __used \
__attribute__((__section__(__sec))) = fn;
#endif
#define __unique_initcall(fn, id, __sec, __iid) \
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
#define ___define_initcall(fn, id, __sec) \
__unique_initcall(fn, id, __sec, __initcall_id(fn))
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define device_initcall(fn) __define_initcall(fn, 6)
device_initcall(binder_init)展开就是:
device_initcall(binder_init)
↓
__define_initcall(binder_init, 6)
↓
___define_initcall(binder_init, 6, .initcall6)
↓
__unique_initcall(binder_init, 6, .initcall6, <modname>__<counter>_<line>_binder_init)
↓
____define_initcall(binder_init,
binder_init,
__initcall__<modname>__<counter>_<line>_binder_init6,
.initcall6.init)
↓
static initcall_t __initcall__<modname>__<counter>_<line>_binder_init6 __used
__attribute__((__section__(.initcall6.init))) = binder_init;
device_initcall(binder_init)的含义是:将binder_init的函数指针赋值给变量__initcall__<modname>__<counter>_<line>_binder_init6,然后将该变量存放在.initcall6.init段中。
涉及的另外几个宏:
_used_使用前提是在编译器编译过程中,如果定义的符号没有被引用,编译器就会对其进行优化,不保留这个符号,而__attribute__((used))的作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号。__PASTE是做简单的字符串拼接:
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
__KBUILD_MODNAME__是Linux kbuild的体系在编译模块的时候生成的。__LINE__在预处理阶段,会被替换成代码行号。__COUNTER__是GNU 编译器的非标准编译器扩展,可以认为它是一个计数器,代表一个整数,它的值一般被初始化为0,在每次编译器编译到它时,会自动 +1。
initcall机制
当我们试图将一个驱动程序加载进内核时,我们需要提供一个xxx_init()函数。这样内核才会定位到该函数,加载驱动,初始化驱动。
binder_init()就是这样一个初始化驱动的函数。但是怎么向内核注册这样一个函数呢?直观的做法是维护一个初始化驱动的函数指针的数组,将binder_init()添加进该数组中。不过这样在多人开发时,容易造成编码冲突。
linux采用了更优雅的方法——initcall机制:
在内核镜像文件中,自定义一个段,这个段里面专门用来存放这些初始化函数的地址,内核启动时,只需要在这个段地址处取出函数指针,一个个执行即可。
.initcallXX.init段
.initcallXX.init段就是专门用来存放各个内核模块的初始化函数的地址。
device_initcall(fn)就是表示将指向fn的函数指针,存放在.initcall6.init段。类似的宏定义有:
#define pure_initcall(fn) __define_initcall(fn, 0) → .initcall0.init
#define core_initcall(fn) __define_initcall(fn, 1) → .initcall1.init
#define core_initcall_sync(fn) __define_initcall(fn, 1s) → .initcall1s.init
#define postcore_initcall(fn) __define_initcall(fn, 2) → .initcall2.init
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s) → .initcall2s.init
#define arch_initcall(fn) __define_initcall(fn, 3) → .initcall3.init
#define arch_initcall_sync(fn) __define_initcall(fn, 3s) → .initcall3s.init
#define subsys_initcall(fn) __define_initcall(fn, 4) → .initcall4.init
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s) → .initcall4s.init
#define fs_initcall(fn) __define_initcall(fn, 5) → .initcall5.init
#define fs_initcall_sync(fn) __define_initcall(fn, 5s) → .initcall5s.init
#define rootfs_initcall(fn) __define_initcall(fn, rootfs) → .initcallrootfs.init
#define device_initcall(fn) __define_initcall(fn, 6) → .initcall6.init
#define device_initcall_sync(fn) __define_initcall(fn, 6s) → .initcall6s.init
#define late_initcall(fn) __define_initcall(fn, 7) → .initcall7.init
#define late_initcall_sync(fn) __define_initcall(fn, 7s) → .initcall7s.init
.initcallXX.init段的定义
.initcallXX.init段的定义是在common/include/asm-generic/vmlinux.lds.h和common/arch/arm64/kernel/vmlinux.lds.S中。
//common/include/asm-generic/vmlinux.lds.h
#define INIT_CALLS_LEVEL(level) \
__initcall##level##_start = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
#define INIT_CALLS \
__initcall_start = .; \
KEEP(*(.initcallearly.init)) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
__initcall_end = .;
//common/arch/arm64/kernel/vmlinux.lds.S
SECTIONS
{
...
.init.data : {
INIT_DATA
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
INIT_RAM_FS
*(.init.altinstructions .init.bss) /* from the EFI stub */
}
...
}
- vmlinux.lds.S中的不是汇编代码,而是Linker Script。
- vmlinux是一个包含linux kernel的静态链接的可执行文件,文件类型通常是linux接受的可执行文件格式ELF。
INIT_CALLS中,定义了16个段,每隔两个段,都会定义一个函数指针,指向这两个段的起始地址,比如:__initcall_0_start指向.initcall_0.init、.initcall_0s.init这两个段的起始地址。INIT_CALLS还定义了两个函数指针__initcall_start、__initcall_end,分别指向这16个段之前、之后的位置。
调用.initcallXX.init段里的初始化函数
代码经过编译、链接后,binder_init这样的初始化函数的函数指针,会按照一定的顺序,插入vmlinux的二进制文件中。在内核启动的时候,由内核一一调用它们。
大致的调用栈是:
核心函数是do_initcalls(),它会完成.initcallXX.init段里的初始化函数的定位,并逐一调用它们:
//__initcall0_start这些函数指针的定义,就是在common/include/asm-generic/vmlinux.lds.h中
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,
};
/* Keep these in sync with initcalls in include/linux/init.h */
static const char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
static void __init do_initcalls(void)
{
int level;
//遍历各个.initcallXX.init段
//initcall_levels数组最后一个值是__initcall_end,所以遍历不包括它
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;
//遍历.initcallXX.init段里存放的各个初始化函数,并逐一调用它们
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)
{
int ret;
//调用初始化函数
ret = fn();
return ret;
}
至此,最终完成了对binder_init()的调用。