build_embed_linux_system

Linux快速部署和SDK构建方法

无论使用厂商,方案商的设计方案,还是接手嵌入式Linux项目,第一步总是安装SDK包,然后在基础上进行修改,编译,开发的工作,这也是大部分嵌入式开发的工作流程。可以看到,SDK是整个项目开发的基础,一般也十分复杂,除了构建SDK的人,即使后续维护的人也很难去理解清楚各个方面。而对于使用者,一般只能够根据官方提供的SDK包使用手册,了解一系列命令进行环境安装,u-boot,kernel,rootfs编译,输出文件打包等,未遇到问题还好,遇到问题往往很难分析,只能依靠厂商的工程师协助解决,这是大部分开发者面临的问题。

我曾经参与过大型医疗设备的后续维护开发,虽然按照需求把产品功能开发完成,不过对于项目的整体仍然如雾里看花,朦朦胧胧。后续在自己的项目实践后,才算真正理解SDK的构建和部署方法,这个也显著提升了我维护大型项目的能力。

structure

在理解SDK的构建方法之前,这里先说明下完整嵌入式Linux项目需要的组成部分。

  1. 交叉编译工具(gcc),很多复杂的项目可能并不是唯一的,不同组件可能需要不同版本的编译工具
  2. U-Boot源码,配置文件、代码,编译脚本
  3. Kernel源码,配置文件、代码,编译脚本
  4. 支持rootfs编译或构建方法(busybox,buildroot(nogui/qt),debian,ubuntu,yocto,android……),对于单个项目基本选择一种即可。
  5. 适配硬件的设备树和驱动程序(gpio,i2c,spi,rtc,pwm,adc,usb,hdmi……)
  6. 应用运行的支持库和可执行文件(mosquitto,qt,openssl,ser2net,openssh,……)
  7. 应用运行的配置文件,最终集成到文件系统中
  8. 构建编译环境和管理SDK命令的脚本

其中交叉编译工具是编译代码,生成最终可执行文件的工具,可参考系统环境平台构建中的相关说明,将交叉编译工具导入到系统环境变量,即可进行后续执行。

U-Boot源码可以使用芯片厂商/方案商开发的版本,再进行适配自己硬件的修改,对于这部分可参考u-boot移植中的相关说明,完成移植并下载到系统中使用。

Kernel源码的处理和U-Boot源码一致,使用芯片厂商/方案商开发的版本,再进行适配自己硬件的修改,此部分参考kernel移植中的相关说明,完成移植并下载到系统中使用。

rootfs是用于管理和存储的一系列的文件的集合,这部分可以自己编译(如busybox,buildroot),或者使用已构建好的系统,并在此基础上扩展安装(如debian,ubuntu),此部分参考rootfs环境构建中的相关说明,实现构建系统的构建。

设备树和驱动程序是将硬件和应用连接的通道,其中设备树是对于硬件的抽象,包含通用的属性以及厂家自定义属性,可参考设备树语法说明,驱动则要去理解Linux源码中driver的部分,或者参考ch03大章节的说明。

支持的库是是用于应用运行的第三方软件和库文件,如支持远程连接的ssh,远程桌面的vnc,mqtt服务的mosquitto,音视频服务的ffmpeg,数据库服务的sqlite,串口转网络的ser2net等,这部分参考库交叉编译实现,里面包含应用运行的支持环境和库编译方法。

最后剩余的就是应用代码,配置文件以及SDK构建和编译的脚本,这部分参考第四部分的说明。另外,构建能够快速编译上述各部分的命令需要脚本的支持,这就涉及linux命令以及shell语法的说明,可参考shell命令说明shell语法说明。到这里,可以看出来,构建一个完整的项目SDK,就是理解和应用嵌入式Linux系统的过程,是对各部分技术和支持的运用,这在下一部分进行说明。

deploy

SDK原理上是由目录,源码,编译脚本共同组成的,能够编译出u-boot,zImage,dts,rootfs,support library,application的集合,理解了这一点,就可以规划sdk的目录和编译环境。

可以看到SDK中包含基础系统的构建部分,在此基础上,我们添加应用实现具体的功能,其中make.sh就是系统构建的脚本,具体如下。

#!/bin/bash
#
# Copyright (c) 2024 zc Co., Ltd
#
# SPDX-License-Identifier: GPL-2.0
#

########################################### global variable Defined #############################################
PATH_PWD="$(pwd)"

CROSS_COMPILE="arm-none-linux-gnueabihf-"
FILE_CROSS_COMPILE="${PATH_PWD}/toolchain/bin/${CROSS_COMPILE}"
THREAD_COUNT=3

#kernel information
CHIP_ARCH="arm"
LINUX_CONFIG="imx_rmk_v7_defconfig"
LINUX_QEMU_CONFIG="imx_qemu_defconfig"
ZIMAGE_FILE=zImage
DTB_EMMC_FILE=imx6ull-14x14-emmc-4.3-800x480-c.dtb
DTB_NAND_FILE=imx6ull-14x14-nand-4.3-800x480-c.dtb
QEMU_DTB_FILE=imx6ull-qemu.dtb

KERNEL_ZIMAGE="${SUPPORT_ENV_KERNEL_DIR}/arch/arm/boot/${ZIMAGE_FILE}"
KERNEL_DTB="${SUPPORT_ENV_KERNEL_DIR}/arch/arm/boot/dts/${DTB_EMMC_FILE}"
KERNEL_NAND_DTB="${SUPPORT_ENV_KERNEL_DIR}/arch/arm/boot/dts/${DTB_NAND_FILE}"
KERNEL_QEMU_DTB="${SUPPORT_ENV_KERNEL_DIR}/arch/arm/boot/dts/${QEMU_DTB_FILE}"

#rootfs information
debian_SIZE=2048

UBOOT_PATH="${PATH_PWD}/u-boot"
KERNEL_PATH="${PATH_PWD}/kernel"
ROOTFS_PATH="${PATH_PWD}/rootfs"
PACKAGE_PATH="${PATH_PWD}/package"

ROOTFS_SHELL_PATH="${ROOTFS_PATH}/shell"
ROOFTS_debian_PATH="${ROOTFS_PATH}/debian"
ROOTFS_IMG_PATH="${ROOTFS_PATH}/img"
###############################################################################################################

function help()
{
    echo
    echo "Usage:"
    echo "  ./make.sh [sub-command] [parameter]"
    echo
    echo "  - sub-command:  elf*|loader|uboot|--spl|--tpl|itb|map|sym|<addr>"
    echo "  - parameter:    parameter for build"
    echo
    echo "Output:"
    echo "  When board built okay, there are output images in current directory"
    echo
    echo "Example:"
    echo
    echo "1. Build:"
    echo "  ./make.sh                          --- build with exist .config, for all"
    echo
    echo "2. Pack:"
    echo "  ./make.sh uboot                    --- pack uboot.img, same as emmc"
    echo "  ./make.sh uboot emmc               --- pack uboot.img with emmc env"
    echo "  ./make.sh uboot sd                 --- pack uboot.img with sdcard env"
    echo "  ./make.sh uboot nand               --- pack uboot.img with nand env"
    echo "  ./make.sh kernel                   --- pack linux kernel"
    echo "  ./make.sh kernel normal            --- pack linux kernel normal mode"
    echo "  ./make.sh kernel qemu              --- pack linux kernel qemu mode"
    echo "  ./make.sh rootfs                   --- pack rootfs, same as buildroot"
    echo "  ./make.sh rootfs buildroot         --- pack rootfs buildroot"
    echo "  ./make.sh rootfs debian            --- pack rootfs debian"
    echo "  ./make.sh rootfs ubuntu            --- pack rootfs ubuntu"
}

exit_if_last_error() {
    if [[ $? -ne 0 ]]; then
        echo "上一条命令执行失败,脚本将退出。"
        exit 1
    fi
}

#编译u-boot环境
compile_u_boot()
{
    config_file=mx6ull_14x14_rmk_emmc_defconfig

    if [ "$1" == "sd" ]; then
        config_file=mx6ull_14x14_rmk_sd_defconfig
    elif [ "$1" == "nand" ];then
        config_file=mx6ull_14x14_rmk_nand_defconfig
    fi

    cd "${UBOOT_PATH}" || return

    echo "====== start u-boot build, Config file:$config_file ======"

    #create boot.scr
    mkimage -C none -A arm -T script -d boot.cmd boot.scr
    mv boot.scr "${PACKAGE_PATH}"/

    #build u-boot
    make "${config_file}"
    make V=1 ARCH=arm CROSS_COMPILE="${FILE_CROSS_COMPILE}"

    exit_if_last_error

    cp u-boot-dtb.imx "${PACKAGE_PATH}"/

    echo "====== u-boot build finished, success! ======"
}

#编译kernel
compile_kernel()
{
    cd "${KERNEL_PATH}" || return

    linux_config_file="${LINUX_CONFIG}"

    if [ "$1" == "qemu" ]; then
        linux_config_file="${LINUX_QEMU_CONFIG}"
    fi

    echo "====== start kernel, Config file:$linux_config_file ======"

    make "${linux_config_file}" CROSS_COMPILE="${FILE_CROSS_COMPILE}" ARCH="${CHIP_ARCH}"
    make -j"${THREAD_COUNT}" CROSS_COMPILE="${FILE_CROSS_COMPILE}" ARCH="${CHIP_ARCH}"

    exit_if_last_error

    cp -avf "${KERNEL_ZIMAGE}" "${PACKAGE_PATH}"/
    cp -avf "${KERNEL_DTB}" "${PACKAGE_PATH}"/
    cp -avf "${KERNEL_NAND_DTB}" "${PACKAGE_PATH}"/
    cp -avf "${KERNEL_QEMU_DTB}" "${PACKAGE_PATH}"/

    echo "====== kernel build finished, success! ======"
}

#构建buildroot系统
compile_buildroot()
{
    echo "====== start create buildroot ======"

    export BR2_TOOLCHAIN_EXTERNAL_PATH="${PATH_PWD}/toolchain"
    make imx6ullrmk_defconfig
    make -j"${CPU_CORE}"

    exit_if_last_error

    echo "====== buildroot build finished, success! ======"

    cp -fv output/images/rootfs.ext2 "${PACKAGE_PATH}"/buildroot.img
}

#构建debian系统
compile_debian()
{
    echo "====== start create debian ======"

    cd "${PACKAGE_PATH}" || return

    if [ ! -f debian.img ]; then
        dd if=/dev/zero of=debian.img bs=1M count=${debian_SIZE}
        mkfs.ext4 debian.img
    fi

    sudo mount -o loop "${PACKAGE_PATH}"/debian.img  "${ROOFTS_debian_PATH}"/

    cd "${ROOTFS_SHELL_PATH}" || return

    chmod 777 install-debian.sh && ./install-debian.sh "${ROOFTS_debian_PATH}"

    echo "====== debian build finished, success! ======"

    sudo umount "${ROOFTS_debian_PATH}"/
}

#构建文件系统的应用
compile_rootfs()
{
    rootfs=buildroot

    if [ "$1" == "debian" ]; then
        rootfs=debian
    elif [ "$1" == "ubuntu" ];then
        rootfs=ubuntu
    fi

    ROOTFS_FILE_PATH="${ROOTFS_PATH}/${rootfs}"

    cd "${ROOTFS_FILE_PATH}" || return

    if [ "${rootfs}" == "buildroot" ]; then
        compile_buildroot
    elif [ "${rootfs}" == "debian" ]; then
        compile_debian
    fi
}

#清除已经编译文件
clean_all()
{
    if [ -d ${PACKAGE_PATH} ]; then
        rm -rf ${PACKAGE_PATH}/*
    fi

    make clean -C ${KERNEL_PATH}
    make clean -C ${UBOOT_PATH}
    make clean -C ${ROOTFS_PATH}/buildroot
}

function process_args()
{
    case $1 in
        *help|--h|-h)
            help
            exit 0
            ;;
        uboot | u-boot | u-boot/)
            compile_u_boot "$2"
            exit 0
            ;;
        kernel | kernel/)
            compile_kernel "$2"
            exit 0
            ;;
        rootfs| rootfs/)
            compile_rootfs "$2"
            exit 0
            ;;
        clean)
            clean_all
            exit 0
            ;;
        esac
}

process_args $*

上述就是编译sdk的脚本,基于此,可以使用简单的命令,如”./make.sh uboot”即可完成uboot的编译,并将最终生成的u-boot-dtb.imx放置在package目录下,可直接下载使用。当然,对于完整的系统,还有进一步的操作,将命令组合再通过export导入到系统中,这样使用全局命令即可编译,具体实现如下。

#命令导入text.env
function imx_build_uboot()
{
    cd "${PLATFORM_UBOOT_PATH}"/ || return

    chmod 777 uboot_upgrade.sh && ./uboot_upgrade.sh

    cdp || return

    ./make.sh uboot $1

    cp -fv ${SUPPORT_ENV_PACKAGE}/u-boot-dtb.imx "${BUILD_TFTP_PATH}"/
    cp -fv ${SUPPORT_ENV_PACKAGE}/boot.scr "${BUILD_TFTP_PATH}"/
}
alias imx_build_uboot="imx_build_uboot"

function build_uboot
{
    echo "uboot compiler:${NEW_KERNEL_CC} ${config_file}"

    if [ "${FIRMWARE_CURRENT_PLATFORMS}" == "ARM" ]; then
        imx_build_uboot $1
    fi
}
alias SysBuildUboot='build_uboot'

#将命令导入到系统环境
source text.env

此时使用SysBuildUboot即可实现之前的./make.sh u-boot的功能,如此就可以简化系统的编译动作,不需要每次都进入目录编译,手动拷贝,这个步骤就是快速构建的实现。对于其它模块,都参考此流程实现,如此即完成了对于SDK的部署,具体可参考本系列相关项目https://github.com/zc110747/remote_manage.git的实现,其中包含上述的脚本和环境实现。

summary

至此,关于如何构建一个SDK的流程进行了大致说明,虽然实现逻辑并不复杂,不过其原理上包含构建嵌入式Linux工程的所有方面,没有一定的基础,理解起来有一定的困难。获取系统运行的u-boot,kernel,rootfs,添加应用需要的支持库,再将应用导入,就可以实现最终的产品。部署就是将这些流程通过shell命令组合的方式,进行指令化,这就大大提高了效率。

Linux SDK的构建包含Linux基础语法,编译系统,shell脚本,平台构建等相关知识,需要长期坚持不懈的积累。当然本篇也只是构建SDK的一种思路,不同芯片往往也有不同的方案,不过从原理上是相通的,理解了SDK的构建方法,也就能够一法通万法,很快掌握其它SDK的使用方法。

next_chapter

返回目录

直接开始下一章节:Linux平台问题解决方法