1. module_init是什么
module_init是Linux内核开发和驱动开发中非常常见的宏,其定义在 include/linux/module.h
中,可以看到,module_init的实现会根据是否定义MODULE而有所不同。MODULE决定了我们编写的驱动,是与内核编译到一起,还是单独编译为ko。
1.1. MODULE的定义
MODULE是通过在编译时,通过编译器的参数来传入的。如下是Makefile中的内容,在编译ko时,会使用如下两个编译选项,如果是链接到内核,则不会使用如下选项。
1/// Makefile
2KBUILD_AFLAGS_MODULE := -DMODULE
3KBUILD_CFLAGS_MODULE := -DMODULE
如果想看编译某个文件时是否由 -DMODULE
选项,可以直接查看编译生成 .xxx.o.cmd
文件。
下面先分析MODULE未使能的情况。
2. MODULE未使能
MODULE未定义的情况下,module_init是一种特殊的initcall。initcall是内核用于声明初始化函数以及控制函数调用顺序的机制。
initcall有多个级别,module_init实际就是device_initcall,其级别为6。initcall最终会声明一个initcall_t静态变量,链接时放到内核的 .init.data
段。
1/// include/linux/module.h
2#ifndef MODULE
3/**
4 * module_init() - driver initialization entry point
5 * @x: function to be run at kernel boot time or module insertion
6 *
7 * module_init() will either be called during do_initcalls() (if
8 * builtin) or at module insertion time (if a module). There can only
9 * be one per module.
10 */
11#define module_init(x) __initcall(x);
12
13/**
14 * module_exit() - driver exit entry point
15 * @x: function to be run when driver is removed
16 *
17 * module_exit() will wrap the driver clean-up code
18 * with cleanup_module() when used with rmmod when
19 * the driver is a module. If the driver is statically
20 * compiled into the kernel, module_exit() has no effect.
21 * There can only be one per module.
22 */
23#define module_exit(x) __exitcall(x);
24
25#else /* MODULE */
26/// ... ...
2.1. initcall分析
首先通过 arch64-linux-gnu-nm -n vmlinux | grep -E -C 2 '_initcall.*(_start|_end)$'
来看一下编译的最终结果。
1ffff8000094fc228 d __setup_debug_boot_weak_hash_enable
2ffff8000094fc240 d __initcall__kmod_ptrace__414_42_trace_init_flags_sys_enterearly
3ffff8000094fc240 D __initcall_start
4ffff8000094fc240 D __setup_end
5ffff8000094fc244 d __initcall__kmod_ptrace__416_66_trace_init_flags_sys_exitearly
6--
7ffff8000094fc2a8 d __initcall__kmod_earlycon__376_41_efi_earlycon_remap_fbearly
8ffff8000094fc2ac d __initcall__kmod_dummy_timer__364_37_dummy_timer_registerearly
9ffff8000094fc2b0 D __initcall0_start
10ffff8000094fc2b0 d __initcall__kmod_core__527_975_bpf_jit_charge_init0
11ffff8000094fc2b4 d __initcall__kmod_shm__486_153_ipc_ns_init0
12--
13ffff8000094fc2bc d __initcall__kmod_pci__516_6911_pci_realloc_setup_params0
14ffff8000094fc2c0 d __initcall__kmod_inet_fragment__696_216_inet_frag_wq_init0
15ffff8000094fc2c4 D __initcall1_start
16ffff8000094fc2c4 d __initcall__kmod_fpsimd__388_2058_fpsimd_init1
17ffff8000094fc2c8 d __initcall__kmod_process__462_734_tagged_addr_init1
18--
19ffff8000094fc3cc d __initcall__kmod_genetlink__620_1498_genl_init1
20ffff8000094fc3d0 d __initcall__kmod_trace_boot__394_671_trace_boot_init1s
21ffff8000094fc3d4 D __initcall2_start
22ffff8000094fc3d4 d __initcall__kmod_debug_monitors__391_139_debug_monitors_init2
23ffff8000094fc3d8 d __initcall__kmod_index__322_194_pi_init2
24--
25ffff8000094fc460 d __initcall__kmod_rpmsg_core__351_710_rpmsg_init2
26ffff8000094fc464 d __initcall__kmod_kobject_uevent__622_814_kobject_uevent_init2
27ffff8000094fc468 D __initcall3_start
28ffff8000094fc468 d __initcall__kmod_setup__397_281_reserve_memblock_reserved_regions3
29ffff8000094fc46c d __initcall__kmod_vdso__390_363_aarch32_alloc_vdso_pages3
30--
31ffff8000094fc4a8 d __initcall__kmod_dmi_id__332_259_dmi_id_init3
32ffff8000094fc4ac d __initcall__kmod_platform__462_596_of_platform_default_populate_init3s
33ffff8000094fc4b0 D __initcall4_start
34ffff8000094fc4b0 d __initcall__kmod_setup__399_406_topology_init4
35ffff8000094fc4b4 d __initcall__kmod_mte__458_603_register_mte_tcf_preferred_sysctl4
36--
37ffff8000094fc694 d __initcall__kmod_vgaarb__423_1564_vga_arb_device_init4s
38ffff8000094fc698 d __initcall__kmod_watchdog__465_479_watchdog_init4s
39ffff8000094fc69c D __initcall5_start
40ffff8000094fc69c d __initcall__kmod_debug_monitors__389_63_create_debug_debugfs_entry5
41ffff8000094fc6a0 d __initcall__kmod_resource__402_2029_iomem_init_inode5
42--
43ffff8000094fc784 d __initcall__kmod_acpi__398_141_acpi_reserve_resources5s
44ffff8000094fc788 d __initcall__kmod_initramfs__399_762_populate_rootfsrootfs
45ffff8000094fc788 D __initcallrootfs_start
46ffff8000094fc78c D __initcall6_start
47ffff8000094fc78c d __initcall__kmod_setup__401_440_register_arm64_panic_block6
48ffff8000094fc790 d __initcall__kmod_cpuinfo__334_359_cpuinfo_regs_init6
49--
50ffff8000094fcd40 d __initcall__kmod_9pnet_virtio__634_831_p9_virtio_init6
51ffff8000094fcd44 d __initcall__kmod_dns_resolver__330_382_init_dns_resolver6
52ffff8000094fcd48 D __initcall7_start
53ffff8000094fcd48 d __initcall__kmod_mounts__426_40_kernel_do_mounts_initrd_sysctls_init7
54ffff8000094fcd4c d __initcall__kmod_panic__390_97_kernel_panic_sysctls_init7
55--
56ffff8000094fce6c d __initcall__kmod_core__606_6212_regulator_init_complete7s
57ffff8000094fce70 d __initcall__kmod_platform__464_603_of_platform_sync_state_init7s
58ffff8000094fce74 D __con_initcall_start
59ffff8000094fce74 d __initcall__kmod_vt__408_3548_con_initcon
60ffff8000094fce74 D __initcall_end
61ffff8000094fce78 d __initcall__kmod_hvc_console__376_246_hvc_console_initcon
62ffff8000094fce7c d __initcall__kmod_8250__387_690_univ8250_console_initcon
63ffff8000094fce80 D __con_initcall_end
64ffff8000094fce80 D __initramfs_start
以 __initcall__kmod_cpuinfo__334_359_cpuinfo_regs_init6
为例来说明一下module_init的效果。
__initcall__kmod_cpuinfo__334_359_cpuinfo_regs_init6
是一个initcall_t类型的静态变量,变量名称可以由 __initcall_name(initcall, __initcall_id(fn), id)
得出。
将变量名拆分开来可得:__ initcall __ [kmod_cpuinfo __ 334 _ 359 _ cpuinfo_regs_init] 6
,[]
内的内容由 __initcall_id(fn)
生成,其格式为 <modname>__<counter>_<line>_<fn>
:
- modname:
__KBUILD_MODNAME
,来自于scripts/Makefile.lib:127: -D__KBUILD_MODNAME=kmod_$(call name-fix-token,$(modname))
,kmod_
是固定的,__KBUILD_MODNAME
在这里是kmod_cpuinfo。 - counter:来自于
__COUNTER__
,是编译器内部定义的计数器,每使用一次__COUNTER__
,其值为自动加一,这里是334。 - line:这个是声明所在的行号,对应的源码为
arch/arm64/kernel/cpuinfo.c:359:device_initcall(cpuinfo_regs_init);
,这里是359。 - fn:函数名,这里是cpuinfo_regs_init。
除去 []
内的部分,变量名的其他部分由 __initcall_name
生成,其格式为 __<prefix>__<iid><id>
:
- prefix:固定为initcall
- iid:
__initcall_id(fn)
生成的内容 - id:
__define_initcall
的第二个参数
2.2. initcall的源码
下面是 include/linux/init.h
的源码,上边是以未定义CONFIG_LTO_CLANG
和CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
来分析的。
1/// include/linux/init.h
2#ifndef MODULE
3
4#ifndef __ASSEMBLY__
5
6/*
7 * initcalls are now grouped by functionality into separate
8 * subsections. Ordering inside the subsections is determined
9 * by link order.
10 * For backwards compatibility, initcall() puts the call in
11 * the device init subsection.
12 *
13 * The `id' arg to __define_initcall() is needed so that multiple initcalls
14 * can point at the same handler without causing duplicate-symbol build errors.
15 *
16 * Initcalls are run by placing pointers in initcall sections that the
17 * kernel iterates at runtime. The linker can do dead code / data elimination
18 * and remove that completely, so the initcall sections have to be marked
19 * as KEEP() in the linker script.
20 */
21
22/* Format: <modname>__<counter>_<line>_<fn> */
23#define __initcall_id(fn) \
24 __PASTE(__KBUILD_MODNAME, \
25 __PASTE(__, \
26 __PASTE(__COUNTER__, \
27 __PASTE(_, \
28 __PASTE(__LINE__, \
29 __PASTE(_, fn))))))
30
31/* Format: __<prefix>__<iid><id> */
32#define __initcall_name(prefix, __iid, id) \
33 __PASTE(__, \
34 __PASTE(prefix, \
35 __PASTE(__, \
36 __PASTE(__iid, id))))
37
38#ifdef CONFIG_LTO_CLANG
39/*
40 * With LTO, the compiler doesn't necessarily obey link order for
41 * initcalls. In order to preserve the correct order, we add each
42 * variable into its own section and generate a linker script (in
43 * scripts/link-vmlinux.sh) to specify the order of the sections.
44 */
45#define __initcall_section(__sec, __iid) \
46 #__sec ".init.." #__iid
47
48/*
49 * With LTO, the compiler can rename static functions to avoid
50 * global naming collisions. We use a global stub function for
51 * initcalls to create a stable symbol name whose address can be
52 * taken in inline assembly when PREL32 relocations are used.
53 */
54#define __initcall_stub(fn, __iid, id) \
55 __initcall_name(initstub, __iid, id)
56
57#define __define_initcall_stub(__stub, fn) \
58 int __init __stub(void); \
59 int __init __stub(void) \
60 { \
61 return fn(); \
62 } \
63 __ADDRESSABLE(__stub)
64#else
65#define __initcall_section(__sec, __iid) \
66 #__sec ".init"
67
68#define __initcall_stub(fn, __iid, id) fn
69
70#define __define_initcall_stub(__stub, fn) \
71 __ADDRESSABLE(fn)
72#endif
73
74#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
75#define ____define_initcall(fn, __stub, __name, __sec) \
76 __define_initcall_stub(__stub, fn) \
77 asm(".section \"" __sec "\", \"a\" \n" \
78 __stringify(__name) ": \n" \
79 ".long " __stringify(__stub) " - . \n" \
80 ".previous \n"); \
81 static_assert(__same_type(initcall_t, &fn));
82#else
83#define ____define_initcall(fn, __unused, __name, __sec) \
84 static initcall_t __name __used \
85 __attribute__((__section__(__sec))) = fn;
86#endif
87
88#define __unique_initcall(fn, id, __sec, __iid) \
89 ____define_initcall(fn, \
90 __initcall_stub(fn, __iid, id), \
91 __initcall_name(initcall, __iid, id), \
92 __initcall_section(__sec, __iid))
93
94#define ___define_initcall(fn, id, __sec) \
95 __unique_initcall(fn, id, __sec, __initcall_id(fn))
96
97#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
98
99/*
100 * Early initcalls run before initializing SMP.
101 *
102 * Only for built-in code, not modules.
103 */
104#define early_initcall(fn) __define_initcall(fn, early)
105
106/*
107 * A "pure" initcall has no dependencies on anything else, and purely
108 * initializes variables that couldn't be statically initialized.
109 *
110 * This only exists for built-in code, not for modules.
111 * Keep main.c:initcall_level_names[] in sync.
112 */
113#define pure_initcall(fn) __define_initcall(fn, 0)
114
115#define core_initcall(fn) __define_initcall(fn, 1)
116#define core_initcall_sync(fn) __define_initcall(fn, 1s)
117#define postcore_initcall(fn) __define_initcall(fn, 2)
118#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
119#define arch_initcall(fn) __define_initcall(fn, 3)
120#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
121#define subsys_initcall(fn) __define_initcall(fn, 4)
122#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
123#define fs_initcall(fn) __define_initcall(fn, 5)
124#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
125#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
126#define device_initcall(fn) __define_initcall(fn, 6)
127#define device_initcall_sync(fn) __define_initcall(fn, 6s)
128#define late_initcall(fn) __define_initcall(fn, 7)
129#define late_initcall_sync(fn) __define_initcall(fn, 7s)
130
131#define __initcall(fn) device_initcall(fn)
132
133#define __exitcall(fn) \
134 static exitcall_t __exitcall_##fn __exit_call = fn
135
136#define console_initcall(fn) ___define_initcall(fn, con, .con_initcall)
137/// ... ...
2.3. rootfs_initcall是什么
rootfs_initcall在5s之后,rootfs_initcall指向的函数也会在do_basic_setup中被调用到。rootfs要从存储介质中读取文件系统镜像,需要在执行rootfs_initcall指向的函数之前,将存储介质准备好,比如:
1/// drivers/mmc/core/core.c
2subsys_initcall(mmc_init);
2.4. console_initcall
为了能尽早输出日志,内核将console相关的initcall单独存放,由console_initcall声明。相关初始化函数在console_init中调用。console_init则在start_kernel中尽量选择较早的时机调用。
1/// kernel/printk/printk.c
2/*
3 * Initialize the console device. This is called *early*, so
4 * we can't necessarily depend on lots of kernel help here.
5 * Just do some early initializations, and do the complex setup
6 * later.
7 */
8void __init console_init(void)
9{
10 int ret;
11 initcall_t call;
12 initcall_entry_t *ce;
13
14 /* Setup the default TTY line discipline. */
15 n_tty_init();
16
17 /*
18 * set up the console device so that later boot sequences can
19 * inform about problems etc..
20 */
21 ce = __con_initcall_start;
22 trace_initcall_level("console");
23 while (ce < __con_initcall_end) {
24 call = initcall_from_entry(ce);
25 trace_initcall_start(call);
26 ret = call();
27 trace_initcall_finish(call, ret);
28 ce++;
29 }
30}
2.5. 链接脚本中initcall段
initcall声明的变量会放到以 .initcall
开头命名的段中,每个级别对应一个 .initcall
段,而这些段又会按顺序放到 .init.data
段中。
1/// include/asm-generic/vmlinux.lds.h
2#define INIT_CALLS_LEVEL(level) \
3 __initcall##level##_start = .; \
4 KEEP(*(.initcall##level##.init)) \
5 KEEP(*(.initcall##level##s.init)) \
6
7#define INIT_CALLS \
8 __initcall_start = .; \
9 KEEP(*(.initcallearly.init)) \
10 INIT_CALLS_LEVEL(0) \
11 INIT_CALLS_LEVEL(1) \
12 INIT_CALLS_LEVEL(2) \
13 INIT_CALLS_LEVEL(3) \
14 INIT_CALLS_LEVEL(4) \
15 INIT_CALLS_LEVEL(5) \
16 INIT_CALLS_LEVEL(rootfs) \
17 INIT_CALLS_LEVEL(6) \
18 INIT_CALLS_LEVEL(7) \
19 __initcall_end = .;
20
21#define CON_INITCALL \
22 __con_initcall_start = .; \
23 KEEP(*(.con_initcall.init)) \
24 __con_initcall_end = .;
1/// arch/arm64/kernel/vmlinux.lds.S
2.init.data : {
3 INIT_DATA
4 INIT_SETUP(16)
5 INIT_CALLS
6 CON_INITCALL
7 INIT_RAM_FS
8 *(.init.altinstructions .init.bss) /* from the EFI stub */
9}
10.exit.data : {
11 EXIT_DATA
12}
2.6. initcall的执行时机
由initcall指定的函数会在do_pre_smp_initcalls和do_basic_setup执行。do_pre_smp_initcalls执行early_initcall指定的函数,这个是在多核处理器和调度系统初始化之前执行的。do_basic_setup会按initcall的级别,依次执行指定的函数。由于lds中没有根据函数名排序,同级别initcall指定的函数的执行顺序会由链接时initcall的布局决定。多次编译的顺序也可能不同。
1/// init/main.c
2static noinline void __init kernel_init_freeable(void)
3{
4 /// ... ...
5 rcu_init_tasks_generic();
6 do_pre_smp_initcalls();
7 lockup_detector_init();
8
9 smp_init();
10 sched_init_smp();
11
12 padata_init();
13 page_alloc_init_late();
14 /* Initialize page ext after all struct pages are initialized. */
15 if (!early_page_ext_enabled())
16 page_ext_init();
17
18 do_basic_setup();
19 /// ... ...
20}
2.7. initcall调试
调用initcall函数时,如果打开了initcall_debug,内核会以KERN_DEBUG等级打印trace信息。
可以通过向bootargs内添加initcall_debug来打开initcall_debug。
1# cat /proc/sys/kernel/printk
26 4 1 7
3# cat /proc/cmdline
4console=ttyAMA0 nokaslr crashkernel=512M-1G:64M,1G-:128M loglevel=6 initcall_debug no_console_suspend root=/dev/vda rw init=/linuxrc
打开后,启动信息可以看到类似如下信息,如果启动过程中,loglevel比debug等级要低,可以使用dmesg命令查看。
1[ 0.051543][ T1] calling trace_init_flags_sys_enter+0x0/0x30 @ 1
2[ 0.051631][ T1] initcall trace_init_flags_sys_enter+0x0/0x30 returned 0 after 0 usecs
3[ 0.051728][ T1] calling trace_init_flags_sys_exit+0x0/0x30 @ 1
4[ 0.051748][ T1] initcall trace_init_flags_sys_exit+0x0/0x30 returned 0 after 0 usecs
5[ 0.051766][ T1] calling cpu_suspend_init+0x0/0x68 @ 1
6[ 0.051801][ T1] initcall cpu_suspend_init+0x0/0x68 returned 0 after 0 usecs
7[ 0.051814][ T1] calling asids_init+0x0/0x110 @ 1
8[ 0.051910][ T1] initcall asids_init+0x0/0x110 returned 0 after 0 usecs
9[ 0.051927][ T1] calling spawn_ksoftirqd+0x0/0x60 @ 1
10[ 0.052809][ T1] initcall spawn_ksoftirqd+0x0/0x60 returned 0 after 4000 usecs
11[ 0.052840][ T1] calling init_signal_sysctls+0x0/0x50 @ 1
12[ 0.052894][ T1] initcall init_signal_sysctls+0x0/0x50 returned 0 after 0 usecs
13[ 0.052912][ T1] calling init_umh_sysctls+0x0/0x50 @ 1
14[ 0.052963][ T1] initcall init_umh_sysctls+0x0/0x50 returned 0 after 0 usecs
在安装驱动,比如insmod hello.ko
时,也会有类似的信息,比如:
1[ 39.313554][ T141] calling hello_init+0x0/0xff8 [hello] @ 141
2[ 39.314090][ T141] initcall hello_init+0x0/0xff8 [hello] returned 0 after 105 usecs
3. MODULE使能
Linux中有些模块既可以链接到内核,也可以选择编译为ko,而这些模块的初始化函数可能是以initcall方式指定的。为了两者相互兼容,会把initcall定义为module_init。
下面分析module_init的实现,源码见《module_init源码》
__copy(initfn)
:从initfn复制函数属性,从gcc-9开始支持。__attribute__((alias(#initfn)))
:为init_module创建别名,指向原来的initfn。___ADDRESSABLE(init_module, __initdata)
:声明一个initcall_t变量,并放到.init.data
段,变量名类似__UNIQUE_ID___addressable_init_module326
。
__inittest
:代码中没有找到调用的地方,但是从v2.6.0对module_init的注释来看,猜测是为了防止编译器警告。
1/* These macros create a dummy inline: gcc 2.9x does not count alias
2 as usage, hence the `unused function' warning when __init functions
3 are declared static. We use the dummy __*_module_inline functions
4 both to kill the warning and check the type of the init/cleanup
5 function. */
init_module是initfn的别名,其地址也是相同的,但是initfn通常会是static函数,而init_module是一个global函数。
1Disassembly of section .init.text:
2
30000000000000000 <init_module>:
4hello_init():
5 0: d503201f nop
6 4: d503201f nop
1/// t代表static,T代表global
20000000000000000 00000000000000c8 t hello_init
30000000000000000 00000000000000c8 T init_module
3.1. module_init源码
1/// include/linux/module.h
2/// ... ...
3#else /* MODULE */
4
5/*
6 * In most cases loadable modules do not need custom
7 * initcall levels. There are still some valid cases where
8 * a driver may be needed early if built in, and does not
9 * matter when built as a loadable module. Like bus
10 * snooping debug drivers.
11 */
12#define early_initcall(fn) module_init(fn)
13#define core_initcall(fn) module_init(fn)
14#define core_initcall_sync(fn) module_init(fn)
15#define postcore_initcall(fn) module_init(fn)
16#define postcore_initcall_sync(fn) module_init(fn)
17#define arch_initcall(fn) module_init(fn)
18#define subsys_initcall(fn) module_init(fn)
19#define subsys_initcall_sync(fn) module_init(fn)
20#define fs_initcall(fn) module_init(fn)
21#define fs_initcall_sync(fn) module_init(fn)
22#define rootfs_initcall(fn) module_init(fn)
23#define device_initcall(fn) module_init(fn)
24#define device_initcall_sync(fn) module_init(fn)
25#define late_initcall(fn) module_init(fn)
26#define late_initcall_sync(fn) module_init(fn)
27
28#define console_initcall(fn) module_init(fn)
29
30/* Each module must use one module_init(). */
31#define module_init(initfn) \
32 static inline initcall_t __maybe_unused __inittest(void) \
33 { return initfn; } \
34 int init_module(void) __copy(initfn) \
35 __attribute__((alias(#initfn))); \
36 ___ADDRESSABLE(init_module, __initdata);
37
38/* This is only required if you want to be unloadable. */
39#define module_exit(exitfn) \
40 static inline exitcall_t __maybe_unused __exittest(void) \
41 { return exitfn; } \
42 void cleanup_module(void) __copy(exitfn) \
43 __attribute__((alias(#exitfn))); \
44 ___ADDRESSABLE(cleanup_module, __exitdata);
45
46#endif
1/// include/linux/init.h
2#define __initdata __section(".init.data")
1/// include/linux/compiler.h
2/*
3 * Force the compiler to emit 'sym' as a symbol, so that we can reference
4 * it from inline assembler. Necessary in case 'sym' could be inlined
5 * otherwise, or eliminated entirely due to lack of references that are
6 * visible to the compiler.
7 */
8#define ___ADDRESSABLE(sym, __attrs) \
9 static void * __used __attrs \
10 __UNIQUE_ID(__PASTE(__addressable_,sym)) = (void *)&sym;
3.2. init_module的执行时机
命令行下使用 insmod
或 modprobe
安装模块,最终系统调用为 init_module
或 finit_module
。
可以使用 strace insmod test.ko
查看使用的具体的系统调用。
系统调用init_module和finit_module用来从用户态加载ko文件,man 2 init_module
可以看到两者的介绍。init_module接收一个ELF文件的路径,而finit_module接收一个文件描述符。
1/// kernel/module/main.c
2SYSCALL_DEFINE3(init_module, void __user *, umod,
3 unsigned long, len, const char __user *, uargs)
4{
5 /// ... ...
6 return load_module(&info, uargs, 0);
7}
8
9SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
10{
11 /// ... ...
12 return load_module(&info, uargs, flags);
13}
init_module和finit_module最后都会调到load_module,load_module的流程如下: