日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux内核很吊之 module_init解析 (下)【转】

發(fā)布時(shí)間:2024/1/17 linux 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux内核很吊之 module_init解析 (下)【转】 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)自:https://blog.csdn.net/richard_liujh/article/details/46758073

版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。 https://blog.csdn.net/Richard_LiuJH/article/details/46758073
Linux內(nèi)核很吊之 module_init解析 (下)
個(gè)人筆記,歡迎轉(zhuǎn)載,請(qǐng)注明出處,共同分享 共同進(jìn)步 ??

http://blog.csdn.net/richard_liujh/article/details/46758073?-- 劉金輝

忙了一段時(shí)間,終于有時(shí)間把inux內(nèi)核很吊之 module_init解析 (下)整理完畢。

從上一篇博文http://blog.csdn.net/richard_liujh/article/details/45669207介紹了module_init宏函數(shù),簡(jiǎn)單來說上篇博文介紹module_init如何注冊(cè)驅(qū)動(dòng)的init函數(shù),這篇博文將詳細(xì)分析kernel啟動(dòng)過程又是如何執(zhí)行我們注冊(cè)的init函數(shù)。

如果了解過linux操作系統(tǒng)啟動(dòng)流程,那么當(dāng)bootloader加載完kernel并解壓并放置與內(nèi)存中準(zhǔn)備開始運(yùn)行,首先被調(diào)用的函數(shù)是start_kernel。start_kernel函數(shù)顧名思義,內(nèi)核從此準(zhǔn)備開啟了,但是start_kernel做的事情非常多,簡(jiǎn)單來說為內(nèi)核啟動(dòng)做準(zhǔn)備工作,復(fù)雜來說也是非常之多(包含了自旋鎖檢查、初始化棧、CPU中斷、立即數(shù)、初始化頁地址、內(nèi)存管理等等等...)。所以這篇博文我們還是主要分析和module_init注冊(cè)函數(shù)的執(zhí)行過程。

start_kernel函數(shù)在 init/main.c文件中,由于start_kernel本身功能也比較多,所以為了簡(jiǎn)介分析過程我把函數(shù)從start_kernel到do_initcalls的調(diào)用過程按照如下方式展現(xiàn)出來


start_kernel -> reset_init -> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
|
|->static int __ref kernel_init(void *unused)
|
|-> kernel_init_freeable( )
|
|-> do_basic_setup();
|
|——> do_initcalls();
在上面的調(diào)用過程中,通過kernel_thread注冊(cè)了一個(gè)任務(wù)kernel_init,kernel_thread的函數(shù)原型如下。
/*
* Create a kernel thread.
*/
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
(unsigned long)arg, NULL, NULL);
}
kernel_thread創(chuàng)建了一個(gè)內(nèi)核線程,也就是創(chuàng)建一個(gè)線程完成kernel_init的任務(wù)。通過kernel_init的逐層調(diào)用,最后調(diào)用到我們目前最應(yīng)該關(guān)心的函數(shù)do_initcalls;
do_initcalls函數(shù)如下

static void __init do_initcalls(void)
{
int level;

for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
這個(gè)函數(shù)看起來就非常簡(jiǎn)單了,里面有for循環(huán),每循環(huán)一次就調(diào)用一次do_initcall_level(level);其實(shí)可以發(fā)現(xiàn)在我們分析kernel源碼時(shí),大部分函數(shù)都能從函數(shù)名猜到函數(shù)的功能,這也是一名優(yōu)秀程序猿的體現(xiàn),大道至簡(jiǎn),悟在天成。

接下來我們就開始具體分析do_initcalls函數(shù)啦~~

for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
這句for循環(huán)很簡(jiǎn)單,循環(huán)執(zhí)行條件是level < ARRAY_SIZE(initcall_levels)。
ARRAY_SIZE是一個(gè)宏,用于求數(shù)組元素的個(gè)數(shù),在文件include\linux\kernel.h文件中

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
當(dāng)然ARRAY_SIZE宏里面還多了一個(gè)__must_be_array(),這個(gè)主要是確保我們傳過來的arr是一個(gè)數(shù)組,防止ARRAY_SIZE的誤用。所以在我們寫kernel驅(qū)動(dòng)程序時(shí),遇到需要求一個(gè)數(shù)組的大小請(qǐng)記得使用ARRAY_SIZE。有安全感又高大上...哈哈

那么,initcall_levels是不是數(shù)組呢?如果是,里面有什么內(nèi)容?

還是在文件main.c中有數(shù)組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,
};
這個(gè)數(shù)組可不能小看他,如果看過module_init解析(上)的朋友,對(duì)數(shù)組里面的名字“__initcall0 __initcall1 ... __initcall7”有一點(diǎn)點(diǎn)印象吧。
談到數(shù)組,我們知道是元素的集合,那么initcall_levels數(shù)組中得元素是什么???(看下面的分析前,請(qǐng)先弄清楚數(shù)組指針?和指針數(shù)組的區(qū)別,不然容易走火入魔...)

static initcall_t *initcall_levels[] __initdata = {
很顯然,這個(gè)數(shù)組定義非常高大上。不管如何高大上,總離不開最基本的知識(shí)吧。所以我先從兩點(diǎn)去探索:
1. 數(shù)組的名字,根據(jù)數(shù)組標(biāo)志性的‘[ ]’,我們應(yīng)該很容易知道數(shù)組名字是initcall_levels

2.數(shù)組的元素類型,由于定義中出現(xiàn)了指針的符號(hào)‘ * ’,也很容知道initcall_levels原來是一個(gè)指針數(shù)組啦。

所以現(xiàn)在我們知道了initcall_levels數(shù)組里面保存的是指針啦,也就是指針的一個(gè)集合而已。掰掰腳趾數(shù)一下也能知道initcall_levels數(shù)組里面有9個(gè)元素,他們都是指針。哈哈

對(duì)于這個(gè)數(shù)組,我們先暫且到這兒,因?yàn)槲覀円呀?jīng)知道了數(shù)組的個(gè)數(shù)了,也就知道for循環(huán)的循環(huán)次數(shù)。(后面還會(huì)繼續(xù)分析這個(gè)數(shù)組,所以要由印象)

我們?cè)倩貋砜纯磀o_initcalls:

static void __init do_initcalls(void)
{
int level;

for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
ARRAY_SIZE求出了數(shù)組initcall_levels的元素個(gè)數(shù)為9,所以level變量從 0 ~ 7都是滿足level < ARRAY_SIZE(initcall_levels) - 1既level < 9 - 1。一共循環(huán)了8次。
循環(huán)8此就調(diào)用了do_initcall_level(level) 8次。
do_initcall_level函數(shù)原型如下:

static void __init do_initcall_level(int level)
{
extern const struct kernel_param __start___param[], __stop___param[];
initcall_t *fn;

strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
static_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
在do_initcall_level函數(shù)中,有如下部分是和內(nèi)核初始化過程調(diào)用parse_args對(duì)選項(xiàng)進(jìn)行解析并調(diào)用相關(guān)函數(shù)去處理的。其中的__start___param和__stop___param也是可以在內(nèi)核鏈接腳本vmlinux.lds中找到的。

extern const struct kernel_param __start___param[], __stop___param[];

strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
static_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);
如果將上面初始化過程中命令行參數(shù)解析過程忽略,那么就剩下的內(nèi)容也就是我們最想看到的內(nèi)容了
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
這個(gè)也很簡(jiǎn)單,不就是一個(gè)for循環(huán)嘛,so easy~!!

那么接下來我們就開始分析這個(gè)for循環(huán):

1. for循環(huán)開始,fn = initcall_levels[level],initcall_levels是上面分析過的數(shù)組,數(shù)組里面存放著指針,所以fn也應(yīng)該是指針咯。那么看看fn的定義

initcall_t *fn;
fn確實(shí)是一個(gè)initcall_t類型的指針,那initcall_t是什么?
在文件include\linux\init.h文件中找到其定義

/*
* Used for initialization calls..
*/
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);
從上面的定義可以知道,initcall_t原來是一個(gè)函數(shù)指針的類型定義。函數(shù)的返回值是int類型,參數(shù)是空 void。從注釋也可以看出,initcall_t是初始化調(diào)用的。
簡(jiǎn)單來說,fn是一個(gè)函數(shù)指針。
2. 每循環(huán)一次,fn++。循環(huán)執(zhí)行的條件是fn < initcall_levels[level+1];

這里fn++就不是很容易理解了,畢竟不是一個(gè)普通的變量而是一個(gè)函數(shù)指針,那么fn++有何作用呢??

首先,fn = initcall_levels[level],所以我們還是有必要去再看看initcall_levels數(shù)組了(之前暫時(shí)沒有分析的,現(xiàn)在開始分析了)

static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
已經(jīng)知道了initcall_levels是一個(gè)指針數(shù)組,也就是說數(shù)組的元素都是指針,指針是指向什么類型的數(shù)據(jù)呢? 是initcall_t類型的,上面剛剛分析過initcall_t是函數(shù)指針的類型定義。
這樣一來,initcall_levels數(shù)組里面保存的元素都是函數(shù)指針啦。

很顯然這是通過枚舉的方式定義了數(shù)組initcall_levels,那么元素值是多少??(數(shù)組中元素是分別是?__initcall0_start?__initcall1_start?__initcall2_start ...?__initcall7_start?__initcall_end)

通過尋找會(huì)發(fā)現(xiàn)在main.c文件中有如下的聲明

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[];
所以__initcall0_start?__initcall1_start?__initcall2_start ...?__initcall7_start?__initcall_end都是initcall_t類型的數(shù)組名,數(shù)組名也就是指針。只是這些都是extern聲明的,所以在本文件里面找不到他們的定義出。那么他們?cè)谀囊粋€(gè)文件??答案還是 鏈接腳本 vmlinux.lds,而且我們已經(jīng)看過這些名字很多次了...
下面再次把鏈接腳本中相關(guān)的內(nèi)容拿出來:(相關(guān)的解釋請(qǐng)參考 module_init 解析--上)

__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);
所以在main.c文件中extern聲明的那些數(shù)組__initcall0_start ?...?__initcall7_start?__initcall_end其實(shí)就是上面鏈接腳本vmlinux.lds中定義的標(biāo)號(hào)(也可以暫且簡(jiǎn)單粗暴認(rèn)為是地址)。
為了好理解,把其中的__initcall0_start單獨(dú)拿出來
__initcall0_start = .; *(.initcall0.init) *(.initcall0s.init)
這里的意思是,__initcall0_start 是一段地址的開始,從這個(gè)地址開始鏈接所有.initcall0.init和.initcall0s.init段的內(nèi)容。那.initcall0.init和.initcall0s.init段有什么東東??這就是上篇博文中解釋的。簡(jiǎn)單來說,就是我們通過module_init(xxx)添加的內(nèi)容,只是module_init對(duì)應(yīng)的level值默認(rèn)為6而已。
總而言之,__initcallN_start(其中N = 0,1,2...7)地址開始存放了一系列優(yōu)先級(jí)為N的函數(shù)。我們通過module_init注冊(cè)的函數(shù)優(yōu)先級(jí)為6

現(xiàn)在我們回過頭再去看看上面的for循環(huán)

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
<span style="white-space: pre;"> </span>do_one_initcall(*fn);
一開始fn = initcall_levels[level],假設(shè)level = 0。也就是fn = initcall_levels[0] =?__initcall0_start。所以fn指向了鏈接腳本中的__initcall0_start地址,每當(dāng)fn++也就是fn逐次指向注冊(cè)到.initcall0.init和.initcall0s.init段中的函數(shù)地址了。for循環(huán)的條件是fn < initcall_levels[level + 1] = initcall_levels[0 + 1] = initcall_level[1] = __initcall1_start。

為了能直觀看出fn增加的范圍,用如下的簡(jiǎn)易方式表達(dá)一下。

__initcall0_start ?__initcall1_start ?__initcall2_start ?__initcall3_start ... ...?__initcall7_start? ? ?__initcall_end

| <--- fn++ -->|| <--- fn++ -->|?| <--- fn++ ->|?| <-- fn++ -->|? ... ...??| <--- fn++ -->|? END

了解這一點(diǎn),我們已經(jīng)接近勝利的彼岸~~

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
最后我們要了解的就是for循環(huán)每次執(zhí)行的內(nèi)容do_one_initcall(*fn),其函數(shù)原型如下
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();

msgbuf[0] = 0;

if (preempt_count() != count) {
sprintf(msgbuf, "preemption imbalance ");
preempt_count() = count;
}
if (irqs_disabled()) {
strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
local_irq_enable();
}
WARN(msgbuf[0], "initcall %pF returned with %s\n", fn, msgbuf);

return ret;
}
do_one_initcall函數(shù)就非常簡(jiǎn)單了,讓我們看看最重要的內(nèi)容如下
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();
這里就是判斷是不是debug模式,無非debug會(huì)多一些調(diào)試的操作。但是不管是哪一種,他們都執(zhí)行 ret = fn( );
因?yàn)閒n就是函數(shù)指針,fn指向的是我們注冊(cè)到__initcall0_start ?...?__initcall7_start的一系列函數(shù)。所以 fn( ); 就是調(diào)用這些函數(shù)。當(dāng)然也包括了驅(qū)動(dòng)中module_init注冊(cè)的函數(shù)啦,只是通過module_init注冊(cè)的level等級(jí)是6,for循環(huán)是從level = 0開始的,這也能看出0是優(yōu)先級(jí)最高,7是優(yōu)先級(jí)最低的。
到現(xiàn)在,module_init的作用已經(jīng)全部分析完畢~
---------------------
作者:Richard_LiuJH
來源:CSDN
原文:https://blog.csdn.net/richard_liujh/article/details/46758073
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!

轉(zhuǎn)載于:https://www.cnblogs.com/sky-heaven/p/10344823.html

總結(jié)

以上是生活随笔為你收集整理的Linux内核很吊之 module_init解析 (下)【转】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。