10. Linux内核的编译¶
这里的Linux内核指的是Kernel,或者称内核也是指的Kernel
10.1. 为什么要自己编译Kernel¶
虽然我们提供的内核已经支持绝大多数功能了,但是对于一些需要定制功能的客户来说不一定有他们需要的功能配置,所以本章将讲解如何配置与编译内核
这里提供一个简单的检测工具查询该内核配置的情况:
1 2 3 | sudo wget https://raw.githubusercontent.com/moby/moby/master/contrib/check-config.sh
sudo chmod 755 ./check-config.sh
./check-config.sh
|
可以看到配置了的内容
没有启动的到kernel里进行配置启动即可
10.2. 下载安装编译镜像系统¶
使用平台:Ubuntu 18.04.5 LTS 版本
可以使用我们提供的虚拟机镜像 https://doc.embedfire.com/products/link/zh/latest/linux/ebf_i.mx6ull.html#id4
也可以自己下载ubuntu 18.04.5 LTS官方镜像搭建
https://mirrors.aliyun.com/ubuntu-releases/bionic/ubuntu-18.04.5-desktop-amd64.iso
10.3. 安装编译工具和依赖¶
编译内核之前需要安装必要的环境工具。
1 sudo apt install make gcc-arm-linux-gnueabihf gcc bison flex libssl-dev dpkg-dev lzop vim
10.4. 获取kernel¶
10.4.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 #查看uboot分支 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 git clone -b ebf_4.19.35_imx6ul https://github.com/Embedfire/ebf_linux_kernel.git
10.5. kernel工程结构分析¶
学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。
ebf_6ull_linux
内核源码目录如下
arch COPYING drivers init kernel make_deb.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 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等。
此处仅列出一些常见的目录。
10.6. 内核配置选项¶
Linux内核的配置系统由三个部分组成,分别是:
Makefile:分布在 Linux内核源代码根目录及各层目录中,定义 Linux 内核的编译规则;
配置文件:给用户提供配置选择的功能,如Kconfig文件定义了配置项, 使用make_deb.sh脚本编译时,使用
arch/arm/configs/npi_v7_defconfig
文件对配置项进行赋值;配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释) 和配置用户界面(linux提供基于字符界面、基于Ncurses 图形界面以及 基于 Xwindows 图形界面的用户配置界面,各自对应于make config、make menuconfig 和 make xconfig)。
读者如果想看我们提供的配置文件npi_v7_defconfig中修改了什么地方,可以通过
make menuconfig KCONFIG_CONFIG=arch/arm/configs/npi_v7_defconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
命令来查看我们的配置,make menuconfig是一个基于文本选择的配置界面,
推荐在字符终端下使用,
而这个配置文件为 npi_v7_defconfig
此时就可以看到在npi_v7_defconfig的配置选择,
可以通过键盘的”上”、”下”、”左”、”右”、”回车”、”空格”、”?”、”ESC”等按键进行选择配置,具体见:
1 2 3 4 5 | #使用图形界面配置需要额外安装 libncurses-dev
sudo apt install libncurses-dev
#执行命令
make menuconfig KCONFIG_CONFIG=arch/arm/configs/npi_v7_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驱动的配置选项。
想要配置其他的驱动也是如此。
10.7. kernel编译¶
编译Kernel有两种方法,一种是编译较为通用的zImage,常用于构建成镜像固件。另一种则是编译成deb安装包,将其下载到板子上安装即可更新Kernel。
10.7.1. 编译内核zImage¶
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 | #清除之前编译环境
make mrproper
#编译内核
make ARCH=arm npi_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
#编译成功后将打印以下信息。
.......
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
|
编译得到的zImage内核在 arch/arm/boot 目录下,设备树在 arch/arm/boot/dts 目录下 设备树分为emmc版本 imx6ull-mmc-npi.dtb 以及 nand版本 imx6ull-nand-npi.dtb 。
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等信息
10.7.2. 编译内核deb安装包(推荐)¶
想要编译内核的deb安装包,以sudo权限运行野火提供的 make_deb.sh 脚本即可, 我们也推荐使用这样的编译方式编译内核,构建出来的deb安装包可直接使用sudo dpkg -i xxx.deb命令 安装在鲁班猫系统上,然后重启使用 cat /proc/version 查看内核是否更新。
ebf_linux_kernel/make_deb.sh内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | deb_distro=bionic
DISTRO=stable
build_opts="-j 16"
build_opts="${build_opts} O=build_image/build"
build_opts="${build_opts} ARCH=arm"
build_opts="${build_opts} KBUILD_DEBARCH=${DEBARCH}"
build_opts="${build_opts} LOCALVERSION=-imx6"
build_opts="${build_opts} KDEB_CHANGELOG_DIST=${deb_distro}"
build_opts="${build_opts} KDEB_PKGVERSION=1.$(date +%g%m)${DISTRO}"
build_opts="${build_opts} CROSS_COMPILE=arm-linux-gnueabihf-"
build_opts="${build_opts} KDEB_SOURCENAME=linux-upstream"
make ${build_opts} npi_v7_defconfig
make ${build_opts}
make ${build_opts} bindeb-pkg
|
说明如下:
deb_distro=bionic: 设置了变量deb_distro,用于指定生成的 Debian 包的目标发行版,这里设置为bionic。
DISTRO=stable: 设置了变量DISTRO,用于指定 Linux 内核的目标发行版,这里设置为stable。
build_opts=”-j 16”: 设置了编译选项,-j 16表示并行编译时使用16个线程。
build_opts=”${build_opts} O=build_image/build”: 添加了编译选项,指定编译输出目录为build_image/build。
build_opts=”${build_opts} ARCH=arm”: 添加了编译选项,指定目标架构为ARM。
build_opts=”${build_opts} KBUILD_DEBARCH=${DEBARCH}”: 添加了编译选项,指定deb包的架构为${DEBARCH}。
build_opts=”${build_opts} LOCALVERSION=-imx6”: 添加了编译选项,设置了内核的本地版本标识为-imx6。
build_opts=”${build_opts} KDEB_CHANGELOG_DIST=${deb_distro}”: 添加了编译选项,指定生成的deb包 changelog 的目标发行版为${deb_distro}。
build_opts=”${build_opts} KDEB_PKGVERSION=1.$(date +%g%m)${DISTRO}”: 添加了编译选项,指定生成的deb包的版本号为当前年份后两位加上当前月份,后跟${DISTRO}(在这种情况下是stable)。
build_opts=”${build_opts} CROSS_COMPILE=arm-linux-gnueabihf-“: 添加了编译选项,指定交叉编译工具链为arm-linux-gnueabihf-。
build_opts=”${build_opts} KDEB_SOURCENAME=linux-upstream”: 添加了编译选项,指定生成的deb包的源名称为linux-upstream。
make ${build_opts} npi_v7_defconfig: 使用 make 命令加载了名为 npi_v7_defconfig 的预配置文件。
make ${build_opts}: 使用 make 命令编译 Linux 内核。
make ${build_opts} bindeb-pkg: 使用 make 命令生成内核deb包。
可能需要修改的参数是:
build_opts=”-j 16”:默认使用16线程,需要根据实际电脑性能修改。
build_opts=”${build_opts} KDEB_PKGVERSION=1.$(date +%g%m)${DISTRO}”:设置了内核deb包的版本号,默认是数字+当前年份后两位加上当前月份后跟stable,如果是大版本更新可修改前面的数字,如果是当月频繁更新,可以将$(date +%g%m)修改为$(date +%g%m%d),添加具体日期,如果是测试版,可修改stable为beta等。
执行以下命令编译内核deb包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ./make_deb.sh
#编译成功打印消息如下
INSTALL debian/headertmp/usr/include/linux/sunrpc/ (1 file)
INSTALL debian/headertmp/usr/include/linux/tc_act/ (15 files)
INSTALL debian/headertmp/usr/include/linux/tc_ematch/ (5 files)
INSTALL debian/headertmp/usr/include/linux/usb/ (13 files)
INSTALL debian/headertmp/usr/include/linux/wimax/ (1 file)
INSTALL debian/headertmp/usr/include/asm/ (38 files)
dpkg-deb: 正在 '../linux-headers-4.19.35-imx6_1stable_armhf.deb' 中构建软件包 'linux-headers-4.19.35-imx6'。
dpkg-deb: 正在 '../linux-libc-dev_1stable_armhf.deb' 中构建软件包 'linux-libc-dev'。
dpkg-deb: 正在 '../linux-image-4.19.35-imx6_1stable_armhf.deb' 中构建软件包 'linux-image-4.19.35-imx6'。
dpkg-genbuildinfo --build=binary
dpkg-genchanges --build=binary >../linux-upstream_1stable_armhf.changes
dpkg-genchanges: 警告: 控制文件声明了包 linux-image-4.19.35-imx6-dbg 但它却不在文件列表中
dpkg-genchanges: info: binary-only upload (no source code included)
dpkg-source --after-build build
dpkg-buildpackage: info: binary-only upload (no source included)
make[1]: 离开目录“/home/qinghui/embedfire/ebf-image-builder/ebf_linux_kernel/build_image/build”
|
构成生成的deb包在内核源码/build_image目录下。
10.8. 镜像组成¶
NXP官方镜像(后简称官方镜像)与鲁班猫镜像(后简称lubancat),我们将kernel,dtb,dtbo打包进了rootfs内,这样更为通用且更换其中如何一部分只需将文件替换即可。官方镜像则是烧写指定地址对于不同芯片存储的地址是不同的不利于移植于其他芯片。
10.9. 内核的烧写¶
详细可参考 Linux内核的烧录