linux驅動 之 module_init解析 (上)【轉】


轉自:https://blog.csdn.net/Richard_LiuJH/article/details/45669207

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/Richard_LiuJH/article/details/45669207
linux內核驅動 之 module_init解析 (上)
歡迎轉載,相互學習,但請注明出處,非常感謝!

http://blog.csdn.net/richard_liujh/article/details/45669207

- 劉金輝

 

寫過linux驅動的程序猿都知道module_init() 這個函數。那么我們來了解一下module_init這個函數的具體功能和執行過程

在kernel源碼目錄中找到include\linux\init.h文件

<span style="font-family:SimSun;font-size:14px;">/**
* 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);</span>
這里面就有對module_init 的定義,我們發現
module_init(x)是一個宏定義,那么_initcall(x)又是什么呢?


#define __initcall(fn) device_initcall(fn)
感覺怪怪的,怎么這么多的宏??再解釋這個之前,我們再來看看更多的宏定義吧


完整的宏定義如下:

__define_initcall:


/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*
* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.
*/

#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
initcalls:


</pre><pre name="code" class="cpp">/
* 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)

Note:下面用xxx_initcall來代表pure_initcall,core_initcall、core_initcall_sync … …
我們可以看到非常多的xxx_initcall宏函數定義,他們都是通過__define_initcall 實現的。在__define_initcall里面包含了兩個參數,一個是fn,另一個則是id。那么,這么多的宏又有何用??

我們來到init\main.c文件中可以找到函數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_initcalls中有一個for循環,那么此循環就是按照優先級順序執行一些函數的。那么問題又來了,執行哪些函數??我們看看do_initcalls這個名字。是不是initcall非常的眼熟?沒錯就是上面我們提到過的宏定義xxx_initcall里面就有initcall。
所以,我們先來解釋一下這些宏有什么用
還是從我們最熟悉的地方module_init(fn)開始說起,其中fn是module_init的參數,fn是一個函數指針(函數名)。

module_init(fn)---> __initcall(fn) ---> device_initcall(fn) ---> __define_initcall(fn, 6)

所以當我們寫module_init(fn)最終我們可以簡化成以下內容(假設module_init的參數為test_init)

module_init(test_init) ---> __define_initcall(test_init, 6)


#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn

簡單補充:


符號 作用 舉例
##
“##”符號可以
是連接的意思
例如 __initcall_##fn##id 為__initcall_fnid
那么,fn = test_init,id = 6時,__initcall_##fn##id 為 __initcall_test_init6
#
“#”符號可以
是字符串化的意思
例如 #id 為 “id”,id=6 時,#id 為“6”


通過上面的定義,我們把module_init(test_init)給替換如下內容

static initcall_t __initcall_test6 __used __attribute__((__section__(".initcall""6" ".init"))) =test_init
是不是看起來更加頭疼,那么我們說簡單一點。通過__attribute__(__section__)設置函數屬性,也就是將test_init放在.initcall6.init段中。這個段在哪用?這就要涉及到鏈接腳本了。

大家可以到kernel目錄arch中,根據自己的處理器平台找到對應的鏈接腳本。例如我現在的平台是君正m200(mips架構),可能大部分是arm架構。

在arch/mips/kernel/vmlinux.lds這個鏈接腳本里面有如下一段代碼


__init_begin = .;
 . = ALIGN(4096); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; }
 .init.data : AT(ADDR(.init.data) - 0) { *(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .; __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .; __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .; . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info) }
 . = ALIGN(4);
當然,關於鏈接腳本又有很多多動要講。所以現在我們不關心里面的具體含義,我們可以觀察到上面有這些字符串使我們比較熟悉的:__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)。鏈接腳本里的東西看似很亂很難,其實是非常有邏輯有規律可循的,我們來簡單解釋下面一行的代碼作用

__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)
其中__initcall6_start是一個符號,鏈接器用到的。__initcall6_start = .; ,其中的 '.'符號是對當前地址的一個引用,也就說把當前的地址給了符號__initcall6_start, *(.initcall6.init) *(.initcall6s.init) 的意思是所有的.initcall6.init段和.initcall6s.init段的內容從__initcall6_start為起始地址開始鏈接。
.initcall0.init .initcall0s.init .initcall1.init .initcall1s.init …… .initcall7.init .initcall7s.init

上面的內容都出現在了鏈接腳本中,而0,0s,1,1s,2,2s …… 6,6s,7,7s 有沒有覺得在哪里見過? 我們回顧一下initcalls里面的定義


#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)
這里面就有0,0s,1,1s,2,2s …… 6,6s,7,7s,也就是__define_initcall(fn, id)中的第二個參數 id。很顯然這個id的值不是我們在調用module_init的時候傳過去的。數字id 0~7代表的是不同的優先級(0最高,module_init對應的優先級為6,所以一般我們注冊的驅動程序優先級為6),鏈接腳本里面根據我們注冊不同的id,將我們的函數fn放入對應的地址里面。根據上面的分析,test_init放在.initcall6.init段中。
在kernel啟動過程中,會調用do_initcalls函數一次調用我們通過xxx_initcall注冊的各種函數,優先級高的先執行。所以我們通過module_init注冊的函數在kernel啟動的時候會被順序執行。

由於時間原因,只能把具體執行過程放在linux內核很吊之 module_init解析 (下)再分析了。
---------------------
作者:Richard_LiuJH
來源:CSDN
原文:https://blog.csdn.net/Richard_LiuJH/article/details/45669207
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM