编译 Linux 内核与 U-Boot:深入探索的过程
之前的文章:《一次搞定交叉编译》 给大家讲了如何安装交叉编译工具链,搭建交叉编译环境。
这篇文章主要教大家如何正确的去编译 Linux Kernel、U-Boot 这些著名的开源软件。
也许很多同学会说:编译是小 case 啊,我都 make 过成千上万次了!
可是你是否有思考过,你编译的时候每一个步骤、执行的每一个命令.....
它背后隐藏的原理是什么?
为什么要这么做?
你的方法是最高效最科学的吗?
你的方法是否潜藏着漏洞?
换一个环境、换一个平台,如果编译的过程中遇到了莫名其妙的错误,你是否知道从哪里去找突破口?
这就是这篇文章要告诉你的。
编译 Linux Kernel
还是以 i.MX 的内核为例。
其实过程很简单,基本上两个命令搞定:
make ARCH=arm imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
第一个命令是配置,第二个命令是编译。
但是这背后却隐藏着很多小细节需要我们去注意。
- ### 配置内核
配置内核的命令是 make ARCH=chiparch xxx_defconfig
,第一个参数 ARCH=chiparch 用来指定芯片的架构,第二个参数 xxx_defconfig 用来指定这次编译对应的配置文件,比如针对 i.MX6、i.MX7 这些芯片的 Linux Kernel,配置命令是这样的:
make ARCH=arm imx_v7_defconfig
如果编译 i.MX8 相关的内核的话,配置命令大概是这样的:
make ARCH=arm64 defconfig
这个命令会从指定的 defconfig 文件里面加载配置,写入到 .config 文件中。内核编译的时候就是根据 .config 文件的内容来决定哪些模块编译,哪些模块不编译的。
make 后面的两个参数是怎么来的?
因为 Linux Kernel 支持大量不同架构的芯片、比如 arm、arm64、x86、mips、risc-v 等等,还支持成千上万的功能特性,在编译的时候我们并不需要把内核支持的所有芯片和功能都编译进去,这样编译出来的内核镜像会非常的庞大。而且 Kernel 中有相当一部分代码是针对特定架构的,比如启动阶段的初始化代码、每个架构都有自己特定的设置,这部分代码大部分是汇编写的,而且相互不兼容:
所以,Linux 内核提供了 defconfig 机制 ,开发者们可以根据自己的芯片、开发板特性自己决定内核中哪些功能需要打开,哪些功能可以关闭,生成一个和自己硬件相关的 defconfig 文件,下次编译的时候加载。这些 defconfig 文件放在 arch/chiparch/configs 下面:
所以在配置的时候,我们需要指定具体的 ARCH、Kernel 的 Kbuild 系统才能在对应的目录下找到你指定的 defconfig 文件。如果没有指定 ARCH、一般默认会去 x86 目录下去找。
因为 i.MX6、 i.MX7 是 Arm32 ,所以对应的 ARCH 为 arm,i.MX8 是 Arm64,所以对应的 ARCH 为 arm64.
另外需要说明一点的是,大家可以看到 arm 和 mips 目录下 defconfig 文件非常多,而比较新的一些架构,比如arm64,risc-v 目录下,只有唯一的一个 defconfig 文件,这和 Linux Kernel 目前的开发风格转变有关:不再鼓励大家提交一堆乱七八糟的 defconfig 文件, 尽量只使用一个通用的 defconfig 文件,这里面尽量打开内核启动需要的模块,而对内核启动影响不大的模块,以模块的形式编译。在这个设计思想下,Arm32 下面有一个通用的 multiv7defconfig, Arm64 和 risc-v 对应的 defconfig 文件名都叫做 defconfig。所以如果现在你想为一个新的芯片提交它自己的 defconfig 文件到 mainline 分支,是不会被接受的,Linux 社区的 Maintainer 会告诉你,把你需要打开的特性加到通用的 defconfig 里面。
当然,我们在本地做开发的时候,一般不会这样玩,我们还是会根据自己的芯片和特定产品形态,创建独立的 defconfig 文件,这样方便深度裁剪。
这个配置文件是如何生成的呢?
我们一般在一个现有配置文件的基础上,根据产品需求,通过 make menuconfig
命令加减配置,然后再通过 make savedefconfig
命令生成新的配置文件:
比如我需要打开 drivers/gpu/drm/imx/dw_hdmi-imx.c
这个 HDMI 驱动,通过查看该 C 文件同目录下 Makefile,可以发现它依赖 DRM_IMX_HDMI
这个配置项:
make ARCH=arm menuconfig
对于对内核还不怎么熟悉的同学来说,如何找到 DRM_IMX_HDMI
这个配置的位置呢?别急,menuconfig 界面也是可以搜索关键字的:
按/键,就是?下面那个键,会弹出下面的界面:
然后在选择框里面输入要查找的关键字,敲Enter就会出现结果:
这里只有一个匹配的选项,所以我们直接在键盘上按1键,就会跳到对应的选项开关处:
然后打开对应的选项即可,<*> 号表示直接编译进内核, 表示以模块的方式编译,< > 则表示不编译。
按↑↓上下移动光标,按←→光标在最下面左右移动。
配置完成后选退出。这时候可以看到 DRM_IMX_HDMI
这个选项的配置已经生效了:
然后执行 make savedefconfig
命令,生成新的配置文件,并用该文件覆盖旧文件。
有人可能会疑问,为什么要用 make savedefconfig
来生成一个中间的 defconfig 文件呢,直接用 .config 去覆盖不是也可以吗?
答案是:make savedefconfig
命令生成的 defconfig 文件更精简,更易读。
- 编译内核 这是最通用的编译内核的命令,第一个告诉内核要编译 arm 架构,第二个参数指定用什么交叉编译工具去编译。编译成功的结果大概是这样的:
最终编译后的镜像是压缩过的 zImage。
同时所有的 dts 文件也会被编译成 dtb。
当然,如果为了编译的快,我们还可以启动并行编译,比如:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
就是同时有 8 个编译进程在运行,并行数目设置多少最合适,这个一般取决于你用来编译的电脑有多少 CPU,以及内存够不够大,一般数据可以设置的和 CPU 个数相等,或者 2 倍。
有人会说,为什么我看到的有些开发板的编译说明文档和这个不太一样,比如下面这个:
这是因为,部分厂家的Kernel,为了满足自己的固件升级设计,做了一些特殊的打包和修改,你虽然看到的编译命令不一样了,其实他们都是基于最基本的编译命令做的封装定制。
它在 Makefile 里面指定了 ARCH 和 CROSS_COMPILE, 所以make 的时候就不需要指定这两个参数了。
然后通过这个修改,当开发者执行 make xxx.img 这种模式的命令的时候,会自动编译 zImage,并通过mkkrnlimg 这个命令对 zImage 打包生成 kernel.img。并且对通过 resource_tool 这个命令对 dtb 也进行了打包。
Linux Kbuid 系统还执行很多其他的 make 命令,可以通过 make help 来查看:
编译 U-Boot
U-Boot 的编译步骤和 Linux Kernel 非常类似,也是两步:
make mx6ull_14x14_evk_defconfig
make CROSS_COMPILE=arm-linux-gnueabihf-
唯一的差别是,U-Boot 在编译的时候不需要指定 ARCH 选项,这是 U-Boot 的编译系统相比 Linux Kbuid 的要给改进点。其实目前最新的 U-Boot 编译系统也是基于 Linux Kbuild 设计的,同样支持 make menuconfig 命令,有对应的 defconfig 文件,在 configs 目录下。
另外一个区别是,U-Boot 也有自己的 dtb,但是最终编译完的 U-Boot 会和 dtb 合并在一起。
编译遇到错误怎么办
我们在编译软件的时候,经常会遇到各种奇奇怪怪的报错,有些是软件本身有 Bug(对于 Linux Kernel、U-Boot 这类比较知名的开源项目,这种Bug 比较少见)、有些是因为编译环境里面缺少一些依赖工具(这种情况很常见)。
对于刚接触的新人来说,一条 make
命令敲下去,发现蹦出来一堆莫名其妙的错误,是很令人沮丧的。
但是不要怕,虽然你是新人,这也没关系,因为现在是互联网时代,对于那些广泛使用的开源项目,你踩到的坑基本都有很多人踩到过,而且这个世界有那么多乐于分享的人:他们遇到问题,勇敢的去分析,寻求解决方法,然后分享出来,他们是这个数字世界的热和光。
所以,如果你遇到一个自己能看得懂搞得定的问题,那很简单,直接去搞定即可,然后如果你乐意,能再写一篇分享心得就更好了。如果你遇到了一个像天书一样,自己完全看不懂的报错,也不要紧,最实用的解决方案就是:直接把这个错误复制出来,粘贴到你的搜索引擎里,点下下一步,开始搜索即可,一条条耐心的去看,一般运气都不会太差。
当然,搜索引擎的选择也是一门学问,如果你用到是百度,能搜到答案最好,如果搜不到,你还可以试试 Bing、*、Github、如果你能用 Google,那就更好了。
相信我,这个方法能解决你所遇到的大部分问题。
比如 我在执行 make ARCH=arm imx_v7_defconfig
就提示 failed 了:
仔细分析这个日志,我们会发现最开始有这样一个异常提示:/bin/sh:1:bison:notfound
一般在分析异常的时候,比较有效的方法是找到最开始报出异常的地方,先解决掉,然后再看是不是最终问题被解决掉了,其实大部分时候都是这样的,把最先冒头的问题解决掉,后面所有的问题都被打掉了。
如果你能看懂这个异常提示,其实你会发现它是说 shell 在执行 bison 这个命令的时候,发现这个命令找不到。
试试在命令行执行下 bison
这个命令,会发现如下提示:
然后按照提示 sudo apt install bison
安装即可解决。
如果你看不出来,也没关系,用前面提到的终极解决方案,直接把这条提示复制到百度里搜搜看:
运气比较好,太多人和我踩了同样的坑,前三条连接任意打开一个,点进去都能找到答案:
sudo apt install bison
安装完后再次执行 make ARCH=arm imx_v7_defconfig
,bison 的报错没了,可是又蹦出来另外一个错误:
和前面一样,还是直接 复制——》粘贴——》搜索:
踩坑的不止我一个啊,打开任意一个链接,就能找到解决办法:
sudo apt install flex
然后再次执行 make ARCH=arm imx_v7_defconfig
,终于成功了。
然后执行 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
开始编译, 发现又报错了:
直接用前面提到的三部曲:复制——》粘贴——》搜索:
打开这些链接,你会找到答案:
sudo apt install libssl-dev
再次开始编译,好不容易编译到最后,又挑出来一个错误:
相信到这里你已经知道该怎么做了,没错,还是 复制——》粘贴——搜索:并且你会很快找到解决问题的方法:
sudo apt-get install lzop
继续编译,终于成功了:
推荐阅读
-
linux 内核 acpi,从内核模块探索 ACPI 与 BIOS 和操作系统之间的关系
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
编译 Linux 内核与 U-Boot:深入探索的过程
-
深入理解Android开发:十四、探索Android应用打包与编译的过程
-
深入解析Linux 0.12内核:第二部分 - 统一与独立编址的笔记
-
南邮OJ Web任务大揭秘:层层挑战剖析 1. 挑战一:迷宫般的目录探索 题目作者似乎穷举了所有可能的目录组合,最终在404.php中的