全面解析Linux 内核 3.10.x - 开始编译<一>

From: 全面解析Linux 内核 3.10.x - 本文章完全基于MIPS架构

一切手工技艺,皆由口传心授 - 夏奈尔首席鞋匠

传授手艺的同时,也传递了耐心、专注、坚持的精神,这是一切手工匠人所必须具备的特质。

1、内核编译体系 - Kbuild

貌似是从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,包含了所有的定义、规则等!

2、寻找编译入口 - 目标文件

编译之处,很多初学者或者一般都总是会记得那么几个步骤:
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 %/, (inity) (init-m) \
(corey) (core-m) (driversy) (drivers-m) \
(nety) (net-m) (libsy) (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..
做为一个技术人,你渐渐的应该选择不去使用这种东西,除了多读书外,多读源码,多读源码,多读源码!重要的事情说三遍!

3、你不知道的vmlinux - 编译流程

上面大抵说明了一件事情那就是怎么编译!到这里其实就可以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

setp by setp

a.热热身 - 编译前script的作用

上面我们将ARCH以及CROSS修改完毕以后,就可以执行第一步!
make menuconfig 保存退出这就是将.config 配置保存!
然后我们 make menuconfig V=1看看都干了些什么,和我们上述分析的是否有差别.
有几个小地方我这里暂时不做解释,到后面我们在来解释。
留点悬念内核小工具番外篇 - 内核中script的几个作用

b.积少成多 - 各级子目录汇编

到这一步其实大抵都明白,不就单独编译每个被选中的.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 的时候就不是如此顺序,具体自己可去查看,顺便思考一下!

b.流水成河 - built-in.o 文件的大作用

在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文件!

c.vmlinux 初具雏形

有了built-in.o以后,就可以进行链接了!
链接指导文件是arch/mips/kernel/vmlmux.lds.S
vmlinux.lds.S 在编译的过程中被处理为vmlinux.lds
vmlinux.lds.S 中将vmlinux的Section 进行了详细的划分!

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)!

请继续接下一篇 – 全面解析Linux 内核 3.10.x - 开始编译<二>


By: Keven - 点滴积累

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值