7. Linux内核的编译与修改

这里的Linux内核指的是Kernel,或者称内核也是指的Kernel

7.1. 为什么要自己编译Kernel

虽然我们提供的内核已经支持绝大多数功能了,但是对于一些需要定制功能的客户来说不一定有他们需要的功能配置,所以本章将讲解如何配置与编译内核。

7.2. 下载安装编译镜像系统

使用平台:Ubuntu 18.04.5 LTS 版本和Ubuntu 20.04 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

7.3. 安装编译工具和依赖

编译内核之前需要安装必要的环境工具。

1
sudo apt install make gcc-aarch64-linux-gnu gcc bison flex libssl-dev dpkg-dev lzop vim

7.4. 获取kernel

7.4.1. 下载源代码

有三个方法获取Kernel源码,一个是Kernel官方内核源码,一个是nxp官方的kernel源码, 一个是经过我们修改适配我们板卡的kernel源码,我们这里使用我们提供的源码为例, 对官方源码感兴趣的小伙伴也可以下载来学习配置。

我们的kernel是根据nxp官方提供的kernel定制的,nxp的kernel是根据kernel官方LTS(长期支持)版本进行芯片适配的。 我们作为嵌入式开发者,一般只需要使用芯片厂商适配好的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_5.4.47_imx8mmini分支
git checkout ebf_5.4.47_imx8mmini

#打印消息如下
Checking out files: 100% (63998/63998), done.
Branch 'ebf_5.4.47_imx8mmini' set up to track remote branch 'ebf_5.4.47_imx8mmini' from 'origin'.
Switched to a new branch 'ebf_5.4.47_imx8mmini'


#重新查看当前分支
qinghui@ebf-dev:~/embedfire/ebf_linux_uboot$ git branch
ebf_4.1.15_imx
* ebf_5.4.47_imx8mmini

也可以在下载时指定分支,如下所示

1
git clone -b ebf_5.4.47_imx8mmini https://github.com/Embedfire/ebf_linux_kernel.git

SDK里面也包含了内核源码,也可以使用SDK里面的。

7.5. kernel工程结构分析

学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。

内核源码目录如下:

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等。

此处仅列出一些常见的目录。

7.6. 内核配置选项

Linux内核的配置系统由三个部分组成,分别是:

  • Makefile:分布在 Linux内核源代码根目录及各层目录中,定义 Linux 内核的编译规则;

  • 配置文件:给用户提供配置选择的功能,如Kconfig文件定义了配置项, 使用make_deb.sh脚本编译时,使用 arch/arm64/configs/imx_v8_defconfig 文件对配置项进行赋值;

  • 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释) 和配置用户界面(linux提供基于字符界面、基于Ncurses 图形界面以及 基于 Xwindows 图形界面的用户配置界面,各自对应于make config、make menuconfig 和 make xconfig)。

读者如果想看我们提供的配置文件imx_v8_defconfig中修改了什么地方,可以通过 make menuconfig KCONFIG_CONFIG=arch/arm64/configs/imx_v8_defconfig ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- 命令来查看我们的配置,make menuconfig是一个基于文本选择的配置界面, 推荐在字符终端下使用, 而这个配置文件为 imx_v8_defconfig 此时就可以看到在imx_v8_defconfig的配置选择, 可以通过键盘的”上”、”下”、”左”、”右”、”回车”、”空格”、”?”、”ESC”等按键进行选择配置,具体见:

1
2
3
4
5
#使用图形界面配置需要额外安装 libncurses-dev
sudo apt install libncurses-dev

#执行命令
make menuconfig KCONFIG_CONFIG=arch/arm64/configs/imx_v8_defconfig ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
building_kernel002

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

building_kernel003
building_kernel004

从图中可以很明显看出 rx8010 配置选项位于 -> Device Drivers 选项下的 -> Real Time Clock 的选项中, 其实也可以按下 "1" 直接可以定位到对应的选项, 然后选中以下内容即可,具体见图:

building_kernel005

可使用 y、n、m 键更改rx8010驱动的配置时, 其中y表示编译进内核中,m表示编译成模块,n表示不编译。 也可使用空格选择rx8010驱动的配置选项。

想要配置其他的驱动也是如此。

修改完成后,右下角选择Save进行保存, 注意要保持到.config ,不能是原路径,然后退出界面,使用以下命令来保存defconfig文件并覆盖原来的配置文件

building_kernel008
1
2
3
4
5
# 保存defconfig文件
make savedefconfig ARCH=arm64

# 覆盖原来的配置文件
cp defconfig arch/arm64/configs/imx_v8_defconfig

7.7. kernel编译

编译Kernel有两种方法,一种是编译较为通用的zImage,常用于构建成镜像固件。另一种则是编译成deb安装包,将其下载到板子上安装即可更新Kernel。

7.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
29
30
31
32
33
#清除之前编译环境
make ARCH=arm64 mrproper

#编译内核
make ARCH=arm64 imx_v8_defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8

#编译成功后将打印以下信息。
.......
AR      drivers/built-in.a
GEN     .version
CHK     include/generated/compile.h
LD      vmlinux.o
MODPOST vmlinux.o
MODINFO modules.builtin.modinfo
LD      .tmp_vmlinux.kallsyms1
KSYM    .tmp_vmlinux.kallsyms1.o
LD      .tmp_vmlinux.kallsyms2
KSYM    .tmp_vmlinux.kallsyms2.o
LD      vmlinux
SORTEX  vmlinux
SYSMAP  System.map
Building modules, stage 2.
MODPOST 547 modules
OBJCOPY arch/arm64/boot/Image
GZIP    arch/arm64/boot/Image.gz
.......
LD [M]  sound/soc/sof/imx/snd-sof-imx8.ko
LD [M]  sound/soc/sof/imx/snd-sof-imx8m.ko
LD [M]  sound/soc/sof/snd-sof-of.ko
LD [M]  sound/soc/sof/snd-sof.ko
LD [M]  sound/usb/snd-usb-audio.ko
LD [M]  sound/usb/snd-usbmidi-lib.ko

编译得到的Image内核在 arch/arm64/boot/ 目录下,设备树在 arch/arm64/boot/dts 目录下

Image生成简述

通过aarch64-linux-gnu-ld命令将 vmlinux.lds head.o piggy.o misc.o decompress.o string.o hyp-stub.o lib1funcs.o ashldi3.o bswapsdi2.o 链接成vmlinux

再通过aarch64-linux-gnu-objcopy命令将vmlinux 以bin格式输出到Image,期间删去了comment等信息

7.7.2. 编译内核deb包(推荐)

想要编译内核的deb安装包,以sudo权限运行野火提供的 make_deb.sh 脚本即可, 我们也推荐使用这样的编译方式编译内核,构建出来的deb安装包可直接使用sudo dpkg -i xxx.deb命令 安装在鲁班猫系统上,然后重启使用 cat /proc/version 查看内核是否更新。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
   ./make_deb.sh

   #编译成功打印消息如下

   HDRINST usr/include/asm/ipcbuf.h
   HDRINST usr/include/asm/stat.h
   HDRINST usr/include/asm/msgbuf.h
   HDRINST usr/include/asm/sockios.h
   HDRINST usr/include/asm/mman.h
   INSTALL ./debian/headertmp/usr/include
   dpkg-deb: building package 'linux-headers-5.4.47-imx8mmini' in '../linux-headers-5.4.47-imx8mmini_1.2406stable_arm64.deb'.
   dpkg-deb: building package 'linux-libc-dev' in '../linux-libc-dev_1.2406stable_arm64.deb'.
   dpkg-deb: building package 'linux-image-5.4.47-imx8mmini' in '../linux-image-5.4.47-imx8mmini_1.2406stable_arm64.deb'.
   dpkg-deb: building package 'linux-image-5.4.47-imx8mmini-dbg' in '../linux-image-5.4.47-imx8mmini-dbg_1.2406stable_arm64.deb'.
   dpkg-genbuildinfo --build=binary
   dpkg-genchanges --build=binary >../linux-upstream_1.2406stable_arm64.changes
   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]: Leaving directory '/home/guest/imx8/ebf-image-builder/ebf_linux_kernel/build_image/build'
   guest@dev107:~/imx8/ebf-image-builder/ebf_linux_kernel$

构成生成的deb包在内核源码/build_image目录下。

  • linux-headers-5.4.47-imx8mmini_1.2406stable_arm64.deb’ :包括创建外部模块所需的头文件。

  • linux-image-5.4.47-imx8mmini_1.2406stable_arm64.deb :一般包括内核镜像和内核模块,我们还添加了设备树及设备树插件。

  • linux-image-5.4.47-imx8mmini-dbg_1.2406stable_arm64.deb :包括GNU glibc之类与用户程序库相关的标头。

使用SDK也可以在SDK顶层目录执行:

1
2
3
4
5
#编译内核
make kernel

#编译内核deb包
make kernel-deb

7.8. 内核的烧写

1、如果从已烧录系统的板卡修改:

如果使用直接编译,将编译生成的Image,重命名为vmlinuz-5.4.47-imx8mm,然后替换板卡/boot/kernel/vmlinuz-5.4.47-imx8mm内核文件,然后执行以下命令添加软连接,以匹配原来内核版本的驱动模块。

1
sudo ln -s /lib/modules/5.4.47-imx8mm /lib/modules/5.4.47+

如果使用内核deb包方式,将linux-image-5.4.47-imx8mm_1stable_armhf.deb传到板卡,执行以下命令进行安装即可,不需要软连接:

1
sudo dpkh -i linux*.deb

替换或者安装完成后,重启板卡即可。

2、如果从镜像包进行修改:

如果是SD镜像包,请编译完整镜像然后烧录到SD卡进行启动,或者参考 修改img镜像内部的文件 章节,挂载boot分区,然后替换。

如果是usb镜像包,将usb烧录包目录下的boot.tar解压,然后重命名并替换kernel目录下的内核,同理需要软连接软连接。注意打包boot.tar时要打包回原来的目录结构。