在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
就是將這些目錄(usr、 kernel、 mm、 fs、 ipc、 security、 crypto、 block)下涉及的文件分別
編譯成 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.o(head.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寄存器中的。
2410, 2440, qt2410 的單板代碼都強制放在這個地方。
內核啓動時,會從 __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 意思是緊接着上面一個分區的意思。