基于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()首要做以下操作:

  1. 创立几个帮助调试的文件和目录
  2. 注册misc设备
  3. 注册binder文件系统
  • binder_init()
    • init_binder_device()
    • init_binderfs()
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()

  • 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驱动履行系统调用时,

  1. 如果是系统调用ioctl(),终究会调用binder_ioctl()
  2. 如果是系统调用mmap(),终究会调用binder_map()
  3. 如果是系统调用open(),终究会调用binder_open()

注册Binder文件系统

Linux将文件系统分为了两层:VFS(虚拟文件系统)、详细文件系统。如下图:

Android Binder初始化

VFS(Virtual Filesystem Switch)不是一种实践的文件系统,它只存在于内存中。它是一个内核软件层,是在详细的文件系统之上笼统的一层,用来处理与Posix文件系统相关的一切调用。它屏蔽了底层各种文件系统杂乱的调用完成,为各种文件系统供给一个通用的接口。

Binder驱动在运用前,有必要:

  • 在VFS上注册
  • 挂载对应的文件系统
注册
  • init_binderfs()
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 thetextsection. 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 functionfoobarin 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的二进制文件中。在内核发动的时分,由内核一一调用它们。

大致的调用栈是:

  • start_kernel()
    • arch_call_rest_init()
      • rest_init()
        • kernel_thread(kernel_init)
          • kernel_init_freeable()
            • do_basic_setup()
              • do_initcalls()

核心函数是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()的调用。

参考资料

Android Binder驱动简析

Android源码剖析 – Binder驱动(上)

Linux 虚拟文件系统四大目标:超级块、inode、dentry、file之间关系

【GCC系列】深入理解Linux内核 — __init宏界说

attribute((section(x))) 运用详解

vmlinux.lds.s 脚本语法

Linux 各种 initcall 的调用原理

linux内核链接脚本vmlinux.lds剖析续篇之 — initcall机制(十三)