news 2026/9/18 23:53:34

Linux环境下IAR嵌入式工具链安装配置与命令行编译实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux环境下IAR嵌入式工具链安装配置与命令行编译实践

很多嵌入式工程师一提IAR,脑子里第一反应就是Windows下的EWARM IDE。我自己干了这么多年固件开发,以前也是这个印象,直到公司开始搭CI流水线、要用Linux服务器统一出固件包,才不得不正视一个问题:IAR到底能不能在Linux环境下跑起来?答案是能,而且官方支持一直都有,只是安装和适配思路跟Windows版本完全不一样。网上的资料零零散散,新手照着折腾很容易卡在许可证、32位库、环境变量这些地方半天出不来。

这篇东西就是我踩过一轮坑之后整理的入门级安装指导,覆盖从下载Linux版IAR工具链、装好底层依赖、配置License,到在命令行下完成一次STM32工程编译的完整链路。适合刚接触Linux开发环境、或者正在被“项目必须上服务器构建”逼着迁移的嵌入式工程师。照着做一遍,你就能在纯命令行里把固件编出来,不再依赖Windows下的IDE。

1. 环境适配的整体思路:先搞清Linux版IAR到底是个什么形态

1.1 Linux版IAR不是拿来“模拟Windows”的

很多人第一次接触Linux版IAR,容易有个误解:以为它是把Windows那个图形界面搬到Linux下跑。实际不是。Linux版IAR是纯粹的命令行工具链,包含编译器(比如ARM系列用的是iccarm)、汇编器、链接器和一些辅助工具,不包含可视化IDE界面。也就是说,你在Linux上要做的是“命令行构建”,不是“打开工程点编译”。日常写代码、查变量、打断点调试,我个人还是建议留在Windows的IAR IDE里做,Linux版更多是给构建服务器、批量编译、版本发布或者Docker容器化场景用的。

想通了这一点,适配思路就清晰了:我们要解决的核心问题就三个。第一,把工具链正确装到Linux系统上,补齐它依赖的库文件。第二,把许可证(License)配好,让命令行编译器能通过授权校验。第三,学会用命令行调用IAR的编译和链接工具,再顺手跟Makefile或CMake结合起来,形成一套可重复的自动化构建流程。这三个问题分别对应后面的第二、三、四章,逐个击破就行。

1.2 为什么要在Linux上跑IAR:场景驱动的三个典型诉求

平时用Windows IDE开发,够用且顺手,为什么要折腾Linux?我在实际项目里遇到的典型诉求有三个。

第一个是CI/CD自动化构建。代码仓库在GitLab或Jenkins上,每次提交都要自动编译、跑静态检查、生成固件包,而构建服务器为了稳定和资源利用率,基本都是Linux系统。这时候如果只有Windows版IAR,要么给服务器装Windows,要么搞个Windows虚拟机,维护成本很高。直接用Linux版工具链,构建流程能写进脚本,干净利落。

第二个是批量构建和参数化编译。比如一套代码要出几十个硬件版本的固件,区别只是宏定义不同、外设配置不同。在Linux下写个脚本循环调用编译器,改一个宏、编一次、出包,整个过程完全自动化。Windows IDE里手动点一遍几十次,人会疯的。

第三个是发布环境和开发环境隔离。固件发布一般要求环境一致性,否则编译产物可能不同。用Linux版IAR打一个Docker镜像,把工具链固定版本放进去,谁构建、什么时候构建,结果都一样。这一点对产品通过认证、追溯编译环境非常有用。

1.3 版本对应关系:别下载错包

IAR官方在Linux版的版本命名上跟Windows是对应的。比如你是EWARM 9.50版本,那就找对应的Linux版9.50安装包,编译器版本、支持的内核、头文件目录结构都是一一对应的。这个对应关系非常重要,因为你的工程是用某个版本的IAR创建的,换一个版本编译,映射文件、链接配置、内核对齐方式都可能出现微妙差异。

下载时注意包名里的平台标识,一般会有linux字样,比如EWARM-9.50.3-linux.tar.gz这类命名。如果进官网下载页面看到文件名带-linux,基本就是对的。有的老版本还会要求特定的glibc版本,安装前最好看一眼官方发布说明里的“System Requirements”章节,确认你的Linux发行版符合要求。别小看这一项,我遇到过有同事装老版本IAR,在Ubuntu 22.04上直接缺32位库,折腾了半天才装好。

2. 从下载到安装:Linux版IAR的完整落地过程

2.1 下载渠道与前置资料的准备

Linux版IAR的安装包从哪里来?最稳妥的渠道就是IAR官网。如果你所在公司已经购买了IAR授权,一般会在IAR Account的管理后台里能看到对应版本的下载入口。没有企业授权的话,也可以去官网注册试用版,试用版同样提供Linux安装包,只是License有时间限制或者代码大小限制。

我在下载前习惯先把三样东西准备好:安装包、License激活信息(序列号或者License Server地址)、官方Release Notes。前两个好理解,Release Notes容易被忽略。其实安装完如果遇到某条命令报参数不对、某个内核文件找不到,查询Release Notes往往比在网上搜答案快得多,因为它会写明当前版本已知问题和环境要求。

下载完成后,先校验一下文件完整性,通常官网会给MD5或者SHA256。千万别省这一步,我在外网上传下载的时候遇到过文件不完整导致解压失败的情况,多花几分钟校验能省一晚上排查时间。

2.2 依赖库安装:Ubuntu/Debian系缺什么补什么

Linux版IAR在较新版本里虽然是64位程序,但历史上很长一段时间核心编译工具都是32位或者依赖32位运行库。所以你在Ubuntu或者Debian上装完后,直接运行iccarm --version很可能会报No such file or directory,或者提示缺少libc.so.6libstdc++.so.6之类的动态库。

这不是路径配错了,是系统里缺32位兼容库。我用的是Ubuntu 20.04 LTS,安装命令大致是这样:

sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install -y libc6:i386 libstdc++6:i386 libncurses5:i386 zlib1g:i386

Debian系发行版基本类似。如果是CentOS或者RHEL,则要开EPEL源后装glibc.i686libstdc++.i686这些包。装完可以用ldd命令检查IAR工具的依赖是否满足,比如:

ldd /opt/iarsystems/arm/armbuild_9503/common/bin/iccarm

如果输出里没有“not found”,说明依赖齐了。这一条可以说是Linux版IAR安装里最容易卡住的地方,我在帮同事排查时,十个有九个问题出在这。

2.3 解压安装实操:tar包安装步骤

IAR的Linux版安装包里会带一个安装脚本,也有部分版本直接提供deb包。我以最常见的tar.gz方式做一遍完整的安装流程。

tar zxvf EWARM-9.50.3-linux.tar.gz cd EWARM-9.50.3-linux chmod +x install.sh sudo ./install.sh

安装脚本会让你确认安装路径,默认一般是/opt/iarsystems。如果当前Linux用户对/opt没有写权限,就一定要用sudo执行。安装完成后,工具链的典型位置是:

/opt/iarsystems/arm/armbuild_9503/bin/iccarm

不同版本对应的路径尾缀不一样,工具链根目录下还会有一堆附加工具,比如ielfx(ELF文件操作工具)、ielftool(格式转换工具)、iarbuild(构建驱动)等,后面写自动化脚本时基本都会用到。

安装过程中如果提示缺少图形库,比如报libgtk-3.so.0找不到,这不是核心编译工具依赖,而可能是某个辅助界面工具需要的。可以用apt-get install libgtk-3-0补上,也可以直接忽略,不影响命令行编译。

2.4 环境变量配置:让命令行直接找到工具

安装完成后,每次编译都要写一长串绝对路径非常痛苦,所以要把工具链bin目录写进PATH。我习惯在/etc/profile.d/下新建一个脚本,比如/etc/profile.d/iar.sh,内容如下:

export IAR_HOME=/opt/iarsystems export PATH=$IAR_HOME/arm/armbuild_9503/bin:$PATH

然后执行:

source /etc/profile.d/iar.sh

这样所有用户登录后都能直接用iccarm命令了。注意如果你改了profile.d的脚本,已经打开的终端不会立即生效,需要重新登录或者source一次。每次装新版IAR或者切换版本,都要同步改这个PATH,这里我建议在脚本里把版本号路径单独抽成变量,方便以后切换。

2.5 验证安装是否成功:跑一个最简单的hello编译

为了方便验证环境是否真的可用,我会写一个最简的C文件,直接调用IAR编译器看能否生成object文件。这里不需要完整的工程配置,只是确认编译器本身没问题。

mkdir -p ~/iar_test && cd ~/iar_test printf 'int main(void){ return 0; }\n' > test.c iccarm test.c -o test.o

顺利的话会在当前目录生成test.o。这一步如果报的错和License有关,就进入第三章的许可证配置环节;如果报找不到头文件、找不到库,则先回到2.2检查依赖。确认test.o生成后,说明工具链的核心三件套(编译、汇编、链接)已经可以跑通了,后面就只剩许可证和构建脚本的问题。

3. 许可证配置与IAR License Manager的使用

3.1 Linux下License的两种形态

IAR的License在Linux下主要分两类:节点锁定(Node Locked)和浮动License服务器(Floating License Server)。节点锁定是指License绑定本机的某个硬件特征码,只要在这台机器上,编译器就能直接验收通过。浮动License则是License安装在局域网内一台专门的服务器上,所有编译机器通过网络向License服务器借用授权,适合团队使用的构建集群。

判断你的授权属于哪种,看公司买License时收到的邮件就知道了。节点锁定的会明确写到“Node Locked”,浮动License会给一个服务器地址和端口。如果公司用的是IAR 9.x版本,License管理统一走IAR License Manager(简称LMS)体系,不再像老版本那样生成一个单独文件。

3.2 IAR License Manager在Linux下的角色

IAR License Manager在Linux下是一个后台服务,负责管理本机License的注册与授权。安装工具链时一般会自动安装LMS组件,路径通常在/opt/iarsystems/common/bin下。可以用下面的命令看一眼:

ls /opt/iarsystems/common/bin/

里面能找到类似IarLicenseManager的可执行文件。LMS的作用是代替你在Windows下打开License Manager图形界面操作那一步,在Linux下这些操作都通过命令行完成。

节点锁定License的注册,一般需要准备好一个.lic文件或者注册码,执行类似命令来激活。不同版本命令稍有差异,但总体思路都是调用LMS的客户端工具把License内容“交”给本机管理服务。激活后可以用状态查询命令确认License是否有效:

IarLicenseManager -query status

如果输出显示许可证状态normal,说明LMS已经能正常识别并验证许可。

3.3 浮动License配置:服务器地址怎么告诉编译器

如果是浮动License,编译机器需要知道License服务器的IP和端口。这个配置有两种方式,一种是把服务器信息写进LMS的配置文件,一种是直接设置环境变量。

我比较推荐设置环境变量的方式,便于在CI里针对不同项目动态指定服务器。常用的环境变量是IAR_LMS_SERVER,格式是IP:PORT或者服务器名:PORT。在/etc/profile.d/iar.sh里加一行:

export IAR_LMS_SERVER=192.168.1.50:1947

注意端口要跟公司License服务器的实际端口一致,用的比较多的是1947。设完后执行source让环境变量生效,再编译一次前面的test.c,就能看到编译器去服务器申请License并成功编译。

命令行构建时如果觉得环境变量不直观,IAR还提供了在工程配置文件里指定License服务器的方式,但对于CI脚本来说环境变量是最灵活的。

3.4 fatal error[lms001]:许可证检查失败的排查思路

标题里热搜词提到的fatal error[lms001]: license check failed. use the iar license manager to re...,我在Linux环境适配过程中遇到太多次了。这个报错的字面意思是“许可证检查失败,请用IAR License Manager重新处理”。

我梳理过几个高频原因,按出现概率排序如下。

第一,License服务器没写或者写错了。比如环境变量漏配,或者服务器端口不通。排查方法是先用ping测服务器IP,再用telnet IP 端口看端口能否连通。连不通则检查防火墙、服务器状态和端口占用。

第二,本机LMS服务没起来。Linux版IAR校验License时会调用本机LMS的本地接口,如果LMS服务异常,客户端会直接报lms001。排查方法是手动启动LMS服务,再用ps -ef | grep -i license确认进程存在。

第三,系统时间与License服务器偏差过大。浮动License是有时间概念的,本机时间如果比服务器快太多,服务端会认为授权非法。这个坑很隐蔽,我就见过一台机器时间跳了几个月,编译永远报lms001,最后用ntpdate同步时间解决的。

第四,用户权限问题。如果当前用户对LMS状态目录没有读写权限,License校验也会失败。一般建议先把安装好的工具链目录和LMS相关目录权限调整好,避免每次都要sudo运行。

遇到lms001别慌,按顺序先查环境变量,再查端口,最后查时间和权限,基本能定位到问题。

3.5 Linux常用命令与License排查结合的实操心得

整个License排查过程中,最常用的几个Linux命令就是echoenvpsnetstattelnet。我一般会先跑一遍全家桶确认环境状态:

echo $IAR_LMS_SERVER env | grep -i iar ps -ef | grep -i license netstat -an | grep 1947

这一套组合拳打完,License服务器配置是否有问题基本就有数了。这里也提醒一句,grep的时候注意大小写,Linux下环境变量名是敏感的,IAR_LMS_SERVER写成小写是完全不同的两个变量,编译器根本识别不到。

4. 从Windows工程到Linux构建:一次完整的命令行编译

4.1 工程转换思路:别再依赖.ewp文件双击了

Windows下IAR工程的核心是.ewp文件,它本质是一个XML格式的工程描述文件,记录了源文件列表、编译选项、链接配置、芯片型号等。在Linux下没有IDE来加载这个文件,但IAR提供了命令行构建工具iarbuild,在部分版本里可以直接用.ewp工程文件来进行命令行编译。

iarbuild project.ewp -build Debug

-build Debug表示构建Debug配置。如果工程里定义了多个配置(Debug、Release),可以这样指定。这种方式对几乎不做工程结构改动的项目最省事,源文件增减都在Windows IDE里改好,Linux这边只是“执行构建”的角色。

不过我个人更推荐在Linux下用CMake来管理构建,尤其是项目代码量大、需要跟其他三方库集成的时候。IAR官方也提供了针对CMake的工具链支持文件,可以把iccarm作为CMAKE_C_COMPILER来用,这样整个工程源文件列表、编译选项都能用CMakeLists.txt管理,Linux和Windows双平台下的构建规则还能统一。对长期维护的项目来说,这笔迁移成本是值得的。

4.2 用CMake驱动IAR编译器的最小配置示例

这里给一个最小可用的CMakeLists.txt示例,假设你的工程比较简单,只有一个main.c和一个外设初始化文件:

cmake_minimum_required(VERSION 3.20) project(linux_iar_demo C ASM) set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER iccarm) set(CMAKE_ASM_COMPILER iasmarm) set(CMAKE_C_FLAGS "--cpu Cortex-M4 --endian little --fpu VFPv4_sp --dlib_config `ls /opt/iarsystems/arm/armbuild_9503/inc/c/Dlib.h` -I inc -D STM32F407xx" CACHE STRING "IAR compile flags") add_executable(demo_fw main.c stm32f4xx_hal_msp.c) set_target_properties(demo_fw PROPERTIES LINKER_LANGUAGE C) set_target_properties(demo_fw PROPERTIES LINK_FLAGS "--config /opt/iarsystems/arm/armbuild_9503/config/lnkstm32f407vg.icf")

这里面的关键点是:

  • CMAKE_SYSTEM_NAMEGeneric,因为交叉编译目标不是Linux本身。
  • CMAKE_C_COMPILER直接指向iccarm,CMake会通过${CMAKE_C_COMPILER}调用它。
  • --dlib_config是IAR指定运行库头文件的参数,Windows下的IDE已经在工程里悄悄帮你写了,命令行编译时必须显式加上。
  • 链接配置文件.icf必须显式指定,否则链接器不知道该把代码放在哪个地址。

配置完成后,生成构建目录并编译:

cmake -B build -S . cmake --build build -j4

如果配置正确,你会看到CMake调用iccarm编译每个.c文件,最后调用链接器生成elf文件。这一步跑通,你的Linux命令行构建就算真正入行了。

4.3 产物处理:elf转hex和bin,发布固件包

IAR编译的默认产物是ELF格式的可执行文件,但实际烧录时,量产用的要么是hex要么是bin。Linux版IAR自带ielftool工具,专门做格式转换。

ielftool --ihex build/demo_fw build/demo_fw.hex ielftool --bin build/demo_fw build/demo_fw.bin

--ihex指定输出Intel HEX格式,--bin指定输出二进制格式。转换完成后可以在构建脚本里把hex、bin文件一并打包存档,配合CI的artifact功能就能实现每次提交自动产出刷机文件。这整个过程跑下来,你基本就脱离了“打开IDE再编译”的习惯,开始进入“脚本驱动构建”的节奏。

4.4 对接自动化流程:Makefile、Jenkins与Docker封装

命令行编译跑通后,下一步就是把它放进自动化流程。我自己的习惯是先写一个简单的Makefile作为中间层,把cmake构建、格式转换、固件打包这些步骤固化下来,供CI调用。例如:

all: build hex bin build: cmake -B build -S . cmake --build build -j4 hex: ielftool --ihex build/demo_fw build/demo_fw.hex bin: ielftool --bin build/demo_fw build/demo_fw.bin clean: rm -rf build

然后在Jenkins或GitLab CI脚本里直接调用make即可。如果是Docker化构建环境,我建议把工具链安装、环境变量、License客户端配置全部写进Dockerfile,构建时用docker run --env IAR_LMS_SERVER=xxx传入License服务器信息,确保所有容器在同一个纯净环境下编译。这一套配合下来,新同事入职再也不用花半天装环境,直接拉镜像就能编。

5. 常见问题与排查技巧实录:Linux版IAR避坑指南

5.1 高频问题速查表

下面这张表是我在实际适配中和帮同事解决问题时总结的高频故障速查表,基本覆盖了入门阶段能够碰到的绝大多数问题。遇到问题先对照这个表查一遍,比自己瞎试高效很多。

现象排查命令原因与解决办法
运行iccarm提示No such file or directoryfile /opt/iarsystems/.../iccarmldd 完整路径缺少32位运行库。执行sudo dpkg --add-architecture i386后安装libc6:i386libstdc++6:i386等包
编译报fatal error[lms001]echo $IAR_LMS_SERVERtelnet 服务器IP 端口License服务器不可达或环境变量未配置。确认服务器地址、端口、防火墙,配置后source环境变量
编译时找不到头文件查看报错的include路径Linux路径分隔符与Windows不同,需要检查命令里include路径使用的是-I还是/I,IAR用的是-I,路径末尾不要有空格
CMake配置时报找不到编译器which iccarmPATH环境变量未生效。重新登录终端或手动source profile.d脚本
解压安装包时中文文件名乱码unzip -O CP936 xxx.zip新版本IAR部分辅助工具压缩包内含中文文件名,在Linux下zip解压默认按UTF-8解码会乱码,指定编码重新解压即可
构建生成的hex文件比预期大ls -l,对照icf文件内存布局链接配置文件.icf里分配了过大RAM段或Flash段,检查链接脚本中stack和heap的大小
执行ielftool转换失败直接运行ielftool --help版本与elf文件格式不匹配,确认当前PATH下的ielftool是同一版本工具链

5.2 关于“linux解压文件乱码”的专项补充

热搜词里出现了“linux 解压文件乱码”,在IAR这个场景下其实也发生过。有次我下载IAR的某个辅助包,在Windows下用压缩软件解压一切正常,放到Linux下unzip直接乱码。原因是Windows下创建的zip默认使用本地编码(常见CP936/GBK),而Linux下unzip默认按UTF-8解码。

解决办法很简单,安装unzip后指定编码:

sudo apt-get install unzip unzip -O CP936 package.zip

个别Linux发行版自带unzip版本较老,不支持-O参数,可以安装p7zip后这样处理:

7z x package.zip

7z对编码兼容性更好,基本都能正常解压。这个小点虽然不起眼,但很可能在一开始就让人怀疑IAR安装包损坏,走冤枉路。

5.3 环境变量不生效的三种常见情况

Linux下环境变量不生效,我总结过三种最容易叫人懵的情况。

第一种是修改/etc/profile.d/下的脚本后没有重新登录。profile.d是在登录时加载的,光开新终端在某些桌面环境下并不会重新读取所有profile文件,必须source一下或者注销重新登录。

第二种是当前shell下先执行过一条设置IAR_LMS_SERVER的命令,后来又在profile里改成别的地址,但当前终端里旧值还残留着。用echo $IAR_LMS_SERVER看到的值跟预期不一样时,先检查是不是这个原因。

第三种是写脚本时变量名大小写写错。Linux环境变量严格区分大小写,IAR_LMS_SERVERiar_lms_server是两个完全不同的变量,编译器只认前者。如果编译时提示未找到License配置,先看一眼环境变量名有没有写对。

5.4 权限问题与构建目录隔离

如果直接用普通用户在/opt下创建构建目录失败,要么是路径没有写权限,要么是构建产物试图写回工具链安装目录。通常我会把所有构建产物统一放到用户目录或者CI的工作区,不在工具链目录下动手。这样还能避免一个隐患:如果构建目录放在工具链安装目录下,清理环境时一不小心误删了工具链文件,那才是真正的灾难。

权限问题还体现在LMS的状态目录上。如果本机LMS运行,但它的工作目录被限制只能root访问,普通用户编译时就会报许可证相关的奇怪错误。解决办法是安装时用固定的普通用户来运行,或者在/var/opt等位置为LMS分配可读写目录。

5.5 独家小技巧:先用官方demo验证一整条链路

在我协助过的所有Linux版IAR迁移项目里,最有效的起步方式不是拿公司现有的大工程直接开刀,而是先用官方自带或者自己写的一个极简demo,把“安装—License—编译—出hex”整条链路验证通。链路通了,后面所有复杂的工程都是在这条链路上增加编译参数而已。

验证demo时尽量选跟目标芯片一致的型号配置,Cortex-M4、Cortex-M0或者你手上实际要用的芯片都可以。这样能顺便确认链接配置文件路径、启动文件路径是否正确。有一次我帮一个团队做适配,他们直接上正式工程,编译报了一百多个头文件路径错误,根本分不清是环境问题还是工程问题。换成demo后十分钟把环境调试好,再回去看正式工程,路径问题一目了然。

写在最后的一点经验

Linux下适配IAR这件事,技术上不算深,但坑确实不少,尤其是License校验和依赖库这两块,几乎每个新环境都会遇到。我的建议是不要试图一次性把Windows那套IDE习惯全搬过来,先解决编译链路,再逐步优化构建脚本和CI流程。等你真正用shell脚本批量出固件的时候,就会觉得当初折腾Linux环境适配还是相当值的。

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

将 1.5B CAD 生成放进 CI,TaoToken 作为请求出口

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

作者头像 李华
网站建设 2026/9/18 23:52:49

grep转义完全指南:BRE、ERE与-F模式下的正则符号处理

我最早意识到“grep转义”是个值得单独写一篇的东西,是因为一次特别丢人的线上操作。当时我在排查一个Nginx日志里的来源IP分布,想精确统计192.168.1.10这个地址出现了多少次,于是很自然地敲了这条命令:grep "192.168.1.10&q…

作者头像 李华
网站建设 2026/9/18 23:51:20

ChromeDriver 116-119 驱动安装全解:版本匹配与多平台配置

做自动化测试这行的人,几乎都被 ChromeDriver 的版本问题绊过一跤。前阵子帮同事看一个跑了两年的采集脚本,报错只有一行SessionNotCreatedException: This version of ChromeDriver only supports Chrome version 114,但本机 Chrome 早就自动…

作者头像 李华
网站建设 2026/9/18 23:49:35

跑 Codex 自动改文件,TaoToken 只提供 Key

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

作者头像 李华