Linux内核4.14版本:ARM64的内核启动过程(四)——do_initcalls

目录

1. __initcall_start等地址是怎么生成的

2. 这个section是怎么构造的

3. 使用示例

4. 总结


init\main.c
int __init_or_module do_one_initcall(initcall_t fn)
{
	int count = preempt_count();
	int ret;
	char msgbuf[64];

	if (initcall_blacklisted(fn))
		return -EPERM;

	if (initcall_debug)
		ret = do_one_initcall_debug(fn);
	else
		ret = fn();

	msgbuf[0] = 0;

	if (preempt_count() != count) {
		sprintf(msgbuf, "preemption imbalance ");
		preempt_count_set(count);
	}
	if (irqs_disabled()) {
		strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
		local_irq_enable();
	}
	WARN(msgbuf[0], "initcall %pF returned with %s\n", fn, msgbuf);

	add_latent_entropy();
	return ret;
}

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

static initcall_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 char *initcall_level_names[] __initdata = {
	"early",
	"core",
	"postcore",
	"arch",
	"subsys",
	"fs",
	"device",
	"late",
};

static void __init do_initcall_level(int level)
{
	initcall_t *fn;

	strcpy(initcall_command_line, saved_command_line);
	parse_args(initcall_level_names[level],
		   initcall_command_line, __start___param,
		   __stop___param - __start___param,
		   level, level,
		   NULL, &repair_env_string);

	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(*fn);
}

static void __init do_initcalls(void)
{
	int level;

	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
		do_initcall_level(level);
}

      do_initcalls()将按顺序从由__initcall_start开始,到__initcall_end结束的section中以函数指针的形式取出这些编译到内核的驱动模块中初始化函数起始地址,来依次完成相应的初始化。而这些初始化函数由__define_initcall(level,fn)指示编译器在编译的时候,将这些初始化函数的起始地址值按照一定的顺序放在这个section中。
      调用其实很简单,下面看看linux是怎么样构造这个section和__initcall_start、__initcall0_start...__initcall_end是怎么生成的。

1. __initcall_start等地址是怎么生成的

看一下文件arch\arm64\kernel\vmlinux.lds.S

vmlinux.lds.S的调用关系是:

	.init.data : {
		INIT_DATA
		INIT_SETUP(16)
		INIT_CALLS
		CON_INITCALL
		SECURITY_INITCALL
		INIT_RAM_FS
		*(.init.rodata.* .init.bss)	/* from the EFI stub */
	}
	
#define INIT_CALLS							\
		VMLINUX_SYMBOL(__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)					\
		VMLINUX_SYMBOL(__initcall_end) = .;
		
#define INIT_CALLS_LEVEL(level)						\
		VMLINUX_SYMBOL(__initcall##level##_start) = .;		\
		KEEP(*(.initcall##level##.init))			\
		KEEP(*(.initcall##level##s.init))			\

      可以看出__initcall_start、__initcall0_start...__initcall_end由以上代码生成的了。

2. 这个section是怎么构造的

查看这个文件include\linux\init.h

/*
 * initcalls are now grouped by functionality into separate
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 *
 * Initcalls are run by placing pointers in initcall sections that the
 * kernel iterates at runtime. The linker can do dead code / data elimination
 * and remove that completely, so the initcall sections have to be marked
 * as KEEP() in the linker script.
 */

#define __define_initcall(fn, id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" #id ".init"))) = fn;

其中 initcall_t 是一个函数指针类型:

   typedef int (*initcall_t)(void);

      而属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中。
      所以这个宏定义的的含义是:
           1) 声明一个名称为__initcall_##fn##id的函数指针(其中##表示替换连接,);linux3.6比2.6多添加了一个id作为扩展。
           2) 将这个函数指针初始化为fn;
           3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init" 的section中(比如level="1",代表这个section的名称是 ".initcall1.init")。
      这个宏会被一下几个宏衍生使用。

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)		__define_initcall(fn, early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 * Keep main.c:initcall_level_names[] in sync.
 */
#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)
#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)

#define __initcall(fn) device_initcall(fn)

3. 使用示例

kernel\audit.c
/* Initialize audit support at boot time. */
static int __init audit_init(void)
{
	int i;

	if (audit_initialized == AUDIT_DISABLED)
		return 0;

	audit_buffer_cache = kmem_cache_create("audit_buffer",
					       sizeof(struct audit_buffer),
					       0, SLAB_PANIC, NULL);

	skb_queue_head_init(&audit_queue);
	skb_queue_head_init(&audit_retry_queue);
	skb_queue_head_init(&audit_hold_queue);

	for (i = 0; i < AUDIT_INODE_BUCKETS; i++)
		INIT_LIST_HEAD(&audit_inode_hash[i]);

	pr_info("initializing netlink subsys (%s)\n",
		audit_default ? "enabled" : "disabled");
	register_pernet_subsys(&audit_net_ops);

	audit_initialized = AUDIT_INITIALIZED;

	kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd");
	if (IS_ERR(kauditd_task)) {
		int err = PTR_ERR(kauditd_task);
		panic("audit: failed to start the kauditd thread (%d)\n", err);
	}

	audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL,
		"state=initialized audit_enabled=%u res=1",
		 audit_enabled);

	return 0;
}
__initcall(audit_init);

4. 总结

      1) __define_initcall(level,fn)的作用就是指示编译器把一些初始化函数的指针(即:函数起始地址)按照顺序放置一个名为 .initcall.init 的section中,这个section又被分成了若干个子section,它们按顺序排列。在内核初始化阶段,这些放置到这个section中的函数指针将供do_initcalls() 按顺序依次调用,来完成相应初始化。
      2) 函数指针放置到的子section由宏定义的level确定,对应level较小的子section位于较前面。而位于同一个子section内的函数指针顺序不定,由编译器按照编译的顺序随机指定。
      3) 因此,如果你希望某个初始化函数在内核初始化阶段就被调用,那么你 就应该使用宏__define_initcall(level,fn) 或 其几个衍生宏 把这个函数fn的对应的指针放置到按照初始化的顺序放置到相关的 section 中。如果某个初始化函数fn_B需要依赖于另外一个初始化函数fn_A的完成,那么你应该把fn_B放在比fn_A对应的level值较大的子section中,    这样,do_initcalls()将在fn_A之后调用fn_B。

      如果你希望某个初始化函数在内核初始化阶段就被调用,那么你就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section 中。 内核初始化时的do_initcalls()将从这个section中按顺序找到这些函数来执行。

      如果你希望某个初始化函数在内核初始化阶段就被调用,那么你就应该使用宏__define_initcall(level,fn) 或 其几个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section 中。 内核初始化时的do_initcalls()将从这个section中按顺序找到这些函数来执行。所以在源码中只要是使用这几个宏或者是衍生宏声明的函数,只要被编译到内核中,就会在do_initcalls被调用,从而初始化设备。

猜你喜欢

转载自blog.csdn.net/yangguoyu8023/article/details/121470838