一直以來寫linux驅(qū)動(dòng),都是按照固定格式,定義一個(gè)初始化和退出函數(shù),書上告訴我這兩個(gè)函數(shù)會(huì)被調(diào)用,至于為什么會(huì)被調(diào)用,在哪調(diào)用,一直不清楚。
偶然的一個(gè)機(jī)會(huì),看到blob里面的代碼,里面有一個(gè)初始化函數(shù)列表。按照一般的編程想法,各部分的初始化函數(shù)會(huì)在一個(gè)固定的函數(shù)里調(diào)用比如:
void init(void)
{
init_a();
init_b();
}
如果再加入一個(gè)初始化函數(shù)呢,那么再init_b()后面再加一行:
init_c();
這樣確實(shí)能完成我們的功能,但這樣有一定的問題,就是不能獨(dú)立的添加初始化函數(shù),每次添加一個(gè)新的函數(shù)都要修改init函數(shù),blob中的初始化函數(shù)就是完全獨(dú)立的,只要用一個(gè)宏來修飾一下:
void init_a(void)
{
}
__initlist(init_a, 1);
它是通過這個(gè)宏來實(shí)現(xiàn)初始化函數(shù)列表的呢?
先來看__initlist的定義:
#define __init __attribute__((unused, __section__(".initlist")))
#define __initlist(fn, lvl) /
static initlist_t __init_##fn __init = { /
magic: INIT_MAGIC, /
callback: fn, /
level: lvl }
看來就是定義了一個(gè)結(jié)構(gòu)體,存了初始化函數(shù)的指針,沒什么特別的。請(qǐng)注意:__section__(".initlist")
這個(gè)屬性起什么作用呢?它告訴連接器這個(gè)變量存放在.initlist區(qū)段,如果所有的初始化函數(shù)都是用這個(gè)宏,那么每個(gè)函數(shù)會(huì)有對(duì)應(yīng)的一個(gè)initlist_t結(jié)構(gòu)體變量存放在.initlist區(qū)段,也就是說我們可以在.initlist區(qū)段找到所有初始化函數(shù)的指針。怎么找到.initlist區(qū)段的地址呢?
extern u32 __initlist_start;
extern u32 __initlist_end;
這兩個(gè)變量起作用了,__initlist_start是.initlist區(qū)段的開始,__initlist_end是結(jié)束,通過這兩個(gè)變量我們就可以訪問到所有的初始化函數(shù)了。
這兩個(gè)變量在那定義的呢?
在一個(gè)連接器腳本文件里
. = ALIGN(4);
.initlist : {
__initlist_start = .;
*(.initlist)
__initlist_end = .;
}
這兩個(gè)變量的值正好定義在.initlist區(qū)段的開始和結(jié)束地址,所以我們能通過這兩個(gè)變量訪問到所有的初始化函數(shù)。
與此類似,內(nèi)核中也是用到這種方法,所以我們寫驅(qū)動(dòng)的時(shí)候比較獨(dú)立,不用我們自己添加代碼在一個(gè)固定的地方來調(diào)用我們自己的初始化函數(shù)和退出函數(shù),連接器已經(jīng)為我們做好了。當(dāng)然module_init還有其他的特性,比如:我們的初始化函數(shù)在完成初始化后,代碼占用的空間會(huì)被釋放,這又是為什么呢?今天晚了,下次再寫。
linux kernel中有很大一部分代碼是設(shè)備驅(qū)動(dòng)代碼,這些驅(qū)動(dòng)代碼都有初始化和反初始化函數(shù),這些代碼一般都只執(zhí)行一次,為了有更有效的利用內(nèi)存,這些代碼所占用的內(nèi)存可以釋放出來。
linux就是這樣做的,對(duì)只需要初始化運(yùn)行一次的函數(shù)都加上__init屬性。在kernel初始化后期,釋放所有這些函數(shù)代碼所占的內(nèi)存空間。它是怎么做到的呢?看過module_init和module_exit 的人知道,連接器把帶__init屬性的函數(shù)放在同一個(gè)section里,在用完以后,把整個(gè)section釋放掉。
口說無憑,我們看源碼,init/main.c中start_kernel是進(jìn)入kernel的第一個(gè)c函數(shù),在這個(gè)函數(shù)的最后一行是
rest_init();
static void rest_init(void)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
cpu_idle();
}
創(chuàng)建了一個(gè)內(nèi)核線程,主函數(shù)init,代碼如下:
static int init(void * unused)
{
lock_kernel();
do_basic_setup();
prepare_namespace();
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
free_initmem();
unlock_kernel();
紅色那行代碼就是用來釋放初始化代碼和數(shù)據(jù)的。
void free_initmem(void)
{
#ifndef CONFIG_XIP_ROM
if (!machine_is_integrator()) {
free_area((unsigned long)(&__init_begin),
(unsigned long)(&__init_end),
"init");
}
#endif
}
接下來就是kernel內(nèi)存管理的事了。
********************************************************************************************************
在Linux底下寫過driver模塊的對(duì)這個(gè)宏一定不會(huì)陌生。module_init宏在MODULE宏有沒有定義的情況下展開的內(nèi)容是不同的,如果這個(gè)宏沒有定義,基本上表明閣下的模塊是要編譯進(jìn)內(nèi)核的(obj-y)。
1.在MODULE沒有定義這種情況下,module_init定義如下:
#define module_init(x) __initcall(x);
因?yàn)?/p>
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
所以,module_init(x)最終展開為:
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
更直白點(diǎn),假設(shè)閣下driver所對(duì)應(yīng)的模塊的初始化函數(shù)為int gpio_init(void),那么module_init(gpio_init)實(shí)際上等于:
static initcall_t __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是聲明一類型為initcall_t(typedef int (*initcall_t)(void))函數(shù)指針類型的變量__initcall_gpio_init_6并將gpio_init賦值與它。
這里的函數(shù)指針變量聲明比較特殊的地方在于,將這個(gè)變量放在了一名為".initcall6.init"節(jié)中。接下來結(jié)合vmlinux.lds中的
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
__initcall_start = .;
*(.initcallearly.init) __early_initcall_end = .; *(.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_end = .;
}
以及do_initcalls:
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
那么就不難理解閣下模塊中的module_init中的初始化函數(shù)何時(shí)被調(diào)用了:在系統(tǒng)啟動(dòng)過程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。
2.在MODULE被定義的情況下(大部分可動(dòng)態(tài)加載的driver模塊都屬于此, obj-m),module_init定義如下:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
這段宏定義關(guān)鍵點(diǎn)是后面一句,通過alias將initfn變名為init_module。前面那個(gè)__inittest的定義其實(shí)是種技巧,用來對(duì)initfn進(jìn)行某種靜態(tài)的類型檢查,如果閣下將模塊初始化函數(shù)定義成,比如,void gpio_init(void)或者是int gpio_init(int),那么在編譯時(shí)都會(huì)有類似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
通過module_init將模塊初始化函數(shù)統(tǒng)一別名為init_module,這樣以后insmod時(shí)候,在系統(tǒng)內(nèi)部會(huì)調(diào)用sys_init_module()去找到init_module函數(shù)的入口地址。
如果objdump -t gpio.ko,就會(huì)發(fā)現(xiàn)init_module和gpio_init位于相同的地址偏移處。簡(jiǎn)言之,這種情況下模塊的初始化函數(shù)在insmod時(shí)候被調(diào)用。
-
連接器
+關(guān)注
關(guān)注
98文章
14669瀏覽量
137244 -
Linux
+關(guān)注
關(guān)注
87文章
11345瀏覽量
210403 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4346瀏覽量
62977
原文標(biāo)題:嵌入式Linux設(shè)備驅(qū)動(dòng)篇module_init 詳解
文章出處:【微信號(hào):gh_c472c2199c88,微信公眾號(hào):嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
嵌入式Linux下的USB設(shè)備驅(qū)動(dòng)技術(shù)
![<b class='flag-5'>嵌入式</b><b class='flag-5'>Linux</b>下的USB<b class='flag-5'>設(shè)備</b><b class='flag-5'>驅(qū)動(dòng)</b>技術(shù)](https://file1.elecfans.com//web2/M00/A5/95/wKgZomUMORmAR5K-AABIzpy1YR8305.jpg)
linux內(nèi)核使用鏈接腳本模仿module_init機(jī)制實(shí)戰(zhàn)
![<b class='flag-5'>linux</b>內(nèi)核使用鏈接腳本模仿<b class='flag-5'>module_init</b>機(jī)制實(shí)戰(zhàn)](https://file1.elecfans.com/web2/M00/89/5F/wKgaomSBofCAExRsAAJF709EkZ8942.jpg)
嵌入式Linux字符驅(qū)動(dòng)LED燈設(shè)計(jì)
嵌入式Linux應(yīng)用開發(fā)詳解 光盤
嵌入式linux設(shè)備驅(qū)動(dòng)開發(fā)詳解SD
ARM嵌入式Linux系統(tǒng)開發(fā)詳解
linux驅(qū)動(dòng)設(shè)備驅(qū)動(dòng)開發(fā)詳解
IAR實(shí)現(xiàn)類linux驅(qū)動(dòng)模塊框架module_init的相關(guān)資料推薦
嵌入式Linux設(shè)備驅(qū)動(dòng)開發(fā)
![<b class='flag-5'>嵌入式</b><b class='flag-5'>Linux</b><b class='flag-5'>設(shè)備</b><b class='flag-5'>驅(qū)動(dòng)</b>開發(fā)](https://file.elecfans.com/web2/M00/48/7E/pYYBAGKhtAmAIbeRAAAWmrBucZE592.jpg)
嵌入式Linux字符設(shè)備驅(qū)動(dòng)的設(shè)計(jì)與應(yīng)用
嵌入式Linux字符設(shè)備驅(qū)動(dòng)的設(shè)計(jì)與應(yīng)用
linux驅(qū)動(dòng)的入口函數(shù)module_init的加載和釋放
![<b class='flag-5'>linux</b><b class='flag-5'>驅(qū)動(dòng)</b>的入口函數(shù)<b class='flag-5'>module_init</b>的加載和釋放](https://file.elecfans.com/web1/M00/90/EA/o4YBAFzOha6AY1jLAABPVTqpUhs955.png)
IAR 實(shí)現(xiàn)類linux驅(qū)動(dòng)模塊框架module_init(init_fun)
![IAR 實(shí)現(xiàn)類<b class='flag-5'>linux</b><b class='flag-5'>驅(qū)動(dòng)</b>模塊框架<b class='flag-5'>module_init</b>(<b class='flag-5'>init</b>_fun)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評(píng)論