module_init的調用過程

不知不覺,其實也已經做bsp快三年了. 有些知識點,開始大概知道一點,但是還是比較抽象的. 經過這麼長時間的使用,還是覺得有必要好好整理一下的.比如module_init.

下面的code是在Android p,kernel/msm-4.14.

首先是module_init(x)函數.在kernel/msm-4.14/include/linux/module.h中:

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 *
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)	__initcall(x);

/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 *
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x)	__exitcall(x);

註釋中說,如果是常駐的driver,那麼會在do_initcalls的時候調到module_init添加的函數.那麼先看看do_initcalls怎麼調過來的吧. 在kernel/msm-4.14/init/main.c中有do_initcalls().  實際上前面還有這樣的調用過程:start_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup->do_initcalls.

static void __init do_initcalls(void)
{
	int level;

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

好,這裏是遍歷所有level,從0開始.往後看do_initcall_level().還是在main.c中:

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);
}

接着看do_one_initcall(*fn),還在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;

	ret = do_one_initcall_debug(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;
}

在函數do_one_initcall_debug中,終於看到有fn()的運行.

static int __init_or_module do_one_initcall_debug(initcall_t fn)
{
	ktime_t calltime, delta, rettime;
	unsigned long long duration;
	int ret;

	if (initcall_debug)
		printk(KERN_DEBUG "calling  %pF @ %i\n", fn, task_pid_nr(current));
	calltime = ktime_get();
	ret = fn();
	rettime = ktime_get();
	delta = ktime_sub(rettime, calltime);
	duration = (unsigned long long) ktime_to_ns(delta) >> 10;
	if (initcall_debug)
		printk(KERN_DEBUG "initcall %pF returned %d after %lld usecs\n",
			 fn, ret, duration);

	if (initcall_debug == 0) {
		if (duration > 100000)
			printk(KERN_WARNING "[debuginit] initcall %pF returned %d after %lld usecs\n", fn,
				ret, duration);
	}

	return ret;
}

那麼fn到底是什麼呢?

從for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)中可以看到fn實際上就是數組initcall_levels[]的一個元素.也就是說,如果level爲6,那麼fn=__initcall6_start,並且fn的類型是initcall_t.

大致理解爲__initcall6_start是函數的入口(地址),fn++就是到下一個同級別的函數入口,一直到下一個level的函數.那麼這些函數又是怎麼放過來的呢?

 

前面後說到,#define module_init(x)    __initcall(x);那麼看看__initcall(x). 還是在init.h中有:

#define __initcall(fn) device_initcall(fn)

再看看device_initcall(fn),還是在init.h中:

#define device_initcall(fn)		__define_initcall(fn, 6)

這裏的6代表module_init的level是6,好像地位不是很高呀. 繼續看__define_initcall(fn, 6),還在init.h中:

#ifdef CONFIG_LTO_CLANG
  /* prepend the variable name with __COUNTER__ to ensure correct ordering */
  #define ___initcall_name2(c, fn, id) 	__initcall_##c##_##fn##id
  #define ___initcall_name1(c, fn, id)	___initcall_name2(c, fn, id)
  #define __initcall_name(fn, id) 	___initcall_name1(__COUNTER__, fn, id)
#else
  #define __initcall_name(fn, id) 	__initcall_##fn##id
#endif

/*
 * 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_name(fn, id) __used \
	__attribute__((__section__(".initcall" #id ".init"))) = fn;

上面這一段內容和下面的msm-4.14/include/asm-generic/vmlinux.lds.h文件中的INIT_CALLS_LEVEL(level)裏面的內容相符的.並且如同註釋中說到的使用了KEEP(). 也可以簡單理解爲通過module_init(x)最終是把我的init函數x添加到__initcall6_start那一塊去的.

#define INIT_CALLS_LEVEL(level)						\
		VMLINUX_SYMBOL(__initcall##level##_start) = .;		\
		KEEP(*(.initcall##level##.init))			\
		KEEP(*(.initcall##level##s.init))			\

#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) = .;

到這裏爲止,也就基本把module_init的調用分析完了. 邁小步,不停步.

參考博文:https://blog.csdn.net/richard_liujh/article/details/45669207

               https://blog.csdn.net/richard_liujh/article/details/46758073

非常感謝! 本文爲學習總結用. 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章