全面解析Linux 核心 3.10.x
一切手工技藝,皆由口傳心授 - 夏奈爾首席鞋匠
傳授手藝的同時,也傳遞了耐心、專注、堅持的精神,這是一切手工匠人所必須具備的特質。
貌似是從2.6開始,核心編譯就開始採用Kbuild體系!
Kbuild幾點觀念:
1.一個配置檔案對應一個自動包含的子目錄樹!
2.目標配置檔案模板是簡化Makefile的主要機制!
3.工具和SDK使得模板具有靈活性!
4.子Makefiles來實現非遞迴Makefile方法!
在編譯核心的時候會讀取兩次Makefile,先讀取頂層Makfile,讀到之後獲取到Kbuild的子Makefile來先編譯子Makefile.
核心中的Kbuild體系用到Makefile的5個部分!
a.頂層Makefile
核心頂層Makefile 位於核心原始碼的頂層目錄,它主要用於指定編譯核心目標檔案(vmlinux)和模組(modules).選擇編譯核心或者模組,這個檔案會被首先讀取,並根據讀到的內容配置編譯環境變數.對於核心或驅動開發人員來說,這個檔案幾乎不用任何修改.
b.config
核心的配置檔案,當配置完menuconfig以後,就會在主目錄下生成一個.config檔案,此檔案一般Demo板都會提供一個參考的condfig(放在arcg/$(ARCH)/configs/),如我下面要使用的是(arch/mips/configs/nlm_xlp_config).
可以直接複製過來,*cp arch/mips/configs/nlm_xlp_config .config*
後續根據自己的需要可以對此檔案增刪修改!
c.arch/$(ARCH)/Makefile
具體體系架構下的頂層Make0file,位於ARCH/$(ARCH)/Makefile,是系統對應平臺的Makefile.
核心頂層Makefile會包含這個檔案來指定平臺相關資訊.這個就確定你的平臺資訊!
d.Kbuild 子 Makefiles
核心原始碼中大約有成百上千個這樣的檔案,一般是每個目錄一個Makefile,同它對應的有一個
Kconfig檔案,其內容是一些預設的編譯選項.每一個子目錄都有一個Kbuild Makefile 檔案,用
來執行從其上層目錄傳遞下來的命令.**注意****Kbuild Makefile 並不直接被當做Makefile 執行,而是從.config 檔案中提取資訊,生成Kbuild完成核心編譯所需的檔案列表.
Kbuild使用到通用的規則等,面向所有的Kbuild Makefiles,包含了所有的定義、規則等!
編譯之處,很多初學者或者一般都總是會記得那麼幾個步驟:
make config/defconfig/menuconfig/xconfig 等
make && make modules_install && make install
真正去將每一個步驟細細研究的可能很少!
那麼我們來分解一下上述幾個步驟:
1.make menuconfig
一般情況下我們都使用次選項,基本上都工作在字元介面並。編譯配置,怎麼個配置法?
這裡值得一提的是,3.10.x版本menuconfig 介面已經變化了,
在上面的內容中我們大概介紹了下Kbuild檔案,首先我們檢視頂層Makefile的內容,看是否有menuconfig這個target呢?
尋找一圈並沒有發現menuconfig這個目標!為什麼呢? 不過的碼海中發現下面這幾句!
1.make *config
ifeq ("$(origin V)", "command line") //make V=1 ...
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
srctree := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
# ===========================================================================
# *config targets only - make sure prerequisites are updated, and descend
# in scripts/kconfig to make the *config target
# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
# KBUILD_DEFCONFIG may point out an alternative default configuration
# used for 'make defconfig'
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG
config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig [email protected]
%config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig [email protected]
在上述的友好提示以及程式碼的明示下,我們大概已經明白!執行make defconfig後首先根本得到的SARCH(這個巨集其實就是指定的ARCH,此巨集可以手動指定,也可以通過make 傳參,如 make ARCH=mips CROSS_COMPILE=mips64-xxx-gcc,指定架構以及交叉鏈子),SARCH的值知道以啦,根據前面的知識,[email protected]表示目標檔案的完整名稱,%表示萬用字元,好,程式碼變變變:
include $(CURDIR)/arch/mips/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG
config: scripts_basic outputmakefile FORCE
$(@) mkdir -p include/linux include/config //Q = @
$(@) $(MAKE) $(build)=scripts/kconfig config
%config: scripts_basic outputmakefile FORCE
$(@) mkdir -p include/linux include/config
$(@)$(MAKE) $(build)=scripts/kconfig *config
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount\
上述target 翻譯過來就是編譯 scipt/basic/Makefile,此處就是編譯fixdep(其實是一個修復平臺包依賴關係的軟體),相信很多人以前編譯核心的時候都需要安裝一個叫build-essential的包,要不然就會出現以下錯誤
*make[1]: [scripts/basic/fixdep] Error 1****
而build-essential 的解釋是:Informational list of build-essential packages…so .are you ok?
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif
在看mkmakefile 同樣是scripts 下的檔案,不過此檔案就不叫編譯.
CONFIG_SHELL是神馬呢?看下面:
# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
上述程式碼號高大上啊,其實就是echo $$BASH (此處的意識是表示BASH的完整路徑,系統變數配置的) or(木有找到環境變數就只能簡單粗暴了) echo /bin/bash ..
ifeq ($(mixed-targets),1)
# ===========================================================================
# We're called with mixed targets (*config and build targets).
# Handle them one by one.
%:: FORCE
$(Q)$(MAKE) -C $(srctree) KBUILD_SRC= [email protected]
上述程式碼中KBUILD_SRC 其實就arch/xxx ..
小技巧:
你可以執行make arch/mips 瞅瞅
遇到你任務想要make 的東東,就揍大膽的去試試吧
2.make menuconfig字元介面的實現
下面程式碼簡單的描述了menuconfig如如果將子目錄拉起來變成一個視覺化的字元介面的!
# Handle descending into subdirectories listed in $(vmlinux-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)[email protected]
# Store (new) KERNELRELASE string in include/config/kernel.release
include/config/kernel.release: include/config/auto.conf FORCE
$(Q)rm -f [email protected]
$(Q)echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))" > [email protected]
上述程式碼中其實最重要的就是vmlinux-dirs 目標,它描述了每個子目錄
vmlinux-dirs :=
之後就是Kconfig 找Kconfig了!
每一個Kconfig檔案都對對應一個子目錄檔案的描述,Kconfig的三個選項* M []分別表示編譯到核心,編譯為模組,不編譯!
三個選項影響同級目錄Makefie檔案中的obj-y obj-$(CONFIG_XX_XX) 不編譯!
說到這裡,大概基本上把我的幾個疑點都搞清楚了。
3.手動修改ARCH
cp arch/mips/configs/xx_deconifg .config
# CROSS_COMPILE specify the prefix used for all executables used
# during compilation. Only gcc and related bin-utils executables
# are prefixed with $(CROSS_COMPILE).
# CROSS_COMPILE can be set on the command line
# make CROSS_COMPILE=ia64-linux-
# Alternatively CROSS_COMPILE can be set in the environment.
# A third alternative is to store a setting in .config so that plain
# "make" in the configured kernel build directory always uses that.
# Default value for CROSS_COMPILE is not to prefix executables
# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
這裡預設的SUBARCH 一般指的是你主機的環境架構!CROSS_COMPILE也是!
那麼我手動將這個地方改一下…
ARCH ?= mips
CROSS_COMPILE ?= mips64-xx-linux-
Ps.這裡我暫時不將交叉鏈子寫出來!一般的鏈子命名都是arch-廠商-linux-.
總結:
其實你想要的東西都已經在你的面前,相信自己,你可以!其他的細節請詳細參考Makefile,世上無難事,只怕就心人!
最後說一句,百度這種東西做為新手,或者搜搜生活還可以,至於你都已經看核心了嘛。還是Google 或者直接就是Source Codinng..
做為一個技術人,你漸漸的應該選擇不去使用這種東西,除了多讀書外,多讀原始碼,多讀原始碼,多讀原始碼!重要的事情說三遍!
上面大抵說明了一件事情那就是怎麼編譯!到這裡其實就可以make 了,孤孤單單的一個command.你可以make ||make ARCH=xx CROSS_COMPILE=xxxxx || make -j n || make V=n || make dir/ make dir/xx.i 等等等等!
一個 make 可以變著花樣玩..
我關心的不是make本身 ,而是make 核心的過程..
從make 開始 — 到生成映象檔案..此過程我是非常感興趣,那麼我們就去研究吧啊..哈哈!
首先基本的編譯都是要經過以下幾個階段
核心編譯流程大抵也如此:
預編譯 – *.i <這部分也一般會被省略,但是可以作為除錯的手段來使用>
編譯 – *.s <這部一般會被省略,這部分可能用處並不是很大,因為如果要看彙編除錯的話,使用除錯工具會事半功倍,如kdb,kgdb等>
彙編 – *.o
子目錄小連結 – built-in.o
總連結 – vmlinux
上面我們將ARCH以及CROSS修改完畢以後,就可以執行第一步!
make menuconfig 儲存退出這就是將.config 配置儲存!
然後我們 make menuconfig V=1看看都幹了些什麼,和我們上述分析的是否有差別.
有幾個小地方我這裡暫時不做解釋,到後面我們在來解釋。
留點懸念核心小工具番外篇 - 核心中script的幾個作用
到這一步其實大抵都明白,不就單獨編譯每個被選中的.c麼,然後生成 -o 麼!是的,這裡就坐了這樣的事情,但是你知道這麼多目錄順序是什麼嗎?
這裡我就強調一下編譯的目錄..
讓我們靜靜的在看一段Makefile 中的片段:
# ===========================================================================
# Build targets only - this includes vmlinux, arch specific targets, clean
# targets and others. In general all targets except *config targets.
ifeq ($(KBUILD_EXTMOD),)
# Additional helpers built in scripts/
# Carefully list dependencies so we do not try to build scripts twice
# in parallel
PHONY += scripts
scripts: scripts_basic include/config/auto.conf include/config/tristate.conf \
asm-generic
$(Q)$(MAKE) $(build)=$(@)
# Objects we will link into vmlinux / subdirs we need to visit
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
core-y := usr/
ifeq ($(KBUILD_EXTMOD),)
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
init-y := $(patsubst %/, %/built-in.o, $(init-y))
core-y := $(patsubst %/, %/built-in.o, $(core-y))
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y := $(patsubst %/, %/built-in.o, $(net-y))
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2)
so…
編譯順序>
init - usr – arch/mips — kernel —- mm —– fs —— ipc ——- security ——– crypto ——— block ———- drivers ———– sound ———— firmware ————- net ————- lib
觀光完畢…
Ps. 如果不信的話,自己去看時間戳.. ls –full-time 可以檢視時間戳哦,精確到毫秒!
Ps1.其實編譯順序不一定非的按照如此,你使用mae -j n 的時候就不是如此順序,具體自己可去檢視,順便思考一下!
在a中我們看到了編譯每個檔案匯聚成N多個.o檔案,然後.o 檔案又被第二次匯聚成built-in.o檔案!
built-in.o 檔案是怎麼生成的呢?
我們以頂層目錄的usr為例子(因為只有一個.c檔案麼!)
gcc -Wp,-MD,usr/.gen_init_cpio.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89 -o usr/gen_init_cpio usr/gen_init_cpio.c
/bin/bash /home/dev/share/hewen/kernel/linux-3.10.92/scripts/gen_initramfs_list.sh -l -d > usr/.initramfs_data.cpio.d
/bin/bash /home/dev/share/hewen/kernel/linux-3.10.92/scripts/gen_initramfs_list.sh -o usr/initramfs_data.cpio -d
mips64-nlm-linux-gcc -Wp,-MD,usr/.initramfs_data.o.d -nostdinc -isystem /opt/Mips_Cross/toolchains_bin/mipscross/linux/bin/../lib/gcc/mips64-nlm-linux/4.6.1/include -I/home/dev/share/hewen/kernel/linux-3.10.92/arch/mips/include -Iarch/mips/include/generated -Iinclude -I/home/dev/share/hewen/kernel/linux-3.10.92/arch/mips/include/uapi -Iarch/mips/include/generated/uapi -I/home/dev/share/hewen/kernel/linux-3.10.92/include/uapi -Iinclude/generated/uapi -include /home/dev/share/hewen/kernel/linux-3.10.92/include/linux/kconfig.h -D__KERNEL__ -DVMLINUX_LOAD_ADDRESS=0xffffffff80100000 -DDATAOFFSET=0 -D__ASSEMBLY__ -mno-check-zero-division -mabi=64 -G 0 -mno-abicalls -fno-pic -pipe -msoft-float -ffreestanding -I/home/dev/share/hewen/kernel/linux-3.10.92/arch/mips/include/asm/mach-netlogic -I/home/dev/share/hewen/kernel/linux-3.10.92/arch/mips/include/asm/netlogic -march=xlp -I/home/dev/share/hewen/kernel/linux-3.10.92/arch/mips/include/asm/mach-generic -msym32 -DKBUILD_64BIT_SYM32 -gdwarf-2 -DINITRAMFS_IMAGE="usr/initramfs_data.cpio" -c -o usr/initramfs_data.o usr/initramfs_data.S
mips64-xxx-linux-ld -m elf64btsmip -r -o usr/built-in.o usr/initramfs_data.o
好吧,正如你所知道的,我使用了make usr V=1
重點在最後一行,mips64-xxx-linux-ld -m elf64btsmip -r -o usr/built-in.o usr/initramfs_data.o
發現了嘛?
四個引數..一一說明:
-m Set emulation
elf64btsmip 模擬型別
-r Generate relocatable output
-o Set output file name
簡單的表示就是將*.o 檔案轉變為指定模擬模式的built-in.o檔案!
有了built-in.o以後,就可以進行連結了!
連結指導檔案是arch/mips/kernel/vmlmux.lds.S
vmlinux.lds.S 在編譯的過程中被處理為vmlinux.lds
vmlinux.lds.S 中將vmlinux的Section 進行了詳細的劃分!
使用readelf -S vmlinux 可以檢視所有的section,下面列出一部分section
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS ffffffff80100000 00004000
000000000053a058 0000000000000000 AX 0 0 32
[ 2] __ex_table PROGBITS ffffffff8063a060 0053e060
0000000000006870 0000000000000000 A 0 0 8
[ 3] .notes NOTE ffffffff806408d0 005448d0
0000000000000024 0000000000000000 A 0 0 4
[ 4] .rodata PROGBITS ffffffff80641000 00545000
000000000019d878 0000000000000000 A 0 0 256
[ 5] .pci_fixup PROGBITS ffffffff807de878 006e2878
0000000000001998 0000000000000000 A 0 0 8
[ 6] __ksymtab PROGBITS ffffffff807e0210 006e4210
000000000000c9d0 0000000000000000 A 0 0 8
[ 7] __ksymtab_gpl PROGBITS ffffffff807ecbe0 006f0be0
0000000000006a20 0000000000000000 A 0 0 8
[ 8] __kcrctab PROGBITS ffffffff807f3600 006f7600
00000000000064e8 0000000000000000 A 0 0 8
[ 9] __kcrctab_gpl PROGBITS ffffffff807f9ae8 006fdae8
0000000000003510 0000000000000000 A 0 0 8
[10] __ksymtab_strings PROGBITS ffffffff807fcff8 00700ff8
0000000000015e3e 0000000000000000 A 0 0 1
[11] __param PROGBITS ffffffff80812e38 00716e38
0000000000000fa0 0000000000000000 A 0 0 8
[12] __modver PROGBITS ffffffff80813dd8 00717dd8
為什麼要說section 呢?
其實section 就是核心比較重要的全域性符號資訊!每個section都有Address + Offset.
注意有的section地址為空,基本都是一些除錯符號資訊! 關於section 這部分其實可以和反彙編等知識組成非常複雜的高階技術!
[MIPS的入口地址]
你只需要知道是MIPS預設地址是0xBFC00000,此地址在無快取的KSEG1的地址區域內,對應的實體地址是0x1FC00000!具體參考體系架構番外篇 - MIPS基本地址空間,即CPU從0x1FC00000開始取第一條指令,這個地址在硬體上已經確定為FLASH的位置,boot將vmlinux(核心映象) 拷貝到RAM中某個空閒地址處,然後一般有個記憶體移動操作,目的地址已經指定:
arch/mips/Makefile
#
# Automatically detect the build format. By default we choose
# the elf format according to the load address.
# We can always force a build with a 64-bits symbol format by
# passing 'KBUILD_SYM32=no' option to the make's command line.
#
ifdef CONFIG_64BIT
ifndef KBUILD_SYM32
ifeq ($(shell expr $(load-y) \< 0xffffffff80000000), 0)
KBUILD_SYM32 = y
endif
endif
ifeq ($(KBUILD_SYM32)$(call cc-option-yn,-msym32), yy)
cflags-y += -msym32 -DKBUILD_64BIT_SYM32
else
ifeq ($(CONFIG_CPU_DADDI_WORKAROUNDS), y)
$(error CONFIG_CPU_DADDI_WORKAROUNDS unsupported without -msym32)
endif
endif
endif
vmlinux.lds
OUTPUT_ARCH(mips)
ENTRY(kernel_entry) #Ps....
PHDRS {
text PT_LOAD FLAGS(7); /* RWX */
note PT_NOTE FLAGS(4); /* R__ */
}
jiffies = jiffies_64;
SECTIONS
{
. = 0xffffffff80100000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
. = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.text.unlikely)
. = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
. = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
. = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
最終地址會被編譯寫入到vmlinux.lds,此檔案最終會以引數 -Xlinker –script -Xlinker vmlinux.lds的形式傳給mips64-xxx-linux-gcc,最終會被連結器mips64-xx-linux-ld來進行操作。mips64-xx-linux-ld會將 .text section的地址連結到 0xFFFFFFFF80100000!見上述section描述!boot會將核心移到實體地址0x00100000處。
核心ELF檔案的入口地址(Entry point),即 boot搬移完核心後,直接跳轉到的地址,由mips64-xx-linux-ld寫入ELF的頭中。使用mips-xxx-linux-readeld -h vmlinux 可檢視header資訊:
Class: ELF64
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: MIPS R3000
Version: 0x1
Entry point address: 0xffffffff8062c7f0
Start of program headers: 64 (bytes into file)
Start of section headers: 79212296 (bytes into file)
Flags: 0x808e0001, noreorder, xlp, mips64r2
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 2
Size of section headers: 64 (bytes)
Number of section headers: 33
Section header string table index: 30
之後依次用下面的方法嘗試設定入口點,一直到成功時候才停止!
1. 命令列選項 -e entry
2. 指令碼中的 ENTRY(symbol)
3. 如果有定義 start 符號,則使用start符號(symbol)
4. 如果存在 .text 節,則使用第一個位元組的地址。
5. 地址0
如當前核心使用的就是ENTRY(symbol),在連結vmlinux.lds的時候設定了核心的entry,ENTRY(kernel_entry)!