



Linux始于1991年,是一名芬兰学生Linus Torvalds的业余爱好项目。该项目日臻成熟、不断发展,如今在世界各地约有1000名贡献者。现在,无论是在嵌入式系统中还是在服务器上,Linux都是必不可少的操作系统。
内核是操作系统的核心部分,其开发并不简单。与其他操作系统相比,Linux拥有许多优点:它是免费的,有良好的文档记录,还有一个大型社区,可以在不同的平台间移植,提供对源代码的访问,并且有许多免费的开源软件。
本书将尽可能让描述具有普适性。有一个特殊的主题,称为设备树,它还不是一个完整的x86特性。因此这个主题将专门讨论ARM处理器,特别是那些完全支持设备树的处理器。为什么采用这些架构?因为它们主要用于台式机和服务器(x86),以及嵌入式系统(ARM)。
本章将讨论以下主题:
●设置开发环境;
●配置和构建Linux内核。
当你在嵌入式系统领域工作时,甚至在设置开发环境之前,你必须熟悉如下专业术语。
●目标机:生成构建(build)所产生的二进制文件的目标机器。这台机器将运行二进制文件。
●宿主机:执行构建过程的机器。
●编译:也称为本地编译或本地构建。当目标机和宿主机相同时,就会发生这种情况。也就是说,当你在机器A(宿主机)上编译一个二进制文件时,它将在同一台机器A(目标机)或其他同类机器上执行。原生编译需要一个原生编译器,因此原生编译器的目标机和宿主机是相同的。
●交叉编译:目标机和宿主机是不同的。在这里,你可以在机器A(宿主机)构建一个将在机器B(目标机)上执行的二进制文件。在这种情况下,宿主机(机器A)必须安装支持目标体系结构的交叉编译器。因此,交叉编译器是目标机和宿主机不同的编译器。
●由于嵌入式计算机的资源(CPU、RAM、磁盘等)有限或较少,因此宿主机通常是强大得多的x86机器,有更多的资源来加速开发过程。然而,在过去几年,嵌入式计算机变得更加强大,它们越来越多地用于本地编译(因此用作宿主机)。一个典型的例子是树莓派4,它具有强大的四核CPU和高达8GB的RAM。
本章将使用x86机器作为宿主机,创建本地构建或进行交叉编译。因此,“本地构建”一词将指代“x86本地构建”。
要快速检查本机信息,可以使用以下命令:
lsb_release -a Distributor ID:Ubuntu Description:Ubuntu 18.04.5 LTS Release:18.04 Codename:bionic
笔者的计算机是一台华硕RoG,它有一颗16核的AMD Ryzen CPU(你可以使用lscpu命令获取这些信息)、16GB的RAM,以及256GB的SSD(Solid State Disk,固态硬盘)和1TB的硬盘驱动器(你可以使用df -h命令获取这些信息)。可以说,一个四核CPU和4GB或8GB的RAM应该足够了,但会导致构建时间延长。笔者最喜欢的编辑器是Vim你也可以自由使用自己最喜欢的编辑器。如果你使用的是台式机,则可以使用目前正被广泛使用的Visual Studio Code(简称VS Code)。
现在我们已经熟悉了将要使用的与编译相关的关键字,下面可以开始设置宿主机了。
在开始开发流程之前,需要设置开发环境。专用于Linux开发的环境非常简单——至少在基于Debian的系统中是这样。
在宿主机上,需要安装以下几个包:
$ sudo apt update
$ sudo apt install gawk wget git diffstat unzip \
texinfo gcc-multilib build-essential chrpath socat \
libsdl1.2-dev xterm ncurses-dev lzop libelf-dev make
在上面的代码中,我们安装了一些开发工具和一些必要的库,以便在配置Linux内核时拥有良好的用户界面。
现在,我们需要安装编译器和工具(链接器、汇编器等),以确保构建过程正常工作并为目标机生成可执行文件。这组工具称为Binutils,编译器加Binutils(如果有的话,再加上其他构建时依赖库)的组合称为工具链。因此,你需要理解“我需要(某个)体系结构的工具链”或类似句子的含义。
在开始编译之前,我们需要安装必要的包和工具用于本地编译或ARM交叉编译,也就是工具链。GCC(GNU Compiler Collection,GNU编译器套件)是Linux内核支持的编译器。Linux内核中定义的许多宏都与GCC相关。因此,我们使用GCC作为(交叉)编译器。
可以使用以下工具链安装命令进行本地编译:
sudo apt install gcc binutils
当需要进行交叉编译时,必须识别并安装正确的工具链。与本机编译器相比,交叉编译器可执行文件的前缀是目标操作系统、体系结构和(有时候是)库的名称。因此,为了识别特定于体系结构的工具链,我们定义了如下命名约定:arch[-vendor][-os]-abi。下面看看这个命名约定中的字段是什么意思。
●arch标识架构,即ARM、MIPS、x86、i686等。
●vendor为工具链供应商(公司),即Bootlin、Linaro、none(如果没有工具链供应商的话)等,或者省略该字段。
●os是目标操作系统,即Linux或none(裸机)。如果省略,则假定为裸机。
●abi代表应用程序二进制接口。它指的是底层二进制文件的结构,包括函数调用约定、参数传递方式等。可能的约定包括eabi、gnueabi和gnueabihf。
❏eabi代表要编译的代码将运行在裸机ARM核心上。
❏gnueabi代表将编译用于Linux的代码。
❏gnueabihf与gnueabi相同,但末尾的hf表示硬浮点,这表明编译器及其底层库使用的是硬件浮点指令,而不是浮点指令的软件实现,如定点软件实现。如果没有可用的浮点硬件,指令将被捕获并由浮点仿真模块执行。使用软件模拟时,功能上唯一的实际区别是执行速度变慢。
下面是一些工具链的名称,读者可以通过它们来理解工具链的命名约定。
●arm-none-eabi:一个针对ARM架构的工具链。它没有供应商,针对裸机系统(不针对操作系统),并遵循ARM EABI。
●arm-none-linux-gnueabi或arm-linux-gnueabi:我们可以使用这个工具链提供的默认配置(ABI)为运行在Linux上的ARM体系结构生成对象。请注意,arm- none-linux-gnueabi与arm-linux-gnueabi是相同的,因为当未指定供应商时,我们假定没有供应商。支持硬件浮点的工具链变体是arm-linux-gnueabihf或arm-none-linux-gnueabihf。
现在我们已经熟悉了工具链的命名约定,从而可以确定哪些工具链能够用于目标架构进行交叉编译。
要在32位的ARM机器上进行交叉编译,可以使用以下命令安装工具链:
sudo apt install gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf
请注意,在Linux树和GCC中,64位的ARM后端/支持称为aarch64。因此,交叉编译器必须有类似gcc-aarch64-linux-gnu*的名称,而Binutils必须有类似binutils -aarch64-linux-gnu*的名称。因此,对于64位的ARM工具链,可以使用以下命令:
sudo apt install make gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
注意
aarch64只支持/提供硬件浮点aarch64工具链,因此不需要在末尾指定hf。
请注意,并非所有版本的编译器都能编译给定的Linux内核版本。因此,考虑Linux内核版本和编译器(GCC)版本是很重要的。虽然前面的命令安装了你所使用的Linux发行版支持的最新版本,但也可以指定特定的版本。要实现这一点,可以使用gcc-<version>- <arch>-linux-gnu*。
例如,要安装aarch64的GCC 8,可以使用以下命令:
sudo apt install gcc-8-aarch64-linux-gnu
现在工具链已经安装完毕,我们可以查看分发包管理器选择的版本。例如,要检查安装了哪个版本的aarch64交叉编译器,可以使用以下命令:
$ aarch64-linux-gnu-gcc --version aarch64-linux-gnu-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 Free Software Foundation, Inc. [...]
对于32位的ARM变体,可以使用以下命令:
$ arm-linux-gnueabihf-gcc --version arm-linux-gnueabihf-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 Free Software Foundation, Inc. [...]
最后,对于本地版本,可以使用以下命令:
$ gcc --version gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 Free Software Foundation, Inc.
现在已经设置好了开发环境,并确保使用了正确的工具版本,我们可以开始下载Linux内核源代码并深入研究它们了。
在内核早期(直到2003年),Linux使用了奇偶版本控制风格,其中奇数表示稳定,偶数表示不稳定。当2.6版本发布时,版本控制模式切换为X.Y.Z。
●X:实际的内核版本,也称为主版本。当向后不兼容的API发生更改时,它会递增。
●Y:一个小的修改。在以向后兼容的方式添加功能之后,它会递增。
●Z:一个补丁,表示与错误修复相关的版本。
这被称为语义版本控制,一直持续到版本2.6.39。后来Linus Torvalds决定将版本更新到3.0,这意味着语义版本控制在2011年结束,之后采用了X.Y方案。
到了3.20版本,Linus认为不能再增加Y了。因此,他决定切换到任意的版本控制方案,每当Y大到用手指数不过来的时候,就增加X。这就是版本从3.20直接变化为4.0的原因。
现在,内核使用任意的X.Y版本控制方案,这与语义版本控制无关。
根据Linux内核发布模型,内核总有两个最新版本:稳定版本和长期支持(Long-Term Support,LTS)版本。所有的bug修复和新特性都由子系统维护者收集和准备,然后提交给Linus Torvalds,以便将它们包含到Linux树中。Linux树又称为主线Linux树,或者称为master Git存储库。每个稳定的版本都起源于此。
每个新的内核版本在发布之前,都会通过发布候选标签提交给社区,以便开发人员测试和完善所有的新功能。根据这个周期内收到的反馈,Linus会决定最终版本是否可以发布。当Linus确信新内核已经准备就绪时,他就会发布最终版本。我们称这个版本为“稳定”版本,而不是“候选版本”:这些版本是vX.Y版本。
发布版本没有严格的时间表,但新的主线内核通常每两三个月发布一次。稳定的内核版本基于Linus版本,也就是主线树版本。
稳定的内核在由Linus发布之后,就会出现在linux-stable树中并成为一个分支,可以接收错误修复。之所以被称为稳定树(stable tree),是因为它用于跟踪先前发布的稳定内核。它由Greg Kroah-Hartman维护和策划,但所有修复都必须首先进入Linus的代码树(后文简称Linus树),即主线存储库。一旦在主线存储库中修复了bug,就可以将其应用于之前发布的内核,这些内核仍然由内核开发社区维护。所有已经向后移植到稳定版本的修复程序,在考虑移植之前都必须满足一组重要的标准,其中之一是它们“必须已经存在于Linus树中”。
注意
修正过错误的内核版本被认为是稳定的。
例如,在Linus发布4.9内核时,稳定内核是基于内核编号方案发布的,即4.9.1、4.9.2、4.9.3,依此类推。这样的版本称为bug修复内核版本(bugfix kernel release),其序列通常缩短为“4.9.Y”,表示其在稳定内核发布树中的分支。每个稳定的内核发布树都由其内核开发者维护,该内核开发者将负责为发布选择必要的补丁,并进行审查/发布。通常,在下一个主线内核可用之前,只会发布几个修正过错误的内核版本,除非它被指定为长期维护内核。
在每个子系统和内核维护者存储库中,我们可以找到Linus树或稳定树。在Linus树中,只有一个分支,也就是主分支。它的标签要么是稳定版本,要么是候选版本。在稳定树中,每个稳定内核版本都有一个分支(名为<A.B>.y,其中<A.B>是Linus树中的发布版本),而每个分支则包含其bug修复内核版本。
在本书中,我们将使用Linus树,可以使用以下命令下载Linus树:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git --depth 1 git checkout v5.10 ls
在上面的命令中,我们使用--depth 1来避免下载历史记录(或者只选择上一次提交的历史记录),这可以显著降低下载文件的大小并节省时间。由于Git支持分支和标记,因此checkout命令允许你切换到特定的标记或分支。在这个例子中,我们切换到v5.10标签。
注意
在本书中,我们将使用Linux内核v5.10。
下面让我们来看看主线内核源代码目录的内容。
●arch/:为了尽可能通用,特定于体系结构的代码会与其他代码分开。该目录包含特定于处理器的代码,这些代码组织在每个体系结构的子目录中,如alpha/、arm/、mips/、arm64/等。
●block/:该目录包含块存储设备的代码。
●crypto/:该目录包含加密API和加密算法的代码。
●certs/:该目录包含证书和签名文件,用于启用模块签名,以便内核加载签名过的模块。
●documentation/:该目录包含用于不同内核框架和子系统的API的描述。在公共论坛上提问之前,你应该先查看该目录。
●drivers/:这是内容最繁多的目录,因为随着设备驱动程序的合并,它会不断增长。该目录包含每个设备的驱动程序,它们被组织在不同的子目录中。
●fs/:该目录包含内核支持的不同文件系统的实现,如NTFS、FAT、ETX{2,3,4}、sysfs、procfs、NFS等。
●include/:该目录包含内核头文件。
●init/:该目录包含初始化和启动代码。
●ipc/:该目录包含进程间通信(Inter-Process Communication,IPC)机制的实现,如消息队列、信号量和共享内存。
●kernel/:该目录包含基本内核中与体系结构无关的部分。
●lib/:该目录包含库例程和一些辅助函数,比如通用的内核对象(kobject)处理程序和循环冗余码(Cyclic Redundancy Code,CRC)计算函数。
●mm/:该目录包含内存管理代码。
●net/:该目录包含网络协议代码(无论网络类型是什么)。
●samples/:该目录包含各个子系统的设备驱动程序示例。
●scripts/:该目录包含内核使用的脚本和工具,此外还包含其他一些有用的工具。
●security/:该目录包含安全框架代码。
●sound/:该目录包含音频子系统代码。
●tools/:该目录包含Linux内核开发和各种子系统的测试工具,如USB、虚拟宿主机测试模块、GPIO、IIO和SPI等。
●usr/:该目录当前包含initramfs实现。
●virt/:这是虚拟化目录,其中包含用于hypervisor的内核虚拟机(Kernel-based Virtual Machine,KVM)模块。
为确保可移植性,任何特定于体系结构的代码都应该放在arch目录下。此外,与用户空间API相关的内核代码(系统调用、/proc、/sys等)不应更改,因为更改会破坏现有的程序。
在本节中,我们熟悉了Linux内核的源代码。在浏览了Linux内核的所有源代码之后,将它们配置得能够编译内核似乎是很自然的事情。
在Linux内核源代码中,有许多可用的驱动程序/特性和构建选项。配置过程包括选择哪些特性/驱动程序,选择的这些选项将成为编译过程的一部分。根据我们是执行本地编译还是交叉编译,有些环境变量必须在配置过程开始之前定义。
内核的Makefile调用的编译器是$(CROSS_COMPILE)gcc。也就是说,CROSS_ COMPILE是交叉编译工具(gcc、as、ld、objcopy等)的前缀,必须在调用make时指定,或者必须在执行任何make命令之前导出。只有gcc以及与之相关的Binutils可执行文件的前缀是$(CROSS_COMPILE)。
请注意,这里做了各种假设,Linux内核构建基础设施会根据目标机体系结构启用各种选项/特性/标志。为此,除了交叉编译器前缀,还必须指定目标机的体系结构,这可以通过ARCH环境变量来完成。
因此,典型的Linux配置或构建命令如下:
ARCH=<XXXX> CROSS_COMPILE=<YYYY> make menuconfig
也可以使用如下命令:
ARCH=<XXXX> CROSS_COMPILE=<YYYY> make <make-target>
如果启动命令时不希望指定这些环境变量,则可以将它们导出到当前shell中,示例如下:
export CROSS_COMPILE=aarch64-linux-gnu export ARCH=aarch64
请记住,如果没有指定这些环境变量,本地宿主机将成为目标机。也就是说,如果省略CROSS_COMPILE或未设置CROSS_COMPILE,$(CROSS_COMPILE)gcc将得到gcc,对于调用的其他工具亦如此(例如,$(CROSS_COMPILE)ld将得到ld)。
以同样的方式,如果ARCH(目标体系结构)被省略或没有设置,则默认执行make的宿主机,默认为$(uname -m)。
因此,要想使用gcc为宿主机体系结构本地编译内核,就应该保持CROSS_COMPILE和ARCH未定义。
Linux内核是一个基于Makefile的项目,其中包含数千个选项和驱动程序。每个启用的选项都可以使另一个选项可用,或者可以将特定的代码拉入构建。要配置内核,可以对基于ncurses的接口使用make menuconfig命令,或对基于X的接口使用make xconfig命令。基于ncurses的接口配置如图1.1所示。
对于大多数选项,你有3个选择。但在配置Linux内核时,我们可以列出5种类型的选项,具体如下。
●布尔选项,有两个选择。
❏(blank),它禁用此功能。当这个选项在配置菜单中突出显示时,你可以按N来省略功能。它等同于false,当它被禁用时,生成的配置选项会在配置文件中被注释掉。
❏(*),它在内核中静态编译。这意味着当内核第一次被加载时,它将始终存在。它等价于true,你可以在配置菜单中选中一个功能并按Y。得到的选项将在配置文件中显示为CONFIG_<OPTION >=y,例如CONFIG_INPUT_ EVDEV=y。
图1.1 内核配置界面
●三态选项,即除了可以接受布尔值状态,还可以接受第3种状态,它在配置窗口中被标记为(M)。该选项在配置文件中的结果为CONFIG_<OPTION>=m,例如CONFIG_ INPUT_ EVDEV=m。要生成一个可加载的模块(如果这个选项允许的话),你可以按下M选择这个特性。
●字符串选项,接受字符串值,例如CONFIG_CMDLINE="noinitrd console= ttymxc0, 115200"。
●Hex选项,接受十六进制值,例如CONFIG_PAGE_ OFFSET=0x80000000。
●Int选项,接受整数值,例如CONFIG_CONSOLE_ LOGLEVEL_DEFAULT=7。
选中的选项将存储在源代码树的根目录下的.config文件中。
想要知道哪种配置在你的平台上可以有效运行是很难的。大多数情况下,不需要从头开始配置。每个arch目录下都有默认配置文件和功能配置文件,你可以将它们作为起点(重要的是要从已经可用的配置开始):
ls arch/<your_arch>/configs/
对于基于ARM的32位CPU,这些配置文件可以在arch/arm/configs/目录中找到。在这种体系结构中,每个CPU系列通常只有一个默认配置。例如,对于i.MX6-7处理器,默认的配置文件是arch/arm/configs/imx_v6_v7_defconfig。但是,在基于ARM的64位CPU上,只有一个大的默认配置可以定制。它位于arch/arm64/configs/目录中,称为defconfig。类似地,对于x86处理器,我们可以在arch/x86/configs/目录中找到相关文件。这里有两个默认的配置文件——i386_defconfig和x86_64_defconfig,分别用于32位和64位的x86体系结构。
给定一个默认配置文件,内核配置命令如下:
make <foo_defconfig>
运行该命令将在主目录(根目录)中生成一个新的.config文件,而旧的.config文件将被重命名为.config.old。这可以方便地恢复以前的配置更改。然后,可以使用以下命令自定义配置:
make menuconfig
保存更改将更新.config文件。虽然你可以与协作者共享此配置,但你最好创建一个与Linux内核源代码中提供的配置文件相同的最小格式的默认配置文件。为此,可以使用以下命令:
make savedefconfig
该命令将创建一个最小的配置文件(因为不存储非默认设置)。生成的默认配置文件称为defconfig,存储在源代码树的根目录中。你可以使用以下命令将其存储到另一个位置:
mv defconfig arch/<arch>/configs/myown_defconfig
这样你就可以在Linux内核源代码中共享同一个参考配置,其他开发者现在可以通过执行以下命令得到与你相同的.config文件:
make myown_defconfig
请注意,对于交叉编译,必须在执行任何make命令之前设置ARCH和CROSS_COMPILE,即使对内核配置也是如此;否则,你的配置可能会出现意外更改。
根据目标系统的不同,可以使用以下配置命令。
●要在64位的x86处理器上进行本地编译,命令非常简单(可以省略编译选项):
make x86_64_defconfig make menuconfig
●给定一个基于ARM i.MX6的32位板,可以执行以下命令:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make imx_v6_v7_defconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make menuconfig
使用第一组命令,你可以将默认选项存储在.config文件中;而使用第二组命令,你可以根据需要更新(添加/删除)各种选项。
对于64位ARM板,可以执行以下命令:
ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu- make defconfig ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu- make menuconfig
你可能会在使用xconfig时遇到Qt4错误。在这种情况下,应该直接使用以下命令来安装缺少的包:
sudo apt install qt4-dev-tools qt4-qmake
注意
你可能正在从旧内核切换到新内核。给定旧的.config文件,你可以将其复制到新的内核源代码树中,然后执行make oldconfig命令。如果新内核中有新选项,系统将提示你是否将这些选项包括进去。但是,你可能希望对这些选项使用默认值。在这种情况下,应执行make olddefconfig命令。最后,如果要拒绝所有新选项,应执行make oldnoconfig命令。
要找到初始配置文件,也可以有更好的选择,尤其当你的计算机已经在运行时。Linux发行版Debian和Ubuntu将.config文件保存在/boot目录下,所以可以使用以下命令复制此配置文件:
cp /boot/config-`uname -r` .config
其他Linux发行版可能不会这样做。因此,建议你始终启用IKCONFIG和IKCONFIG_ PROC内核配置选项,这将允许你通过/proc/config.gz访问.config文件。这是一个标准方法,适用于嵌入式发行版。
现在可以配置内核了,让我们列举一些有用的内核配置特性,这些内核配置特性可能值得你在内核中启用。
●IKCONFIG和IKCONFIG_PROC:笔者认为它们最重要。它们使内核配置在运行时可用,你可以在其他系统中重用此配置,或者只是查找特定功能的启用状态。例如:
# zcat /proc/config.gz | grep CONFIG_SOUND
CONFIG_SOUND=y
CONFIG_SOUND_OSS_CORE=y
CONFIG_SOUND_OSS_CORE_PRECLAIM=y
# CONFIG_SOUNDWIRE is not set
#
●CMDLINE_EXTEND和CMDLINE:前者是一个布尔值,它允许你从配置中扩展内核命令行;后者是一个包含实际命令行扩展值的字符串,例如CMDLINE= "noinitrd usbcore.authorized_default=0 "。
●CONFIG_KALLSYMS:这是一个布尔选项,用于访问/proc/kallsyms目录中的内核符号表(其中包含了符号与其地址之间的映射)。这对于跟踪器和其他需要将内核符号映射到地址的工具非常有用。该选项在输出oops消息时使用;否则代码清单将产生十六进制输出,会变得很难解读。
●CONFIG_PRINTK_TIME:当输出来自内核的消息时,该选项显示时间信息。这对于运行时发生的事件进行时间戳标记会有所帮助。
●CONFIG_INPUT_EVBUG:允许你调试输入设备。
●CONFIG_MAGIC_SYSRQ:允许你对系统进行一些控制(例如重新启动、转储一些状态信息等),即使在系统崩溃之后,只需要使用一些组合键即可执行。
●DEBUG_FS:允许你启用对调试文件系统的支持,可以在其中调试GPIO、CLOCK、DMA、REGMAP、IRQ和许多其他子系统。
●FTRACE和DYNAMIC_FTRACE:允许你启用功能强大的ftrace跟踪程序,以跟踪整个系统。启用ftrace跟踪程序后,它的一些枚举选项也可以启用,具体如下。
❏FUNCTION_TRACER:允许你跟踪内核中的任何非内联函数。
❏FUNCTION_GRAPH_TRACER:与前一个枚举选项的功能相同,但它显示了一个调用图(调用函数和被调用函数)。
❏IRQSOFF_TRACER:该枚举选项使你能够跟踪内核中IRQ的周期。
❏PREEMPT_TRACER:允许你测量抢占关闭的延迟。
❏SCHED_TRACER:允许你调度延迟跟踪。
现在内核已经配置好了,之后必须构建以生成一个可运行内核。在1.2.3节中,我们将描述内核的构建过程,以及预期的构建组件。
这一步需要你使用与配置步骤相同的shell,否则就必须重新定义ARCH和CROSS_COMPILE环境变量。
Linux内核是一个基于Makefile的项目。构建这样的项目需要使用make工具并执行make命令。对于Linux内核,make命令必须以普通用户身份在主内核源目录下执行。
默认情况下,如果没有指定,则make目标是所有的文件。在Linux内核源代码中,对于x86体系结构,它指向(或依赖)vmlinux bzImage modules目标;对于ARM或aarch64体系结构,它指向vmlinux zImage modules dtbs目标。
在这些目标中,bzImage是一个特定于x86体系架构的make目标,它会生成具有相同名称bzImage的二进制文件。Vmlinux也是一个make目标,用于生成一个名为vmlinux的Linux映像。zImage和dtbs都是ARM和aarch64体系架构专用的make目标:前者生成具有相同名称的Linux映像,后者则为目标机CPU构建设备树源。Modules作为一个make目标,将构建所有选定的模块(在配置中用m标记)。