From: 全面解析Linux 内核 3.10.x - 本文章完全基于MIPS架构
传授手艺的同时,也传递了耐心、专注、坚持的精神,这是一切手工匠人所必须具备的特质。
貌似是从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完成内核编译所需的文件列表.
e.scripts/Makefile.*
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 $@
%config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig $@
在上述的友好提示以及代码的明示下,我们大概已经明白!执行make defconfig后首先根本得到的SARCH(这个宏其实就是指定的ARCH,此宏可以手动指定,也可以通过make 传参,如 make ARCH=mips CROSS_COMPILE=mips64-xxx-gcc,指定架构以及交叉链子),SARCH的值知道以啦,根据前面的知识,$@表示目标文件的完整名称,%表示通配符,好,代码变变变:
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
1.1 为什么有两个target config和%config ?
第一个当然对应就是我们知道的make config 啦!
第二个%config的意思就是匹配所有的make *config!
1.2 *config 的依赖 scripts_basic outputmakefile FORCE
scripts_basic 代码片段:
# 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 代码片段:
# 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 ..
FORCE target 代码片段:
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= $@
上述代码中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)=$@
# Store (new) KERNELRELASE string in include/config/kernel.release
include/config/kernel.release: include/config/auto.conf FORCE
$(Q)rm -f $@
$(Q)echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))" > $@
上述代码中其实最重要的就是vmlinux-dirs 目标,它描述了每个子目录
vmlinux-dirs := (patsubst (filter %/, (init−y) (init-m) \
(core−y) (core-m) (drivers−y) (drivers-m) \
(net−y) (net-m) (libs−y) (libs-m)))
之后就是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默认地址是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)!
请继续接下一篇 – 全面解析Linux 内核 3.10.x - 开始编译<二>
By: Keven - 点滴积累