linux內核結構和啓動過程
(以下內容來自教學課件)
一、Linux內核結構
arch
與體系結構相關的代碼。對應於每個支持的體系結構,有一個相應的子目錄如x86、arm等與之對應,相應目錄下有對應的芯片與之對應
drivers
設備驅動代碼,佔整個內核代碼量的一半以上,裏面的每個子目錄對應一類驅動程序,如:char:字符設備、block:塊設備、net:網絡設備等
fs
文件系統代碼,每個支持的文件系統有相應的子目錄,如cramfs,yaffs,jffs2等
include
這裏包括編譯內核所需的大部分頭文件
與平臺無關的頭文件include/linux
各類驅動或功能部件的頭文件(/media、/mtd、/net等)
與體系相關的頭文件arch/arm/include/
與平臺相關的頭文件arch/arm/mach-s5pv210/include/mach
lib
與體系結構無關的內核庫代碼
與體系結構相關的內核庫代碼在arch/arm/lib下
init
內核初始化代碼,其中的main.c中的start_kernel函數是系統引導起來後運行的第1個函數,這是研究內核開始工作的起點
ipc
內核進程間通信代碼
mm
與體系結構無關的內存管理代碼
與處理器體系結構相關的代碼在arch/arm/mm下
kernel
內核管理的核心代碼
net
網絡部分代碼,其每個目錄對應於網絡的一個方面
scripts
存放一些腳本文件,如配置內核時用到的make menuconfig命令
Documentation
內核相關文檔
crypto
常用加密及散列算法,和一些壓縮及CRC校驗算法
二、Linux編程風格
縮進
採用製表符(Tab)進行縮進,而不是空格
禁止製表符和空格混合使用
命名規範
Linux規定名稱中不允許使用混合大小寫字符
單詞之間用下劃線分隔
避免取有疑惑的簡單名稱,如pad(),應該寫成platform_add_devices()
代碼長度
每行儘量不超過80個字符(可進行有意義分行切割)
函數體代碼長度儘量不超過兩屏
函數局部變量儘量不超過十個
根據函數使用頻率和函數體大小可以使用inline聲明,以提高調用效率
註釋
一般情況註釋用於描述代碼可以做什麼和爲什麼要做,儘量不寫實現方式
函數的修改和維護日誌統一集中在文件開頭
其他
指針中的"*"號應靠近變量名,而不是類型關鍵字
函數之間用空行隔開
函數導出申明緊跟在函數定義的下面
等等......
代碼風格的事後修正
indent命令是大多數Linux系統中都帶有的工具,當得到一段與內核編碼風格大相徑庭的代碼時,可以通過這個工具進行調整:
#indent -kr -i8 -ts8 -sob -l80 -ss -bs -psl <filename>
三、Linux內核啓動引導過程
在瞭解Linux啓動流程之前,首先需要了解Linux鏡像的格式及其產生過程
Image:直接生成並未壓縮的內核,一般用於PC機
zImage:Image的壓縮版,採用gzip進行壓縮,比Image體積小,但啓動時需要進行自解壓,嵌入式系
統中一般採用此種方法
uImage:是u-boot專用的一種內核鏡像格式,它是在zImage的基礎上又添加了一個長度爲0x40的標籤頭,在u-boot啓動時會去掉此頭信息,仍按zImage啓動,頭信息主要用於區分不同格式的內核鏡像
xipImage:片上執行的未壓縮內核,(如norflash等)
(ps:實際應用中由於norflash比較貴,所以大多使用NAND flash作爲存儲器件,而NAND flash不支持片上執行,故xipImage用的比較少)
bootpImage:將內核與根文件系統製作在一起的鏡像
zImage產生過程:vmlinux(內核代碼生成的鏡像)->Image(objcopy對vmlinux二進制化處理)->compressed/vmlinux(壓縮的Image加上head.S和misc.c)->zImage(vmlinux二進制化)
Linux內核的啓動過程大體上可以分爲3個階段:
1、內核解壓(彙編+C)
主要由arch/arm/boot/compressed/對zImage完成解壓,並調用call_kernel跳轉到下階段代碼
2、板級引導階段(彙編)
主要進行cpu和體系結構的檢查、cpu本身的初始化以及頁表的建立等
3、通用內核啓動階段(C語言)
進入init/main.c文件,從start_kernel開始進行內核初始化工作,最後調用rest_init
rest_init()會創建內核第一個線程,並進入線程函數kernel_init()
在kernel_init()中會初始化各種驅動並建立起標準輸入/標準輸出/錯誤輸出,最後調用init_post()
在init_post()中會釋放初始化內存段,標誌着內核啓動完成,並努力尋找一個用戶進程,通過kernel_execve()函數加載,將該進程作爲系統的第一個用戶進程init,進程號爲1,至此內核啓動完成
四、內核的配置與編譯
make menuconfig完成Linux內核配置裁剪
根據配置裁剪的結果配合Makefile完成內核編譯
linux2.6以後的版本是通過每層目錄的Kconfig和Makefile實現了整個Linux內核的分佈式配置
Kconfig:對應內核模塊的配置菜單
Makefile:對應內核模塊的編譯選項
當執行make menuconfig時,配置工具會自動根據根目錄下的ARCH變量讀取arch/$(ARCH)/Kconfig文件來生成配置界面(引用其它目錄的kconfig,通過改變宏定義的方式進行條件編譯),這個文件是所有文件的總入口,其它目錄的Kconfig都由它來引用
配置界面的內容來自於所有目錄的Kconfig, 選擇"*", " ", 或"M"進行條件編譯,對應宏定義在頂層.config文件中
在讀取配置界面的同時,系統會讀取頂層目錄下的.config文件生成所有配置選項的默認值
3、當修改完配置並保存後,系統會更新頂層目錄下的.config文件
4、當執行make時,各層的Makefile會根據.config文件中的編譯選項來決定哪些文件會被編譯到內核中,或編譯成模塊
Kconfig語法格式可以參考具體文件,如:drivers/char/Kconfig
menu "Character devices"
config VT
bool "virtual terminal" if EMBEDDED
depends on !s390
select INPUT
default y
--help--
if you say Y here,you will get support.....
config代表一個選項的開始,最終會出現在.config中(會自動增加一個CONFIG_前綴)
bool代表此選項僅能選中或不選中,bool後面的字符串代表此選項在make menuconfig中的名字
tristate:代表可以選擇編譯、不編譯、編譯成模塊
string:字符串; hex:16進制的數; int:10進制的數
depends on:依賴其餘的選項
default:默認選項值
select:表示當前config被選中時,此選項也被選中
menu/endmenu:表示生成一個菜單
choice/endchoice:表示選擇性的菜單條目
comment:註釋信息,菜單和.config文件中都會出現
source:用於包含其它Kconfig
將自己開發的內核代碼加入到Linux內核中,需要有3個步驟:
1. 確定把自己開發代碼放入到內核合適的位置
2. 把自己開發的功能增加到Linux內核的配置選項中,使用戶能夠選擇此功能
3. 構建或修改Makefile,根據用戶的選擇,將相應的代碼編譯到最終生成的Linux內核中去
比如要把一個key1*5鍵盤驅動添加進內核
步驟如下:
Step1:將s5pv210-key15-simple.c拷到drivers/char/目錄下
Step2: vi driver/char/Kconfig,在Kconfig文件結尾,在endmenu的前面加入一個config選項
config UNSP210_KEY15
bool "key1*5 driver for sunplusedu unsp210 boards"
default y
help
this is GPIO driver for unsp210 boards.
Step3:make menuconfig(添加配置選項)
Device driver->
character devices->
[*] key1*5 driver for sunplusedu unsp210 boards
Step4: vi driver/char/Makefile添加內容如下:
obj-$(CONFIG_UNSP210_KEY15) += s5pv210-key15-simple.o
Step5:make (更新內核鏡像到開發板)
Step6:交叉編譯測試程序,並放到開發板運行
#arm-linux-gcc key15_test.c -o key15_test