news 2026/10/3 13:16:01

Orin NX系统迁移实录:从整盘克隆到环境重建的完整路线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Orin NX系统迁移实录:从整盘克隆到环境重建的完整路线

1. 迁移前先想清楚:这件事的本质是什么

1.1 迁移不是“复制文件”,而是一次系统全量搬迁

拿到“Orin NX 迁移实录”这个题目时,我最先想到的不是命令、不是镜像,而是一个被很多人忽略的问题:你手里的两个 Orin NX,到底差在哪。

我见过不少朋友的迁移教训——把整块 SD 卡或者 eMMC 里的内容直接拷到新板子上,开机卡在启动阶段,或者系统起来了但 GPU 算力没有正确加载,又或者 JetPack 自带的库和驱动版本对不上。原因很简单:迁移不是搬家,不是把文件从旧房间搬到新房间就完事,而是“系统 + 环境 + 数据 + 设备适配”四层内容的整体搬迁。Orin NX 这类嵌入式计算平台,比普通 PC 更敏感的地方在于,它的系统是和硬件紧耦合的,L4T(Linux for Tegra)内核、设备树、固件、启动链都跟模组型号和载板设计绑定。单纯拷贝文件,等于只搬了家具,没搬水电。

所以,做迁移规划的第一步,不是急着插线开机,而是把一个完整系统拆解成几个可独立处理的层次。按我习惯的做法,至少分四层:第一层是 Bootloader 和内核配置,包括 QSPI、eMMC、NVMe 上的启动分区和设备树;第二层是根文件系统,也就是 Ubuntu 或定制系统的核心目录结构、系统库和基础工具;第三层是运行环境,包括 CUDA、cuDNN、TensorRT 这些加速组件,以及 Python 虚拟环境、conda 环境、容器镜像;第四层才是业务数据、代码仓库、数据库、模型权重、日志这些“真正的资产”。

只有把这四层拆清楚,迁移路线才谈得上“总览”。

1.2 迁移的核心矛盾:设备差异 + 环境存量 + 数据连续性

两台 Orin NX 之间的迁移,难点从来不在于“如何执行命令”,而在于三个矛盾的集中碰撞。

第一,设备差异。即便是同一个型号的 Orin NX 模组,如果是 8GB 和 16GB 内存版本,分区表、Swap 配置、内存压力模型都不同;如果模组型号一样但载板不同(比如换了第三方载板、换了电源方案、换了接口布局),设备树、GPIO 定义、摄像头/显示器驱动都可能有差异。这些差异如果不在迁移前识别清楚,等到开机跑业务的时候才会突然爆发,那时候排查成本高得多。

第二,环境存量。嵌入式平台上的环境经常是“用着用着就长出来”的——一个项目装一个 CUDA 版本,另一个项目需要特定 Python 版本,某次调试临时编译了一个内核模块。这种环境存量如果只是“重装系统再逐个重装软件”,会消耗大量时间,而且很容易丢配置。迁移的核心价值应该是把这些存量作为一个整体保留或重建,而不是从零开始。

第三,数据连续性。对于一台跑着实时业务的设备,比如正在做视觉检测、自动化控制或者数据采集的 Orin NX,迁移意味着业务中断。你要考虑的不只是“怎么搬”,还有“搬多久”“怎么验证搬完没坏”。

把这三个矛盾想清楚,你才会理解为什么迁移必须先做规划、再做备份、再动手迁移、最后做验证,而不是上来就开干。

2. 两条主流路线:整盘克隆与全量重装

2.1 整盘克隆:一条命令搬家的诱惑与代价

整盘克隆是最直观的思路:把旧系统做成镜像,写到新板子上,然后修设备树和固件差异。做法上通常是使用dd直接读取旧系统的存储设备,生成一个镜像文件,再通过 USB 烧录或磁盘写入工具恢复到新板子。

这个路线的最大优势是“环境还原度极高”——系统、驱动、库、配置、数据全都在一个镜像里,不需要逐个重装。对于运行环境特别复杂、软件依赖纠缠不清的设备,克隆几乎是唯一能保证“迁移后环境一致性”的方法。

但它有几个需要提前评估的代价。首先是存储差异问题:旧系统如果是 64GB eMMC,新板子如果用 128GB 的 NVMe,你克隆过去的分区大小仍然是按 64GB 布局的,多余空间要么手动扩展分区,要么重新调整 LVM/分区结构。其次是硬件适配问题:直接克隆到一个载板不同、外设不同的新板子上,内核和设备树不对应,大概率会出问题,你需要额外编译或替换设备树,甚至可能需要调整内核配置。

如果你是同一型号载板之间的迁移,克隆是首选;如果模组型号或载板有变化,克隆则意味着“克隆完还要做适配手术”。关于后者我补充一句:这不代表克隆方案就不可用,只是你要把“适配”作为一个独立的后续步骤写进计划里,而不是指望一条命令解决所有问题。

2.2 全套重装:最“笨”但最稳的路

另一条路是把新板子当成一张白纸,从刷入 JetPack / SDK Manager 开始,重新部署系统、安装依赖、迁移数据。

重装的好处非常清晰:新系统与新硬件的适配最干净,不存在残留驱动、旧配置互相干扰的问题。特别是当你遇到旧板上系统已经“脏了”(比如多次卸载安装残留、Python 环境冲突、误删系统组件)的情况,重装反而是一次净化。

坏处也明显:耗时长、依赖多、容易漏配。一个跑了一年多的系统,里面装过多少 apt 包、编译过多少第三方库、写过多少环境变量,你很难一一记住。所以,如果你选择重装路线,前置条件就是“必须先做完整的资产盘点”,把系统里所有需要重新部署的东西列成清单,然后逐项验证。

说实话,行业里很多迁移项目最终走的都是重装路线,不是因为它效率高,而是因为它的失败率最低、问题最可控。对于一个连续跑了很久的系统,迁移时重装一次,反而能把很多“历史包袱”甩掉。

2.3 混合路线是常态

聊到这里你可能已经感觉到了:多数实际迁移并不是二选一,而是混着来——系统层走克隆或刷机(把基础系统和新板子适配好),环境层走重装(CUDA、TensorRT 逐个部署),数据层走备份迁移(rsync、数据库 dump、代码仓库 clone)。我在做迁移规划时,通常先尝试克隆,评估系统能否正常启动和适配;如果不行,就改为在克隆基础上修设备树和新内核;再不行,就直接刷官网镜像,然后重装环境和恢复数据。

这个思路并不是偷懒,而是成本控制——能保住的就保住,保不住的就重建,每一层用最适合的方式处理,这就是“路线总览”里最核心的决策逻辑。

3. 迁移实施前的物料准备与盘点清单

3.1 硬件与外设检查

在敲任何命令之前,先把你手头的硬件情况摸清楚。两台 Orin NX 的模组型号是否一致、内存大小是多少、载板是官方开发套件还是第三方载板、存储用的是 eMMC 还是 NVMe 还是 SD 卡、电源适配器功率是否满足新板要求。这些信息直接决定了后续的操作路径。

以 Orin NX 开发套件为例,官方载板通常支持 USB-C 供电和数据传输,刷机模式(Force Recovery Mode)通过按键和跳线进入。如果是第三方载板,刷机方式、串口位置、按键定义都可能不同,务必先查清楚载板手册再动手。

另外提醒一点:迁移操作前,确保主机(就是你用来刷机或做镜像的那台电脑)有足够的存储空间和稳定的供电。我给 Orin NX 做整盘备份时,镜像文件动辄几十 GB,如果主机磁盘不足,备份到一半就会失败,反而浪费大把时间。

3.2 系统与分区盘点

迁移前要在旧系统上做一次完整的“系统体检”,把这些信息记录下来:系统版本(JetPack 版本、L4T 版本、Ubuntu 版本)、内核版本、当前使用的设备树、分区布局(用lsblk或gnome-disks查看)、启动方式(GRUB、U-Boot、直接 EFI)、是否配置了 Swap 文件和 Swap 分区。

这些数据看起来琐碎,但它们决定了你迁移后能不能正常启动。比如 JetPack 5.x 和 JetPack 6.x 之间,内核版本、驱动模型、CUDA 默认版本都有明显差异;如果新板子上的 JetPack 版本和旧板子不一致,你原本编译的内核模块和第三方库可能全部失效。

提示:做完分区盘点后,建议把fdisk -l、lsblk -f、uname -a、dpkg -l | grep nvidia这些输出分别保存成文件,放到主机上而不是旧板子上,避免迁移过程中数据丢失。

3.3 数据与应用资产的分类记录

数据盘点往往是迁移中最容易被低估的一环。我建议把数据分成三类来记录:第一类是代码和配置文件,包括 Git 仓库、/etc下的改动、服务配置、环境变量配置、Shell 脚本;第二类是环境资产,包括 Python 虚拟环境、conda 环境、Docker 镜像和容器、apt 安装的软件包清单、pip 安装的包清单;第三类是业务数据,包括数据库文件、模型权重、日志、图片和视频素材。

每一类数据应该单独记录其位置、大小、迁移方式。比如代码可以直接用 Git 服务端迁移或者打包拷贝;数据库建议用 dump 方式导出再导入,而不是直接复制数据文件;模型权重文件比较大,建议先压缩再拷贝。

我这里给一张我常用的盘点表模板,你可以直接照抄:

数据类别典型位置推荐迁移方式备注
源码与配置/home/xxx/workspace,/etc,~/.config,~/.bashrcgit clone / rsync注意隐藏文件
Python 环境~/.venv、~/miniconda3/envs、/usr/local/lib/python3.*导出 requirements.txt / conda 导出 yml相同平台下也可直接打包目录
系统包apt/dpkg 列表、/usr/local下源码编译的库apt-mark 清单 + 手动编译脚本源码编译部分麻烦,提前准备脚本
数据库PostgreSQL/MySQL 数据目录pg_dump / mysqldump 导出 SQL不要直接拷贝数据目录
模型与数据/data、/opt、任意大文件路径tar + rsync / 外置存储对拷校验 MD5/SHA256
DockerDocker images/volumedocker save/load、卷目录打包注意镜像跨架构问题

这张表的价值不只是帮你“记得带数据”,它同时是迁移完成后的验收清单,每个条目清了勾,迁移才算真正完成了一半。

4. 关键决策点:镜像、分区、环境这三道坎

4.1 镜像与固件版本的匹配

迁移中最隐蔽的一个坑,是镜像版本和新板子硬件之间的匹配关系。JetPack 和 L4T 的版本是对应的,而 L4T 又对应特定的 U-Boot/BSP 版本。如果你克隆的是一个旧 JetPack 的完整系统镜像,把它烧到需要新 L4T 驱动的载板上,可能会遇到外设无法识别、GPU 不工作甚至启动卡死的问题。

所以,在任何迁移动手之前,先干一件事:查清旧板子当前用的 L4T 版本(dpkg -l | grep nvidia-l4t能看到核心包版本),再查新板子官方支持哪些版本。如果新板子官方只支持更高的 L4T 版本,你就要做一个决定:升级系统版本,还是找与旧系统同版本的官方镜像来做适配。在大多数情况下,新开发套件板载的模组会有一个“出厂支持的最低版本”起点,比如 Orin NX 16GB 模组通常要求 JetPack 5.1.2 以上。

这个匹配工作在迁移开始前完成,你后面就能少走很多弯路。否则等到系统刷完了才发现摄像头不亮、NVMe 不识别,就只能回头查版本兼容表,白白折腾一次。

4.2 分区方案的差异

旧板子上的存储布局未必适合新板子。举一个常见例子:旧系统装在一个 128GB 的 NVMe SSD 上,分区方案是/和/home分开,外加一个 8GB 的 Swap;新板子的存储是一个 64GB 的 eMMC。这个时候如果你直接把整盘镜像写过去,装是能装,但空间肯定不够,启动后很可能因根分区写满而崩溃。

所以,在迁移规划阶段我推荐做以下动作:先记录旧系统的分区布局和数据占用,再对照新板子存储介质去规划分区方案。常用策略是:系统分区尽量按新介质重新布局,或是在克隆后通过growpart+resize2fs扩展分区;数据目录迁移到更大的独立分区。

分区调整这件事,技术细节多,但核心原则简单:迁移规划时就要画出新板子上的存储蓝图,而不是等镜像写好后再想办法。

4.3 环境层怎么搬最省力

环境层包括两部分,一部分是 NVIDIA 生态组件(CUDA、cuDNN、TensorRT、DeepStream 等),另一部分是用户自己的开发环境(Python 虚拟环境、conda、Docker、Node/Python/Go 等工具链)。

NVIDIA 组件这部分,除非你克隆的是型号和 JetPack 版本完全一致的系统,否则我都建议作为“重装项”处理,用官方包管理器或 SDK Manager 安装。原因在于,CUDA 等组件跟内核驱动模块耦合比较紧,跨版本拷贝或混装,很容易造成nvcc版本和实际驱动不匹配,运行时报错难排查。

用户环境部分,最省力的方式是做“环境打包”:Python 项目用pip freeze或poetry export导出依赖清单,conda 环境用conda env export导出 yml;Docker 镜像用docker save保存为 tar 文件再导入。这样一来,迁移时环境是“重建”出来的,带有明确的版本记录,以后出了问题也容易回溯。

注意:pip freeze导出的依赖列表中可能包含一些和平台强相关的包,比如某些 GPU 加速库、系统依赖比较深的包。迁移后如果安装失败,优先检查这些包的源码和版本,必要时升级/降级处理。

5. 迁移执行的推荐顺序

5.1 阶段一:建立数据盘点清单

动手前的第一件事,就是在旧系统上完成完整的资产盘点。打开终端,逐个执行这些命令并保存输出:

  • lsblk -f查看磁盘和分区
  • df -h查看空间占用
  • dpkg -l > dpkg-list.txt记录 apt 包清单
  • pip list > pip-list.txt、conda list > conda-list.txt记录 Python 包
  • docker images、docker ps -a记录容器和镜像情况
  • cat /etc/fstab、cat /etc/network/interfaces(或 netplan 配置)记录网络和挂载配置
  • systemctl list-unit-files --state=enabled记录开机服务

把所有的输出文件集中拷到主机上,本次迁移过程中的每个阶段都会用到这些清单。这一步枯燥,但它是整条路的基石。

5.2 阶段二:备份与验证

备份阶段不要只备份一份,至少做两层:一层是系统层面,用dd对整个系统盘做镜像(或至少对根分区、启动分区做镜像),另一层是数据层面,用 rsync 或 tar 打包用户数据。备份完成后,一定要做校验,比如比较 SHA256 校验和,或者检查 tar 包能否正常列出内容。备份如果不验证,等于没备份。

这里分享一个很容易被忽略的细节:备份前尽量停止正在写入数据的服务,数据库要先 dump 或进入快照模式,否则备份文件内部是一致的,但在线拷贝可能会导致脏数据或文件系统层面不一致。

5.3 阶段三:系统层迁移

系统层的迁移动作,取决于你在第 2 节里选择的路线。如果是克隆路线:先制作镜像(离线状态下用dd或专用工具如 Clonezilla),再写入新板子存储,然后按需调整分区大小,最后适配设备树和内核;如果是重装路线:把新板子进入 Force Recovery 模式,用 SDK Manager 或jetson-disk-image-customizer刷入官方镜像,然后处理分区布局。

这个阶段要留足够长的时间,特别是镜像写入和首次启动检查,不要赶进度。系统能起来只是第一步,你要确认网络正常、GPU 能被tegrastats或nvidia-smi正确识别、外设(相机、显示、串口)都能工作,才说明系统层迁移基本完成。

5.4 阶段四:应用与数据恢复

系统层稳定后,开始恢复业务。顺序建议是:先安装 NVIDIA 组件(CUDA/TensorRT 等),再恢复到用户环境层面(Python 虚拟环境/conda/Docker),然后恢复代码和配置,最后恢复业务数据。

这个顺序有讲究:NVIDIA 组件在最底层,驱动和库的版本定了,上层环境才有依据;代码和配置放在数据之前,是因为代码通常可以直接从仓库拉取或拷贝,适合早点确认无误;数据库等大块数据的恢复往往耗时长、校验步骤多,放在最后也是合理的。

恢复过程中,每装完一个环境,立刻做一个冒烟测试。比如装了 CUDA 跑一下 device query,装了 Python 环境后启动一下项目的 import 检查,装了数据库后执行一次简单的查询。这个习惯能帮你把错误拦截在单项阶段,避免最后混合故障无从下手。

5.5 阶段五:验证与跟进

最后是整套系统的联动验证。很多迁移项目死在“单项都好了,整体跑不起来”的阶段,因为你不知道旧系统里存在哪些跨模块的隐性依赖。所以我建议准备一个端到端的验证用例——如果原来是一个推理项目,就完整跑一遍推理流程;如果是数据采集项目,就接上真实信号源采集一组数据跟旧板子的结果对比。

这个阶段如果发现性能不如旧板,先别急着怀疑硬件。优先排查电源模式(Orin NX 的电源模式直接影响 CPU/GPU 频率)、散热风扇策略、交换分区是否配置完整。有不少“迁移后性能下降”的案例,其实是新系统里默认电源模式是低功耗档位导致的。

6. 验证清单与回归测试

6.1 系统层验证清单

验证项命令/方法预期结果
内核版本uname -a与迁移规划中记录的版本一致或符合新板要求
L4T 版本dpkg -l | grep nvidia-l4t-core版本匹配 JetPack 组合
磁盘分区lsblk -f分区布局正确,空间容量符合新板规划
启动日志dmesg | grep -i error无硬件错误或驱动崩溃
GPU 识别/usr/bin/tegrastats或nvidia-smi能正确显示 GPU 使用率/驱动版本
电源模式nvpmodel -q处于预期模式(如 MAXN)
网络ip a、ping网关有线/无线网络正常

6.2 功能层验证清单

验证项方法预期结果
外设接入接入相机/USB 设备后查看lsusb、dmesg设备被正确枚举
显示输出xrandr或连接显示器分辨率与刷新率正常
代码仓库git clone/git status仓库完整,无损坏
Python 环境激活虚拟环境后运行import关键模块无缺少依赖
数据库连接数据库并执行若干 SQL数据完整,权限正常
业务服务systemctl status <服务名>服务正常启动并运行

6.3 性能层的回归观察

性能验证不要只看一个点,我建议从三个维度观察:CPU 满载时的温度与频率稳定性、GPU 推理的耗时对比、存储读写速率(用dd或fio简单测试)。如果新板子的性能和旧板子相比差太多,优先排查散热和电源模式,其次是驱动版本和 BIOS/固件设置。

另外,建议把迁移前的基准数据记录下来。迁移前跑过哪些 benchmark、大概跑了多长时间,迁移后拿同样的测试再跑一遍,结果一对比就知道系统层有没有问题。这个对比数据也方便你在后续应用层排查性能瓶颈时作为参考。

7. 几个容易忽略的坑与我的实操心得

7.1 备份和拷贝是两回事

有次我帮一台设备做迁移,对方说“我已经 cp -a 全部拷出来了”,结果到新板子上恢复的时候,发现一堆符号链接失效、文件属主变了、SELinux/AppArmor 上下文丢了。原因不复杂:cp -a在普通目录拷贝时好用,但面对/etc、/var这种包含链接、设备文件、特殊权限的系统目录,效果并不理想。

所以我的建议是:系统层用分区镜像方案,数据层用 rsync 或 tar 打包,并且每次备份完在独立目录中做一次“试恢复”。试恢复就是拷贝出来解压验证内容和无损坏,再在原机上对比文件数量、大小和 MD5。这一步花不了多少时间,但能救你一次。

7.2 环境里的隐性依赖

很多迁移后“跑不起来”的问题,都出在用户环境里那些说不清道不明的隐性依赖上。比如你某个 Python 模块明明装过,但它是依赖一个系统库的版本;或者你的项目里通过环境变量隐式引用了一个路径,比如/opt/xxx/,迁移后这个路径根本不存在。

应对思路是:迁移前在旧系统上抓一次“运行时依赖快照”——通过ldd查看关键二进制依赖的动态库、通过env记录环境变量、通过ls -l /lib/modules/记录内核模块。必要时用strace跑一遍核心程序启动过程,看看它打开过哪些文件、访问过哪些路径。这个快照是迁移后对照排查的“底稿”。

7.3 不要在旧系统上边迁移边跑业务

这个坑我踩过,而且印象特别深。当时为了尽量缩短停机时间,我在旧板子还在跑任务的时候就直接做系统镜像备份,结果备份文件里某些数据偏移处就是写入了一半的状态,恢复后数据库文件损坏,折腾了整整一天才把数据捞回来。

迁移这件事,容不得侥幸。宁可先停服务、再备份、再迁移,把停机窗口做得干净利落,也不要为了省半个小时把整个过程拖住。停机时间的长短并不是衡量迁移水平的标准,迁移完成后的系统健康才是。

7.4 关于“两个 Orin NX”的特殊之处

这个系列标题里提到“两个 Orin NX”,在实操中通常意味着两台设备需要同时或先后完成迁移。我的建议是:先选一台作为“先导机”,跑完一整套流程,把问题全部踩平、把步骤文档完善,再复制到第二台。这样第二台的迁移时间通常能压缩到第一台的 30% 左右,而且成功率更高。

如果你手头的两台板子硬件配置一致,连镜像都可以共用;但如果存在差异(比如内存不同、存储介质不同),镜像需要分别制作和处理,不要偷懒用同一份镜像硬烧。另外,两台板子在迁移后要分别做性能验证,不能因为第一台没问题就默认第二台也没问题——模组个体之间的体质差异、散热条件差异是真实存在的。

我个人在实际操作中的体会是:迁移这件事,百分之八十的成败在规划阶段就已经决定了。把层次拆清楚、把数据盘点做全、把每条路线的代价想明白,后面执行起来反而简单。如果你正准备从完整系统迁移到空板,不妨先把这篇路线总览看两遍,再对照盘点表列一次清单,等到真正动手刷机的时候,你会发现每一步都在计划之内,心里是踏实的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 13:14:35

基于fMRI分析思路的宽场光学成像数据处理工具箱

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

作者头像 李华
网站建设 2026/10/3 13:14:28

常用半导体封装尺寸与PCB焊盘设计速查:从DIP到BGA

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

作者头像 李华
网站建设 2026/10/3 13:13:45

把《重点总结》变高分指南:自考数据结构备考策略

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

作者头像 李华
网站建设 2026/10/3 13:13:36

锂电池热失控预警怎么做?多参数融合+边缘AI的工程实践

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

作者头像 李华
网站建设 2026/10/3 13:13:03

编译原理实验拆解:词法分析器与递归下降语法分析器实现

简介&#xff1a;面向NUAA南航计算机科学与技术、物联网工程专业的编译原理课程实验资源包&#xff0c;聚焦词法分析与语法分析两大核心模块&#xff0c;提供可运行的C源码与测试代码&#xff0c;帮助学习者从零实现编译器前端组件。压缩包共7个文件&#xff0c;包含2个cpp源程…

作者头像 李华
网站建设 2026/10/3 13:13:00

el-table实现Excel式方向键移动光标:单元格高亮与滚动跟随实战

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

作者头像 李华