本人【Luckfox幸狐 RV1106 Linux 开发板测评】帖子链接:
一、开箱及测试
二、SDK获取与编译镜像
三、GPIO点灯
四、通过PC机共享网络接入Internet和Ubuntu下Python点灯
五、编译Buildroot系统并测试摄像头
六、PWM控制——命令和C方式
七、PWM控制——Python和设备树方式
八、ADC和UART测试
九、Python控制I2C驱动OLED
十、C程序控制I2C驱动OLED
十一、SPI驱动LCD
十二、实现FrameBuffer设备及LVGL应用
十三、五向开关作为LVGL输入设备
十四、LVGL显示DHT11的温湿度
十五、MPU6050的内核驱动
出于对Luckfox SDK进行内核构建所用build.sh脚本的好奇(手里还有一个吃灰的RK3568板子,最近发现它的SDK也是利用了build.sh,猜想是瑞芯微原厂方案),近期花时间把它的源码整个分析了一下,记录于本篇测评报告。注:分析过程利用了文心一言结合自己的理解,若有错误欢迎大家指正。
1、开篇部分的变量定义和导出
build.sh直接运行的代码集中在文件开篇和结尾部分,中间大段篇幅则是bash函数的定义。而开篇部分代码主要是各种变量的定义和导出,比如下述是build.sh的前14行代码:
#!/bin/bash
set -eE
export LC_ALL=C
export LD_LIBRARY_PATH=
function unset_env_config_rk()
{
local tmp_file=`mktemp`
env | grep -oh "^RK_.*=" > $tmp_file || true
source $tmp_file
rm -f $tmp_file
}
unset_env_config_rk
##############################################################################
# 总结,上述代码主要工作:
# 1. "set -eE"设置脚本在遇到错误时退出:
# -e: 如果任何语句的执行结果不是true,则退出bash;
# -E: 当脚本成功完成时,其退出状态是0,非零值通常表示某种错误。
# 2. "export LC_ALL=C"设置了语言环境(C语言)以避免本地化差异。
# 3. "export LD_LIBRARY_PATH="清空了LD_LIBRARY_PATH环境变量。
# 4. 定义了一个函数"unset_env_config_rk",并调用:
# 该函数从当前环境变量中筛选出所有以RK_开头的变量;
# 将RK_开头变量存放在临时文件mktemp中,
# source临时文件,即设置其中的RK_开头变量成为环境变量它们;
# 删除临时文件(实际测试一般没有RK_开题变量)。
##############################################################################
接着,利用realpath和dirname命令获取build.sh文件自身所在的路径,进而得到Luckfox SDK的绝对路径了。本人也是在分析到这里时发现了SDK根目录上的build.sh实际是一个软链接,指向<SDK>/project/build.sh。
##############################################################################
# Global Variable Configure
##############################################################################
_FDS="\\ \n" # _DFS:“反斜杠+回车”即字符串转换拼贴
cmd=`realpath $0` # realpath:本文件($0表示)的绝对路径
COMMON_DIR=`dirname $cmd` # dirname:路径部分即不包含脚本文件名
# 实际:COMMON_DIR=<SDK>/project/
# 根目录build.sh是一个软链接,指向project/build.sh
PROJECT_TOP_DIR=$(realpath $COMMON_DIR/) # 指向<SDK>/project
SDK_ROOT_DIR=$(realpath $COMMON_DIR/..) # COMMON_DIR父目录即SDK根目录绝对路径
SDK_SYSDRV_DIR=${SDK_ROOT_DIR}/sysdrv # 指向<SDK>/sysdrv
SDK_MEDIA_DIR=${SDK_ROOT_DIR}/media # 指向<SDK>/media
SDK_APP_DIR=${PROJECT_TOP_DIR}/app # 指向<SDK>/project/app
BOARD_CONFIG=$SDK_ROOT_DIR/.BoardConfig.mk # BOARD_CONFIG指向.BoardConfig.mk
TARGET_PRODUCT_DIR=${PROJECT_TOP_DIR}/cfg # 指向<SDK>/project/cfg
GLOBAL_ROOT_FILESYSTEM_NAME=rootfs # 根文件系统名称
GLOBAL_OEM_NAME=oem # oem名称
GLOBAL_FS_TYPE_SUFFIX=_fs_type # 文件系统类型后缀
GLOBAL_INITRAMFS_BOOT_NAME="" # initramfs的启动名称,此处空
GLOBAL_PARTITIONS="" # 分区信息,此处空
GLOBAL_SDK_VERSION="" # SDK版本,此处空
######################################################
# if语句检查当前系统的CPU核心数:
# 如果只有一个核心在线(getconf _NPROCESSORS_ONLN返回1)
# 则RK_JOBS被设置为1
# 如果有多个核心在线,则RK_JOBS被设置为在线核心数减1。
# 这个变量通常用于make命令的-j选项,以并行地执行构建任务。
######################################################
if [ `getconf _NPROCESSORS_ONLN` -eq 1 ]; then
export RK_JOBS=1
else
export RK_JOBS=$((`getconf _NPROCESSORS_ONLN` - 1 ))
fi
export RK_BUILD_VERSION_TYPE=RELEASE # 构建版本类型RELEASE
######################################################
# SDK_ROOT_DIR:再次导出SDK的根目录路径。
# RK_PROJECT_OUTPUT:设置项目输出的根目录。
# RK_PROJECT_TOP_DIR:设置项目顶级目录。
# RK_PROJECT_PATH_MEDIA、RK_PROJECT_PATH_SYSDRV、
# RK_PROJECT_PATH_APP:设置项目不同部分的输出目录。
# RK_PROJECT_OUTPUT_IMAGE:设置输出镜像的目录。
# RK_PROJECT_PATH_RAMDISK、RK_PROJECT_PATH_FASTBOOT等:
# 设置其他特定输出目录。
# PATH:添加RK_PROJECT_PATH_PC_TOOLS到PATH中,
# 这样在执行命令时可以直接访问该目录下的工具。
# RK_PROJECT_FILE_ROOTFS_SCRIPT、
# RK_PROJECT_FILE_OEM_SCRIPT:设置不同脚本文件的路径。
# RK_PROJECT_TOOLS_MKFS_*:
# 设置创建不同文件系统所需工具的脚本路径。
######################################################
export SDK_ROOT_DIR=$SDK_ROOT_DIR
export RK_PROJECT_OUTPUT=$SDK_ROOT_DIR/output/out
export RK_PROJECT_TOP_DIR=$PROJECT_TOP_DIR
export RK_PROJECT_PATH_MEDIA=$SDK_ROOT_DIR/output/out/media_out
export RK_PROJECT_PATH_SYSDRV=$SDK_ROOT_DIR/output/out/sysdrv_out
export RK_PROJECT_PATH_APP=$SDK_ROOT_DIR/output/out/app_out
export RK_PROJECT_PATH_PC_TOOLS=$SDK_ROOT_DIR/output/out/sysdrv_out/pc
export RK_PROJECT_OUTPUT_IMAGE=$SDK_ROOT_DIR/output/image
export RK_PROJECT_PATH_RAMDISK=$SDK_ROOT_DIR/output/out/ramdisk
export RK_PROJECT_PATH_FASTBOOT=$SDK_ROOT_DIR/output/out/fastboot
export RK_PROJECT_PATH_RAMDISK_TINY_ROOTFS=$RK_PROJECT_PATH_RAMDISK/tiny_rootfs
export PATH=$RK_PROJECT_PATH_PC_TOOLS:$PATH
export RK_PROJECT_FILE_ROOTFS_SCRIPT=$RK_PROJECT_OUTPUT/S20linkmount
export RK_PROJECT_FILE_OEM_SCRIPT=$RK_PROJECT_OUTPUT/S21appinit
export RK_PROJECT_FILE_RECOVERY_SCRIPT=$RK_PROJECT_PATH_RAMDISK_TINY_ROOTFS/etc/init.d/S10linkdev
export RK_PROJECT_FILE_RECOVERY_LUNCH_SCRIPT=$RK_PROJECT_PATH_RAMDISK_TINY_ROOTFS/etc/init.d/S99lunch_recovery
export RK_PROJECT_TOOLS_MKFS_SQUASHFS=mkfs_squashfs.sh
export RK_PROJECT_TOOLS_MKFS_EXT4=mkfs_ext4.sh
export RK_PROJECT_TOOLS_MKFS_UBIFS=mkfs_ubi.sh
export RK_PROJECT_TOOLS_MKFS_JFFS2=mkfs_jffs2.sh
export RK_PROJECT_TOOLS_MKFS_ROMFS=mkfs_romfs.sh
export RK_PROJECT_TOOLS_MKFS_EROFS=mkfs_erofs.sh
export RK_PROJECT_TOOLS_MKFS_INITRAMFS=mkfs_initramfs.sh
######################################################
# RK_PROJECT_ROOTFS_TYPE:可能根文件系统的类型。
# OTA_SCRIPT_PATH:设置OTA(Over-The-Air)脚本的路径。
# ENV_CFG_FILE:设置环境配置文件的路径。
# ENV_SIZE 和 ENV_OFFSET:可能存储环境变量的大小和偏移量。
######################################################
RK_PROJECT_ROOTFS_TYPE=""
OTA_SCRIPT_PATH=$RK_PROJECT_PATH_RAMDISK
ENV_CFG_FILE=$RK_PROJECT_OUTPUT_IMAGE/.env.txt
ENV_SIZE=""
ENV_OFFSET=""
2、结尾部分的实际业务逻辑代码
build.sh结尾部分是实际的业务逻辑,也就是执行:build.sh + 参数项的执行代码:
#=========================
# build targets
#=========================
# 设置了当脚本中任何命令返回非零退出状态时执行的错误处理函数err_handler。
trap 'err_handler' ERR
# 切换到脚本定义的$PROJECT_TOP_DIR目录,即<SDK>/project
cd $PROJECT_TOP_DIR
# 调用函数:从当前 shell 环境中移除所有与 RK_ 前缀相关的环境变量,
# 这些环境变量通常是在 BoardConfig*.mk 文件中通过 export 语句定义的。
unset_board_config_all
# ./build.sh lunch即sh脚本执行时第一个参数为lunch,则执行build_select_board LUNCH-FORCE
if [ "$1" = "lunch" ];then
# 选择并设置目标开发板的构建配置(见260行),即选择开发板更新defconfig文件
build_select_board LUNCH-FORCE
fi
# 如果$BOARD_CONFIG指定的配置文件不存在,则调用build_select_board函数。
# 即从未执行./build.sh lunch,那么需要强制进入设置目标开发板的阶段
if [ ! -e "$BOARD_CONFIG" ];then
build_select_board
fi
# 如果$BOARD_CONFIG是一个符号链接,则执行source命令来加载它,用于导入环境变量或函数定义。
# BOARD_CONFIG指向.BoardConfig.mk,即./build.sh lunch得到的开发板配置文件
[ -L "$BOARD_CONFIG" ] && source $BOARD_CONFIG
# 设置交叉编译工具链和将其路径添加到PATH环境变量中。
# (之前一直好奇Luckfox编译镜像不要设置环境变量,因为这里做了设置)
export RK_PROJECT_TOOLCHAIN_CROSS=$RK_TOOLCHAIN_CROSS
export PATH="${SDK_ROOT_DIR}/tools/linux/toolchain/${RK_PROJECT_TOOLCHAIN_CROSS}/bin":$PATH
# 如果目标根文件系统是ubuntu,则根据LF_SUBMODULES_BY的值来复制对应的.gitmodules文件,并更新Git子模块。
if [[ "$LF_TARGET_ROOTFS" = "ubuntu" ]];then
if [[ "$LF_SUBMODULES_BY" = "github" ]];then
cp ${SDK_ROOT_DIR}/.gitmodules.github ${SDK_ROOT_DIR}/.gitmodules
else
if [[ "$LF_SUBMODULES_BY" = "gitee" ]];then
cp ${SDK_ROOT_DIR}/.gitmodules.gitee ${SDK_ROOT_DIR}/.gitmodules
else
exit 0
fi
fi
git submodule update --init --recursive
fi
# 如果命令行参数中包含help或-h,则显示帮助信息。
if echo $@|grep -wqE "help|-h"; then
# 应该是分析是否有第2参数,若有则进一步调用usage$2来输出子参数信息。
# 比如:./build.sh help exec,那么就会输出exec的帮助信息。
# 后经实际测试,二级信息输出只支持:media、sysdrv、kernel、uboot、rootfs
# 即有函数usagemedia等。
if [ -n "$2" -a "$(type -t usage$2)" == function ]; then
echo "###Current Configure [ $2 ] Build Command###"
eval usage$2
else
usage
fi
exit 0
fi
# 检查指定的交叉编译gcc是否存在,如果不存在则输出错误信息并退出脚本。
if ! ${RK_PROJECT_TOOLCHAIN_CROSS}-gcc --version &> /dev/null; then
msg_error "Not found toolchain ${RK_PROJECT_TOOLCHAIN_CROSS}-gcc for [$RK_CHIP] !!!"
msg_info "Please run these commands to install ${RK_PROJECT_TOOLCHAIN_CROSS}-gcc"
echo ""
echo " cd ${SDK_ROOT_DIR}/tools/linux/toolchain/${RK_PROJECT_TOOLCHAIN_CROSS}/"
echo " source env_install_toolchain.sh"
echo ""
exit 1
fi
# 根据RK_PROJECT_TOOLCHAIN_CROSS的值设置库类型(uclibc或glibc)。
# 看来Luckfox基于RV1106应该是导入glibc库了。
case $RK_PROJECT_TOOLCHAIN_CROSS in
arm-rockchip830-linux-uclibcgnueabihf)
export RK_LIBC_TPYE=uclibc
;;
*)
export RK_LIBC_TPYE=glibc
;;
esac
# 设置构建过程中使用的各种输出目录路径。
export RK_PROJECT_PACKAGE_ROOTFS_DIR=$RK_PROJECT_OUTPUT/rootfs_${RK_LIBC_TPYE}_${RK_CHIP}
export RK_PROJECT_PACKAGE_OEM_DIR=$RK_PROJECT_OUTPUT/oem
export RK_PROJECT_PACKAGE_USERDATA_DIR=$RK_PROJECT_OUTPUT/userdata
export RK_PROJECT_PATH_BOARD_BIN=$RK_PROJECT_PATH_SYSDRV/board_${RK_LIBC_TPYE}_${RK_CHIP}
# 调用build_check和__PREPARE_BOARD_CFG函数进行构建前的检查和准备板级配置。
build_check
__PREPARE_BOARD_CFG
# $#传递当前脚本(即./build.sh)运行的输入参数个数,这个数值存在num中。
num=$#
option=""
# 最终的build.sh执行逻辑
# 脚本命令包含参数1,则根据参数执行不同的功能(前面定义的函数)。
# 不同的功能函数实际先存储在变量option中。
# 其中clean需要参数2,以确定clean的部分。
while [ $# -ne 0 ]
do
case $1 in
DEBUG) export RK_BUILD_VERSION_TYPE=DEBUG;;
all) option=build_all ;;
save) option=build_save ;;
allsave) option=build_allsave ;;
check) option=build_check ;;
clean) option="build_clean $2";break;;
firmware) option=build_firmware ;;
ota) option=build_ota ;;
updateimg) option=build_updateimg ;;
unpackimg) option=build_unpack_updateimg ;;
factory) option=build_factory ;;
recovery) option=build_recovery ;;
env) option=build_env ;;
meta) option=build_meta ;;
driver) option=build_driver ;;
sysdrv) option=build_sysdrv ;;
uboot) option=build_uboot ;;
kernel) option=build_kernel ;;
rootfs) option=build_rootfs ;;
media) option=build_media ;;
app) option=build_app ;;
info) option=build_info ;;
tool) option=build_tool ;;
*) option=usage ;;
esac
if [ $((num)) -gt 0 ]; then
shift
fi
done
# 最后利用eval命令执行option指向的函数功能
eval "${option:-build_allsave}"
上述代码先判断是否执行了“build.sh lunch”命令,进而实现开发板选型的逻辑。最后的while循环决定了build.sh可以附带的其它命令参数,比如本人实际使用过的:“build.sh clean”、“build.sh kernel”等。而这些命令会通过调用函数来实现,比如build_all()、build_clean()等,这些函数的定义代码也就是在文件的中间部分,限于篇幅这里不做介绍了,附上本人添加了注释后的build.sh文件(由于时间关系仅注释了一半的函数定义)。
build.sh
(78.04 KB, 下载次数: 7)