hi,大家好!我是crazycatjack。最近在学习linux内核的配置、编译及makefile文件。今天总结一下学习成果,分享给大家^_^
1.解压缩打补丁
首先是解压缩你获取到的linux内核。这里我用到的是linux.2.22.6版本的内核。在linux下命令行通过tar xjf linux.2.22.6.tar.bz2解压内核。然后,如果你需要对这个内核打补丁的话,用patch命令:patch -px 7d71316bb0b983b7bd3504426ac04554直接进行make menuconfig。这是最麻烦的一种方法,所有的配置都需要你来操作。
2>在默认配置上自己修改,也就是修改defconfig文件。使用 find -name *defconfig*查找你的架构对应的默认配置文件。我是在arch/arm/configs找到自己板子的默认配置文件。执行defconfig文件: make xxx_defconfig。xxx是你具体使用的板子型号。执行这一操作后,结果保存在.config文件。然后再执行make menuconfig命令。这时的配置就是在默认配置上稍加修改就可以了。
3>使用厂家的配置文件。如果你的硬件有厂家提供的config文件那是最轻松的。直接cp xxx .config。然后执行make menuconfig。
这里详细给大家讲一下内核的配置。linux的内核配置,就是为了生成.config文件。因为在编译时需要用.config文件生成其他相关配置文件。我们的配置项大多是例如config_xxxdriver,这里的xxxdriver指的是各种驱动。我们需要告诉内核,这些驱动是编译进内核,还是编译成模块。通过查找config_xxxdriver,我们可以发现,它出现在四个地方:
1>c源代码
2>子目录makefile:drivers/xxx/makefile
3>include/config/auto.conf
4>include/linux/autoconf.h
这里首先说明:.config文件在进行内核编译时(make uimage)生成了include/config/auto.conf和include/linux/autoconf.h。通过查看c源代码我们发现config_xxxdriver是一个宏定义,等于一个常量。在include/linux/autoconf.h中宏定义config_xxxdriver为一个常量,可能是0或1。那么现在有一个问题,就是config_xxxdriver到底被编译进内核还是编译成一个模块呢?这在c语言中是无法进行区分的,这种区分体现在哪里呢?这种区分体现在子目录的makefile文件中。在子目录的makefile中,若有 obj -y += xxx.o则表示xxx.c被编译进内核;obj -m +=xxx.o则表示xxx被编译成模块,为xxx.ko。include/config/auto.conf文件则是对config_xxxdriver进行赋值,为y时表示编译进内核,为m时表示编译成独立模块。
#这里是include/config/auto.conf的部分内容
# automatically generated make config: don't edit
# linux kernel version: 2.6.22.6
# sun nov 27 18:34:38 2016
#
config_cpu_s3c244x=y
config_cpu_copy_v4wb=y
config_crypto_cbc=y
config_cpu_s3c2410_dma=y
config_crypto_ecb=m
config_smdk2440_cpu2440=y
#这里是drivers/i2c/makefile
# makefile for the i2c core.
#
obj-$(config_i2c_boardinfo) += i2c-boardinfo.o
obj-$(config_i2c) += i2c-core.o
obj-$(config_i2c_chardev) += i2c-dev.o
obj-y += busses/ chips/ algos/
ifeq ($(config_i2c_debug_core),y)
extra_cflags += -ddebug
endif
3.编译内核
通过上面的描述,我们可以知道,在每个driver下,都有一个makefile文件。来定义这个驱动是编译进内核还是编译成模块。这里稍稍提一下。上面我们讲到了在makefile中单个文件怎样编译进内核和编译成模块。但是如果有两个以上的文件该如何书写呢?这里举个例子:obj -y += a.o b.o就表示将a.c和b.c编译进内核。
obj -m += ab.o
ab -objs := a.o b.o
就可以表示将a.c和b.c共同编译成为一个模块。过程就是 a.c生成a.o , b.c生成b.o。a.o 和b.o共同生成ab.ko。在以往的对uboot启动内核的代码分析中,我们提到过编译内核生成的uimage是由两部分组成的:头部+linux内核。这个头部包含了很多初始化的参数信息,例如内核的加载地址和入口地址。在编译内核时,我们直接执行make uimage即可。那么在文件中是怎样定义uimage的呢?又是怎样生成uimage的呢?
首先,我们通过查找makefile,发现uimage在arch/arm/makefile中,而这个架构目录下的makefile被包含进顶层目录的makefile中。这样我们在执行 make uimage时,顶层目录的makefile就可以调用架构子目录下的makefile,实现对内核的编译,生成uimage。
顶层目录下makefile中相关命令:
include $(srctree)/arch/$(arch)/makefile
架构目录下makefile相关命令:
zimage image xipimage bootpimage uimage: vmlinux
这就是刚刚所说的顶层makefile调用架构目录下makefile,架构目录下makefile生成uimage,而且依赖于vmlinux文件。下面我们就开始讲解如何生成vmlinux文件。在顶层makefile中,我们找到了有关生成vmlinux的大部分命令。
顶层目录makefile:
init-y := init/
init-y := $(patsubst %/, %/built-in.o, $(init-y))
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/built-in.o, $(core-y))
libs-y := lib/
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2)
drivers-y := drivers/ sound/
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y := net/
net-y := $(patsubst %/, %/built-in.o, $(net-y)) = net/built-in.o
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) force
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds := arch/$(arch)/kernel/vmlinux.lds
export kbuild_vmlinux_objs := $(vmlinux-all)
架构目录makefile:
zimage image xipimage bootpimage uimage: vmlinux
head-y := arch/arm/kernel/head$(mmuext).o arch/arm/kernel/init_task.o
我已经把顶层目录和架构目录下生成vmlinux的命令摘选出来。首先,我们看要想生成vmlinux,需要vmlinux-lds文件、vmlinux-init文件、vmlinux-main文件。其中,vmlinux-lds是链接脚本文件,定义了代码段,数据段的存放位置。这里我们接着往下看,vmlinux-init需要head-y和init-y,通过查看两个makefile,我们可以得到经过转换后的结果:
head-y :=
arch/arm/kernel/head$(mmuext).o arch/arm/kernel/init_task.o
init-y := $(patsubst %/, %/built-in.o, $(init-y)) = init/built-in.o
core-y := $(patsubst %/, %/built-in.o, $(core-y))
= usr/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o
libs-y := $(libs-y1) $(libs-y2) =lib/lib.a lib/built-in.o
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) = drivers/built-in.o sound/built-in.o
net-y := $(patsubst %/, %/built-in.o, $(net-y)) = net/built-in.o
现在已经分析了内核编译的全部过程。那怎样知道我们分析的到底对不对,通过实际执行make uimage我们就可以看到执行过程。这是执行make uimage过程中的部分相关命令:
arm-linux-ld -el -p --no-undefined -x -o vmlinux
-t arch/arm/kernel/vmlinux.lds
arch/arm/kernel/head.o
arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o
可以看到,首先目标要生成vmlinux,然后是链接脚本为vmlinux.lds。开始生成第一个文件:head.o,第二个文件:init_task.o。这和我们分析的完全一致。接下来以此类推,和我们分析的相同,也就是说我们分析的是正确的。
sections
{
. = (0xc0000000) + 0x00008000;
.text.head : {
_stext = .;
_sinittext = .;
*(.text.head)
}
.init : { /* init code and data */
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = align(16);
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
这是链接脚本vmlinux.lds中的部分内容。首先定义了虚拟地址:(0xc0000000) + 0x00008000。 然后是首先执行头部文件,这与我们分析的完全一致。代码段,初始化代码段等等。
这就是linux内核的从配置到编译的全部分析了^_^
