The kernel build system, kbuild
, is a set of make
scripts that take the configuration information from the .config
file, work out the dependencies and compile everything that is necessary to produce a kernel image containing all the statically linked components, possibly a device tree binary and possibly one or more kernel modules. The dependencies are expressed in the makefiles that are in each directory with buildable components. For instance, these two lines are taken from drivers/char/Makefile
:
obj-y += mem.o random.o obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o
The obj-y
rule unconditionally compiles a file to produce the target, so mem.c
and random.c
are always part of the kernel. In the second line, ttyprintk.c
is dependent on a configuration parameter. If CONFIG_TTY_PRINTK
is y
it is compiled as a built in, if it is m
it is built as a module and, if the parameter is undefined, it is not compiled at all.
For most targets, just typing make
(with the appropriate ARCH
and CROSS_COMPILE
) will do the job, but it is instructive to take it one step at a time.
To build a kernel image, you need to know what your bootloader expects. This is a rough guide:
zImage
file using the bootz
commandbzImage
filezImage
fileHere is an example of building a zImage
file:
$ make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- zImage
It is the same when building bzImage
and uImage
targets.
There is a small issue with building a uImage
file for ARM with multi-platform support, which is the norm for the current generation of ARM SoC kernels. Multi-platform support for ARM was introduced in Linux 3.7. It allows a single kernel binary to run on multiple platforms and is a step on the road toward having a small number of kernels for all ARM devices. The kernel selects the correct platform by reading the machine number or the device tree passed to it by the bootloader. The problem occurs because the location of physical memory might be different for each platform, and so the relocation address for the kernel (usually 0x8000 bytes from the start of physical RAM) might also be different. The relocation address is coded into the uImage
header by the mkimage
command when the kernel is built, but it will fail if there is more than one relocation address to choose from. To put it another way, the uImage
format is not compatible with multi-platform images. You can still create a uImage binary from a multi-platform build so long as you give the LOADADDR
of the particular SoC you are hoping to boot this kernel on. You can find the load address by looking in mach-[your SoC]/Makefile.boot
and noting the value of zreladdr-y
.
In the case of a BeagleBone Black, the full command would look like this:
$ make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- LOADADDR=0x80008000 uImage
A kernel build generates two files in the top level directory: vmlinux
and System.map
. The first, vmlinux
, is the kernel as an ELF binary. If you have compiled your kernel with debug enabled (CONFIG_DEBUG_INFO=y
), it will contain debug symbols which can be used with debuggers like kgdb
. You can also use other ELF binary tools such as size
:
$ arm-cortex_a8-linux-gnueabihf-size vmlinux text data bss dec hex filename 8812564 790692 8423536 18026792 1131128 vmlinux
System.map
contains the symbol table in human readable form.
Most bootloaders cannot handle ELF code directly. There is a further stage of processing which takes vmlinux
and places those binaries in arch/$ARCH/boot
that are suitable for the various bootloaders:
Image
: vmlinux
converted to raw binary.zImage
: For the PowerPC architecture, this is just a compressed version of Image
, which implies that the bootloader must do the decompression. For all other architectures, the compressed Image
is piggybacked onto a stub of code that decompresses and relocates it.uImage
: zImage
plus a 64-byte U-Boot header.While the build is running, you will see a summary of the commands being executed:
$ make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-zImage CC init/main.o CHK include/generated/compile.h CC init/version.o CC init/do_mounts.o CC init/do_mounts_rd.o CC init/do_mounts_initrd.o LD init/mounts.o [...]
Sometimes, when the kernel build fails, it is useful to see the actual commands being executed. To do that, add V=1
to the command line:
$ make ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- V=1 zImage [...] arm-cortex_a8-linux-gnueabihf-gcc -Wp,-MD,init/.do_mounts_initrd.o.d -nostdinc -isystem /home/chris/x-tools/arm-cortex_a8-linux-gnueabihf/lib/gcc/arm-cortex_a8-linux-gnueabihf/4.9.1/include -I./arch/arm/include -Iarch/arm/include/generated/uapi -Iarch/arm/include/generated -Iinclude -I./arch/arm/include/uapi -Iarch/arm/include/generated/uapi -I./include/uapi -Iinclude/generated/uapi -include ./include/linux/kconfig.h -D__KERNEL__ -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-dwarf2-cfi-asm -mabi=aapcs-linux -mno-thumb-interwork -mfpu=vfp -funwind-tables -marm -D__LINUX_ARM_ARCH__=7 -march=armv7-a -msoft-float -Uarm -fno-delete-null-pointer-checks -O2 --param=allow-store-data-races=0 -Wframe-larger-than=1024 -fno-stack-protector -Wno-unused-but-set-variable -fomit-frame-pointer -fno-var-tracking-assignments -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DCC_HAVE_ASM_GOTO -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(do_mounts_initrd)" -D"KBUILD_MODNAME=KBUILD_STR(mounts)" -c -o init/do_mounts_initrd.o init/do_mounts_initrd.c [...]
The next step is to build the device tree, or trees if you have a multi-platform build. The dtbs target builds device trees according to the rules in arch/$ARCH/boot/dts/Makefile
using the device tree source files in that directory:
$ make ARCH=arm dtbs ... DTC arch/arm/boot/dts/omap2420-h4.dtb DTC arch/arm/boot/dts/omap2420-n800.dtb DTC arch/arm/boot/dts/omap2420-n810.dtb DTC arch/arm/boot/dts/omap2420-n810-wimax.dtb DTC arch/arm/boot/dts/omap2430-sdp.dtb ...
The .dtb
files are generated in the same directory as the sources.
If you have configured some features to be built as modules, you can build them separately using the modules
target:
$ make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- modules
The compiled modules have a .ko
suffix and are generated in the same directory as the source code, meaning that they are scattered all around the kernel source tree. Finding them is a little tricky but you can use the modules_install
make target to install them in the right place. The default location is /lib/modules
in your development system, which is almost certainly not what you want. To install them into the staging area of your root filesystem (we will talk about root filesystems in the next chapter), provide the path using INSTALL_MOD_PATH
:
$ make -j4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- INSTALL_MOD_PATH=$HOME/rootfs modules_install
Kernel modules are put into the directory /lib/modules/[kernel version]
, relative to the root of the filesystem.