5. Linux内核的编译¶
这里的Linux内核指的是Kernel,或者称内核也是指的Kernel
5.1. 为什么要自己编译Kernel¶
虽然我们提供的内核已经支持绝大多数功能了,但是对于一些需要定制功能的客户来说不一定有他们需要的功能配置,所以本章将讲解如何配置与编译内核。
5.2. 安装编译工具和依赖¶
编译内核之前需要安装必要的环境工具。
1 | sudo apt install make gcc-arm-linux-gnueabihf gcc bison flex libssl-dev dpkg-dev lzop vim
|
如果gcc-arm-linux-gnueabihf编译版本差异导致内核编译不通过,也可以使用以下版本的编译器:
https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/
下载gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
或者访问资料网盘/6-开发软件/交叉编译工具链/,进行下载。
下载完成后解压并导出环境变量:
1 2 3 4 5 6 7 8 9 10 11 | #解压交叉编译工具链压缩包
tar xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar
#进入交叉编译工具链bin目录,并查看
cd gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/ && ls && pwd
#导出环境变量,需要根据实际指定编译工具链的绝对路径
export PATH=/root/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
#查看编译工具链,如果COLLECT_LTO_WRAPPER变量为指定的路径,即配置成功
arm-linux-gnueabihf-gcc -v
|
以上配置为临时导出环境变量,打开其他终端或者重启都需要重新导出环境变量, 如需永久保存需要将导出环境变量的命令写入~/.bashrc文件末尾,并执行source ~/.bashrc 重新加载配置。
5.3. 获取kernel源码¶
5.3.1. 下载源代码¶
有三个方法获取Kernel源码,一个是Kernel官方内核源码,一个是NXP官方的kernel源码,一个是经过我们修改适配我们板子的kernel源码, 我们这里使用我们提供的源码为例,对官方源码感兴趣的小伙伴也可以下载来学习配置。
我们的kernel是根据NXP官方提供的kernel定制的,NXP的kernel是根据kernel官方某一版本进行芯片适配的。 我们作为嵌入式开发者,一般只需要使用芯片厂商适配好的kernel进行开发即可,对于从kernel官方下载新版本来配置这是芯片厂商做的事情, 感兴趣的伙伴也可以根据NXP官方提供的芯片手册进行适配新版本的kernel。
由于imx6ull这款芯片较为常用所以NXP官方将适配补丁提交给了kernel官方,这样kernel官方基本每一个版本都适配了这款芯片新版本的kernel官方的kernel也是可以直接编译来使用的。
Kernel官方内核源码:https://git.kernel.org/
NXP内核源码: https://github.com/Freescale/linux-fslc
EBF内核源码:https://github.com/Embedfire/ebf_linux_kernel.git
注意:本教程以野火提供的内核源码为分析样本,也十分建议使用野火适配好的。
使用野火提供的:
1 | git clone https://github.com/Embedfire/ebf_linux_kernel.git
|
通常一个内核仓库往往维护着不同分支的内核源码,进入仓库目录下可通过命令查看及切换内核分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #查看分支
git branch -a
#打印消息如下,默认为master主分支。
* ebf_4.1.19_imx
remotes/origin/HEAD -> origin/ebf_4.1.19_imx
remotes/origin/ebf_4.1.15_imx
remotes/origin/ebf_4.19.35_imx6ul
remotes/origin/ebf_4.19_imx
remotes/origin/ebf_4.19_imx_timeout_15S_PMIC
remotes/origin/ebf_4.19_star
remotes/origin/ebf_5.10.25_rk3328
remotes/origin/ebf_5.4.47_imx8mmini
remotes/origin/master
remotes/origin/test
#切换ebf_4.19.35_imx6ul分支
git checkout ebf_4.19.35_imx6ul
#打印消息如下
Checking out files: 100% (63998/63998), done.
Branch 'ebf_4.19.35_imx6ul' set up to track remote branch 'ebf_4.19.35_imx6ul' from 'origin'.
Switched to a new branch 'ebf_4.19.35_imx6ul'
#重新查看当前分支
qinghui@ebf-dev:~/embedfire/ebf_linux_uboot$ git branch
ebf_4.1.15_imx
* ebf_4.19.35_imx6ul
|
也可以在下载时指定分支,如下所示:(建议使用以下操作进行拉取,拉取体积小)
1 2 | #使用-b参数指定ebf_4.19.35_imx6ul分支,使用--depth指定拉取最新的10次提交
git clone -b ebf_4.19.35_imx6ul --depth=10 https://github.com/Embedfire/ebf_linux_kernel.git
|
5.3.2. kernel工程结构分析¶
学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。
ebf_6ull_linux
内核源码目录如下
arch COPYING drivers init kernel make_lite.sh README sound
block CREDITS firmware ipc lib Makefile samples tools
build_image crypto fs Kbuild LICENSES mm scripts usr
certs Documentation include Kconfig MAINTAINERS make_lite.sh net security virt
我们可以看到Linux内核源码目录下是有非常多的目录,且目录下也有非常多的文件, 下面我们简单分析一下这些目录的主要作用。
arch :主要包含和硬件体系结构相关的代码,如arm、x86、MIPS,PPC,每种CPU平台占一个相应的目录,例如我们使用的imx系列CPU就在 arch/arm/mach-imx
目录下,Linux内核目前已经支持30种左右的CPU体系结构。arch中的目录下存放的是各个平台以及各个平台的芯片对Linux内核进程调度、内存管理、 中断等的支持,以及每个具体的SoC和电路板的板级支持代码。
block :在Linux中block表示块设备(以块(多个字节组成的整体,类似于扇区)为单位来整体访问),譬如说SD卡、Nand、硬盘等都是块设备,block目录下放的是一些Linux存储体系中关于块设备管理的代码。
crypto :这个目录下存放的是常用加密和散列算法(如md5、AES、 SHA等),还有一些压缩和CRC校验算法。
Documentation:内核各部分的文档描述。
drivers :设备驱动程序,里面列出了linux内核支持的所有硬件设备的驱动源代码,每个不同的驱动占用一个子目录,如char、block、 net、 mtd、 i2c等。
fs :fs就是file system,里面包含Linux所支持的各种文件系统,如EXT、FAT、 NTFS、 JFFS2等。
include :目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在 include/linux
子目录下,与cpu架构相关的头文件在include目录下对应的子目录中。
init :内核初始化代码,这个目录下的代码就是linux内核启动时初始化内核的代码。
ipc :ipc就是 inter process commuication
,进程间通信,该目录下都是linux进程间通信的代码。
kernel :kernel就是Linux内核,是Linux中最核心的部分,包括进程调度、定时器等,而和平台相关的一部分代码放在arch/*/kernel目录下。
lib :lib是库的意思,lib目录下存放的都是一些公用的有用的库函数,注意这里的库函数和C语言的库函数不一样的,因为在内核编程中是不能用C语言标准库函数的,所以需要使用lib中的库函数,除此之外与处理器结构相关的库函数代码被放在 arch/*/lib/
目录下。
mm : 目录包含了所有独立于 cpu 体系结构的内存管理代码,如页式存储管理内存的分配和释放等,而与具体硬件体系结构相关的内存管理代码位于 arch/*/mm
目录下,例如 arch/arm/mm/fault.c
。
net : 网络协议栈相关代码,net目录下实现各种常见的网络协议。
scripts :这个目录下全部是脚本文件,这些脚本文件不是linux内核工作时使用的,而是用了配置编译linux内核的。
security :内核安全模型相关的代码,例如最有名的SELINUX。
sound : ALSA、 OSS音频设备的驱动核心代码和常用设备驱动。
usr : 实现用于打包和压缩的cpio等。
此处仅列出一些常见的目录。
5.4. 编译kernel¶
编译Kernel有两种方法,一种是编译较为通用的zImage,常用于构建成镜像固件,另一种则是编译成deb安装包,但buildroot系统没有包管理工具,无法使用,以下介绍直接编译内核的方法
5.4.1. 编译内核zImage¶
可以手动执行命令或者使用野火编写的make_lite.sh脚本进行编译,推荐使用make_lite.sh脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #方法一:手动编译
#清除之前编译输出
make mrproper
#编译内核
make ARCH=arm npi_v7_lite_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
#方法二:使用编译便捷脚本 (推荐)
#查看脚本内容
cat ./make_lite.sh
#清除之前编译输出
./make_lite.sh cleanall
#编译内核并打包驱动模块
./make_lite.sh all
#编译成功后将打印以下信息。
.......
LD [M] sound/soc/fsl/snd-soc-fsl-esai.ko
LD [M] sound/soc/fsl/snd-soc-fsl-micfil.ko
LD [M] sound/soc/fsl/snd-soc-fsl-rpmsg-i2s.ko
LD [M] sound/soc/fsl/snd-soc-fsl-sai.ko
LD [M] sound/soc/fsl/snd-soc-fsl-ssi.ko
LD [M] sound/soc/fsl/snd-soc-imx-audmux.ko
LD [M] sound/soc/fsl/snd-soc-imx-wm8960.ko
LD [M] sound/soc/fsl/snd-soc-fsl-utils.ko
LD [M] sound/soc/generic/snd-soc-simple-card-utils.ko
LD [M] sound/soc/generic/snd-soc-simple-card.ko
LD [M] sound/soc/generic/snd-soc-simple-scu-card.ko
LD [M] sound/soc/snd-soc-core.ko
LD [M] sound/usb/snd-usbmidi-lib.ko
LD [M] sound/usb/snd-usb-audio.ko
AS arch/arm/boot/compressed/bswapsdi2.o
AS arch/arm/boot/compressed/piggy.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
/* 省略后面编译的.ko信息 */
|
编译得到的zImage内核在 arch/arm/boot
目录下,设备树在 arch/arm/boot/dts
目录下,设备树分为:
emmc版本:imx6ull-mmc-npi-lite.dtb
nand版本:imx6ull-mmc-npi-lite.dtb
使用脚本编译打包得到的驱动模块位于内核源码根目录的 modules/
,压缩包名称为:
modules.tar.bz2
5.4.2. zImage生成简述¶
通过arm-linux-gnueabihf-ld命令将 vmlinux.lds head.o piggy.o misc.o decompress.o string.o hyp-stub.o lib1funcs.o ashldi3.o bswapsdi2.o 链接成vmlinux。
再通过arm-linux-gnueabihf-objcopy命令将vmlinux 以bin格式输出到zImage,期间删去了comment等信息。
5.5. 修改kernel配置选项¶
Linux内核的配置系统由三个部分组成,分别是:
Makefile:分布在 Linux内核源代码根目录及各层目录中,定义 Linux 内核的编译规则;
配置文件:给用户提供配置选择的功能,如Kconfig文件定义了配置项, 使用
make_lite.sh
脚本编译时,使用arch/arm/configs/npi_v7_lite_defconfig
文件对配置项进行赋值;配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释) 和配置用户界面(linux提供基于字符界面、基于Ncurses 图形界面以及 基于 Xwindows 图形界面的用户配置界面,各自对应于make config、make menuconfig 和 make xconfig)。
读者如果想看我们提供的配置文件npi_v7_lite_defconfig中修改了什么地方,可以通过
make menuconfig KCONFIG_CONFIG=arch/arm/configs/npi_v7_lite_defconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
命令来查看我们的配置,make menuconfig是一个基于文本选择的配置界面,
推荐在字符终端下使用,
而这个配置文件为 npi_v7_lite_defconfig
此时就可以看到在npi_v7_lite_defconfig的配置选择,
可以通过键盘的”上”、”下”、”左”、”右”、”回车”、”空格”、”?”、”ESC”等按键进行选择配置,具体见:
1 2 3 4 5 | #使用图形界面配置需要额外安装 libncurses-dev
sudo apt install libncurses-dev
#执行命令
make menuconfig KCONFIG_CONFIG=arch/arm/configs/npi_v7_lite_defconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
|

比如我们选择配置我们开发板的ds18b20驱动: ds18b20
,
如果读者找不到这个配置选项在哪里,可以使用 make menuconfig
中的搜索功能,
在英文输入法状态下按下”/”则可以进行搜索,输入”ds18b20”找到改配置选项的位置,
当输入错误时,可使用 Ctrl+退格键 删除输入。
具体见:


从图中可以很明显看出 ds18b20
配置选项位于 -> Device Drivers
选项下的 -> Embedfire Modules
下的 -> Embedfire Modules (EBF_MODULE [=y])
的选项中,
其实也可以按下 "1"
直接可以定位到对应的选项,
然后选中以下内容即可,具体见图:

可使用 y、n、m
键更改ds18b20驱动的配置时,
其中y表示编译进内核中,m表示编译成模块,n表示不编译。
也可使用空格选择ds18b20驱动的配置选项。
想要配置其他的驱动也是如此。
修改完成后,选择右下角Save进行保存, 注意不要保存到原路径,而是保存到.config ,然后使用以下命令来保存defconfig文件并覆盖原来的配置文件。

1 2 3 4 5 | # 保存defconfig文件
make savedefconfig ARCH=arm
# 覆盖原来的配置文件
cp -f defconfig arch/arm/configs/npi_v7_lite_defconfig
|
这样保存的原因是配置文件默认是精简版本的,编译使用时会和默认的配置文件进行比较从而得到完整的配置,如果直接保存则是完整版本的,会比精简版多几千行配置,不利于观察、修改。