C語(yǔ)言 module_init函數(shù)與initcall案例詳解
module_init這個(gè)函數(shù)對(duì)做驅(qū)動(dòng)的人來(lái)說(shuō)肯定很熟悉,這篇文章用來(lái)跟一下這個(gè)函數(shù)的實(shí)現(xiàn)。
在include/linux/init.h里面有module_init的定義,自然,因?yàn)橐粋€(gè)module可以在內(nèi)核啟動(dòng)時(shí)自動(dòng)加載進(jìn)內(nèi)核,也可以由我們手動(dòng)在需要時(shí)加載進(jìn)內(nèi)核,基于這種場(chǎng)景,內(nèi)核使用了MODULE這個(gè)宏,見(jiàn)代碼:
#ifndef MODULE
#ifndef __ASSEMBLY__
...
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define __initcall(fn) device_initcall(fn)
#define module_init(x) __initcall(x);
#else /* MODULE */
...
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));...
當(dāng)我們使用make menuconfig來(lái)配置內(nèi)核時(shí),將某個(gè)module配置為m時(shí),MODULE這個(gè)宏就被定義了,而當(dāng)配置為y時(shí),則沒(méi)有定義,具體的實(shí)現(xiàn)在kernel的根Makefile(-DMODULE)里。
現(xiàn)在我們先看下第一種情況,即把module配置為m的情況,即else分支的代碼。
先看下initcall_t的定義:
typedef int (*initcall_t)(void);
它是一個(gè)接收參數(shù)為void, 返回值為int類型的函數(shù)指針。這樣就明白了,其實(shí)前兩句話只是做了一個(gè)檢測(cè),當(dāng)你傳進(jìn)來(lái)的函數(shù)指針的參數(shù)和返回值與initcall_t不一致時(shí),就會(huì)有告警。
重點(diǎn)在第三句,是使用alias將initfn變名為init_module,我們知道,kernel 2.4版本之前都是用init_module來(lái)加載模塊的。這樣做應(yīng)該是為了不用修改load module的那塊代碼吧。
當(dāng)我們調(diào)用insmod將module加載進(jìn)內(nèi)核時(shí),會(huì)去找init_module作為入口地址,即是我們的initfn, 這樣module就被加載了。
取nvme.ko為例,我們可以通過(guò)objdump -t nvme.ko 查看該模塊的符號(hào)表,發(fā)現(xiàn)init_module和nvme_init指向同一個(gè)偏移量。如下:

現(xiàn)在看第二種情況,即我們選擇將模塊編進(jìn)內(nèi)核,讓它隨內(nèi)核啟動(dòng)而加載。
這種情況下module_init最終會(huì)調(diào)用__define_initcall宏,這個(gè)宏的作用就是將我們的初始化函數(shù)放在".initcall" level ".init"中。
在這里是.initcall6.init, 它的位置可以在Vmlinux.lds.h里面找到:
#define INITCALLS \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)
而INITCALL可以在vmlinux.lds.S里面找到:
.init.text : AT(ADDR(.init.text) - LOAD_OFFSET) {
__init_begin = .;
_sinittext = .;
*(.init.text)
_einittext = .;
}
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { *(.init.data) }
. = ALIGN(16);
.init.setup : AT(ADDR(.init.setup) - LOAD_OFFSET) {
__setup_start = .;
*(.init.setup)
__setup_end = .;
}
.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
INITCALLS
__initcall_end = .;
}
.con_initcall.init : AT(ADDR(.con_initcall.init) - LOAD_OFFSET) {
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
}
上面貼出來(lái)的代碼是系統(tǒng)啟動(dòng)時(shí)存放初始化數(shù)據(jù)的地方,執(zhí)行完成后不再需要,會(huì)被釋放掉。根據(jù)上面的內(nèi)存布局,可以列出初始化宏和內(nèi)存的對(duì)應(yīng)關(guān)系:
_init_begin -------------------
| .init.text | ---- __init
|-------------------|
| .init.data | ---- __initdata
_setup_start |-------------------|
| .init.setup | ---- __setup_param
__initcall_start |-------------------|
| .initcall1.init | ---- core_initcall
|-------------------|
| .initcall2.init | ---- postcore_initcall
|-------------------|
| .initcall3.init | ---- arch_initcall
|-------------------|
| .initcall4.init | ---- subsys_initcall
|-------------------|
| .initcall5.init | ---- fs_initcall
|-------------------|
| .initcall6.init | ---- device_initcall
|-------------------|
| .initcall7.init | ---- late_initcall
__initcall_end |-------------------|
| |
| ... ... ... |
| |
__init_end -------------------
而各個(gè)initcall被調(diào)用的地方在kernel_init-》do_basic_setup-》do_initcalls里面:
static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call < __initcall_end; call++) {
ktime_t t0, t1, delta;
char *msg = NULL;
char msgbuf[40];
int result;
if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
t0 = ktime_get();
}
result = (*call)();
...
}
到此這篇關(guān)于C語(yǔ)言 module_init函數(shù)與initcall案例詳解的文章就介紹到這了,更多相關(guān)C語(yǔ)言 module_init函數(shù)與initcall內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入剖析Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼
- 詳解C語(yǔ)言用malloc函數(shù)申請(qǐng)二維動(dòng)態(tài)數(shù)組的實(shí)例
- C語(yǔ)言中二維數(shù)組作為函數(shù)參數(shù)來(lái)傳遞的三種方法
- C語(yǔ)言函數(shù)傳遞數(shù)組和傳遞地址的區(qū)別你知道嗎
- VS2017開(kāi)發(fā)C語(yǔ)言出現(xiàn)“no_init_all“的解決辦法
- C語(yǔ)言的數(shù)組指針與函數(shù)指針詳解
- C語(yǔ)言全方位講解指針與地址和數(shù)組函數(shù)堆空間的關(guān)系
- C語(yǔ)言創(chuàng)建數(shù)組實(shí)現(xiàn)函數(shù)init,empty,reverse
相關(guān)文章
詳解C++中二進(jìn)制求補(bǔ)運(yùn)算符與下標(biāo)運(yùn)算符的用法
這篇文章主要介紹了C++中二進(jìn)制求補(bǔ)運(yùn)算符與下標(biāo)運(yùn)算符的用法,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-01-01
C語(yǔ)言深入分析浮點(diǎn)型數(shù)據(jù)存儲(chǔ)
使用編程語(yǔ)言進(jìn)行編程時(shí),需要用到各種變量來(lái)存儲(chǔ)各種信息。變量保留的是它所存儲(chǔ)的值的內(nèi)存位置。這意味著,當(dāng)您創(chuàng)建一個(gè)變量時(shí),就會(huì)在內(nèi)存中保留一些空間。您可能需要存儲(chǔ)各種數(shù)據(jù)類型的信息,操作系統(tǒng)會(huì)根據(jù)變量的數(shù)據(jù)類型,來(lái)分配內(nèi)存和決定在保留內(nèi)存中存儲(chǔ)什么2022-08-08
利用C++如何實(shí)現(xiàn)一個(gè)阻塞隊(duì)列詳解
這篇文章主要給大家介紹了關(guān)于利用C++如何實(shí)現(xiàn)一個(gè)阻塞隊(duì)列的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
一文詳解C++關(guān)鍵字nullptr及與NULL的區(qū)別
這篇文章主要給大家詳細(xì)介紹了C++關(guān)鍵字nullptr,及?NULL與nullptr的區(qū)別,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
4組C語(yǔ)言中順序讀寫(xiě)文件的函數(shù)分享
這篇文章主要為大家詳細(xì)介紹了4組C語(yǔ)言中實(shí)現(xiàn)順序讀寫(xiě)文件的函數(shù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03

