Linux內核初探

在uboot學習的時候, 咱們知道了一個龐大的程序,感受無從下手,但其實,經過韋老師和一些老手的經驗告訴我,若是咱們不是專門弄uboot的,通常只用知道怎麼用就好了。確實這個東西太大了並且花那麼多時間去弄這個也不值得。同理,uboot的終極奧義是啓動內核,如今uboot的簡單應用咱們已經會了,內核是一個比uboot更加龐大的傢伙,只是2440相關的文件都1w多個,要想一接觸就弄清楚每一步是幹什麼的也幾乎不可能,可是內核和uboot不一樣,裏面須要細細研究和借鑑的東西不少,只是剛開始,咱們仍是跟着師傅學一點皮毛,能跑上應用程序再說。顯而易見的是如今沒有什麼教程能教會你linux內核的所有知識,除非linus來教你,^_^。linux

內核操做方式如同uboot同樣,依舊先下載內核源碼,而後解壓縮,而後打補丁。接着就是配置了。ubuntu

如下部份內容借鑑韋老師一學員的筆記,我太懶了,有現成的決不本身來。數組

 搜索到了目錄部分截圖以下:網絡

 而後咱們到arm構架下查看有沒有咱們熟悉的配置項,結果是有的,2410:架構

 如今執行make s3c2410_defconfig報錯以下:函數

Makefile:416: *** mixed implicit and normal rules: deprecated syntaxpost

Makefile:1449: *** mixed implicit and normal rules: deprecated syntax學習

make: *** No rule to make target 'menuconfig'.  Stopui

由於個人ubuntu是16.04新版的make編譯器:spa

 

新版Makefile不支持這樣的組合目標:config %config(一個有通配符,另外一個沒有通配符)

 

解決方法:

修改linux-2.6.22.6 頂層Makefile 416行:

config %config: scripts_basic outputmakefile FORCE

改成:

%config: scripts_basic outputmakefile FORCE

 

修改linux-2.6.22.6 頂層 Makefile 1449行:

/ %/: prepare scripts FORCE

改成:

%/: prepare scripts FORCE

能夠看到生成了.config:

而後make menuconfig,這個會去調用剛纔生成的.config

 像上面內核配置的界面,高亮顯示的第一個字母是這個選項的快捷鍵(不區分大小寫),按下  / 建進入搜索模式,具體介紹上面圖片的英文有說明。

上面是默認配置,和默認配置同等級別或者更高級別的是使用廠家提供的config文件,這裏使用韋老師的config_ok文件試試:

使用廠家的配置文件覆蓋了.config以後,執行make menuconfig:

 

 

 

 若是根文件系統沒有下載到內存,啓動內核的時候的會失敗。

等於m表明的是編譯或模塊。看看.config中網卡DM9000的配置:

咱們在內核文件全局搜索一下:grep -w選項表示精確查找

須要注意的是,能搜素到DM9000是咱們要執行make或者make uImage以後,不然搜索不到哦。

能夠看看autocnf.h看看自動配置頭文件。

還有 Makefile 中有:
drivers\net\Makefile
#obj-$(CONFIG_DM9000) += dm9000.o
#obj-$(CONFIG_DM9000) += dm9ks.o
則:
a. C 源代碼中用到 CONFIG_DM9000.C 語言語法看確定是個宏。宏只能在 C 文件或是
頭文件中定義。
依照這裏的狀況,應該是在「include/linux/autoconf.h 」這個頭文件中定義。
b. 子目錄下的 Makefile 中有 CONFIG_DM9000 配置項(如 drivers/net/Makefile
c. include/config/auto.conf 中有。
d. include/linux/autoconf.h 中有。從 autoconf.h 可猜想這個文件是自動生成的。它裏面的內
容來源於
make 內核時, make 機制會自動根據生成的 「.config」 配置文件,生成 autconf.h 這個文
件。
autoconf.h 中搜索 DM9000 獲得: #define CONFIG_DM9000 1
可見 CONFIG_DM9000 被定義成一個宏,爲「1」。
整個文件 autoconfi.h 中的宏基本都是定義爲「1」,就是說無論在 .config 配置文件中,
配置項=y,仍是=m
在這個由 .config 生成的頭文件 autoconf.h 中都被定義成「1」。
C 語言源碼中就只使用這些 宏 了。這些等於 y 等於 m 的在 autoconf.h 中定義宏時都定
義爲「1」,它們的區別則在
使用這些宏的 C 語言中體現不出來了。這些區別是在 Makefile 中定義。看子目錄下的
Makefile.
看子目錄 dervice/net/Makefile 文件。搜索其中 CONFIG_DM9000 文件。
對於內核的 Makefile,它的子目錄的 Makefie 很簡單。
2. 內核子目錄 Makefile:
1.格式比較簡單:內核子目錄下的 Makefile
obj-y += xxx.o
xxx.c 的文件最後會被編譯進內核中去。
obj-m += yyy.o
yyy.c 文件最後會編譯成 可加載 的模塊 yyy.ko
這兩種格式。
以下示例:
obj-$(CONFIG_DM9000) += dm9000.o
意思:若 CONFIG_DM9000 這個變量被定義爲 y 的話,這個 dm9000.c 就會被編譯進內核
中去。
若這個配 置 項 CONFIG_DM9000 被 定義爲 m 的話 ,這 個 dm9000.c 會被編譯成
dm9000.ko 模塊
y m 的區別就是在內核的子目錄下的 Makefile 中體現的。子目錄下的 Makefile
中的 CONFIG_DM9000 是誰來定義的呢?
也是由其餘來定義的。由 "include/config/auto.conf " 來定義(這個文件也是來源於 make
內核時的 .config)。從 auto.conf
可知它也是自動生成的。 裏面的內核和 .config 文件很像。其中的項要麼等於 y,要麼等於
m,或其餘值。
顯然這個 include/config/auto.conf 文件是會被別人包含進去。(被頂層的 Makefile 包含)

配置內核時生成了 .config 文件。而後 make menuconfig 或是 make uImage 時:
1.config 被自動來建立生成了一個 include/linux/autoconfig.h 文件 .這個頭文件被 C
代碼去對應裏面的配置。
2.config 也被自動來生成一個 include/config/auto.conf 文件。這個文件由頂層 Makefile
來包含。由子目錄下的 Makefile 來用它。

3、 內核 Makefile
分析 Makefile:找到第一個目標文件和連接文件。

對於 Makefile 的文檔在 Documentation\kbuild 下的 makefiles.txt 對內核的 makefile 講的很透徹。 

子目錄下的 Makefile 很簡單,就只有幾條格式:
obj-y += a.o b.o
obj-m += a.o

2. 架構相關的 Makefile 。(arch/$(ARCH)/Makefile
分析一個 Makefile 時,從它的命令開始分析。編譯內核時是直接 make make uImage
從頂層 Makefile 一直往下走時會涉及到全部的東西。
<1> make uImage 時這個目標 uImage 不在頂層的 Makefile 中,在 arch/arm/Makefile
定義了這個目標。
咱們是在頂層目錄 make uImage 的,則可知頂層 Makefile 會包含 arch/arm/Makefile

 

3. 頂層目錄的 Makefile.
make uImage 命令往下分析。
<1> 目標 uImage 定義在 arch/arm/Makefile 中,找到 uImage 目標所在行,查看它相關
的依賴。

在頂層目錄直接輸入 make ,默認就是執行第一個目標, "all"就是第一個目標。這個目標也
是依賴於 vmlinux 。即都是要先生成 vmlinux .

init-y := $(patsubst %/, %/built-in.o, $(init-y))
這是一個 Makefile 的函數。
%/ 表明的是 init/目錄下的全部文件。
%/built-in.o 至關於在 init/下的文件所有編譯成 built-in.o
這個函數的意思是: init-y := $(patsubst %/, %/built-in.o, $(init-y)) = init/built-in.o
init-y 等於 init 目錄下全部涉及的那些文件,這些文件會被編譯成一個 built-in.o
patsubst :替換通配符。
$(patsubst %.c%.o$(dir) )中, patsubst $(dir)中的變量符合後綴是.c 的所有替換成.o
任何輸出。
或者可使用
obj=$(dir:%.c=%.o)
效果也是同樣的。

vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
core-y :核心 libs-y:庫 drivers-y:驅動 net-y:網絡
Makefile 中搜索 core-y 有以下依賴:
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/built-in.o, $(core-y))
意思是最後 core-y = usr/built-in.o
+= kernel/built-in.o
+= mm/built-in.o
+= fs/built-in.o
+= ipc/built-in.o
+= security/built-in.o
+= crypto/built-in.o
+= block/built-in.o
就是將這些目錄(usrkernelmmfsipcsecuritycryptoblock)下涉及的文件分別
編譯成 built-in.o
不是全部文件,而是涉及到的文件。

libs-y : 依賴
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2)
最後 libs-y = lib/lib.a
+= lib/built-in.o
drivers-y : 驅動
drivers-y := drivers/ sound/ (依賴了這兩個目錄)
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
意思是最後 drivers-y = drivers/built-in.o (drivers 目錄下全部涉及的文件編譯成
built-in.o 文件)
+= sound/built-in.o (sound 目錄下全部涉及的編譯成 built-in.o 文件)
net-y : 網絡
net-y := net/
net-y := $(patsubst %/, %/built-in.o, $(net-y))
意思是最後,將 net/目錄下的全部涉及到的文件編譯 built-in.o 這個文件。
從面的依賴文件展開來看,源材料就是上面這一大堆東西。這些東西如何組合成一個內核
(連接成在一塊),要看 vmlinux 如何編譯的。

4. vmlinux 如何編譯

 

編譯時是經過這些命令來編譯的。這些命令最終會生成什麼東西?能夠經過這裏一一分析下
去。這裏涉及的腳本、函數太龐大了。沒精
力去作。
想知道上在的源材料如何編譯成內核:
方法 1:分析 Makefile .
方法 2:直接編譯內核。 看編譯過程。
a.rm vmlinux 先刪除原來編譯獲得的內核。
b.make uImage V=1 (V=1 是更加詳細的列出那些命令。 )
咱們關心詳細命令的最後一條。

分析後肯定兩個方面:
第一個文件是誰:從上面 make uImage 最後一條命令可知,內核第一個文件是
arch/arm/kernel/head.ohead.S
連接腳本: arch/arm/kernel/vmlinux.lds (決定內核如何排布).

再接着是放全部文件的「.init.text」段。
等等、、、
這些全部文件排放在相應的 「段」 中,排放的順序就是以下「連接腳本」後面「.o」文件
的排布順序:

 

4、 機器 ID,啓動參數
1. 創建 SI 工程:先是添加全部的代碼,再去除不相關的代碼。
添加完全部的文件代碼後,再移除 ARCH 目錄(由於裏面有不須要的代碼),移除後再進到
ARCH 目錄從新
添加 ARM 相關的代碼(由於這裏處理的是 ARM 平臺)

最後添加上平臺: Plat-s3c24xx 平臺,其餘平臺不須要加。
<2> include 目錄。裏面也有不少東西,先「Remove Tree」後,再挑選咱們須要的加進工
程。
include 目錄中, asm 開頭的目錄顯然是 架構 相關的頭文件。咱們只關心 Asm-arm
arm 架構的頭文件。

head.S 作的事情:
0.判斷是否支持此 CPU
1.如何比較 機器 ID 是:(判斷是否支持單板)
3.建立頁表。
4.使能 MMU
5.跳轉到 start_kernel (它就是內核的第一個 C 函數)
2. 分析內核源代碼:
<1> 經過 make uImage V=1 詳細查看內核編譯時的最後一條命令可知。
內核中排布的第一個文件是: arch/arm/kernel/head.S
連接腳本: arch/arm/kernel/vmlinux.lds
UBOOT 啓動時首先在內存裏設置了一大堆參數。

3. 內核啓動:最終目標是就運行應用程序。對於 Linux 來講應用程序在 根文件系統中。須要掛接文件系統。
 
1)處理 UBOOT 傳入的參數。
內核中排布的第一個文件是: arch/arm/kernel/head.S

4. 內核:
處理 UBOOT 傳入的參數
1.首先判斷是否支持這個 CPU
2.判斷是否支持這個單板。(UBOOT 啓動內核時傳進來的:機器 ID bd->bi_arch_number
UBOOT 代碼,對於這塊開發板是: gd->bd->bi_arch_number = MACH_TYPE_SMDK2410

這個 362 這個值存在哪裏?從彙編的 C 語言交互規則知道。這個參數 bi_arch_number 是存在r1寄存器中的。

24102440qt2410 的單板代碼都強制放在這個地方。
內核啓動時,會從 __arch_info_begin = . 開始讀,讀到 __arch_info_end = . 一個一個的將
單板
信息取出來。將裏面的機器 ID UBOOT 傳進來的機器 ID 比較。相同則表示內核支持這個
單板。

下面就是比較機器 ID了。(內核中的和 UBOOT傳進來的)arch\arm\kernel\head-common.S
從「__lookup_machine_type:」中可知 r5=__arch_info_begin
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
r5 是: __arch_info_begin
r1 UBOOT 傳來的參數: bi_arch_number
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6

blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
最後比較成功後,會回到: head.S
以上 單板機器 ID 比較完成。

 

 

 

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);建立內核線程。暫且
認爲它是調用 kernel_init 這個函數。這個函數中又有一個: prepare_namespace(),它其中
又有一個: mount_root();掛接根文件系統。

 

 

縮進表示調用關係, prepare_namespace 中執行完了 掛接根文件系統 後,會執行 init_post
函數,

在這個函數中會打開 /dev/console 和 執行應用程序。

__setup(「root=,root_dev_setup):其中 __setup 是一個宏。
大 概 意 思 是 發 現 在 命 令 行 參 數 : bootargc=ninitrd root=/dev/mtdblock3 init=/linuxrc
console=ttySAC0
中的 root= 時,就以這個 root= 來找到「root_dev_setup」這個函數。而後調用這個函數。
這個函數將:
/dev/mtdblock3 init=/linuxrc console=ttySAC0 保存到
strlcpy(saved_root_name, line, sizeof(saved_root_name));」中的變量 saved_root_name 中。
這個變量是個數組。
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}
__setup("root=", root_dev_setup);
__setup 這也是個宏,這個宏也是定義一個結構體。通過分析 UBOOT 和內核可知,這個宏
是個 結構體 。結構體裏面有「root=
root_dev_setup 函數指針(結構體中有「名字」和「函數指針」)。

這個結構體中的 段屬性強制將其放一個段.init.setup 中,這個段在連接腳本中。

 

 

**********************************************************************
內核啓動流程:
arch/arm/kernel/head.S
start_kernel
setup_arch //解析 UBOOT 傳入的啓動參數
setup_command_line //解析 UBOOT 傳入的啓動參數
parse_early_param
do_early_param
__setup_start __setup_end,調用 early 函數
unknow_bootoption
obsolete_checksetup
__setup_start __setup_end,調用非 early 函數
rest_init
kernel_init
prepare_namespace
mount_root //掛接根文件系統
init_post
//執行應用程序
early」「非 early」是:
__setup("root=",root_dev_setup)
#define __setup(str, fn)
__setup_param(str,fn,fn,0)中參數"0".
**********************************************************************
從代碼裏知道,這裏 early 這個成員爲 0.則沒「do_early_param」 和「parse_early_param」,

如今是 root=/dev/mtdblock3 ,咱們說過在 FLASH 中沒有分區表。那這個分區 mtdblock3
體如今寫死的
代碼。和 UBOOT 同樣: 「bootloader|參數|內核|文件系統」在代碼中寫死。也是用這個分
區,在代碼裏也要寫死。
啓動內核時,會打印出這些「分區信息」。

MTDPART_OFS_APPEND 這個 offset 意思是緊接着上面一個分區的意思。 

相關文章
相關標籤/搜索