ros2 嵌入式程序的交叉编译 —— 基于 Chroot 文件系统隔离
Title: ros2 嵌入式程序的交叉编译 —— 基于 Chroot 文件系统隔离
文章目录
关联博文:
I. 前言
1. 问题提出
复杂交叉场景 (如编译多个依赖关联的 ROS2 包) 无论怎么设置编译环境参数,
系统编译时会优先从主机的 /usr/bin、/usr/lib 等路径搜索工具, 而非自动识别挂载的镜像目录,
故而总是存在混用主机 host 系统原生环境的情况, 这是交叉编译出现问题的原因.
比如主机系统中运行 colcon build 并将日志直接输出到控制台及启用详细模式 console_direct+.
colcon build \
--merge-install \
--cmake-force-configure \
--packages-up-to XXXX \
--cmake-args \
-DCMAKE_TOOLCHAIN_FILE=/home/robot/crosscompile_ws/toolchain-aarch64.cmake \
--event-handlers console_direct+
[错误输出例 1]:
– Using all available rosidl_typesupport_cpp: rosidl_typesupport_fastrtps_cpp;rosidl_typesupport_introspection_cpp – Found PythonInterp: /usr/bin/python3 (found suitable version “3.10.12”, minimum required is “3.6”) – Found python_cmake_module: 0.10.0 (/mnt/embedded_rootfs/opt/ros/humble/share/python_cmake_module/cmake) – Found PythonLibs: /mnt/embedded_rootfs/usr/lib/aarch64-linux-gnu/libpython3.10.so (found suitable version “3.10.12”, minimum required is “3.5”) – Using PYTHON_EXECUTABLE: /usr/bin/python3 – Using PYTHON_INCLUDE_DIRS: /mnt/embedded_rootfs/usr/include/python3.10 – Using PYTHON_LIBRARIES: /mnt/embedded_rootfs/usr/lib/aarch64-linux-gnu/libpython3.10.so
[错误输出例 2]:
CMake Error in CMakeLists.txt:
Imported target “nav2_costmap_2d::layers” includes non-existent path“/opt/ros/humble/include/nav2_msgs”
in its INTERFACE_INCLUDE_DIRECTORIES. Possible reasons include:
The path was deleted, renamed, or moved to another location.
An install or uninstall procedure did not complete successfully.
The installation package was faulty and references files it does not
provide.CMake Warning at CMakeLists.txt:41 (add_executable):
Cannot generate a safe runtime search path for target XXXXX_node
because there is a cycle in the constraint graph:dir 0 is [/mnt/embedded_rootfs/opt/ros/humble/lib]
dir 2 must precede it due to runtime library [libnav2_util_core.so]
dir 1 is [/home/robot/crosscompile_ws/install/lib]
dir 2 is [/opt/ros/humble/lib]
dir 0 must precede it due to runtime library [libtf2_msgs__rosidl_generator_c.so]Some of these libraries may not be found correctly.
CMake Generate step failed. Build files cannot be regenerated correctly.
参考 ros2 嵌入式程序的交叉编译.
2. 问题思路 —— chroot 技术
这时想到了 chroot 隔离技术.
chroot (Change Root) 是 Linux/Unix 系统中的一个功能, 用于 修改进程的根目录视图, 从而创建一个隔离的文件系统环境. 它是最早的容器化技术之一, 但隔离性较弱, 主要用于系统维护、交叉编译和测试环境搭建等场景.
chroot通过 修改进程的根目录, 使其只能访问指定目录下的文件. 进程认为该目录是文件系统的根 /, 无法访问外部的真实文件系统 (除非挂载).
chroot 适合复杂场景, 如编译多个依赖关联的 ROS2 包.
通过 chroot 命令将 “当前 Shell 环境” 切换到镜像内部, 此时所有工具 (包括 Python、gcc、CMake) 都会默认使用镜像内的版本, 无需逐个指定路径.
chroot 完全切换环境, 确保所有依赖 (包括 Python、ROS2 工具链) 都来自镜像文件, 编译结果能直接在嵌入式设备上运行.
II. 环境隔离
1. 基本原理
利用 chroot 实现文件系统的隔离, 即可实现交叉编译环境与主机 host 环境的隔离.
chroot 的核心作用是将当前 Shell 的根目录 (/) 替换为指定目录 (即嵌入式镜像挂载点 /mnt/embedded_rootfs).
在 chroot 内部:
- 所有路径都以镜像的 / 为起点, 比如 chroot 内的 /usr 实际对应主机的 /mnt/embedded_rootfs/usr;
- 主机的原始路径 (如 /home/robot、/root) 在 chroot 内完全 “不可见”, 除非你在 chroot 前就把这些主机路径挂载到镜像的某个目录下.
2. 挂载镜像文件系统
挂载镜像文件系统
robot@robot:~/crosscompile_ws/rootfs_image$ sudo bash ./auto_mount.sh
其中脚本 auto_mount.sh 参考 ros2 嵌入式程序的交叉编译 —— 镜像文件的制作.
3. 安装 QEMU 模拟工具
主机是 x86_64 架构, 镜像内是 arm64 架构 (香橙派常用的 aarch64), 需先安装 qemu-user-static 实现 “跨架构模拟”, 否则无法在 x86_64 主机上运行 aarch64 架构的工具.
QEMU (Quick Emulator) 是一款免费开源模拟器, 有多种模式, 包括 User mode 使用者模式、System mode 系统模式等.
User mode 使用者模式下, QEMU 运行针对不同指令编译的单个 Linux 程序. 这种模式轻量级且性能较好, 可以实现交叉编译 (cross-compilation) 与交叉偵错 (cross- debugging).
而System mode 系统模式 (全虚拟化) 模拟完整的计算机系统, 包括 CPU、内存、存储设备、网络设备等. 这种模式可运行不同架构的完整操作系统, 性能较低且指令翻译开销大, 用于内核开发、嵌入式系统测试.
为了嵌入式应用端程序交叉编译的环境隔离只需安装 QEMU 的 user mode
sudo apt update
# 安装 qemu-user-static 和 binfmt 支持
sudo apt install qemu-user-static binfmt-support
# 将 qemu-aarch64-static 复制到镜像的 /usr/bin 目录, 让镜像内的程序能被主机模拟运行
sudo cp /usr/bin/qemu-aarch64-static /mnt/embedded_rootfs/usr/bin/
binfmt_misc 作为内核机制, 通过文件头特征识别二进制格式, 当执行 arm 二进制时, 内核自动调用 qemu-aarch64-static 翻译指令.
需确保目标文件系统中有对应的 ARM 版动态库 (如/lib/aarch64-linux-gnu/), 镜像文件系统本身就是 aarch64 架构的, 自然能保证.
已经拷贝到镜像文件系统中去的 qemu-aarch64-static, 在下次重新挂载该镜像时该拷贝还存在, 故上述拷贝执行一次就行.
4. 挂载主机编译目录
先将主机编译工作目录 (/home/robot/crosscompile_ws) 挂载到 chroot 环境中.
让主机的编译目录 /home/robot/crosscompile_ws 和镜像内编译目录 /mnt/embedded_rootfs/crosscompile_ws 形成双向同步.
在主机上修改 crosscompile_ws 里的代码, 镜像内的 /crosscompile_ws 会实时同步, 反之亦然.
# 在镜像目录 (主机路径 /mnt/embedded_rootfs) 下创建挂载点 (主机系统中执行)
sudo mkdir -p /mnt/embedded_rootfs/crosscompile_ws
# 把主机的 /home/robot/crosscompile_ws 绑定挂载到镜像的 /crosscompile_ws (主机系统中执行)
sudo mount --bind /home/robot/crosscompile_ws /mnt/embedded_rootfs/crosscompile_ws
5. 挂载主机系统依赖
chroot 环境中交叉编译时还可能依赖主机系统目录, 需在主机上挂载完整, 避免在编译时出现依赖错误.
主机 host 终端中执行确保以下目录已挂载到 chroot 环境中.
sudo mount --bind /dev /mnt/embedded_rootfs/dev # 设备文件
sudo mount -t devpts devpts /mnt/embedded_rootfs/dev/pts # 伪终端, 关键
sudo mount --bind /proc /mnt/embedded_rootfs/proc # 进程信息
sudo mount --bind /sys /mnt/embedded_rootfs/sys # 系统内核信息
sudo mount --bind /run /mnt/embedded_rootfs/run # 运行时文件, 如 PID 文件
sudo mount --bind /etc/resolv.conf /mnt/embedded_rootfs/etc/resolv.conf # DNS 配置, 联网用
sudo mount --bind /tmp /mnt/embedded_rootfs/tmp # 临时文件目录, 编译时可能用到
III. 交叉编译
1. 进入 chroot 环境 (镜像文件系统的环境)
sudo chroot /mnt/embedded_rootfs /bin/bash
2. 加载嵌入式 ROS 2 Humble 的环境变量
source /opt/ros/humble/setup.bash
这是关键步骤, 不然无法使用 ros2 环境, 出现如下错误
CMake Error at CMakeLists.txt:9 (find_package):
By not providing “Findament_cmake.cmake” in CMAKE_MODULE_PATH this project
has asked CMake to find a package configuration file provided by
“ament_cmake”, but CMake did not find one.Could not find a package configuration file provided by “ament_cmake” with
any of the following names:ament_cmakeConfig.cmake
ament_cmake-config.cmakeAdd the installation prefix of “ament_cmake” to CMAKE_PREFIX_PATH or set
“ament_cmake_DIR” to a directory containing one of the above files. If
“ament_cmake” provides a separate development package or SDK, be sure it
has been installed.
3. chroot 环境内执行编译
# 进入编译目录
cd /crosscompile_ws
# 清除之前的编译缓存
rm -rf build/ install/ log/
# 编译
colcon build
等待全部编译完成.
IV. 退出交叉编译
1. 退出 chroot 环境
root@robot:/crosscompile_ws# exit
2. 卸载所有挂载绑定
卸载之前挂载的所有绑定和虚拟文件系统, 否则可能会导致文件系统损坏或资源泄漏.
卸载顺序应遵循 “先挂载的后卸载” 原则, 避免因依赖关系导致卸载失败.
sudo umount /mnt/embedded_rootfs/tmp
sudo umount /mnt/embedded_rootfs/etc/resolv.conf
sudo umount /mnt/embedded_rootfs/run
sudo umount /mnt/embedded_rootfs/sys
sudo umount /mnt/embedded_rootfs/proc
sudo umount /mnt/embedded_rootfs/dev/pts # 先卸载 dev/pts
sudo umount /mnt/embedded_rootfs/dev # 再卸载 /dev
sudo umount /mnt/embedded_rootfs/crosscompile_ws
sudo umount /mnt/embedded_rootfs # 卸载整个镜像文件
V. 嵌入式中运行
编译完毕后, 将交叉编译得到的 install 文件夹利用 scp 命令拷贝传输到嵌入式主板
sudo scp -r ./install/ bingda@192.168.88.30:/home/bingda/ros2_install_crosscompile/
登入嵌入式系统后, 进入这个文件夹并执行 source, 以加载 ROS2 环境变量和配置.
cd ~/ros2_install_crosscompile
source ./install/setup.bash
下面就可以 launch 节点运行测试了.
VI. 脚本实现
为了自动化, 我们将以上过程脚本化实现.
1. 挂载和进入 chroot 环境的脚本
#!/bin/bash
#sudo bash ./auto_mount_chroot_env.sh
echo -e "\033[1;33mUsage:\033[0m \033[1;36msudo bash ./auto_mount_chroot_env.sh\033[0m"
# 检查是否以root运行
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run with sudo or as root"
exit 1
fi
# 配置参数
IMAGE_FILE="/home/robot/crosscompile_ws/rootfs_image/ubuntu22_ros2_humble_shrunk.img"
MOUNT_POINT="/mnt/embedded_rootfs"
WS_PATH="/home/robot/crosscompile_ws"
TARGET_WS="$MOUNT_POINT/crosscompile_ws"
# 检查镜像文件是否存在
if [ ! -f "$IMAGE_FILE" ]; then
echo "Error: Image file $IMAGE_FILE does not exist"
exit 1
fi
# 创建挂载点(如果不存在)
if [ ! -d "$MOUNT_POINT" ]; then
echo "Create mount point: $MOUNT_POINT"
sudo mkdir -p "$MOUNT_POINT"
else
echo "Mount point already exists: $MOUNT_POINT"
fi
# 提取第二个分区(Linux filesystem)的起始扇区
# 从fdisk输出中过滤包含"Linux filesystem"的行,然后提取起始扇区数值
START_SECTOR=$(fdisk -l "$IMAGE_FILE" | grep "Linux filesystem" | awk '{print $2}')
if [ -z "$START_SECTOR" ] || ! [[ "$START_SECTOR" =~ ^[0-9]+$ ]]; then
echo "Error: Unable to extract valid starting sector!"
echo "Please check if the partition exists or the partition type is correct"
exit 1
fi
# 计算偏移量(扇区大小默认512字节)
SECTOR_SIZE=512
OFFSET=$((START_SECTOR * SECTOR_SIZE))
echo "Located partition: StartSector=$START_SECTOR, SectorSize=${SECTOR_SIZE}bytes, Offset=${OFFSET}bytes"
# 执行镜像挂载操作
echo "Mounting image to $MOUNT_POINT..."
sudo mount -o loop,offset=$OFFSET -t ext4 "$IMAGE_FILE" "$MOUNT_POINT"
# 检查镜像挂载是否成功
if [ $? -ne 0 ]; then
echo "Error: Image mounting failed!"
exit 1
fi
# 检查并创建工作空间挂载点
echo "Checking workspace mount point..."
if [ ! -d "$TARGET_WS" ]; then
echo "Workspace mount point does not exist, creating directory..."
sudo mkdir -p "$TARGET_WS"
else
echo "Workspace mount point already exists, skipping creation..."
fi
# 挂载工作空间
echo "Mounting workspace..."
sudo mount --bind "$WS_PATH" "$TARGET_WS"
# 挂载必要的系统目录
echo "Mounting system directories..."
sudo mount --bind /dev "$MOUNT_POINT/dev"
sudo mount -t devpts devpts "$MOUNT_POINT/dev/pts"
sudo mount --bind /proc "$MOUNT_POINT/proc"
sudo mount --bind /sys "$MOUNT_POINT/sys"
sudo mount --bind /run "$MOUNT_POINT/run"
sudo mount --bind /etc/resolv.conf "$MOUNT_POINT/etc/resolv.conf"
sudo mount --bind /tmp "$MOUNT_POINT/tmp"
# 进入chroot环境
echo "Entering chroot environment..."
sudo chroot "$MOUNT_POINT" /bin/bash -c "
echo 'Successfully entered chroot environment'
source /opt/ros/humble/setup.bash
echo 'ROS environment initialized'
cd /crosscompile_ws
echo 'Working directory changed to: \$(pwd)'
exec /bin/bash
"
# 退出chroot后的清理提示
echo "Note: After exiting the chroot environment, please unmount all mount points to ensure data safety."
2. 运行编译
rm ./install/one_package_name ./build/one_package_name ./log/one_package_name -rf
colcon build
3. 退出 chroot 环境
exit
4. 卸载脚本
#!/bin/bash
# 显示使用说明
echo -e "\033[1;33mUsage:\033[0m \033[1;36msudo bash ./auto_unmount_chroot_env.sh\033[0m"
# 检查是否以root运行
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run with sudo or as root"
exit 1
fi
MOUNT_DIR="/mnt/embedded_rootfs"
# 定义需要卸载的路径列表
UNMOUNT_PATHS=(
"$MOUNT_DIR/tmp"
"$MOUNT_DIR/etc/resolv.conf"
"$MOUNT_DIR/run"
"$MOUNT_DIR/sys"
"$MOUNT_DIR/proc"
"$MOUNT_DIR/dev/pts"
"$MOUNT_DIR/dev"
"$MOUNT_DIR/crosscompile_ws"
"$MOUNT_DIR"
)
# 按顺序卸载
for path in "${UNMOUNT_PATHS[@]}"; do
umount -l "$path"
done
# 检查是否所有挂载点都已卸载
ALL_UNMOUNTED=true
for path in "${UNMOUNT_PATHS[@]}"; do
if mountpoint -q "$path"; then
echo "Warning: $path is still mounted"
ALL_UNMOUNTED=false
fi
done
# 根据检查结果输出相应信息
if [ "$ALL_UNMOUNTED" = true ]; then
echo "All mounts have been unmounted."
else
echo "Some mounts are still active. You may need to unmount them manually."
exit 1
fi
VII. 结论
这种方法比主机系统上设置环境变量来实现交叉编辑的方法, 编译变量的设置更加彻底, 交叉编译环境更加可控.
只是网上资料的简单整理, 记录以备忘.
版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/woyaomaishu2/article/details/152937384?spm=1001.2014.3001.5502
本文作者:wzf@robotics_notes
更多推荐
所有评论(0)