Linux 中 initcall 机制详解

源码基于:Linux 5.4

0. 前言

Linux 对驱动程序提供静态编译进内核和动态加载两种方式,当采用静态方式时,开发者如果想要在系统中启动这个驱动通常调用类似 xxx_init() 接口。

最直观的做法:开发者试图添加一个驱动初始化程序时,在内核启动 init 程序的某个地方直接添加调用自己驱动程序的 xxx_init() 接口函数,在内核启动时就自然会启动这个驱动程序,类似:

void kernel_init()
{
    a_init();
    b_init();

    ...

    m_init();
}

但是,这种做法在小系统中或许可以,对于 linux 庞大的系统来说,驱动很多,不可能每添加一个驱动就会改动一下 kernel_init() 代码,这将会是一场灾难。

Linux 内核提供了解决方案:

  • 在编译的时候,通过使用告知编译器连接,自定义一个专门用来存放这些初始化函数的地址段,将对应的函数入口统一放在一起;
  • 驱动程序中调用linux 内核提供的专门的 xxx_init() 接口,由编译器来收集这些入口函数,集中存放在一个地方;
  • 内核启动时,统一扫描这段的开始地址,按照顺序执行被添加的驱动初始化程序;
  • init 初始化代码,基本上只会执行一次,因此在这类 xxx_init() 代码所在的特殊段在初始化 完成之后会被内存管理器回收,同时节省了这部分的内存;

1. initcall 源码

上文提到过 Linux 对驱动程序提供静态编译进内核和动态加载两种方式,Linux 的 initcall 机制也是根据静态编译和动态加载的两种方式选择不同的编译、运行流程。

include/linux/init.h

#ifndef MODULE

... //静态加载

#else

... //动态加载

#endif

MODULE 是在编译的时候,通过编译器参数来传入。例如,在编译 ko 时会使用如下两个编译选项,如果是链接到内核,则不会使用:

//Makefile

KBUILD_AFLAGS_MODULE  := -DMODULE
KBUILD_CFLAGS_MODULE  := -DMODULE

通过 MODULE 的配置,选择静态编译还是动态加载。

本文将分开单独剖析这两种情况下的 initcall 机制。

2. 静态编译

2.1 initcall 接口 

include/linux/init.h


/*
 * 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)

#define __exitcall(fn)						\
	static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn)	___define_initcall(fn, con, .con_initcall)

对于静态编译 initcall 接口如上,其中 pure_initcall() 只能在静态编译中存在。

当然,对于静态编译的驱动也可以调佣 module_init() 接口:

include/linux/module.h

#define module_init(x)	__initcall(x);

#define module_exit(x)	__exitcall(x);

此时的 module_init() 就是 device_initcall()。

2.2 initcall 级别

initcall 在静态编译时,函数的调用顺序是有级别控制的,详细可以查看下文第 2.5 节和第 2.6 节。

调用的顺序是:

early-->
0-->0s-->
1-->1s-->
2-->2s-->
3-->3s-->
4-->4s-->
5-->5s-->
rootfs-->
6-->6s-->
7-->7s-->
console

2.3 __define_initcall()

include/linux/init.h

#ifdef CONFIG_LTO_CLANG
  /*
   * With LTO, the compiler doesn't necessarily obey link order for
   * initcalls, and the initcall variable needs to be globally unique
   * to avoid naming collisions.  In order to preserve the correct
   * order, we add each variable into its own section and generate a
   * linker script (in scripts/link-vmlinux.sh) to ensure the order
   * remains correct.  We also add a __COUNTER__ prefix to the name,
   * so we can retain the order of initcalls within each compilation
   * unit, and __LINE__ to make the names more unique.
   */
  #define ___lto_initcall(c, l, fn, id, __sec) \
	static initcall_t __initcall_##c##_##l##_##fn##id __used \
		__attribute__((__section__( #__sec \
			__stringify(.init..##c##_##l##_##fn)))) = fn;
  #define __lto_initcall(c, l, fn, id, __sec) \
	___lto_initcall(c, l, fn, id, __sec)

  #define ___define_initcall(fn, id, __sec) \
	__lto_initcall(__COUNTER__, __LINE__, fn, id, __sec)
#else
  #define ___define_initcall(fn, id, __sec) \
	static initcall_t __initcall_##fn##id __used \
		__attribute__((__section__(#__sec ".init"))) = fn;
#endif
#endif

#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

下文会继续细化分析,这里提前提示:

__define_initcall() 其实就是定义了一个 static initcall_t 的函数指针

include/linux/init.h

typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);

2.3.1 __used

include/linux/compiler_attributes.h


#define __used                          __attribute__((__used__))

这是一种 attribute 修饰属性的一种,意思是告诉编译器:这个静态符号在编译的时候,即使没有使用也要保留,不能优化掉。

详细可以查看《__attribute__机制详解》一文。

2.3.1 __attribute__ ((__section__(...)))

__attribute__ 是 GNU C 的一大特色,可以用来修饰对象、函数、结构体类型等等。

这里用来修改 section,意思是将作用的函数放入指定的 section name 对应的段中。

详细可以查看《__attribute__机制详解》一文。

2.3.2 __stringify()

include/linux/stringify.h

#define __stringify_1(x...)	#x
#define __stringify(x...)	__stringify_1(x)

将 __stringify() 中内容字符串化。

2.4 举例理解initcall接口

上面initcall 接口最终有各种宏转换,可能看着还是一头雾水。本小节用实例来剖析这个接口。

假如,我们在驱动使用如下接口:

module_init(hello_init);

那么,在编译的时候编译器会通过 initcall 接口产生:

static initcall_t __initcall_1_23_hello_init6 __attribute__(__used) \
    __attribute__((__section__(".initcall6.init..1_23_hello_init"))) = hello_init;

2.5 linux 编译后的initcall 函数

查看编译好的 System.map:

...
ffffffc012032ee0 d __initcall_223_42_trace_init_flags_sys_enterearly
ffffffc012032ee0 D __initcall_start
ffffffc012032ee0 D __setup_end
ffffffc012032ee8 d __initcall_224_66_trace_init_flags_sys_exitearly
ffffffc012032ef0 d __initcall_163_146_cpu_suspend_initearly
ffffffc012032ef8 d __initcall_151_267_asids_initearly
ffffffc012032f00 d __initcall_167_688_spawn_ksoftirqdearly
ffffffc012032f08 d __initcall_343_6656_migration_initearly
...
ffffffc012032f90 d __initcall_312_768_initialize_ptr_randomearly
ffffffc012032f98 D __initcall0_start
ffffffc012032f98 d __initcall_241_771_bpf_jit_charge_init0
ffffffc012032fa0 d __initcall_141_53_init_mmap_min_addr0
ffffffc012032fa8 d __initcall_209_6528_pci_realloc_setup_params0
ffffffc012032fb0 d __initcall_339_1143_net_ns_init0
ffffffc012032fb8 D __initcall1_start
ffffffc012032fb8 d __initcall_160_1437_fpsimd_init1
ffffffc012032fc0 d __initcall_181_669_tagged_addr_init1
...
ffffffc012033178 d __initcall_347_1788_init_default_flow_dissectors1
ffffffc012033180 d __initcall_360_2821_netlink_proto_init1
ffffffc012033188 D __initcall2_start
ffffffc012033188 d __initcall_165_139_debug_monitors_init2
ffffffc012033190 d __initcall_141_333_irq_sysfs_init2
...
ffffffc0120332b8 d __initcall_304_814_kobject_uevent_init2
ffffffc0120332c0 d __initcall_184_1686_msm_rpm_driver_init2s
ffffffc0120332c8 D __initcall3_start
ffffffc0120332c8 d __initcall_173_390_debug_traps_init3
ffffffc0120332d0 d __initcall_161_275_reserve_memblock_reserved_regions3
...
ffffffc012033370 d __initcall_132_5273_gsi_init3
ffffffc012033378 d __initcall_149_547_of_platform_default_populate_init3s
ffffffc012033380 D __initcall4_start
...
ffffffc012033878 D __initcall5_start
...
ffffffc0120339d8 d __initcall_317_1188_xsk_init5
ffffffc0120339e0 d __initcall_211_194_pci_apply_final_quirks5s
ffffffc0120339e8 d __initcall_168_680_populate_rootfsrootfs
ffffffc0120339e8 D __initcallrootfs_start
ffffffc0120339f0 D __initcall6_start
...
ffffffc012034b30 D __initcall7_start
...
ffffffc012034c88 d __initcall_150_554_of_platform_sync_state_init7s
ffffffc012034c90 d __initcall_123_29_alsa_sound_last_init7s
ffffffc012034c98 D __con_initcall_start
ffffffc012034c98 d __initcall_151_246_hvc_console_initcon
ffffffc012034c98 D __initcall_end
ffffffc012034ca0 D __con_initcall_end

从 System.map 得知:

  • __initcall_start ~ __initcall_end 所有函数指针都是连续的,相差8 个字节;
  • __initcall_start 就是第一个 early 级别的 initcall 函数指针,同理 __initcall0_start 就是第一个 level 0 级别的initcall 函数指针,以此类推;
  • rootfs 级别的 initcall 函数是插在 level 5s 之后,level 6 级别之前;
  • console 级别的函数在 level 7s 之后,__initcall_end 之前;

当然通过命令 readelf 或者 objdump (objdump -h vmlinux.o)都能看到各个字段:

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .initcall0.init 00000020  0000000000000000  0000000000000000  00000040  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  1 .initcall1.init 000001d0  0000000000000000  0000000000000000  00000060  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  2 .initcall2.init 00000138  0000000000000000  0000000000000000  00000230  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  3 .initcall2s.init 00000008  0000000000000000  0000000000000000  00000368  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  4 .initcall3.init 000000b0  0000000000000000  0000000000000000  00000370  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  5 .initcall3s.init 00000008  0000000000000000  0000000000000000  00000420  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  6 .initcall4.init 000004f0  0000000000000000  0000000000000000  00000428  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  7 .initcall4s.init 00000008  0000000000000000  0000000000000000  00000918  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  8 .initcall5.init 00000168  0000000000000000  0000000000000000  00000920  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  9 .initcall5s.init 00000008  0000000000000000  0000000000000000  00000a88  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 10 .initcall6.init 00001140  0000000000000000  0000000000000000  00000a90  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 11 .initcall7.init 00000140  0000000000000000  0000000000000000  00001bd0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 12 .initcall7s.init 00000028  0000000000000000  0000000000000000  00001d10  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 13 .con_initcall.init 00000008  0000000000000000  0000000000000000  00001d38  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 14 .initcallearly.init 000000b8  0000000000000000  0000000000000000  00001d40  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 15 .initcallrootfs.init 00000008  0000000000000000  0000000000000000  00001df8  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA

2.6 initcall 的函数如何被调用

init/main.c

start_kernel()
    ---->arch_call_rest_init()
        ---->rest_init()
            ---->kernel_init()
                ---->kernel_init_freeable()
                    ---->do_basic_setup()
                        ---->do_initcalls()
init/main.c

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

static void __init do_initcalls(void)
{
	int level;

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

for 循环针对一个指针数组轮询,该数组是一个静态的 initcall_entry_t 类型,这些数据都存放在 __initdata 段。

指针数组的类型为 initcall_entry_t,是 initcall_t 的另一种叫法,在上文第 2.3 节已经说明过 initcall_t 函数指针类型。

继续来看下这个指针数组中的元素:__initcall0_start ~ __initcall_end,而这些元素的值在本 c 文件中已经声明:

init/main.c

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

不难看出,数组 initcall_levels 中元素存放的是这些函数指针数组的首地址。

那么这些实际的指针数组是在哪里呢?从上文得知,initcall 函数都会被定义成 static initcall_t 类型,并且保存在 .initcall##level##.init 段中,那么 initcall_levels 与其是怎么关联的呢?

答案在 vmlinux.lds.h 中。

2.6.1 vmlinux.lds.h

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

在这里首先定义了__initcall_start,将其关联到".initcallearly.init"段。

然后对每个level定义了INIT_CALLS_LEVEL(level),将INIT_CALLS_LEVEL(level)展开之后的结果是定义 __initcall##level##_start,并将__initcall##level##_start关联到".initcall##level##.init"段和".initcall##level##s.init"段。

        __initcall_start = .;           \
        *(.initcallearly.init)          \
        __initcall0_start = .;          \
        *(.initcall0.init)              \
        *(.initcall0s.init)             \
        // 省略1、2、3、4、5
        __initcallrootfs_start = .;     \
        *(.initcallrootfs.init)         \
        *(.initcallrootfss.init)            \
        __initcall6_start = .;          \
        *(.initcall6.init)              \
        *(.initcall6s.init)             \
        __initcall7_start = .;          \
        *(.initcall7.init)              \
        *(.initcall7s.init)             \
        __initcall_end = .;

上面这些代码段最终在kernel.img中按先后顺序组织,也就决定了位于其中的一些函数的执行先后顺序。

.init 或者 .initcalls 段的特点就是,当内核启动完毕后,这个段中的内存会被释放掉。

2.6.2 do_initcall_level()

init/main.c

static void __init do_initcall_level(int level)
{
	initcall_entry_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);

	trace_initcall_level(initcall_level_names[level]);
	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(initcall_from_entry(fn));
}

do_initcall_level() 函数的参数 level 是之前是initcall_levels 数组的索引,从第0个开始。

这里再用一个 for 循环,跳到 initcall_levels 内部元素 (函数指针数组)进行轮询,fn 初始值为函数指针数组的起始地址,后面 fn++ 相当于函数指针 +1,即跳到下一个函数指针。

即,for 循环中,根据传入的 level,确定需要轮询的 .initcall##level##.init 段的所有函数指针,一直到下一个 .intcall##(level+1)##.init 段。

 

另外,需要注意 do_one_initcall() 的参数就是获取函数指针的内容,而这个内容就是注册进来的 initcall 的实际初始化函数。

如上面的举例:

module_init(hello_init);

 这里最终就看成调用 do_one_initcall(hello_init);

init/main.c

int __init_or_module do_one_initcall(initcall_t fn)
{
	...

	ret = fn();
	...
	return ret;
}

3. 动态加载

当模块以 ko 的形式存在,并被加载重定位到内核,其作用域和静态连接的代码是完全等价的。这种运行方式的优点:

  • 可根据系统需要运行动态加载模块,以扩充内核功能,不需要时可以将其卸载,以释放内存空间;
  • 当需要修改内核功能时,只需要编译模块,而不必重新编译整个内核;

当然,有些模块是必须要编译到内核,随内核一起运行,从不卸载,例如 vfs、platform_bus 等。

3.1 initcall 接口

当动态加载时,会在Makefile中添加上 MODULE 的定义:

//Makefile

KBUILD_AFLAGS_MODULE  := -DMODULE
KBUILD_CFLAGS_MODULE  := -DMODULE

而initcall 代码也将从 init.h 转换到 module.h:

include/module.h


#ifndef MODULE

...

#else

#define early_initcall(fn)		    module_init(fn)
#define core_initcall(fn)		    module_init(fn)
#define core_initcall_sync(fn)		module_init(fn)
#define postcore_initcall(fn)		module_init(fn)
#define postcore_initcall_sync(fn)	module_init(fn)
#define arch_initcall(fn)		    module_init(fn)
#define subsys_initcall(fn)		    module_init(fn)
#define subsys_initcall_sync(fn)	module_init(fn)
#define fs_initcall(fn)			    module_init(fn)
#define fs_initcall_sync(fn)		module_init(fn)
#define rootfs_initcall(fn)		    module_init(fn)
#define device_initcall(fn)		    module_init(fn)
#define device_initcall_sync(fn)	module_init(fn)
#define late_initcall(fn)		    module_init(fn)
#define late_initcall_sync(fn)		module_init(fn)

#define console_initcall(fn)		module_init(fn)

/* Each module must use one module_init(). */
#define module_init(initfn)					\
	static inline initcall_t __maybe_unused __inittest(void)		\
	{ return initfn; }					\
	int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)					\
	static inline exitcall_t __maybe_unused __exittest(void)		\
	{ return exitfn; }					\
	void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));

#endif

moudle_init() 共做了两件事情:

  • 定义 static initcall_t __inittest() { ... }
  • 声明 int init_module();

__inittest 仅仅是为了检测定义的函数是否符合 initcall_t 类型,不过不是 __inittest 类型在编译的时候会报错。所以真正使用的是 init_module() 函数的声明。

注意:

  • __copy(initfn):从 initfn 赋值函数属性,从 gcc-9 开始支持;
  • __attribute__((alias(#initfn))):为 init_module 创建别名,指向原来的 initfn;

这里alias 是 gcc 的特有属性,将定义 init_module 为函数initfn 的别名。即对于module_init() 作用就是定义一个变量名 init_module,其地址与 initfn 是一样的。

3.2 insmod

编译后的模块 xxx.ko 需要通过 insmod 或 modprobe 将其加载到内核,由于 insmod 是bubybox 提供的用户层命令,所以需要阅读 busybox 源码:

modutils/insmod.c

int insmod_main(int argc UNUSED_PARAM, char **argv)
{
    char *filename;
    int rc;

    /* Compat note:
     * 2.6 style insmod has no options and required filename
     * (not module name - .ko can't be omitted).
     * 2.4 style insmod can take module name without .o
     * and performs module search in default directories
     * or in $MODPATH.
     */

    IF_FEATURE_2_4_MODULES(
        getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
        argv += optind - 1;
    );

    filename = *++argv;
    if (!filename)
        bb_show_usage();

    rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
    if (rc)
        bb_error_msg("can't insert '%s': %s", filename, moderror(rc));

    return rc;
}

bb_init_module():

modutils/modutils.c

int FAST_FUNC bb_init_module(const char *filename, const char *options)
{
    size_t image_size;
    char *image;
    int rc;
    bool mmaped;

    if (!options)
        options = "";

//TODO: audit bb_init_module_24 to match error code convention
#if ENABLE_FEATURE_2_4_MODULES
    if (get_linux_version_code() < KERNEL_VERSION(2,6,0))
        return bb_init_module_24(filename, options);
#endif

    /*
     * First we try finit_module if available.  Some kernels are configured
     * to only allow loading of modules off of secure storage (like a read-
     * only rootfs) which needs the finit_module call.  If it fails, we fall
     * back to normal module loading to support compressed modules.
     */
# ifdef __NR_finit_module
    {
        int fd = open(filename, O_RDONLY | O_CLOEXEC);
        if (fd >= 0) {
            rc = finit_module(fd, options, 0) != 0;
            close(fd);
            if (rc == 0)
                return rc;
        }
    }
# endif

    image_size = INT_MAX - 4095;
    mmaped = 0;
    image = try_to_mmap_module(filename, &image_size);
    if (image) {
        mmaped = 1;
    } else {
        errno = ENOMEM; /* may be changed by e.g. open errors below */
        image = xmalloc_open_zipped_read_close(filename, &image_size);
        if (!image)
            return -errno;
    }

    errno = 0;
    init_module(image, image_size, options);
    rc = errno;
    if (mmaped)
        munmap(image, image_size);
    else
        free(image);
    return rc;
}

init_module() 定义如下:

modutils/modutils.c
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)

最终进行syscall 的系统调用:

kernel/module.c

SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, uargs)
{
	int err;
	struct load_info info = { };

	err = may_init_module();
	if (err)
		return err;

	pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
	       umod, len, uargs);

	err = copy_module_from_user(umod, len, &info);
	if (err)
		return err;

	return load_module(&info, uargs, 0);
}

其实无论是 insmod 或者是 modprobe,最终都是调用到内核的 load_module()。

下面的流程为:

kernel/module.c

load_module()
    ---->do_init_module()
        ---->do_one_initcall()

最终 do_one_initcall() 同静态编译:

init/main.c

int __init_or_module do_one_initcall(initcall_t fn)
{
	...

	ret = fn();
	...
	return ret;
}

你可能感兴趣的:(unix/linux,linux,initcall,module_init,device_init)