Linux 內核:initcall機制與module_init
背景
在做分享的時候,被資深的同事問起關於驅動加載時機與probe的問題。發現自己並不熟悉,因此學習了解一下。
學習本文你就可以知道,內核驅動中各個部分的加載時機,以及驅動是在什么時候被加載的。
參考文檔:
- https://www.cnblogs.com/downey-blog/p/10486653.html
- https://blog.csdn.net/weixin_37571125/article/details/78665184
initcall機制的由來
我們都知道,linux對驅動程序提供靜態編譯進內核和動態加載兩種方式,當我們試圖將一個驅動程序編譯進內核時,開發者通常提供一個xxx_init()函數接口以啟動這個驅動程序同時提供某些服務。
那么,根據常識來說,這個xxx_init()函數肯定是要在系統啟動的某個時候被調用,才能啟動這個驅動程序。
最簡單直觀地做法就是:開發者試圖添加一個驅動初始化程序時,在內核啟動init程序的某個地方直接添加調用自己驅動程序的xxx_init()函數,在內核啟動時自然會調用到這個程序。
類似這樣子:
void init(void)
{
a_init();
b_init();
// ...
z_init();
}
但是,回頭一想,這種做法在單人開發的小系統中或許可以,但是在linux中,如果驅動程序是這樣通過手動添加的話,那就是一場災難。
不難想到另一種方式,就是集中提供一個地方,如果你要添加你的驅動初始化程序,你就將你的初始化函數在這個地方進行添加,在內核啟動的時候統一掃描這個地方,再執行這一部分的所有被添加的驅動程序。
比如,直接在C文件中作一個列表,在里面添加初始化函數:
#include <stdio.h>
void a_init(void) {
printf("%s\n", __func__);
}
void b_init(void) {
printf("%s\n", __func__);
}
void(*fun_list[]) (void)={
a_init,
b_init,
};
void init(void)
{
int i;
void(*pfun) (void);
for (i = 0; i < sizeof(fun_list)/sizeof(fun_list[0]); ++i) {
printf("%d\n", i);
fun_list[i]();
}
}
但這個方法也不夠完美。
Linux內核是如何解決的?
1、Linux源碼在編譯的時候,通過使用告知編譯器鏈接,自定義一個專門用來存放這些初始化函數的地址段,將對應的函數入口統一放在一起;
2、雖然我們沒有手動將函數添加到函數隊列中,但實際上我們使用內核提供的xxx_init()以后,編譯器就可以替我們將對應的函數入口集中存在一個地方。
3、等到在內核啟動的時候統一掃描這個段的開始,按照順序,就可以執行這一部分的所有被添加的驅動程序。
對上層而言,linux內核提供xxx_init(init_func)宏定義接口,驅動開發者只需要將驅動程序的init_func使用來修飾,這個函數就被自動添加到了上述的段中,開發者完全不需要關心實現細節。
對於各種各樣的驅動而言,可能存在一定的依賴關系,需要遵循先后順序來進行初始化,考慮到這個,linux也對這一部分做了分級處理。
initcall與分級
定位到Linux內核源碼中的 include/linux/init.h,可以看到有如下代碼:
#ifndef MODULE
// 靜態加載
// ...
#else
// 動態加載
// ...
#endif
顯然,MODULE 是可配置的。上面部分用於將模塊靜態編譯連接進內核,下面部分用於編譯可動態加載的模塊。
接下來我們對這兩種情況進行分析。
結論:
-
靜態加載時,將不同的
xx_initcall放在不同的代碼段中,Linux內核在執行的時候,根據次序,遍歷並執行對應的函數。 -
動態加載通過系統調用
靜態加載,#ifndef MODULE
// 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)
/**
* 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);
xxx_init_call(fn)的原型其實是__define_initcall(fn, n)。
- n是一個數字或者是數字+s,這個數字代表這個fn執行的優先級,數字越小,優先級越高
- 帶s的fn優先級低於不帶s的fn優先級。
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
需要注意的是,根據官方注釋可以看到early_initcall(fn)只針對內置的核心代碼,不能描述模塊。
__define_initcall
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
我們看看如何解析這個宏__define_initcall:
_*attribute*_()是gnu C中的擴展語法,它可以用來實現很多靈活的定義行為,這里不細究。_*attribute*_((_*section*_(".initcall" #id ".init")))表示編譯時將目標符號放置在括號指定的段中。- 在宏定義中,
#的作用是將目標字符串化,##在宏定義中的作用是符號連接,將多個符號連接成一個符號,並不將其字符串化。 __used是一個宏定義,#define __used __attribute__((__used__))用於作用是告訴編譯器這個靜態符號在編譯的時候即使沒有使用到也要保留這個符號。
為了更方便地理解,我們拿舉個例子來說明,開發者聲明了這樣一個函數:module_init(hello_init);,那么有:
- 首先宏展開成:
__define_initcall(hello_init, 6) - 然后接着展開:
static initcall_t __initcall_hello_init6 = test_init;這就是定義了函數指針變量。 - 同時聲明
__initcall_hello_init6這個變量即使沒被引用也保留符號,且將其放置在內核鏡像的.initcall6.init段處。
GNU編譯工具鏈支持用戶自定義section,所以我們閱讀一些GUN項目的時候,會發現類似用法:
__attribute__((__section__("section-name")))
__attribute__用來指定變量或結構位域的特殊屬性,其后的雙括弧中的內容是屬性說明,它的語法格式為:__attribute__ ((attribute-list))。它有位置的約束,通常放於聲明的尾部且“ ;” 之前。此時
attribute-list為__section__(“.initcall6.init”)。通常,編譯器將生成的代碼存放在.text段中。但有時可能需要其他的段,或者需要將某些函數、變量存放在特殊的段中,section屬性就是用來指定將一個函數、變量存放在特定的段中。
即, module_init(hello_init) 展開為:
static initcall_t __initcall_hello_init6 __used \
__attribute__((__section__(".initcall6.init"))) = hello_init
這里的 initcall_t 是函數指針類型,如下:
typedef int (*initcall_t)(void);
對應的段:.initcall
既然我們知道了xxx_initcall是怎么定義而且目標函數的放置位置,那么使用xxx_initcall()修飾的函數是怎么被調用的呢?
我們就從內核C函數起始部分也就是start_kernel開始往下挖,這里的調用順序為:
start_kernel
-> rest_init();
-> kernel_thread(kernel_init, NULL, CLONE_FS);
-> kernel_init()
-> kernel_init_freeable();
-> do_basic_setup();
-> do_initcalls();
這個do_initcalls()就是我們需要尋找的函數了,在這個函數中執行所有使用xxx_initcall()聲明的函數,接下來我們再來看看它是怎么執行的:
// init/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,
};
static void __init do_initcall_level(int level)
{
initcall_t *fn;
// ...
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);
}
在上述代碼中,定義了一個靜態的initcall_levels數組,這是一個指針數組,數組的每個元素都是一個指針.
do_initcalls()循環調用do_initcall_level(level),level就是initcall的優先級數字,由for循環的終止條件ARRAY_SIZE(initcall_levels) - 1可知,總共會調用do_initcall_level(0)~do_initcall_level(7),一共七次。
而do_initcall_level(level)中則會遍歷initcall_levels[level]中的每個函數指針,initcall_levels[level]實際上是對應的__initcall##level##_start指針變量,然后依次取出__initcall##level##_start指向地址存儲的每個函數指針,並調用do_one_initcall(*fn),實際上就是執行當前函數。
可以猜到的是,這個__initcall##level##*start所存儲的函數指針就是開發者用xxx_initcall()宏添加的函數,對應".initcall*##level##.init"段。
// init/main.c
int __init_or_module do_one_initcall(initcall_t fn)
{
int count = preempt_count();
int ret;
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();
// ...
return ret;
}
static int __init_or_module do_one_initcall_debug(initcall_t fn)
{
ktime_t calltime, delta, rettime;
unsigned long long duration;
int ret;
pr_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;
pr_debug("initcall %pF returned %d after %lld usecs\n",
fn, ret, duration);
return ret;
}
do_one_initcall(*fn)的執行:判斷initcall_debug的值,如果為真,則調用do_one_initcall_debug(fn);如果為假,則直接調用fn。
事實上,調用do_one_initcall_debug(fn)只是在調用fn的基礎上添加一些額外的打印信息,可以直接看成是調用fn。
鏈接文件
那么,在initcall源碼部分有提到,在開發者添加xxx_initcall(fn)時,事實上是將fn放置到了".initcall##level##.init"的段中。
但是在do_initcall()的源碼部分,卻是從initcall_levels(__initcall##level##_start指針)取出,initcall_levels[level]是怎么關聯到".initcall##level##.init"段的呢?
答案在vmlinux.lds.h中:
// include/asm-generic/vmlinux.lds.h
#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) = .;
在這里首先定義了__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中按先后順序組織,也就決定了位於其中的一些函數的執行先后順序(__initcall_hello_init6 位於 .initcall6.init 段中)。.init 或者 .initcalls 段的特點就是,當內核啟動完畢后,這個段中的內存會被釋放掉。這一點從內核啟動信息可以看到
Freeing unused kernel memory: 124K (80312000 - 80331000)
到這里,__initcall##level##_start和".initcall##level##.init"段的對應就比較清晰了,所以,從initcall_levels[level]部分一個個取出函數指針並執行函數就是執行xxx_init_call()定義的函數。
如何被調用
那么存放於 .initcall6.init 段中的__initcall_hello_init6是怎么樣被調用的呢?我們看文件 init/main.c,代碼梳理如下:
start_kernel
|
--> rest_init
|
--> kernel_thread
|
--> kernel_init
|
--> kernel_init_freeable
|
--> do_basic_setup
|
--> do_initcalls
|
--> do_initcall_level(level)
|
--> do_one_initcall(initcall_t fn)
kernel_init 這個函數是作為一個內核線程被調用的(該線程最后會啟動第一個用戶進程init)。
我們着重關注 do_initcalls 函數,如下:
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
函數 do_initcall_level 如下:
static void __init do_initcall_level(int level)
{
// 省略
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
函數 do_one_initcall 如下:
int __init_or_module do_one_initcall(initcall_t fn)
{
int ret;
// 省略
ret = fn();
return ret;
}
initcall_levels 的定義如下:
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
initcall_levels[] 中的成員來自於 INIT_CALLS 的展開,如“__initcall0_start = .;”,這里的 __initcall0_start是一個變量,它跟代碼里面定義的變量的作用是一樣的,所以代碼里面能夠使用__initcall0_start。
因此在 init/main.c 中可以通過 extern 的方法將這些變量引入,如下:
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[];
到這里基本上就明白了,在 do_initcalls 函數中會遍歷 initcalls 段中的每一個函數指針,然后執行這個函數指針。因為編譯器根據鏈接腳本的要求將各個函數指針鏈接到了指定的位置,所以可以放心地用do_one_initcall(*fn)來執行相關初始化函數。
我們例子中的 module_init(hello_init) 是 level6 的 initcalls 段,比較靠后調用,很多外設驅動都調用 module_init 宏,如果是靜態編譯連接進內核,則這些函數指針會按照編譯先后順序插入到 initcall6.init 段中,然后等待 do_initcalls 函數調用。
例子
便於理解,我們需要一個示例來梳理整個流程,假設我是一個驅動開發者,開發一個名為beagle的驅動,在系統啟動時需要調用beagle_init()函數來啟動啟動服務。
我需要先將其添加到系統中:
core_initcall(beagle_init)
core_initcall(beagle_init)宏展開為__define_initcall(beagle_init, 1),所以beagle_init()這個函數被放置在".initcall1.init"段處。
在內核啟動時,系統會調用到do_initcall()函數。
根據指針數組initcall_levels[1]找到__initcall1_start指針,在vmlinux.lds.h可以查到:__initcall1_start對應".initcall1.init"段的起始地址,依次取出段中的每個函數指針,並執行函數。
添加的服務就實現了啟動。
可能有些C語言基礎不太好的朋友不太理解do_initcall_level()函數中依次取出地址並執行的函數執行邏輯:
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
fn為函數指針,fn++相當於函數指針+1,相當於:內存地址+sizeof(fn)
而initcall_levels[level]指向當前".initcall##level##s.init"段,initcall_levels[level+1]指向".initcall##(level+1)##s.init"段,兩個段之間的內存就是存放所有添加的函數指針。
也就是從".initcall##level##s.init"段開始,每次取一個函數出來執行,並累加指針,直到取完。
動態加載,#else
模塊代碼有兩種運行方式:
- 一是靜態編譯連接進內核,在系統啟動過程中進行初始化;
- 一是編譯成可動態加載的module,通過insmod動態加載重定位到內核。
這兩種方式可以在Makefile中通過obj-y或obj-m選項進行選擇。
而一旦可動態加載的模塊目標代碼(.ko)被加載重定位到內核,其作用域和靜態鏈接的代碼是完全等價的。所以這種運行方式的優點顯而易見:
- 可根據系統需要運行動態加載模塊,以擴充內核功能,不需要時將其卸載,以釋放內存空間;
- 當需要修改內核功能時,只需編譯相應模塊,而不必重新編譯整個內核。
因為這樣的優點,在進行設備驅動開發時,基本上都是將其編譯成可動態加載的模塊。但是需要注意,有些模塊必須要編譯到內核,隨內核一起運行,從不卸載,如 vfs、platform_bus等。
實際上,上述的機制就是通過module_init實現的。
module_init
// include/linux/init.h
/* Don't use these in loadable modules, but some people do... */
#define early_initcall(fn) module_init(fn)
#define core_initcall(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define rootfs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)
#define console_initcall(fn) module_init(fn)
#define security_initcall(fn) module_init(fn)
__inittest 僅僅是為了檢測定義的函數是否符合 initcall_t 類型,如果不是 __inittest 類型在編譯時將會報錯。所以真正的宏定義是:
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
因此,用動態加載方式時,可以不使用 module_init 和 module_exit 宏,而直接定義 init_module 和 cleanup_module 函數,效果是一樣的。
alias 屬性是 gcc 的特有屬性,將定義 init_module 為函數 initfn 的別名。
所以 module_init(hello_init) 的作用就是定義一個變量名 init_module,其地址和 hello_init 是一樣的。
// filename: HelloWorld.c
#include <linux/module.h>
#include <linux/init.h>
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Bye Bye World\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
上述例子編譯可動態加載模塊過程中,會自動產生 HelloWorld.mod.c 文件,內容如下:
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
可知,其定義了一個類型為 module 的全局變量 __this_module,成員 init 為 init_module(即 hello_init),且該變量鏈接到 .gnu.linkonce.this_module 段中。
insmod
編譯后所得的 HelloWorld.ko 需要通過 insmod 將其加載進內核,由於 insmod 是 busybox 提供的用戶層命令,所以我們需要閱讀 busybox 源碼。
insmod_main
// 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;
}
insmod_main
|
--> bb_init_module
|
--> init_module
bb_init_module
// modutils/modutils.c
/* Return:
* 0 on success,
* -errno on open/read error,
* errno on init_module() error
*/
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
而 init_module 定義如下:
// modutils/modutils.c
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
因此,該系統調用對應內核層的 sys_init_module 函數。
關於系統調用,參考《Linux系統調用》
文件(
include/linux/syscalls.h)中,有:#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)從而形成
sys_init_module函數。
SYSCALL_DEFINE3
對應的內核實現
// 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);
}
SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
{
int err;
struct load_info info = { };
err = may_init_module();
if (err)
return err;
pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags);
if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS
|MODULE_INIT_IGNORE_VERMAGIC))
return -EINVAL;
err = copy_module_from_fd(fd, &info);
if (err)
return err;
return load_module(&info, uargs, flags);
}
代碼梳理:
SYSCALL_DEFINE3(init_module, ...)
load_module
do_init_module(mod)
do_one_initcall(mod->init);
從而執行了mod->init,即module_init(xx)中的xx初始化函數。
