嵌入式Linux入门:i.MX8MM开发板Hello World实战与交叉编译详解
1. 项目概述从零到一的嵌入式Linux“Hello World”对于任何一位刚踏入嵌入式Linux开发大门的朋友来说第一个程序“Hello World”的意义远不止于在屏幕上打印一行文字那么简单。它更像是一把钥匙一把用来验证整个开发环境、工具链、编译流程以及最终在目标板上运行是否畅通无阻的钥匙。尤其是在像i.MX8MM这样功能强大的异构多核处理器平台上这个简单的程序背后串联起了从宿主机你的电脑到目标板开发板的完整知识链路。很多人觉得这太基础直接跳过结果在后续开发驱动或应用时被交叉编译、库依赖、文件系统部署等问题搞得焦头烂额。今天我们就以迅为的i.MX8MM开发板为舞台彻底拆解这个“Hello World”程序我会把每一步操作背后的逻辑、可能踩的坑以及我积累的一些小技巧毫无保留地分享给你。无论你是刚刚从单片机转向Linux的工程师还是在校学生跟着走完这一趟你就能建立起一个清晰、稳固的嵌入式Linux开发基础认知框架。2. 开发环境搭建与核心工具链解析在动手写代码之前一个稳定、高效的开发环境是重中之重。嵌入式Linux开发通常采用“交叉编译”模式即在性能强大的x86电脑宿主机上使用专门的编译器生成能在ARM架构处理器如i.MX8MM上运行的代码。这个专门的编译器就是交叉编译工具链。2.1 宿主机Linux环境选择与配置虽然你可以在Windows上通过虚拟机和共享目录进行开发但我强烈建议直接将宿主机安装为Linux发行版例如Ubuntu 20.04 LTS或22.04 LTS。这能避免大量因文件系统、权限、符号链接等差异导致的问题让开发过程更纯粹。注意迅为提供的资料通常基于特定的Ubuntu版本进行测试。为了最大程度兼容建议优先使用资料指定的版本。如果使用其他版本或发行版遇到依赖库问题时需要自行灵活解决。安装好Ubuntu后第一件事是更新软件源并安装一些基础开发工具sudo apt update sudo apt upgrade -y sudo apt install -y build-essential git vim net-tools openssh-serverbuild-essential包含了gcc, g, make等本地编译所需的核心工具。虽然我们主要用交叉编译器但一些配置脚本可能需要本地编译器。openssh-server方便后续通过ssh远程登录到宿主机进行操作特别是在虚拟机环境中非常方便。2.2 交叉编译工具链的获取与安装这是整个环境搭建的核心。工具链决定了你的代码能否正确地在目标架构上运行。对于i.MX8MMCortex-A53核心我们需要ARM 64位aarch64的工具链。1. 工具链来源选择通常有三个来源芯片原厂NXP提供通过NXP官方Yocto项目构建与BSP板级支持包匹配度最高推荐首选。你可以在NXP官网搜索“i.MX8MM Yocto Project”找到并下载对应的aarch64-poky-linux-gcc工具链。开发板厂商迅为提供迅为通常会将其适配、测试好的工具链随资料提供。这是最省心、兼容性最有保障的方式。第三方工具链如Linaro通用性强更新活跃但可能需要自行处理一些库依赖。2. 安装与配置假设你从迅为资料中获得了名为gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz的工具链包。# 1. 创建并进入工具链存放目录 sudo mkdir -p /opt/toolchains sudo chown $USER:$USER /opt/toolchains # 将目录所有权改为当前用户避免每次sudo cd /opt/toolchains # 2. 解压工具链 (请将路径替换为你的实际文件路径) tar -xvf /path/to/your/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz # 3. 配置环境变量将其添加到当前用户的bashrc文件中 echo export PATH/opt/toolchains/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH ~/.bashrc echo export ARCHarm64 ~/.bashrc echo export CROSS_COMPILEaarch64-linux-gnu- ~/.bashrc # 4. 使环境变量立即生效 source ~/.bashrc关键点解析ARCHarm64告诉编译系统如Linux内核的makefile我们目标架构是ARM 64位。CROSS_COMPILEaarch64-linux-gnu-这是交叉编译器的前缀。当执行make命令时它会自动调用aarch64-linux-gnu-gcc、aarch64-linux-gnu-ld等工具。你可以通过which aarch64-linux-gnu-gcc来验证是否配置成功。2.3 验证工具链与目标板连接工具链安装好后写一个最简单的C程序验证其能否生成ARM可执行文件。// test_toolchain.c #include stdio.h int main() { printf(Toolchain test OK!\n); return 0; }使用交叉编译器编译aarch64-linux-gnu-gcc -o test_arm test_toolchain.c使用file命令查看生成的文件格式file test_arm如果输出中包含ELF 64-bit LSB executable, ARM aarch64恭喜你交叉编译工具链工作正常。接下来确保你的i.MX8MM开发板已经启动并可以通过网络如SSH或串口与宿主机通信。通常迅为开发板默认IP是192.168.1.xxx段。使用ping命令测试连通性并使用scp或tftp为后续传输程序做好准备。3. “Hello World”程序深度剖析与交叉编译实战现在让我们进入正题编写、编译并分析我们的第一个程序。3.1 编写标准的Hello World程序创建一个工作目录并编写源码mkdir ~/hello_world cd ~/hello_world vim hello.chello.c内容如下#include stdio.h #include unistd.h int main(int argc, char *argv[]) { int count 0; while(1) { printf(Hello, Embedded Linux on i.MX8MM! Count: %d\n, count); sleep(1); // 每秒打印一次方便观察 } return 0; }这个程序比简单的打印多了一个循环和计数目的是为了让程序持续运行方便我们后续观察进程、测试终止等操作更具实践意义。3.2 交叉编译过程详解与Makefile编写直接使用命令行编译aarch64-linux-gnu-gcc -o hello hello.c -static这里的关键参数是-static它代表静态链接。为什么这里建议使用静态链接动态链接程序运行时需要依赖目标板文件系统中的动态链接库如libc.so。如果目标板上的库版本与编译时工具链的库版本不一致会导致程序无法运行报错“No such file or directory”或“version GLIBC_2.29‘ not found”。静态链接编译器会将所有需要的库函数代码都打包到最终的可执行文件hello里。这样生成的文件会比较大可能几MB但它的优点是自成一体不依赖目标板上的任何动态库。对于第一个测试程序这能排除库依赖问题确保程序一定能跑起来是快速验证环境的最佳选择。对于更复杂的项目我们通常会编写Makefile来管理编译规则。# Makefile CROSS_COMPILE ? aarch64-linux-gnu- CC : $(CROSS_COMPILE)gcc TARGET : hello all: $(TARGET) $(TARGET): hello.c $(CC) -o $ $^ -static clean: rm -f $(TARGET) .PHONY: all clean使用make命令即可编译。Makefile的好处在于可以方便地管理多个源文件、添加编译选项如优化等级-O2、调试信息-g等。3.3 可执行文件传输到开发板编译生成的hello文件在宿主机上我们需要将其放到开发板的文件系统中。有多种方式1. 使用SCP推荐安全方便前提是开发板已开启ssh服务且网络互通。scp hello root192.168.1.100:/home/root/输入开发板密码迅为默认常为root或空即可。2. 使用TFTP适用于无SSH环境或内核开发阶段需要在宿主机搭建TFTP服务器开发板通过tftp命令获取。# 在开发板U-Boot或Linux命令行下 tftp ${loadaddr} 192.168.1.50:hello # ${loadaddr}是内存地址需根据实际情况修改3. 使用U盘/SD卡最原始但可靠的方式将文件拷贝到存储介质再挂载到开发板。4. 在i.MX8MM开发板上运行与调试4.1 登录开发板并运行程序通过串口终端或SSH登录到开发板ssh root192.168.1.100进入程序所在目录并运行cd /home/root chmod x hello # 添加可执行权限 ./hello你应该能看到终端每秒打印一行“Hello, Embedded Linux on i.MX8MM! Count: x”。4.2 程序运行状态监控与管理一个在后台持续运行的程序我们需要学会如何管理它。放入后台运行在程序运行时按CtrlZ可以将其挂起然后输入bg命令让其到后台继续运行。查看进程使用ps命令查看。ps aux | grep hello你会看到进程的PID进程ID、CPU和内存占用等信息。终止进程kill PID # 发送默认的TERM信号请求进程终止 kill -9 PID # 发送KILL信号强制立即终止进程查看程序输出如果程序在后台运行其输出可能不会显示在当前终端。可以通过jobs命令查看后台作业或用fg命令将其调回前台。4.3 动态链接与静态链接的实践对比现在我们来验证之前提到的库依赖问题。重新编译一个动态链接版本# 在宿主机 aarch64-linux-gnu-gcc -o hello_dynamic hello.c # 去掉 -static 参数 scp hello_dynamic root192.168.1.100:/home/root/在开发板上运行./hello_dynamic可能出现两种情况成功运行说明开发板文件系统中的C库版本与工具链中的完全兼容。这是最理想的情况。失败并报错例如/lib/aarch64-linux-gnu/libc.so.6: version \GLIBC_2.34 not found。这说明工具链使用的库版本比开发板上的更新。如何查看可执行文件的依赖使用交叉工具链中的readelf或objdump命令在宿主机上执行aarch64-linux-gnu-readelf -d hello_dynamic | grep NEEDED或者使用file命令也能看出是动态链接file hello_dynamic # 输出会包含 dynamically linked 和 interpreter /lib/ld-linux-aarch64.so.1解决动态链接依赖问题方案A推荐使用与开发板根文件系统同源的工具链进行编译。例如如果你的根文件系统是用Yocto或Buildroot构建的就使用对应构建系统产生的SDK工具链。方案B将缺失的库从工具链的sysroot目录拷贝到开发板的对应路径下但要注意库之间的兼容性容易引发系统不稳定。方案C在开发板上使用ldd命令检查缺失的库然后单独安装它们如果开发板支持包管理如opkg或apt。这个对比实验能让你深刻理解嵌入式Linux中“构建环境”与“运行环境”一致性的重要性。5. 进阶探索从Hello World延伸出的关键技能第一个程序跑通只是起点。围绕这个简单的程序我们可以深入好几个对后续开发至关重要的方向。5.1 使用GDB进行交叉调试打印日志是基础调试手段但真正的“大杀器”是GDB。我们需要在宿主机上安装交叉编译版本的GDB客户端aarch64-linux-gnu-gdb并在开发板上运行GDB服务端gdbserver。1. 在开发板上启动gdbservergdbserver :2345 ./hello # 表示在2345端口启动调试服务等待宿主机连接如果开发板没有gdbserver需要从工具链的sysroot中拷贝或通过包管理器安装。2. 在宿主机上启动交叉GDB并连接aarch64-linux-gnu-gdb ./hello (gdb) target remote 192.168.1.100:2345 (gdb) break main # 在main函数设置断点 (gdb) continue # 继续运行连接成功后你就可以像调试本地程序一样进行单步执行、查看变量、查看堆栈等操作。这对于分析复杂的程序逻辑或死机问题不可或缺。5.2 将Hello World集成到根文件系统我们每次通过scp传程序太麻烦。更正规的做法是将程序打包进开发板的根文件系统Rootfs镜像中。以Buildroot为例在Buildroot的output/target目录下即根文件系统的内容创建/usr/bin/等目录如果不存在。将静态编译的hello程序拷贝到该目录下。重新制作根文件系统镜像如make或运行Buildroot的打包脚本。将新镜像烧录到开发板或SD卡。这样开发板每次启动后直接在命令行输入hello就可以运行你的程序。你还可以进一步修改/etc/init.d或systemd服务文件让hello作为守护进程在开机时自动启动。5.3 性能与资源监控初探程序跑起来后它消耗了多少资源使用开发板上的工具可以简单评估top或htop命令实时查看进程的CPU和内存占用率。运行./hello 后在top里观察hello进程的%CPU和%MEM。time命令测量程序运行时间。虽然我们的程序是死循环但你可以修改代码让其执行固定次数后退出然后用time ./hello测试。查看文件大小对比静态链接和动态链接版本的大小ls -lh hello hello_dynamic你会发现静态链接的hello可能比动态链接的hello_dynamic大一个数量级。这就是空间换来的独立性。6. 常见问题排查与实战心得在实际操作中你几乎一定会遇到下面这些问题。我把它们和我的解决思路整理出来希望能帮你节省大量时间。6.1 问题速查表问题现象可能原因排查步骤与解决方案编译错误aarch64-linux-gnu-gcc: command not found1. 工具链未安装。2. 环境变量未正确设置或未生效。1.echo $PATH查看路径是否包含工具链的bin目录。2. 检查~/.bashrc文件确认导出语句无误并执行source ~/.bashrc。3. 使用绝对路径执行编译器如/opt/toolchains/.../bin/aarch64-linux-gnu-gcc --version。开发板运行程序报错No such file or directory1. 文件确实不存在或路径错误。2.最常见程序是动态链接但缺少所需的动态库。3. 程序架构不匹配如用了x86程序。1.ls -l确认文件存在且有执行权限(chmod x)。2.使用file ./hello确认是ARM可执行文件。3.使用ldd ./hello在开发板上查看缺失的库。若ldd显示not a dynamic executable则是静态链接应能直接运行。若显示某些库not found需解决库依赖。程序运行后无输出或立即退出1. 程序逻辑问题如无限循环条件错误。2. 标准输出被重定向或缓冲区问题。3. 交叉编译器优化导致异常。1. 在代码中增加日志或使用printf调试。2.printf输出有行缓冲确保字符串以\n结尾或调用fflush(stdout)。3. 编译时尝试不加优化-O0并加入调试信息-g。scp或网络连接失败1. 开发板IP地址不正确或未开机。2. 网络不在同一网段。3. 开发板防火墙或ssh服务未开启。1.ping 开发板IP测试连通性。2. 检查宿主机和开发板的子网掩码和网关。3. 通过串口登录开发板检查ifconfig确认IP检查ps静态编译的程序仍然运行失败1. 使用了不兼容的系统调用或特性。2. 文件系统权限或属性问题。3. 极少数情况工具链与内核版本严重不匹配。1. 使用strace ./hello跟踪系统调用看在哪一步出错。2. 检查文件系统是否为只读mount命令查看尝试在/tmp目录下运行。3. 确认开发板内核版本尝试使用更保守的工具链。6.2 实操心得与避坑指南工具链版本管理不同的项目、不同的内核或文件系统可能需要不同的工具链。我习惯在/opt/toolchains/下为每个项目建立子目录并在项目的Makefile或环境配置脚本中显式指定绝对路径避免全局环境变量冲突。坚持使用版本控制即使是hello.c这样的单文件也建议从一开始就使用git管理。git init,git add .,git commit -m “first hello”。这个习惯会在项目复杂后拯救你。善用strace和ltrace当程序行为诡异时strace跟踪系统调用和ltrace跟踪库函数调用是强大的诊断工具。在开发板上运行strace ./hello你可以看到程序打开哪些文件、进行哪些网络通信、在哪里收到信号等很多权限问题、路径问题一目了然。编译警告就是错误对待交叉编译产生的警告要像对待错误一样严肃。很多内存越界、类型不匹配的问题在x86上可能侥幸运行在ARM上就会导致段错误Segmentation Fault。编译时务必加上-Wall -Wextra选项并尽力消除所有警告。理解文件系统布局知道你的程序应该放在哪里。临时测试可以放/home/root或/tmp。正式部署时用户程序通常放/usr/bin或/usr/local/bin配置文件放/etc日志放/var/log。遵循FHS文件系统层次结构标准能让你的系统更规范。从在屏幕上打印出第一行“Hello World”开始你就已经成功打通了嵌入式Linux应用开发的全链路编码、交叉编译、传输、运行、调试。这个过程里遇到的每一个错误和解决它的方法都比那行文字本身更有价值。记住静态链接是你的“安全网”在初步验证时大胆使用而理解动态链接和系统依赖则是你走向专业开发的必经之路。接下来你可以尝试为这个程序增加一个命令行参数比如./hello -n 5让它打印5次后退出这又会涉及到getopt函数的使用和参数解析一步步地去探索更广阔的世界吧。

相关新闻

STM32 GPIO八种工作模式详解:从原理到实战应用

STM32 GPIO八种工作模式详解:从原理到实战应用

1. 从引脚到系统:理解GPIO模式的必要性很多刚开始接触STM32的朋友,拿到开发板后第一个程序往往是点灯。照着例程,配置一下引脚,设置一下高低电平,灯就亮了,感觉好像挺简单。但当你需要驱动一个按键、连接一…

2026/10/17 7:15:38 阅读更多 →
C/C++工程师的HTTP协议实战指南:从字节流到高性能服务架构

C/C++工程师的HTTP协议实战指南:从字节流到高性能服务架构

1. 项目概述:从C/C视角重新审视HTTP交互最近在带团队做几个高并发的后台服务,发现不少工作三五年的C/C工程师,对HTTP协议的理解还停留在“发个请求,收个响应”的层面。一旦遇到连接池管理、长连接复用、或者需要手动构造复杂请求体…

2026/10/16 1:45:38 阅读更多 →
3D建模高模与低模:从概念到PBR工作流的完整解析

3D建模高模与低模:从概念到PBR工作流的完整解析

1. 项目概述:从“高模”与“低模”的日常困惑说起如果你刚接触3D建模,或者正在学习游戏美术、影视特效,那么“高模”和“低模”这两个词一定是你绕不开的坎。它们听起来像是一对孪生兄弟,却又代表着建模流程中两个截然不同的阶段和…

2026/10/16 15:24:03 阅读更多 →

最新新闻

拆解供应链体系:核心指标、牛鞭效应与信息流优化实践

拆解供应链体系:核心指标、牛鞭效应与信息流优化实践

做供应链这个领域十几年,每次跟人介绍自己的工作,十次有八次会被理解成“管仓库的”或者“搞运输的”。这也难怪,供应链这个词听起来确实抽象,但它本质上就是一家企业、甚至一个行业能不能赚钱、能不能活下去的底层逻辑。这篇是这…

2026/10/17 7:15:05 阅读更多 →
职称论文难写?实测4款AI工具助你选题、写作、润色、查重一步到位

职称论文难写?实测4款AI工具助你选题、写作、润色、查重一步到位

职称论文这东西,写过的人都懂:白天要上班,晚上要带娃,只能靠周末和深夜去挤那几千字的论文。更要命的是,评审专家一个个火眼金睛,既要看你的理论深度,又要看你有没有工程实践支撑,最…

2026/10/17 7:15:05 阅读更多 →
基于YOLOv8与PyQt5的桌面端七类人脸情绪识别系统实战

基于YOLOv8与PyQt5的桌面端七类人脸情绪识别系统实战

做一个人脸情绪识别系统,最怕的就是搞成“实验室玩具”——模型在测试集上准确率挺高,一放到真实摄像头前就原形毕露;或者模型勉强能跑,但界面粗糙得没法给别人演示。这篇文章记录一下我用YOLOv8配合PyQt5实现一套桌面端七类情绪识…

2026/10/17 7:15:05 阅读更多 →
具身智能的Scaling Law:零样本泛化如何被数据规模与质量双重驱动

具身智能的Scaling Law:零样本泛化如何被数据规模与质量双重驱动

1. 这不是“又一个大模型突破”,而是机器人智能演进的关键拐点最近在内部技术复盘会上,我反复回看一段30秒的视频:Helix 2.5机械臂在完全陌生的家庭厨房里,第一次见到微波炉、第一次接触带盖玻璃餐盒、第一次面对没拆封的即食燕麦…

2026/10/17 7:15:05 阅读更多 →
医院NTP时钟服务器:医疗时间同步的底层生命线

医院NTP时钟服务器:医疗时间同步的底层生命线

1. 为什么医院里一台“不起眼”的NTP时钟服务器,比很多设备更不能出错?你进过三甲医院的急诊抢救室吗?心电监护仪屏幕上的时间跳动、护士站电子病历系统右下角的数字、检验科全自动生化分析仪打印报告的时间戳、手术室麻醉记录单自动生成的起…

2026/10/17 7:15:05 阅读更多 →
Ling-3.1-flash工具调用完整教程:Function Calling接入指南,快速打造你的AI编程助手

Ling-3.1-flash工具调用完整教程:Function Calling接入指南,快速打造你的AI编程助手

人工智能大模型基础模型推理模型 【免费下载链接】Ling-3.1-flash 项目地址: https://ai.gitcode.com/hf_mirrors/inclusionAI/Ling-3.1-flash 点击查看 免费下载 Ling-3.1-flash 是 inclusionAI 开源的 560B 总参数 MoE 大模型,每 token 激活 25B 参数…

2026/10/17 7:14:05 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →