无论使用厂商,方案商的设计方案,还是接手嵌入式Linux项目,第一步总是安装SDK包,然后在基础上进行修改,编译,开发的工作,这也是大部分嵌入式开发的工作流程。可以看到,SDK是整个项目开发的基础,一般也十分复杂,除了构建SDK的人,即使后续维护的人也很难去理解清楚各个方面。而对于使用者,一般只能够根据官方提供的SDK包使用手册,了解一系列命令进行环境安装,u-boot,kernel,rootfs编译,输出文件打包等,未遇到问题还好,遇到问题往往很难分析,只能依靠厂商的工程师协助解决,这是大部分开发者面临的问题。
我曾经参与过大型医疗设备的后续维护开发,虽然按照需求把产品功能开发完成,不过对于项目的整体仍然如雾里看花,朦朦胧胧。后续在自己的项目实践后,才算真正理解SDK的构建和部署方法,这个也显著提升了我维护大型项目的能力。
在理解SDK的构建方法之前,这里先说明下完整嵌入式Linux项目需要的组成部分。
其中交叉编译工具是编译代码,生成最终可执行文件的工具,可参考系统环境平台构建中的相关说明,将交叉编译工具导入到系统环境变量,即可进行后续执行。
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系统的过程,是对各部分技术和支持的运用,这在下一部分进行说明。
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的实现,其中包含上述的脚本和环境实现。
至此,关于如何构建一个SDK的流程进行了大致说明,虽然实现逻辑并不复杂,不过其原理上包含构建嵌入式Linux工程的所有方面,没有一定的基础,理解起来有一定的困难。获取系统运行的u-boot,kernel,rootfs,添加应用需要的支持库,再将应用导入,就可以实现最终的产品。部署就是将这些流程通过shell命令组合的方式,进行指令化,这就大大提高了效率。
Linux SDK的构建包含Linux基础语法,编译系统,shell脚本,平台构建等相关知识,需要长期坚持不懈的积累。当然本篇也只是构建SDK的一种思路,不同芯片往往也有不同的方案,不过从原理上是相通的,理解了SDK的构建方法,也就能够一法通万法,很快掌握其它SDK的使用方法。
直接开始下一章节:Linux平台问题解决方法