kernel calls module_ Init (x) process

Posted by Gargouil on Mon, 03 Jan 2022 05:53:32 +0100

1, Foreword

module_ Init (x) is the entry function of the driver module. The driver module is divided into static compilation and dynamic compilation. In static compilation, the kernel calls module_ What is the whole process of init (x)?

2, Call process

1. module_init( x )

Take a look at the module first_ Init (x) is in the Linux source directory include / Linux / module How h is defined.

/*
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion  // Functions that run at kernel startup 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);                   // moduele_init is defined here

 /**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed      // Functions that run when the driver is unloaded
 *
 * 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);

module_init(x) notes:
module_init(x) will be invoked to module_ in the do_initcalls() period (static compilation) and insmod (dynamic compilation). Init (x), each module can only have one entry function (module_init(x)).

Note: call do_ Process before initcalls:

start_kernel --> rest_init --> kernel_init_freeable-->do_basic_setup-->do_initcalls

So the next goal is to analyze do_ What did initcalls do next?

By the way, the following explanation of exit function is translated:
module_exit(x): if the module is dynamically compiled into the kernel, the module_ When exit() is used with rmmod, cleanup is used_ Module () processing code; If the module is statically compiled into the kernel module_exit() is invalid. Each module can only have one exit function (module_exit(x)).

2. do_initcalls( x )

do_ Initcalls (x) is in the directory init / main C, see the code:

static initcall_t *initcall_levels[] __initdata = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

static void __init do_initcalls(void)
{
	int level;

	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)     /* Traverse all level s */
		do_initcall_level(level);                         /* Execute to do_initcall_level(level) function*/
}


2,
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);	/* Execute to do_one_initcall() function*/
}
3,
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);   /* Execute to do_one_initcall_debug() function*/
	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;
}
4,
static int __init_or_module do_one_initcall_debug(initcall_t fn)
{
	ktime_t calltime, delta, rettime;
	unsigned long long duration;
	int ret;

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

	return ret;
}

3. do_initcall_level(level)

Execute to do_initcall_level(level) function

/* 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);	/* Execute to do_one_initcall() function*/
}

4. do_one_initcall(*fn)

Execute to do_one_initcall(*fn) function

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);    /* Execute to do_one_initcall_debug() function*/
	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;
}

5. do_one_initcall_debug(initcall_t fn)

Execute to do_one_initcall_debug(initcall_t fn) function

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

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

	return ret;
}

Here, I finally see the operation of fn(), so what is fn? do_ initcall_ There is such a code in level (int level):

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

If level=6, fn =__ initcall6_start and do_one_initcall_debug(initcall_t fn) shows that the fn type is initcall_ t. And in include / init typedef int (*initcall_t)(void) in H, indicating fn__ initcall6_start is of type int.

Roughly understood as__ initcall6_start is the entry (address) of the function. fn + + is the function entry to the next same level until the next level. So how do you put these functions?

6. init.h

Why with init What about h? At the beginning:

#define module_init(x)    __initcall(x);

So look__ initcall(x), in init H includes:

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

This connects the beginning and the end. Keep looking__ define_initcall(fn, 6)

/*
 * initcalls are now grouped by functionality into separate     /* initcalls Now group into separate subsections by function. The order within a subsection is determined by the order of links. */
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 	/* For backward compatibility, initcall() places the call in the device init subsection */
 * 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			/* Initcalls It is run by placing a pointer in the initcall part of the runtime iteration of the kernel. The linker can eliminate dead code / data and completely delete it, so the initcall section must be marked as KEEP () in the linker script.*/
 * 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;

The above paragraph and the following include / ASM generic / vmlinux lds. Init in H file_ CALLS_ The content in level (level) is consistent. As mentioned in the comments, KEEP() is used. It can also be simply understood as through module_init(x) finally adds my init function x to__ 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) = .;

So far, module_ The whole calling process of init (x) is roughly completed.

This article is the process that the author first joined the company, learning driven development, combined with network materials and actual development. Thank those who helped me in my study.