Linux內核開發移植
Linux內核版本變遷及其獲得
Linux
是最受歡迎的自由電腦操作系統內核, 是一個用C
語言寫成, 并且符合POSIX
標準的類Unix
操作系統
Linux
是由芬蘭黑客Linus Torvalds
開發的, 目的是嘗試在英特爾x86
架構上提供自由免費的類Unix
操作系統
Linux
操作系統的誕生、 發展、 和成長過程依賴于五個重要支柱: unix
操作系統、 minix
操作系統、 GNU
計劃、 POSIX
標準和互聯網
內核版本號從Linux1.0以后主要分為兩個階段:
Linux1.0-2.6, 數字包括四部分“ A.B.C.D”
- A代表主版本號, 如1994年的1.0, 1996年的2.0, 2011年的3.0
- B代表次版本號, 表示一些重大的修改, 偶數表示穩定版, 奇數表示開發版
- C代表末版本號, 代表著一個新版本的發布(包括安全補丁、 bug修復、 新增功能、 或驅動程序), 一般數字變化范圍比較大
- D代表一些BUG修復, 對已經加入的安全補丁、 bug修復、 新增功能或驅動程序做的微調或添加新的特性, 2.6版本之后經常出現
Linux2.6之后的版本, 數字主要包括三部分"A.B.C"或"A.B.C-rcn"
- A代表主版本號
- B代表次版本號, 隨著一個新版本的發布而增加, 與上一階段中的數字” C“功能類似, 但不再代表穩定與否
- C代表穩定版本號, 如果只有數字則代表穩定版本, 如果帶"-rcn"表示最終穩定版本之前的候選版本(Release Candidate), n代表候選版本號
各版本之間的關系及命名規則可以參考網站:https://zh.wikipedia.org/wiki/Linux%E5%86%85%E6%A0%B8
注意: page9中的EOL: end of life: 指從此往后不在對應的版本上進行更新。 下面展示了Linux內核的版本變遷過程:
Linux內核結構
Linux內核是Linux系統軟件的核心, 它的性能對整個系統的性能起決定作用
Linux內核文件數目將近4萬個, 他們分別位于頂層目錄下的20個子目錄中, 了解Linux內核的工作過程,必須從了解其目錄結構做起
我們后面研究并移植的內核為:3.4.39, 首先我們從官網上下載3.4.39的內核源碼linux-3.4.39.tar.bz2將其在虛擬機上解壓, 使用tree命令可以在虛擬機下查看其目錄結構
Linux內核源代碼主要包含以下子目錄:
- arch 與體系結構相關的代碼。 對應于每個支持的體系結構, 有一個相應的子目錄如
x86
、arm
等與之對應, 相應目錄下有對應的芯片與之對應 - crypto 常用加密及散列算法, 和一些壓縮及
CRC
校驗算法 - Documentation 內核相關文檔, 關于內核各部分的通用解釋和注釋
- drivers 設備驅動代碼, 占整個內核代碼量的一半以上, 里面的每個子目錄對應一類驅動程序, 如:
block
:塊設備、char
:字符設備、net
:網絡設備等 - fs 文件系統代碼, 每個支持的文件系統有相應的子目錄, 如
cramfs
,yaffs
,jffs2
等 - include 這里包括編譯內核所需的大部分頭文件 , 與平臺無關的頭文件放在
include/linux
子目錄下 , 各類驅動或功能部件的頭文件(/media
、/mtd
、/net
等), 與體系相關的頭文件arch/arm/include/
, 與平臺相關的頭文件路徑arch/arm/mach-s5p6818/include/mach
- init 內核初始化代碼,其中的
main.c
中的start_kernel
函數是系統引導起來后運行的第1個函數, 這是研究內核工作過程的起點 - ipc 內核進程間通信代碼
- kernel 內核管理的核心代碼, 此目錄下的文件實現了大多數
linux
系統的內核函數, 體系結構相關的代碼在arch/arm/kernel
- lib 與體系結構無關的內核庫代碼,與系統調用相關 , 與體系結構相關的內核庫代碼在
arch/arm/lib
下 - mm 與體系結構無關的內存管理代碼 , 與處理器體系結構相關的代碼在
arch/arm/mm
下 - net 網絡部分代碼, 其每個目錄對應于網絡的一個方面
- scripts 存放一些腳本文件, 如配置內核時用到的
make menuconfig
命令等
Linux kernel組成:

Linux內核部分模塊:
進程管理、 進程調度、 系統調用、 中斷處理、 下半部和工作隊列、 內核同步、 定時管理、 內存管理、虛擬文件系統、 塊設備、 網絡子系統、 進程地址空間、 模塊機制等等…
Linux編程風格 :
像所有其他大型軟件項目一樣, Linux制定了一套編碼風格
跟選擇一個唯一確定的風格相比, 到底選擇什么樣的風格反而顯得不是那么重要
編碼風格的主要規范伴隨著Linus一貫的幽默,都記錄在內核源碼的Documentation/Coding Style中了。
制定編碼風格的目的, 是為了提高編程效率, 吸引更多的開發者眼球
縮進
- 采用制表符(Tab)進行縮進, 而不是空格
- 禁止制表符和空格混合使用
花括號使用如下:
if (flag) {fun();//...
}void fun (void)
{ //..
}if (flag)fun();
//...
命名規范
- Linux規定名稱中不允許使用混合大小寫字符
- 單詞之間用下劃線分隔
- 避免取有疑惑的簡單名稱, 如pad(), 應該寫成 platform_add_devices()
代碼長度
- 每行盡量不超過80個字符(可進行有意義分行切割)
- 函數體代碼長度盡量不超過兩屏
- 函數局部變量盡量不超過十個
- 根據函數使用頻率和函數體大小可以使用inline聲明, 以提高調用效率
注釋
- 一般情況用于描述代碼可以做什么和為什么要做, 盡量不寫實現方式
- 函數的修改和維護日志統一集中在文件開頭
在程序中對ifdef的處理
一般不這么用:
//...
#ifdef CONFIG_FUNfun();
#endif
//...
而是在聲明處使用ifdef
#ifdef CONFIG_FUN
void fun (void)
{//...
}
#else
inline void fun (void) { }
#endif
其他
- 指針中的“ *” 號應靠近變量名, 而不是類型關鍵字
- 函數之間用空行隔開
- 函數導出申明緊跟在函數定義的下面
代碼風格的事后修正
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的基礎上又添加了一個長度為64字節的標簽頭, 在u-boot啟動時會去掉此頭信息, 仍按zImage啟動, 頭信息主要用于區分不同格式的內核鏡像
xipImage : 片上執行的未壓縮內核, (如norflash等)
zImage鏡像產生過程:
vmlinux -> Image -> compressed / vmlinux -> zImage
vmlinux 是由以下內核代碼生成的非壓縮鏡像 (arch/arm/kernel/head.s、 kernel/、 mm/、 fs/、 ipc/、 crypto/、 lib/、drivers/、 net/等等)
Image 是使用objcopy工具對vmlinux進行二進制化處理得到的鏡像
arch/arm/boot/compressed/vmlinux
由壓縮的Image
和 compressed/head.S
、 misc.c
等文件組成
zImage
是由compressed/vmlinux
使用objcopy
工具二進制化得到
再對zImage
加上頭部就成為了uImage

boot.img的生成過程 :

boot.img
是使用一種文件系統打包工具制作的, 相當于把各種非必須文件打包在一起成為一整個文件, 然后被燒寫進設備
Linux內核的啟動過程大體上可以分為3個階段:
內核解壓( 匯編+C)
- 主要由
arch/arm/boot/compressed/
對zImage
完成解壓(C語言),并跳轉到下階段代碼
板級引導階段( 匯編)
- 主要進行對
cpu
和體系結構的檢查、cpu
本身的初始化以及頁表的建立等
通用內核啟動階段( C語言)
- 進入
init/main.c
文件, 從start_kernel
開始進行內核初始化工作,最后調用rest_init
由rest_init()
創建并進入內核第一個線程, 線程函數為 kernel_init()
在kernel_init()
中會初始化各種驅動并建立起標準輸入/ 標準輸出/錯誤輸出, 最后調用
在init_post()
中會釋放初始化內存段, 標志著內核啟動完成, 并努力尋找一個用戶進程, 通過kernel_execve()函數加載, 將該進程作為系統的第一個用戶進程init,進程號為1
內核啟動完成, 接下來就是用戶的事兒了
Linux內核的配置與編譯
Linux內核配置裁剪過程
make menuconfig
Linux內核編譯過程
根據配置裁剪的結果配合Makefile
完成內核編譯
Linux內核這一配置編譯機制
把驅動程序v_motor_driver.c(振動馬達驅動)添加 到內核源碼來驗證整個過程
在Linux2.6以后的版本中, 文件的組織是通過 Kconfig 和 Makefile 來實現的
通過每層目錄的 Kconfig 和 Makefile 實現了整個Linux 內核的分布式配置
- Kconfig: 對應內核模塊的配置菜單
- Makefile: 對應內核模塊的編譯選項
Kconfig包含了當前目錄下所有模塊的配置
子目錄的Makefile在被頂層Makefile調用時, 會負責編譯當前目錄下的所有模塊
當有新的模塊被加入時, 需要更改當前目錄下的Kconfig文件, 并且更改對應的Makefile文件, 這樣在最終編譯之前, 可以通過
make menuconfig 對需要添加的模塊進行配置和添加
當保存make menuconfig時, 系統除了會自動更新.config外, 還會將選項以宏的形式保存在內核根目錄下的include/generated/autoconf.h文件下
當執行make menuconfig
時, 配置工具會自動根據根目錄下的ARCH
變量讀取arch/$(ARCH)/Kconfig
文件來生成配置界面, 這個文件是所有文件的總入口, 其它目錄的Kconfig
都由它來引用, 如圖所示:

ARM
平臺為例講解其具體配置過程
當執行make menuconfig
時, 系統首先讀取 arch/arm/Kconfig
生成整個配置界面
在讀取配置界面的同時, 系統會讀取頂層目錄下的.config
文件生成所有配置選項的默認值
當修改完配置并保存后, 系統會更新頂層目錄下的.config
文件
當執行make
時, 各層的Makefile
會根據.config
文件中的編譯選項來決定哪些文件會被編譯到內核中, 或編譯成模塊
Kconfig
語法格式可以參考具體文件, 如:drivers/char/Kconfig
# SPDX-License-Identifier: GPL-2.0
#
# Character device configuration# comment: 注釋信息, 菜單和.config文件中都會出現
# string: 字符串; hex: 16進制的數; int: 10進制的數
# choice/endchoice: 表示選擇性的菜單條目# menu/endmenu: 表示生成一個菜單
menu "Character devices"
# source: 用于包含其它Kconfig
source "drivers/tty/Kconfig"
#config代表一個選項的開始,最終會出現在.config中(會自動增加一個CONFIG_前綴)
config DEVMEM#bool代表此選項僅能選中或不選中,bool后面的字符串代表此選項在make menuconfig中的名字bool "/dev/mem virtual device support"#default: 默認選項值default yhelpSay Y here if you want to support the /dev/mem device.The /dev/mem device is used to access areas of physicalmemory.When in doubt, say "Y".config DEVKMEMbool "/dev/kmem virtual device support"# On arm64, VMALLOC_START < PAGE_OFFSET, which confuses kmem read/write# depends on:依賴其余的選項depends on !ARM64helpSay Y here if you want to support the /dev/kmem device. The/dev/kmem device is rarely used, but can be used for certainkind of kernel debugging operations.When in doubt, say "N".source "drivers/tty/serial/Kconfig"
source "drivers/tty/serdev/Kconfig"config TTY_PRINTK#tristate: 代表可以選擇編譯、 不編譯、 編譯成模塊tristate "TTY driver to output user messages via printk"depends on EXPERT && TTYdefault n---help---If you say Y here, the support for writing user messages (i.e.console messages) via printk is available.The feature is useful to inline user messages with kernelmessages.In order to use this feature, you should output user messagesto /dev/ttyprintk or redirect console to this TTY.If unsure, say N.config VIRTIO_CONSOLEtristate "Virtio console"depends on VIRTIO && TTY# select: 表示當前config被選中時, 此選項也被選中select HVC_DRIVERhelpVirtio console for use with hypervisors.Also serves as a general-purpose serial device for datatransfer between the guest and host. Character devices at/dev/vportNpn will be created when corresponding ports arefound, where N is the device number and n is the port numberwithin that device. If specified by the host, a sysfsattribute called 'name' will be populated with a name forthe port which can be used by udev scripts to create asymlink to the device.//....endmenu
將自己開發的內核代碼加入到Linux內核中, 需要有3個步驟:
把自己編寫的代碼放到內核中合適的位置
把自己開發的功能增加到Linux內核的配置選項中, 使用戶能夠選中這項功能并編譯
構建或修改Makefile, 根據用戶的選擇, 將相應的代碼編譯到最終生成的Linux內核中去
將 振動馬達的驅動添加進內核為例 , 講解配置機制的具體應用
添加步驟如下:
Step1: 將v_motor_driver.c
拷到drivers/char/
目錄下
Step2: vi driver/char/Kconfig,在Kconfig
文件結尾, 在endmenu
的前面加入一個config
選項
config 6818_VMOTORbool "The Vibration motor driver ofr S5P6818"default yhelpThe driver for Vibration motor deviceendmenu
Step3: make menuconfig(添加配置選項)
Device driver —> character devices —> [*] The Vibration motor driver for S5P6818
Step4: vi driver/char/Makefile添加內容如下:
obj-$(CONFIG_6818_VMOTOR) += v_motor_driver.c
Step5: make (更新內核鏡像到開發板)
Step6: 交叉編譯 測試程序, 并放到開發板運行
arm-linux-gcc v_motor_test.c -o v_motor_test./v_motor_test 1
Linux3.4.39內核移植
以Linux3.4.39內核在開發板上的移植為例, 學習如何從一個純凈的Linux內核編譯、 配置得到適合在一個特定平臺運行的鏡像。 具體移植步驟如下:
- 源碼解壓
- 添加
BSP
- 修改頂層和
BSP
下Makefile
- 修改鏈接地址機器
ID
號并拷貝默認配置文件 - 移植串口驅動支持
emmc
驅動移植ext4
文件系統的支持移植- 內核配置、 編譯
練習:
- 添加鍵盤驅動到Linux-3.4.39
- Linux-3.4.39內核移植
- Linux-3.4.39內核LCD移植
- Linux-3.4.39內核修改開機logo