编译STM32MP1开发板内核:使用Yocto

yocto编译内核

yocto默认规则的BB文件命名规范是由PN PV 和PR三个变量组成,其中PN是recipes名,PV代表版本号,PR代表子版本号。
比如meta-qt中随便找一个bb文件:

可以看到bb文件名为python3-gsocketpool_0.1.6.bb
其中python3-gsocketpool同recipe名,0.1.6是版本号名,子版本号不写默认是r1
在yocto中bb文件的名称是可以通过bitbake命令进行编译的,比如:

bitbake python3-gsocketpool

但是在yocto中编译内核的时候通常使用如下指令:

 bitbake  virtual/kernel

在yocto中内核bb文件如下:

正常使用bitbake linux-fsmp1也是可以的。下面分析一下virtual/kernel的由来

recipes可以覆盖或者添加到可用于指定该包的符号名字的PROVIDES变量。然后依赖于它的包可以引用符号名字。如果多个包或者同一个包的多个版本提供相同的功能性,那么这是有意义的:

PROVIDES =+ virtual/kernel

这个用在recipes中针对kernel的声明将增加符号名字virtual/kernel到名字列表。符号名字实际上做什么不重要,但是为了避免符号名字和实际其他菜谱名字的无意冲突,开发者已经采用惯例来使用前缀virtual/用于符号名字 。

kernel的版本可能随着发展需要会进行升级,这时源码中就会存在多个相同符号名字的bb文件。这时候需要指定使用的具体版本号。
在yocto中使用PREFERRED_PROVIDER变量来解决这个问题
比如:

PREFERRED_PROVIDER_virtual/kernel = "linunx-fsmp1_5.4"

这样就会指定系统使用fsmp1_5.4的内核来构建系统,通常这种配置都会在发行版的conf文件中有涉及。

yocto内核编译分析

内核编译及产物

本小节想详细分析一下yocto中内核的编译过程。
编译内核:

bitbake virtual/kernel

配置内核以后再编译内核:

bitbake virtual/kernel -c menuconfig
bitbake virtual/kernel -C compile

build-openstlinuxeglfs-fsmp1a/tmp-glibc/work/fsmp1a-ostl-linux-gnueabi/linux-fsmp1/5.4.31-r0路径下看到编译输出:
最终kernel的打包产物在package下面:

内核bb文件详细分析

下面fsmp1开发板的linux-fsmp1_5.4bb文件,删减部分无关信息:

SUMMARY = "Linux STM32MP Kernel"
SECTION = "kernel"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=bbea815ee2795b2f4230826c0c6b8814"

include linux-fsmp1.inc

LINUX_VERSION = "5.4"
LINUX_SUBVERSION = "31"
SRC_URI = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-${LINUX_VERSION}.${LINUX_SUBVERSION}.tar.xz;name=kernel"
SRC_URI[kernel.sha256sum] = "a11083f8f809887f6a0f8d4467532385b99418f17998fe6e837807491c276eeb"

SRC_URI += " \
    file://${LINUX_VERSION}/${LINUX_VERSION}.${LINUX_SUBVERSION}/0001-ARM-stm32mp1-r1-MACHINE.patch \
    file://${LINUX_VERSION}/${LINUX_VERSION}.${LINUX_SUBVERSION}/0002-ARM-stm32mp1-r1-CPUFREQ.patch \
    ......
"

PV = "${LINUX_VERSION}.${LINUX_SUBVERSION}"
S = "${WORKDIR}/linux-${LINUX_VERSION}.${LINUX_SUBVERSION}"

# ---------------------------------
# Configure devupstream class usage
# ---------------------------------
BBCLASSEXTEND = "devupstream:target"

# SRC_URI_class-devupstream = "git://github.com/STMicroelectronics/linux.git;protocol=https;branch=v${LINUX_VERSION}-stm32mp;name=linux"
# SRCREV_class-devupstream = "b8663f5fdb5cfd6f243b72c9fac82c24b2594294"
SRCREV_FORMAT_class-devupstream = "linux"
PV_class-devupstream = "${LINUX_VERSION}+github+${SRCPV}"

# ---------------------------------
# Configure default preference to manage dynamic selection between tarball and github
# ---------------------------------
STM32MP_SOURCE_SELECTION ?= "tarball"

DEFAULT_PREFERENCE = "${@bb.utils.contains('STM32MP_SOURCE_SELECTION', 'github', '-1', '1', d)}"

# ---------------------------------
# Configure archiver use
# ---------------------------------
include ${@oe.utils.ifelse(d.getVar('ST_ARCHIVER_ENABLE') == '1', 'linux-fsmp1-archiver.inc','')}

# -------------------------------------------------------------
# Defconfig
#
KERNEL_DEFCONFIG        = "stm32_fsmp1a_defconfig"
FSMP1A_USB_SERIAL		= "fsmp1a"

KERNEL_CONFIG_FRAGMENTS = "${@bb.utils.contains('KERNEL_DEFCONFIG',	'stm32_fsmp1a_defconfig', '${S}/arch/arm/configs/fragment-01-multiv7_cleanup.config', '', d)}"
KERNEL_CONFIG_FRAGMENTS += "${@bb.utils.contains('KERNEL_DEFCONFIG', 'stm32_fsmp1a_defconfig', '${S}/arch/arm/configs/fragment-02-multiv7_addons.config', '', d)}"
.......

KERNEL_CONFIG_FRAGMENTS += "${@bb.utils.contains('DISTRO_FEATURES', 'systemd', '${WORKDIR}/fragments/5.4/fragment-03-systemd.config', '', d)} "
KERNEL_CONFIG_FRAGMENTS += "${@bb.utils.contains('COMBINED_FEATURES', 'optee', '${WORKDIR}/fragments/5.4/fragment-04-optee.config', '', d)}"
KERNEL_CONFIG_FRAGMENTS += "${WORKDIR}/fragments/5.4/fragment-05-modules.config"
KERNEL_CONFIG_FRAGMENTS += "${@oe.utils.ifelse(d.getVar('KERNEL_SIGN_ENABLE') == '1', '${WORKDIR}/fragments/5.4/fragment-06-signature.config','')} "

SRC_URI += "file://${LINUX_VERSION}/fragment-03-systemd.config;subdir=fragments"
SRC_URI += "file://${LINUX_VERSION}/fragment-04-optee.config;subdir=fragments"
......

# -------------------------------------------------------------
# Kernel Args
#
KERNEL_EXTRA_ARGS += "LOADADDR=${ST_KERNEL_LOADADDR}"

重要变量介绍:
LIC_FILES_CHKSUM:许可文件的名字和MD5校验和。
LINUX_VERSION: 正在构建的Linux内核的版本号
SRC_URI: 指定到Linux内核tar包的路径。另外,变量必须指定一个包含内核配置的defconfig文件。
SRC_URI[md5sum],SRC_URI[sha256sum]: 用于远程下载的校验和
S:内核源被解压到的目录。
COMPATIBLE_MACHINE:被内核支持的机器的名字列表。
KERNEL_FEATURES:包含内核特性配置的文件列表。
KMACHINE:Linux内核已知的硬件机器的名字。每个内核菜谱都必须设置这个变量。

从linux-fsmp1_5.4bb文件中可以看到主要干了几件事:

  1. 指定内核的版本号、校验和、下载路径
  2. 添加内核的patch 和 config片段到SRC_URI变量
  3. 指定了一个内核启动参数,即内核的链接地址为${ST_KERNEL_LOADADDR}
  4. 包含 了两个引用文件linux-fsmp1.inc 和linux-fsmp1-archiver.inc

可以看到实际的定制细节没有在linux-fsmp1_5.4bb中体现,linux-fsmp1-archiver.inc里面并没有干什么事,重头戏都在linux-fsmp1.inc里面。下面重点关注这个文件内容:

COMPATIBLE_MACHINE = "(stm32mpcommon)"
inherit kernel
DEPENDS += "openssl-native util-linux-native libyaml-native"
B = "${WORKDIR}/build"
# Configure build dir for externalsrc class usage through devtool
EXTERNALSRC_BUILD_pn-${PN} = "${WORKDIR}/build"

# To share config fragments between layers
FILESEXTRAPATHS_prepend := "${THISDIR}:"

# -------------------------------------------------------------
# Do not deploy kernel module with specfic tarball
MODULE_TARBALL_DEPLOY = "0"
do_deploy[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}/kernel"

#---------------------------------------------------------------
# Module kernel signature
KERNEL_SIGN_ENABLE ?= "0"
EXTRA_OEMAKE += "${@oe.utils.ifelse(d.getVar('KERNEL_SIGN_ENABLE') == '1', 'INSTALL_MOD_STRIP=1','')}"

#
# Force task order for link creation
#
python () {
    d.appendVarFlag("do_unpack", "postfuncs", " do_symlink_kernsrc")
}
do_symlink_kernsrc[noexec] = "1"


# ---------------------------------------------------------------------
# Defconfig
#
#If the defconfig is contained on the kernel tree (arch/${ARCH}/configs)
#you must use the following line
do_configure_prepend() {
    unset CFLAGS CPPFLAGS CXXFLAGS LDFLAGS MACHINE
    if [ ! -z ${KERNEL_DEFCONFIG} ];
    then
        bbnote "Kernel customized: configuration of linux STM by using DEFCONFIG: ${KERNEL_DEFCONFIG}"
        oe_runmake ${PARALLEL_MAKE} -C ${S} O=${B} CC="${KERNEL_CC}" LD="${KERNEL_LD}" ${KERNEL_DEFCONFIG}
    else
        if [ ! -z ${KERNEL_EXTERNAL_DEFCONFIG} ];
        then
            bbnote "Kernel customized: configuration of linux STM by using external DEFCONFIG"
            install -m 0644 ${WORKDIR}/${KERNEL_EXTERNAL_DEFCONFIG} ${B}/.config
            oe_runmake -C ${S} O=${B} CC="${KERNEL_CC}" LD="${KERNEL_LD}" oldconfig
        else
            bbwarn "You must specify KERNEL_DEFCONFIG or KERNEL_EXTERNAL_DEFCONFIG"
            die "NO DEFCONFIG SPECIFIED"
        fi
    fi

    if [ ! -z "${KERNEL_CONFIG_FRAGMENTS}" ]
    then
        for f in ${KERNEL_CONFIG_FRAGMENTS}
        do
            bbnote "file = $f"
            # Check if the config fragment was copied into the WORKDIR from
            # the OE meta data
            if [ ! -e "$f" ]
            then
                echo "Could not find kernel config fragment $f"
                exit 1
            fi
        done

        bbnote "${S}/scripts/kconfig/merge_config.sh -m -r -O ${B} ${B}/.config ${KERNEL_CONFIG_FRAGMENTS} 1>&2"
        # Now that all the fragments are located merge them.
        (${S}/scripts/kconfig/merge_config.sh -m -r -O ${B} ${B}/.config ${KERNEL_CONFIG_FRAGMENTS} 1>&2 )
    fi

    yes '' | oe_runmake -C ${S} O=${B} CC="${KERNEL_CC}" LD="${KERNEL_LD}" oldconfig
    #oe_runmake -C ${S} O=${B} savedefconfig && cp ${B}/defconfig ${WORKDIR}/defconfig.saved
}

# ---------------------------------------------------------------------
do_install_append() {
    # Install KERNEL_IMAGETYPE for kernel-imagebootfs package
    install -m 0644 ${KERNEL_OUTPUT_DIR}/${KERNEL_IMAGETYPE} ${D}/${KERNEL_IMAGEDEST}

    if ${@bb.utils.contains('MACHINE_FEATURES','gpu','true','false',d)}; then
        # when ACCEPT_EULA are filled
        echo "blacklist etnaviv" > ${D}/${sysconfdir}/modprobe.d/blacklist.conf
    fi
}

# ---------------------------------------------------------------------
# Support checking the kernel load address parameter: expecting proper value for ST kernel.
#
python do_loadaddrcheck() {
    if not d.getVar('ST_KERNEL_LOADADDR'):
        bb.fatal('Missing ST_KERNEL_LOADADDR value for ST kernel build: please define it in your machine.')
}

PACKAGES =+ "${KERNEL_PACKAGE_NAME}-headers ${KERNEL_PACKAGE_NAME}-imagebootfs"
FILES_${KERNEL_PACKAGE_NAME}-headers = "${exec_prefix}/src/linux*"
FILES_${KERNEL_PACKAGE_NAME}-image  += "boot/ ${KERNEL_IMAGEDEST}"
FILES_${KERNEL_PACKAGE_NAME}-imagebootfs = "boot/*.dtb boot/${KERNEL_IMAGETYPE}"
FILES_${KERNEL_PACKAGE_NAME}-modules += "${sysconfdir}/modprobe.d"

重要内容解析:

COMPATIBLE_MACHINE:指定了内核支持的机器的名字是 stm32mpcommon
inherit kernel :引用了OE的kernel类,帮我们作绝大多数的编译内核工作
DEPENDS : 指定内核的依赖包
B:指定内核编译的目录
FILESEXTRAPATHS_prepend:将本菜谱的路径添加到构建系统的搜索路径
do_deploy[sstate-outputdirs]: 指定内核部署的构建输出目录,最终的路径在tmp-glibc/deploy/images/fsmp1a/kernel下
实际编译以后的构建输出如下:

EXTRA_OEMAKE += "${@oe.utils.ifelse(d.getVar('KERNEL_SIGN_ENABLE') == '1', 'INSTALL_MOD_STRIP=1','')}"  

指定编译内核的配置选项,INSTALL_MOD_STRIP表示是否去掉module的符号等调试选项,减小module的占用内存大小

d.appendVarFlag("do_unpack", "postfuncs", " do_symlink_kernsrc"):

yocto中可以在任务的主函数之前和之后运行函数。这是使用任务的“prefuncs”和“postfuncs”标志来列出要运行的函数完成的。上面的意思就是在do_unpack 解压内核以后运行symlink_kernsrc。

do_symlink_kernsrc[noexec] = "1" :

强制每次编译kernel都作一次symlink_kernsrc动作,noexec表示不关注共享缓存状态,强制执行

do_configure_prepend():

do_configure_prepend函数表示在作kernel的配置之前先运行这段函数,这段函数的大致流程如下:

  1. 如果指定了${KERNEL_DEFCONFIG}或者 ${KERNEL_EXTERNAL_DEFCONFIG},则使用指定的默认配置配置内核
  2. 如果指定了${KERNEL_CONFIG_FRAGMENTS}配置片段,则将所有的配置片段进行合并然后使用合并后的配置作为.config配置内核
    在linux-fsmp1_5.4bb中指定了 KERNEL_DEFCONFIG 和KERNEL_CONFIG_FRAGMENTS ,所以实际配置内核会使用与arch对应的multi_v7_defconfig配置加上linux-fsmp1_5.4bb中指定的所有配置片段合并后的配置文件作为内核的配置。
do_install_append():

第一部分:

install -m 0644 ${KERNEL_OUTPUT_DIR}/${KERNEL_IMAGETYPE} ${D}/${KERNEL_IMAGEDEST}

通过bitbake virtual/kernel -e 查看环境变量可以知道这几个变量的含义分别是:
KERNEL_OUTPUT_DIR:arch/arm/boot
KERNEL_IMAGETYPE: zImage vmlinux Image uImage
KERNEL_IMAGEDEST:boot

所以该函数的意思是在执行完install流程以后将arch/arm/boot下的 zImage vmlinux Image uImage 都安装到安装目录image/boot下面。
第二部分:
如果MACHINE_FEATURES指定了gpu选项则配置blacklist etnaviv 添加到模块自动加载目录

do_loadaddrcheck():该函数检查bb文件是否指定了内核的链接地址

PACKAGES =+ "${KERNEL_PACKAGE_NAME}-headers ${KERNEL_PACKAGE_NAME}-imagebootfs":

在打包过程中创建 -headers 和-imagebootfs两个包

FILES_${KERNEL_PACKAGE_NAME}-headers = "${exec_prefix}/src/linux*"
FILES_${KERNEL_PACKAGE_NAME}-image  += "boot/ ${KERNEL_IMAGEDEST}"
FILES_${KERNEL_PACKAGE_NAME}-imagebootfs = "boot/*.dtb boot/${KERNEL_IMAGETYPE}"
FILES_${KERNEL_PACKAGE_NAME}-modules += "${sysconfdir}/modprobe.d"

指定被放进特定包中的目录和文件的列表,下面是所有被打包的内容:
headers 表示将内核的所有头文件放到/usr/src/linux-*下面

分析Done

物联沃分享整理
物联沃-IOTWORD物联网 » 编译STM32MP1开发板内核:使用Yocto

发表评论