6. Linux内核的编译与修改

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

6.1. 为什么要自己编译Kernel

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

6.2. 获取kernel

6.2.1. 下载源代码

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

我们的kernel是根据算能官方提供的kernel定制的,算能的kernel是根据kernel官方LTS(长期支持)版本进行芯片适配的。 我们作为嵌入式开发者,一般只需要使用芯片厂商适配好的kernel进行开发即可, 对于从kernel官方下载新版本来适配这是芯片厂商做的事情。

目前LubanCat-sg200x-Linux-SDK使用kernel 5.10.4版本。

Kernel官方内核源码:https://www.kernel.org/

算能内核源码: https://github.com/sophgo/linux_5.10

使用我们提供的:

SDK中包含了内核源码,不做单独仓库存放,需要获取SDK从而获取内核源码。

可以参考: SDK源码获取 章节。

内核源码位于:sophon-image-build/linux_5.10

6.3. 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平台占一个相应的目录。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等。

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

6.4. 内核配置选项

6.4.1. 配置内核选项,使用LubanCat板卡配置

使用的配置文件都保存在板级目录的中,路径为:sophon-image-build/build/boards/sg200x/sg200x_xxx/linux/sg200x_xxx_defconfig

以下以sg2000_lubancat_riscv_ubuntu_sd_defconfig为例进行说明

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

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

  • 配置文件:给用户提供配置选择的功能,如Kconfig文件定义了配置项, 在编译时,使用内核配置文件对配置项进行赋值;

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

我们可以通过 make menuconfig KCONFIG_CONFIG=xxx配置文件路径 命令来查看我们的配置,make menuconfig是一个基于文本选择的配置界面, 推荐在字符终端下使用,可以通过键盘的”上”、”下”、”左”、”右”、”回车”、”空格”、”?”、”ESC”等按键进行选择配置,具体见:

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

#进入内核源码顶层目录
cd linux_5.10

#执行命令,需要指定实际配置文件路径
make menuconfig KCONFIG_CONFIG=../build/boards/sg200x/sg2000_lubancat_riscv_ubuntu_sd/linux/sg2000_lubancat_riscv_ubuntu_sd_defconfig

或者通过SDK通过的编译命令打开,在sdk顶层目录执行:

1
2
3
4
5
6
7
8
#初始化编译环境,输入当前用户的密码
source build/cvisetup.sh

#加载对应的板级配置文件
defconfig sg2000_lubancat_riscv_ubuntu_sd

#打开menuconfig图形化配置界面
menuconfig_kernel
building_kernel002

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

building_kernel003
building_kernel004

从图中可以很明显看出 CH341 配置选项位于 -> Device Drivers``下的 ``-> USB support (USB_SUPPORT [=n]), 因此要开启USB_SERIAL_CH341,需要先开启USB_SUPPORT 其实也可以按下 "1" 直接可以定位到对应的选项, 然后选中以下内容即可,具体见图:

先开启USB_SUPPORT:

building_kernel005

然后开启USB_SERIAL_CH341:

building_kernel005

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

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

修改完成后,选择右下角Save进行保存。

如果是指定KCONFIG_CONFIG参数打开的界面默认保存到原路径即可,默认覆盖原来板级目录的配置文件。

building_kernel006

如果是通过menuconfig_kernel打开的界面,保存到的.config具体是保存到了linux_5.10/build/sg2000_lubancat_riscv_ubuntu_sd/.config,需要将.config覆盖到板级的配置文件中,这样才能下次编译时清除编译输出 后再加载也能加载修改后的配置文件。

building_kernel006

6.5. kernel编译

在LubanCat-SDK中,自动编译脚本基本上都存放在cvisetup.sh中,这是SDK的主要功能入口

  • emmc分区使用cmdline partition方式管理分区,内核是以二进制形式打包的,各个部分存放的地址经过了严格定义,读取文件时通过二进制文件头去判断文件的种类。

  • sd分区是通过gpt分区表进行管理的,内核和dtb都打包到了boot分区的boot.sd中,启动时加载boot分区的boot.sd进行启动。

具体可参照: U-boot启动内核过程

在cvisetup.sh中,U-boot的构建函数是function build_kernel() 具体内容如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function build_kernel()
{(
print_notice "Run ${FUNCNAME[0]}() function"
_build_kernel_env
cd "$BUILD_PATH" || return
make kernel || return "$?"

# generate boot.itb image.
if [[ ${1} != noitb ]]; then
    pack_boot || return "$?"
fi
)}

function _build_kernel_env()
{
export KERNEL_OUTPUT_FOLDER RAMDISK_OUTPUT_FOLDER SYSTEM_OUT_DIR
}

build_kernel包括:

  • _build_kernel_env设置了构建 kenrel 所需的环境变量。

  • cd “$BUILD_PATH”进入sophon-image-build/build目录

  • make kernel编译kernel。

可查看sophon-image-build/build/Makefile查看make kernel是如何进行的,此处不展开讲解。

借助SDK可以很方便地进行编译,进入SDK顶层目录,以sg2000_lubancat_riscv_ubuntu_sd为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#初始化编译环境,输入当前用户的密码
source build/cvisetup.sh

#加载对应的板级配置文件
defconfig sg2000_lubancat_riscv_ubuntu_sd

#清除kernel编译输出
clean_kernel

#构建kernel
build_kernel

构建生成的内核和设备树打包进了sophon-image-build/install/soc_sg2000_lubancat_riscv_ubuntu_sd/boot.sd,如果是emmc则是boot.emmc。