Ubuntu 22.04内核编译与修改实战:从环境配置到启动验证
1. 项目概述与核心价值如果你在Ubuntu 22.04上折腾过硬件驱动、想给系统打个实时补丁或者单纯想深入理解操作系统到底是怎么跑起来的那么修改内核源码几乎是必经之路。这听起来像是只有内核黑客才会干的活但实际上很多实际的开发场景比如为特定硬件编写驱动、优化系统调度策略、甚至修复某个影响自己业务的已知Bug都绕不开这一步。网上很多教程要么过于简略只告诉你“make”和“make install”要么就是一股脑把几十个步骤扔给你中间哪个环节出错了新手根本不知道从何查起。我自己在给团队的自研设备适配Ubuntu系统时反复编译内核不下百次踩遍了从环境依赖到配置选项再到启动失败的几乎所有坑。这篇内容就是把我这些年积累的、从零开始修改并成功运行自定义Ubuntu 22.04内核的完整流程和避坑经验系统地梳理出来。目标很明确让你不仅能跟着步骤做出来更能理解每一步背后的意图遇到报错时知道该往哪个方向排查。无论你是嵌入式开发者、系统研究员还是对Linux底层充满好奇的极客这份指南都将是一份能直接“抄作业”的实操手册。2. 环境准备与源码获取动手之前一个干净、稳定的基础环境至关重要。直接在主力机上操作风险较高我强烈建议使用虚拟机如VMware Workstation Pro或VirtualBox或一台独立的测试机。为编译内核预留足够的资源建议至少分配4核CPU、8GB内存和50GB的磁盘空间。编译过程非常消耗CPU和内存资源不足会导致编译极慢甚至失败。2.1 安装必备的编译工具链首先更新软件包列表并安装一系列不可或缺的开发工具和库。这些包提供了编译器、链接器、内核头文件以及处理各种配置文件所需的工具。sudo apt update sudo apt upgrade -y sudo apt install -y build-essential libncurses-dev libssl-dev bison flex libelf-dev dwarves zstd这里解释一下几个关键包的作用build-essential: 包含了GCC编译器、make等核心编译工具。libncurses-dev: 提供make menuconfig图形化配置界面所需的字符图形库。libssl-dev: 内核模块签名和某些加密功能需要OpenSSL开发库。bison flex: 语法分析器生成器用于处理内核构建过程中的某些语法解析。libelf-dev dwarves: 用于处理ELF可执行与可链接格式文件dwarves工具包中的pahole对内核数据结构优化至关重要缺少它可能导致配置错误。zstd: 高性能压缩算法用于内核镜像压缩。注意如果安装过程中提示某些包找不到请检查你的/etc/apt/sources.list文件确保启用了“universe”和“multiverse”软件源。Ubuntu 22.04默认通常已启用。2.2 获取内核源码的两种可靠途径你需要获取与当前系统内核版本一致或接近的源码以保证最大的兼容性。先查看当前内核版本uname -r输出可能类似于5.15.0-91-generic。其中5.15.0-91是版本号generic是风味flavor。途径一从Ubuntu官方仓库安装源码包推荐这是最稳妥、依赖关系最清晰的方法。Ubuntu为每个发行版维护了特定版本的内核源码树。启用源码仓库编辑/etc/apt/sources.list确保每一行deb开头的源地址后面都有一行对应的deb-src开头行。如果被注释了取消注释。sudo sed -i /^# deb-src /s/^# // /etc/apt/sources.list sudo apt update搜索并安装源码包apt search linux-source在输出中找到与你的内核主版本如5.15匹配的包例如linux-source-5.15.0。sudo apt install linux-source-5.15.0安装后源码压缩包位于/usr/src/目录下通常是linux-source-5.15.0.tar.bz2。解压源码cd /usr/src sudo tar -xaf linux-source-5.15.0.tar.bz2这会解压出一个linux-source-5.15.0的目录。为了方便可以创建一个软链接或直接进入该目录操作。途径二从kernel.org获取主线内核如果你需要最新的主线内核特性可以从 https://www.kernel.org 下载。但请注意主线内核可能不包含Ubuntu特定的补丁和驱动与你的硬件和软件栈的兼容性需要更多测试。使用wget或curl下载例如wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.148.tar.xz tar -xvf linux-5.15.148.tar.xz cd linux-5.15.148对于初次尝试我强烈建议使用途径一。它确保了与当前系统模块、驱动和固件的兼容性成功率最高。3. 内核配置与修改策略解析获取源码后直接修改代码然后编译是行不通的。你必须先根据你的系统硬件生成一个正确的配置文件.config文件它决定了哪些功能、驱动会被编译进内核或编译为模块。3.1 生成初始配置文件进入你的内核源码目录。如果你使用Ubuntu的源码包目录名可能是linux-source-5.15.0。最安全的方法是继承当前运行内核的配置。Ubuntu将配置存储在/boot目录下cp /boot/config-$(uname -r) .config这个命令将当前内核的配置复制到源码根目录并命名为.config。3.2 配置工具的选用与交互有了基础的.config文件你需要运行一个配置工具来调整它以加入你的修改或启用/禁用某些选项。make olddefconfig(首选)这是一个非交互式命令。它会基于已有的.config文件将新增的配置项设置为默认值并保持你原有的选择不变。在更新了源码或从旧配置迁移时这个命令能智能地处理新增的配置项避免手动逐一设置。执行make olddefconfig屏幕上会快速滚动一些“NEW”配置项被设置为默认值的信息。这是将现有配置适配到新源码树的标准操作。make menuconfig(图形化配置)如果你需要进行可视化的、详细的配置调整这是最常用的工具。它是一个基于ncurses的文本图形界面。make menuconfig界面中[*]表示编译进内核[M]表示编译为模块[ ]表示不编译。使用方向键导航空格键切换状态/键可以搜索配置项。修改完成后选择Save保存到.config文件。实操心得在menuconfig中如果你只是做少量特定修改比如启用一个驱动建议先make olddefconfig再进入menuconfig搜索修改。不要在里面漫无目的地浏览和修改很容易破坏配置的完整性导致编译失败。make xconfig或make gconfig这两个是图形界面更丰富的配置工具需要Qt或GTK库支持。对于新手menuconfig已经足够。3.3 针对性修改配置的实例假设你的修改目的是“为我的USB转串口适配器添加最新的CP210x驱动支持”。定位配置项在源码目录下使用grep搜索关键词。但更高效的方式是在make menuconfig界面直接按/输入CP210X。它会告诉你这个配置项位于Device Drivers - USB support - USB Serial Converter support - USB CP210x family of UART Bridge Controllers路径是Device Drivers-USB support-USB Serial Converter support-USB CP210x family of UART Bridge Controllers。修改配置进入menuconfig按此路径导航。你会发现该选项可能被设置为[M]模块或[*]内置。如果你希望这个驱动一定被加载可以将其改为[*]。保存退出。这个例子展示了如何为特定硬件修改配置。如果你的修改是源码层面的例如修改调度器参数那么配置步骤可能只是确保相关子系统如CPU/Task scheduling and fair scheduler是启用的。4. 源码修改与内核编译全流程这是核心环节我们将把配置好的内核编译成可安装的二进制包。4.1 执行编译命令在源码根目录下执行编译命令。-j参数指定并行编译的作业数通常设置为你的CPU核心数1或2以最大化利用多核性能。你可以用nproc命令查看核心数。make -j$(($(nproc)1))或者如果你有16个逻辑核心make -j16编译过程会持续一段时间从十几分钟到一小时以上取决于硬件性能。你会看到大量输出信息在滚动。第一次编译时不要被警告warning吓到只要不是错误error导致编译停止通常可以继续。内核编译会产生很多警告很多是良性的。踩坑记录编译过程中最常见的错误之一是“缺少头文件”或“某函数未定义”。这通常是因为没有安装必要的开发包回到第2.1节检查。源码树不干净。如果你之前编译过建议先make mrproper这会清除所有编译产物和配置文件慎用或make clean清除编译产物但保留.config。配置冲突。如果错误指向某个驱动或子系统可以尝试在menuconfig中禁用它再重新编译。4.2 编译内核模块内核编译完成后需要单独编译那些标记为[M]的模块sudo make modules -j$(($(nproc)1))4.3 安装内核模块将编译好的模块文件安装到系统的/lib/modules/目录下目录名会是你新内核的版本号。务必使用sudosudo make modules_install执行后检查/lib/modules/目录下是否出现了一个以新内核版本命名的文件夹版本号可以在源码根目录的include/config/kernel.release文件里查看或者通过make kernelversion命令。4.4 安装内核镜像与相关文件这个步骤会将内核镜像vmlinuz、初始内存盘initrd和System.map文件安装到/boot目录并更新引导加载器通常是GRUB的配置。sudo make install这个命令实际上执行了以下几个操作将arch/x86/boot/bzImage复制为/boot/vmlinuz-你的新内核版本。使用update-initramfs生成对应的initrd.img-版本。将System.map复制到/boot。运行update-grub将新内核项添加到GRUB启动菜单。5. 引导配置与重启验证安装完成后重启系统并选择新内核启动是最后一步也是最紧张的一步。5.1 检查GRUB引导项在重启前可以检查GRUB是否已识别新内核sudo update-grub查看输出信息应该能看到类似 “Found linux image: /boot/vmlinuz-5.15.0-custom” 和 “Found initrd image: /boot/initrd.img-5.15.0-custom” 的提示。你也可以直接查看/boot/grub/grub.cfg文件不要手动编辑它搜索你的新内核版本号确认其菜单项已存在。5.2 重启并选择新内核现在可以重启了sudo reboot在GRUB启动菜单界面通常需要按ShiftBIOS系统或EscUEFI系统来显示菜单。如果没看到菜单可能是GRUB被设置为直接启动。你可以在/etc/default/grub中修改GRUB_TIMEOUT为一个正数如5秒然后运行sudo update-grub。在菜单中选择你刚编译安装的内核条目通常会显示版本信息。如果启动成功进入系统后打开终端再次运行uname -r如果输出的版本号与你编译的版本一致那么恭喜你自定义内核已成功运行5.3 制作可卸载的DEB包进阶技巧直接使用make install是便捷的但它将文件分散安装到系统目录不便于管理和卸载。更专业的方式是生成Debian软件包.deb。安装打包工具sudo apt install -y kernel-package fakeroot注在较新版本的Ubuntu中kernel-package可能已被其他方法取代但此方法在22.04上仍可工作在源码目录执行打包命令。首先确保你已经make clean过并且.config文件已就绪。fakeroot make -j$(($(nproc)1)) deb-pkg这个过程会重新编译内核并在上层目录生成多个.deb文件例如linux-image-5.15.0-custom_5.15.0-custom-1_amd64.deb(内核镜像)linux-headers-5.15.0-custom_5.15.0-custom-1_amd64.deb(内核头文件)linux-libc-dev_5.15.0-custom-1_amd64.deb(C库开发文件)安装生成的DEB包cd .. sudo dpkg -i linux-image-*.deb linux-headers-*.deb安装过程会自动处理依赖、更新initramfs和GRUB。这种方法的优势你可以用sudo dpkg -r linux-image-5.15.0-custom干净地卸载这个内核就像卸载任何其他软件一样。这对于测试多个自定义内核版本非常有用。6. 常见问题排查与深度优化指南即使严格按照步骤操作你也可能会遇到一些问题。这里汇总了最常见的问题及其解决方案。6.1 编译阶段错误排查错误现象可能原因解决方案fatal error: xxx.h: No such file or directory缺少对应的内核头文件或开发包。1. 确认已安装linux-headers-$(uname -r)。2. 使用apt search或apt-file search xxx.h查找提供该头文件的包并安装。recipe for target ‘xxx’ failed源码或配置错误工具链问题。1. 检查.config文件是否完整来自/boot的通常是好的。2. 尝试make clean后重新make olddefconfig再编译。3. 确认所有必备开发包第2.1节已安装。编译过程被Killed系统内存或交换空间不足。1. 增加虚拟机或物理机的内存。2. 创建更大的交换文件sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile。3. 减少编译并行数如使用make -j2。pahole not found缺少dwarves包。安装dwarves包sudo apt install dwarves。6.2 启动阶段故障排除启动问题可能原因解决方案黑屏无法进入系统显卡驱动问题尤其是NVIDIA独显。1. 在GRUB菜单按e编辑启动项在linux行末尾添加nomodeset然后按F10启动。进入系统后安装合适的闭源驱动。2. 在编译内核时确保Device Drivers - Graphics support下正确的开源驱动如AMD的AMDGPU Intel的i915被启用或内置。卡在[ OK ] Started …或A start job is running for …文件系统或存储控制器驱动缺失。1. 最可能的原因是initramfs没有包含必要的驱动模块。在旧内核下编辑/etc/initramfs-tools/modules文件添加你硬盘控制器所需的模块名如nvme,ahci,sd_mod等然后运行sudo update-initramfs -u -k 旧内核版本。2. 在新内核配置中确保对应的存储驱动如CONFIG_SATA_AHCI,CONFIG_NVME_CORE被编译进内核[*]而不是模块[M]。提示Kernel panic - not syncing: VFS: Unable to mount root fs根文件系统无法挂载。1. 同上initramfs问题或驱动缺失。2. 检查GRUB启动参数中的root是否正确指向你的根分区UUID。可以在旧内核下用blkid命令查看。3. 确保内核配置中支持你的根文件系统类型如CONFIG_EXT4_FSy。新内核不在GRUB菜单中update-grub未成功执行或探测失败。1. 手动运行sudo update-grub并观察输出。2. 检查/boot目录下vmlinuz-*和initrd.img-*文件是否存在。3. 检查/etc/default/grub中GRUB_DISABLE_RECOVERY或GRUB_DISABLE_SUBMENU等设置。6.3 内核调试与性能优化建议成功启动后你可能会想验证修改是否生效或者进行一些优化。验证修改如果你修改了某个内核参数比如sysctl参数对应的源码启动后可以去/proc或/sys文件系统下查看。例如修改了TCP拥塞控制算法可以cat /proc/sys/net/ipv4/tcp_congestion_control查看。查看内核日志使用dmesg命令可以查看内核启动和运行过程中的日志这对于排查驱动加载失败等问题非常有用。结合grep过滤如dmesg | grep -i error。编译优化为了获得更小的内核体积或更快的编译速度可以在.config文件中调整CONFIG_DEBUG_INFOn可以大幅减少内核镜像大小但会移除调试信息不利于问题追踪。CONFIG_CC_OPTIMIZE_FOR_SIZEy或CONFIG_CC_OPTIMIZE_FOR_PERFORMANCEy可以针对大小或性能进行优化。使用make localmodconfig可以基于当前已加载的模块生成一个最小化的配置只编译你系统真正需要的驱动这能显著减少编译时间和内核体积。但注意这适用于固定硬件环境的设备对于需要热插拔硬件的桌面环境可能不适用。增量编译在修改少量源码后不需要make clean直接再次执行make -jN即可进行增量编译只重新编译改动过的部分速度极快。修改和编译内核是一个需要耐心和细致的过程。第一次成功启动自定义内核的成就感是无与伦比的。记住每次修改配置或代码后从make olddefconfig或menuconfig到make -jN再到sudo make modules_install和sudo make install或制作deb包这是一个完整的闭环。保持操作环境的干净善用版本控制如git来管理你对源码的修改并始终在GRUB菜单中保留一个已知可工作的旧内核作为备份这样你就可以大胆地进行实验了。当系统因为你的内核修改而展现出新的特性或更好的性能时你会觉得这一切的折腾都是值得的。

相关新闻

MLOps工具选型指南:从实验管理到生产部署

MLOps工具选型指南:从实验管理到生产部署

1. MLOps工具全景解析:从实验管理到生产部署 在机器学习项目从原型到生产的全生命周期中,MLOps工具链的选择直接决定了团队协作效率和模型迭代速度。根据核心功能差异,当前主流MLOps工具可分为三大阵营:实验管理类(MLf…

2026/10/20 12:21:50 阅读更多 →
模块化机房与传统机房在设计用途上的差异分析

模块化机房与传统机房在设计用途上的差异分析

1. 引言随着企业数字化转型的深入,数据中心基础设施的建设模式正在发生显著变化。模块化机房作为一种新兴的建设方式,正逐步挑战传统机房的主导地位。本文围绕模块化机房与传统机房在设计用途上的差异展开分析,帮助读者理解两者在建设理念、适…

2026/10/19 19:36:15 阅读更多 →
深入URP源码:从渲染原理到移动端性能优化实战

深入URP源码:从渲染原理到移动端性能优化实战

1. 项目概述:为什么我们要深入URP源码?如果你是一名Unity开发者,尤其是对图形渲染、性能优化或者想从Built-in管线迁移到URP感到头疼,那么“深入URP源码”这件事,可能比你想象中更有价值。这不仅仅是为了面试时能多聊两…

2026/10/17 2:46:21 阅读更多 →

最新新闻

Unity WebGL 音频加载实战:从网络请求到播放的完整链路

Unity WebGL 音频加载实战:从网络请求到播放的完整链路

1. 为什么 Web 端加载音频和编辑器里完全是两码事如果你在 Unity 编辑器里写过AudioClip加载&#xff0c;大概率会觉得这事没什么难度&#xff1a;Resources.Load<AudioClip>或者挂个AudioSource引用&#xff0c;点运行就能出声。但一旦把项目构建成 WebGL 版本丢到浏览器…

2026/10/20 12:21:12 阅读更多 →
Maven settings.xml 替换阿里云镜像:配置、避坑与验证

Maven settings.xml 替换阿里云镜像:配置、避坑与验证

简介&#xff1a;这份资源是一份可直接替换使用的Maven settings.xml配置文件&#xff0c;面向Java开发者、后端工程师及需要优化项目构建效率的技术团队。它针对Maven默认从中央仓库拉取依赖速度偏慢的问题&#xff0c;通过预置阿里云镜像配置&#xff0c;帮助读者在本地或团队…

2026/10/20 12:21:12 阅读更多 →
阿里云短信接口开发示例:从跑通代码到生产级工程化改造

阿里云短信接口开发示例:从跑通代码到生产级工程化改造

简介&#xff1a;这份资源是面向.NET开发者的阿里云短信接口调用示例包&#xff0c;适合需要快速接入短信验证码、通知推送等场景的初中级工程师参考。包内共92个文件&#xff0c;以68个cs源码文件为核心&#xff0c;辅以18个dll动态库、2个csproj工程文件及少量txt、xml说明与…

2026/10/20 12:21:12 阅读更多 →
运行时高级技巧:JVM内存监控、进程管理与动态加载实战

运行时高级技巧:JVM内存监控、进程管理与动态加载实战

写过框架、做过中间件的老哥们&#xff0c;几乎每天都挂在嘴边的就是运行时&#xff08;Runtime&#xff09;这个词。JVM 有 runtime&#xff0c;Node.js 有 runtime&#xff0c;连容器编排也天天喊 runtime。可我刚入行那几年&#xff0c;一直觉得 runtime 是“虚拟机自己管的…

2026/10/20 12:21:12 阅读更多 →
通信工程师中级综合能力:核心框架与高效备考策略

通信工程师中级综合能力:核心框架与高效备考策略

考通信工程师中级&#xff0c;很多人会把《综合能力》当成一门"背多分"的科目&#xff0c;觉得全是客观题&#xff0c;考前突击一下就能过。我一开始也这么想&#xff0c;直到拿着某培训机构的内部讲义过了一遍&#xff0c;又跟着老师拆了近两年的真题分布&#xff0…

2026/10/20 12:21:12 阅读更多 →
Audio2Face 2023.2.0:AI驱动面部动画与口型同步实战解析

Audio2Face 2023.2.0:AI驱动面部动画与口型同步实战解析

平时折腾过不少动画管线工具的人应该都知道&#xff0c;做虚拟人、做游戏过场、做短视频口播的时候&#xff0c;最烦的不是模型贴图和绑定&#xff0c;而是让角色的嘴型和表情在音频上对得自然。手动调 blend shape 帧曲线能调到怀疑人生&#xff0c;一段三十秒的语音&#xff…

2026/10/20 12:20:11 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →