在uboot學習的時候, 我們知道了一個龐大的程序,感覺無從下手,但其實,通過韋老師和一些老手的經驗告訴我,如果我們不是專門弄uboot的,一般只用知道怎么用就行了。確實這個東西太大了而且花那么多時間去弄這個也不值得。同理,uboot的終極奧義是啟動內核,現在uboot的簡單應用我們已經會了,內核是一個比uboot更加龐大的家伙,只是2440相關的文件都1w多個,要想一接觸就弄清楚每一步是干什么的也幾乎不可能,但是內核和uboot不同,里面需要細細研究和借鑒的東西很多,只是剛開始,我們還是跟着師傅學一點皮毛,能跑上應用程序再說。顯而易見的是現在沒有什么教程能教會你linux內核的全部知識,除非linus來教你,^_^。
內核操作方式如同uboot一樣,依舊先下載內核源碼,然后解壓縮,然后打補丁。接着就是配置了。
以下部分內容借鑒韋老師一學員的筆記,我太懶了,有現成的決不自己來。
搜索到了目錄部分截圖如下:
然后我們到arm構架下查看有沒有我們熟悉的配置項,結果是有的,2410:
現在執行make s3c2410_defconfig報錯如下:
Makefile:416: *** mixed implicit and normal rules: deprecated syntax
Makefile:1449: *** mixed implicit and normal rules: deprecated syntax
make: *** No rule to make target 'menuconfig'. Stop
因為我的ubuntu是16.04新版的make編譯器:
新版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 來用它。
三、 內核 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”文件
的排布順序:
四、 機器 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 意思是緊接着上面一個分區的意思。