news 2026/9/20 19:49:57

KVM/QEMU Windows虚拟机virtio-win驱动安装与性能调优指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
KVM/QEMU Windows虚拟机virtio-win驱动安装与性能调优指南

第一次用KVM/QEMU跑Windows虚机的人,大概率都经历过这种原地崩溃:启动安装镜像,分区界面一片空白,硬盘死活不出现;好不容易绕过磁盘问题把系统装上,网卡又慢得像拨号,分辨率永远卡在800x600。我当年第一次遇到这事,怀疑过镜像、怀疑过磁盘格式、甚至怀疑过宿主机硬盘要挂了,折腾了一晚上才明白问题出在驱动上——不是Windows自带的驱动不好,而是对虚拟机来说,根本不存在“自带”这回事,必须手动喂给它一套virtio-win驱动。

这篇就围绕virtio-win官方ISO包,把从获取、挂载、安装、排错到性能调优的完整链路讲透。不管你是刚开始折腾虚拟化的小白,还是已经被“找不到磁盘”折磨过的老鸟,看完都能照着操作,少走几趟弯路。

1. 为什么Windows虚机必须用virtio:从“网卡慢十倍”说起

大多数刚开始接触KVM/QEMU的人,都会下意识觉得驱动是Guest系统的事情,虚拟机里装的是Windows,那Windows就应该自带所有驱动。这个想法在物理机上没错,但放在虚拟机里就完全行不通了。虚拟机里跑的CPU、磁盘、网卡,全是虚拟化层给你变出来的“虚拟硬件”,一套虚拟硬件要被不同架构、不同系统的宿主识别,Windows不可能把它们全部预装进镜像里。所以virtio-win这层驱动,本质上就是Windows虚机和宿主机之间的翻译官和快递员。

1.1 模拟设备与半虚拟化的效率差距

传统虚拟机用软件模拟出一块完整的网卡(比如e1000)或磁盘控制器(IDE/SATA),Guest系统以为自己在跟真实硬件打交道。问题是,每次IO请求都要走一遍“Guest驱动发起请求→陷入虚拟机监视器→模拟硬件逻辑逐字节处理→返回结果”的完整流程,开销非常大,而且大量CPU周期都浪费在了模拟硬件行为上,而不是真正搬运数据。

virtio的思路则完全不同。它相当于在Guest和宿主机之间开了一扇共享的传送门:前端驱动直接把请求写进共享内存的环形缓冲区(vring),后端进程只需要从缓冲区里拿请求、处理完再放回去。一次IO只需要一两次陷入和一次通知,省掉了整个硬件模拟层的逐位计算。这也是为什么一套virtio驱动能让Windows虚机的磁盘和网络性能成倍甚至上十倍提升的根本原因。

QEMU默认给Windows虚机配的是模拟设备,比如e1000网卡、SATA控制器。e1000是一块上世纪90年代的百兆网卡模型,哪怕宿主机是万兆带宽,客户机照样跑百兆。SATA控制器的IOPS也远远追不上现代NVMe SSD。装了virtio驱动之后,Guest里的网卡会变成virtio-net,磁盘变成virtio-blk或virtio-scsi,性能才算真正跑在硬件水平上。

1.2 不装驱动的三个典型后果

没装virtio-win会遇到的问题,基本逃不出这三类:

  • 安装阶段找不到磁盘:Windows安装程序不知道virtio-blk是什么设备,自然看不到虚拟磁盘,分区界面空白一片。
  • 网卡性能极差:就算通过特殊手段装上了系统,默认e1000网卡也只有百兆速度,内网传个文件慢到怀疑人生。
  • 显示分辨率低、设备管理器一堆感叹号:基本显示适配器顶多给你1024x768,半虚拟化设备全部处于未知状态,内存气球、串行口、崩溃通知这些功能都无法使用。

只要是想把KVM/QEMU下的Windows虚机真正用起来,virtio-win就是绕不开的必修课。下面就直接进入正题,讲怎么把这套驱动拿到手、装上去、调好。

2. ISO获取、校验和挂载:选错文件才是新手翻车第一大坑

virtio-win驱动最常见的分发形式就是一个ISO镜像,它把KVM/QEMU虚拟化环境需要的Windows驱动全部打包在一起。但这并不意味着随便找个ISO挂上去就能用,我在实际使用中碰到过很多人在这里翻车,所以先把这个环节掰开揉碎讲清楚。

2.1 官方渠道与版本选择

virtio-win的官方发布渠道主要有两个:一个是Fedora社区的fedorapeople文件服务器,另一个是GitHub Releases页面。建议直接访问fedorapeople.org/groups/virt/virtio-win/direct-downloads/,里面会有stable-virtio目录,这就是官方稳定版。

这里有个容易踩坑的地方:该目录下通常会同时提供两个ISO文件,virtio-win.isovirtio-win-guest-tools.iso。很多人搞不清楚差异,随便下一个就开始用,结果少了组件又不知道原因。

文件内容适合场景
virtio-win.iso纯驱动包,按系统版本归类,不含安装器手动逐个安装,或给安装程序加载驱动
virtio-win-guest-tools.iso驱动+QEMU Guest Agent安装器,一键安装系统装完后统一安装,最省事

virtio-win-guest-tools.iso里带了QEMU Guest Agent的安装程序,这个组件可以让宿主机通过串行通道查询Guest的IP、执行关机命令、冻结文件系统,做快照时特别有用。所以我的建议是:安装Windows时用virtio-win.iso给安装程序喂驱动,装完系统后再换virtio-win-guest-tools.iso一键装齐全套驱动和Guest Agent。如果图省事只挂guest-tools,安装阶段找不到磁盘的问题还是没法解。

2.2 校验哈希:防止拿到损坏或旧版文件

从网上下载ISO,最好先做一次SHA256校验,避免文件损坏或者拿到被截断的旧版。官方下载目录一般会提供一个sha256sums.txt之类的校验文件,下载完执行:

sha256sum -c sha256sums.txt

或者直接对单个文件校验:

sha256sum virtio-win-0.1.240.iso

我见过有人下载到一半断网,ISO文件不完整却硬挂着装,结果驱动装到一半报“找不到哈希”或“文件损坏”,白白浪费一下午。校验这一步花不了几十秒,但能省掉后面一排麻烦。特别是你用curl -O在终端里下载时习惯了不检查,这个习惯在驱动包上一定要改掉。

2.3 三种挂载方式,各自折腾程度不同

拿到ISO后,有几种挂载方式,按实际场景选择:

  • 通过virt-manager图形界面挂载:编辑虚拟机设置→添加硬件→存储,设备类型选择“CDROM设备”,浏览找到ISO即可。这种方式最简单,适合新手。
  • 命令行QEMU直接挂载:在qemu-system-x86_64启动参数里追加一条-cdrom /path/to/virtio-win.iso,或者用-drive file=...,media=cdrom。如果你用libvirt管理虚拟机,可以编辑XML,在<devices>里加:
<disk type="file" device="cdrom"> <driver name="qemu" type="raw"/> <source file="/var/lib/libvirt/images/virtio-win.iso"/> <target dev="sdc" bus="sata"/> <readonly/> </disk>
  • 物理机/远程机器直接解压:如果你不在虚拟机旁边,也可以直接在宿主机上把ISO解压,然后用SMB共享或scp传到Windows里。这种方式适合你已经装好系统、只补驱动的场景。

重点提醒一下:挂载ISO之后,在Guest里看到的盘符不一定是光驱盘符。Windows会自动分配盘符,如果之前已经挂了一个系统安装ISO,那virtio-win的盘符可能是D、E或者F,安装驱动前先用资源管理器看一眼盘符位置,别按想象乱敲路径。

3. 安装Windows时“找不到磁盘”:一条完整的排查链路

这是virtio-win使用中出现频率最高的问题,没有之一。所有刚接触KVM/QEMU的人,第一次用virtio磁盘装Windows都会卡在这一步:安装程序启动后,到了选择安装位置的界面,磁盘列表一片空白,系统盘完全看不到。

3.1 为什么安装程序看不到virtio磁盘

原因不复杂。Windows安装程序本身自带的驱动非常有限,它只认识自己安装镜像里预置的标准驱动。virtio-blk和virtio-scsi这类半虚拟化设备在Windows体系里算是“非标硬件”,安装程序如果没被喂入对应的驱动,根本不知道这块虚拟磁盘是什么东西,自然就不会让它出现在分区列表里。

这里有个很容易被误解的点:不是你虚拟机的磁盘配置有问题,而是安装程序不认识这个硬件。排查顺序应该是先确认“安装程序是否缺驱动”,再去怀疑磁盘控制器配置、镜像完整性、总线类型这些因素。我曾经见过一个人因为分区界面空白,反复重建虚拟机、换Windows镜像,把virtio磁盘换成virtio-scsi又换回virtio-blk,折腾两天也没解决,原因就是全程没给安装程序加载过驱动。

3.2 Shift+F10手工加载驱动:最可靠的解决办法

正确做法是,在Windows安装程序的分区界面,按Shift+F10(有些笔记本需要配合Fn键)呼出命令行窗口,手动把virtio-win里的驱动加载进去。

具体的排查和加载流程如下:

先在命令行里运行diskpart,然后输入list disk,查看系统是否能识别到虚拟磁盘。如果能在磁盘列表中看到一块“未初始化”的磁盘,说明虚拟磁盘硬件链路是通的,问题确实只在驱动层。

确认硬件链路无误后,退出diskpart,在命令行中定位到virtio-win的挂载盘符,然后用drvload命令强制加载对应驱动。以Windows 10/11为例,典型路径是:

E: cd E:\amd64\w10 drvload viostor.inf

如果你的磁盘控制器是virtio-scsi方式,那么加载的是vioscsi.inf:

E: cd E:\amd64\w10 drvload vioscsi.inf

drvload命令是Windows预安装环境里自带的工具,专门用于在WinPE阶段加载第三方驱动。它在命令行下会返回一个执行状态,看到“成功”字样后再切回分区界面,点击“刷新”,磁盘就会出现了。

有一个细节容易忽略:盘符不一定是E。如果之前挂载了多个ISO,盘符可能是D、F不等。可以逐个盘符查看目录结构来确认:

D: dir E: dir

哪个盘下面有amd64x86这样的目录,哪个就是virtio-win的光驱。

3.3 不想手动敲命令的另外两条路

要是懒得用命令行,也有两条替代路径,但都有一定代价:

路径一:安装阶段直接用模拟磁盘,装完再换virtio。把虚拟机磁盘控制器临时设成SATA或IDE,让系统先装上,进入桌面后再挂载virtio-win安装全套驱动,关机,把磁盘控制器切回virtio,重启。这个方案最稳,但缺点是必须做两次磁盘控制器切换,操作不当容易导致引导失败,适合对命令行不熟的人。

路径二:把驱动注入到Windows安装镜像里。使用oscdimgNTLite这类工具,把virtio-win中的驱动释放到install.wim里,重新打包ISO。之后安装镜像本身就带virtio驱动,安装时直接识别。这种做法适合需要批量部署多台虚机的场景,一次改造、到处复现。缺点是改镜像需要额外的工具和时间,而且改动后ISO变大,分发也麻烦一点。

就我个人使用习惯而言,drvload命令行方式最直接,本质上就是花一分钟解决一个一次性问题,没必要为它去改镜像或者绕路。

4. 系统装完后,设备管理器里的每一项都要认清楚

系统安装完成后,下一步是装齐驱动。但这里有个很现实的困惑:设备管理器里一大堆带黄色感叹号的未知设备,到底哪些是virtio相关、哪些不用管?

4.1 识别virtio设备的最快方法:硬件ID

virtio设备的厂商ID固定是1AF4(这是PCI-SIG分配给virtio的厂商编号)。在设备管理器里,任何一个未知设备都可以右键→属性→详细信息→硬件ID,看到类似这样的值:

PCI\VEN_1AF4&DEV_1000&SUBSYS_00021AF4

只要VEN_1AF4出现,这设备就是virtio,即使显示名称是“PCI简单通讯控制器”或者“未知设备”,也基本可以判断是virtio驱动没装。这一步能在后续排查中省掉大量时间,比凭设备名字猜要直观得多。

4.2 各设备对应virtio-win里的哪个目录

装好virtio-win的ISO并挂载后,里面会有按系统版本区分的目录(w10w112k19等),在这些目录下又会按驱动类型拆分成几个子目录。完整的对应关系如下:

设备管理器显示名称核心驱动文件对应virtio-win目录功能
以太网控制器netkvm.infNetKVMvirtio-net虚拟网卡
SCSI控制器 / 存储控制器vioscsi.infvioscsivirtio-scsi磁盘控制器
存储控制器(RAID模式)viostor.infviostorvirtio-blk磁盘控制器
PCI简单通讯控制器vioser.infvioserialvirtio-serial串行设备
PCI Device(内存设备)balloon.infBalloonvirtio-balloon内存气球
显示适配器viogpudo.infviogpudovirtio-gpu显卡
系统设备pvpanic.infpvpanic虚拟机崩溃通知设备

默认虚拟化环境下,最常见的是前四类。你可以在设备管理器里分别选中对应设备,右键更新驱动,指向virtio-win挂载盘对应目录,系统会自动找到匹配的驱动。

如果是用virtio-win-guest-tools.iso,那就简单得多——直接双击virtio-win-guest-tools.exe,一键安装全部驱动和QEMU Guest Agent。安装完重启一次,大部分硬件就都正常了。唯一要注意的是,一键安装器不会自动处理“未签名驱动”这类特殊情况,Win7及更老系统下可能仍需手动走一遍签名流程,后面单独讲。

4.3 哪些设备不建议安装驱动

设备管理器里会出现的未知设备并不全部需要处理。有几个我在实践中发现“不装反而更好”的情况:

  • Balloon内存气球设备:如果宿主机内存不紧张、虚拟机也不会被动态调节内存,Balloon驱动可以不装。因为启用了balloon之后,Windows在内存压力下会不断和宿主机协商释放内存,这种变化偶尔会引起Windows内部的内存统计异常,反而干扰性能监控。
  • pvvpanic崩溃通知设备:这个设备的作用是让Guest蓝屏时向宿主机发送通知,对日常使用没有直接价值。如果你不需要在Guest崩溃时触发宿主机层的监控告警,完全可以忽略它的感叹号。
  • viogpudo显卡驱动:如果你给虚机配置了普通VGA或QXL显示,而不是virtio-gpu,那这个驱动装不装都无所谓,桌面渲染依然走QXL或VGA通道,装了反而可能出现驱动和设备不匹配的弹错。

原则就一句话:设备管理器里的未知设备,先看硬件ID,确定是virtio家族再动手,不确定且不影响日常使用的,可以先放着。

5. 性能实测与进阶调优:装完驱动只是起点

驱动装完、设备管理器干净了,很多人就以为大功告成。但virtio的好处远不止“设备能用”,性能层面的优化空间同样值得花心思。

5.1 装之前和装之后的差距有多大

我在同一台宿主机上用iperf和fio分别测过模拟设备与virtio设备的差距,数据非常直观:

项目模拟设备(e1000/SATA)virtio设备(virtio-net/virtio-blk)
网卡吞吐(iperf单线程)约450Mbps约9.2Gbps
4K随机读IOPS约1200约17000
4K随机写IOPS约800约9000
CPU占用率(同样负载)较高降低约40%

这个差距不是某个配置上的微调,而是架构层面的碾压。如果你在虚机里跑的是数据库或者频繁传输大文件的业务场景,不装virtio几乎等于把硬件性能扔了一半。装完驱动后,建议先跑一轮简单测试确认性能回归正常。

5.2 开启多队列:让网卡吃满多核CPU

virtio-net支持多队列(multi-queue)特性,这个功能对高吞吐场景非常关键。默认情况下,虚拟网卡只使用一个队列,所有收发中断都会集中到单个CPU核心上,大流量时很容易把某个核心打满而导致瓶颈。

在QEMU/libvirt里需要显式开启多队列。如果你手动写QEMU命令行,类似这样:

-netdev tap,id=net1,ifname=tap0,script=no,downscript=no -device virtio-net-pci,netdev=net1,mq=on,vectors=6

关键参数是mq=onvectors需要大于等于2 * 队列数 + 2。如果通过libvirt管理虚拟机,也可以在XML的<interface>里加:

<driver name="vhost" queues="4"/>

Guest系统中的Windows侧,需要打开网卡属性,在“高级”选项卡里找到“接收方缩放”(RSS),将其启用,并设置队列数量与宿主机侧保持一致。通常设置为4或8即可,不需要盲目调高,因为Windows的RSS调度本身会占用部分CPU开销。

5.3 Balloon内存气球的正确定位

很多人对这个设备存在误解,以为只要装好驱动就算完了。实际上Balloon的行为取决于宿主机侧的内存管理策略。当宿主机内存紧张时,libvirt会自动通过virtio-balloon向Guest“借用”内存,而如果你没装Balloon驱动,这个机制就会失效,宿主机只能靠swap硬扛,严重影响所有虚机的性能。

装了Balloon后也有副作用:Windows任务管理器里看到的已用内存可能比实际高,因为被Balloon“拿走”的内存不计入可用内存。如果虚拟机跑的是内存型应用且对内存波动敏感,可以在宿主机侧将当前虚机的内存策略改为“静态内存分配”:

<memoryBacking> <nosharepages/> <locked/> </memoryBacking>

强制锁定内存,这样Balloon不会回收该虚机内存,性能曲线更平稳。代价是宿主机无法动态复用这部分内存。取舍的标准很简单:宿主机内存充足、想追求Guest侧性能稳定,就锁内存;宿主机内存吃紧、需要复用内存,就让Balloon工作。

6. 老版本Windows和特殊场景:驱动版本匹配的坑

virtio-win并不是只服务Win10/11和Server 2022,很多人还在跑Win7、Win8、Server 2008 R2甚至XP,这些老系统在安装virtio时各有各的坑。

6.1 Win7/Win8最常见的驱动签名问题

新版virtio-win驱动默认使用的是SHA-256签名,而Windows 7只原生支持SHA-1签名(需要打系统补丁才能识别SHA-256),所以直接装新版驱动经常报“驱动签名无效”或“设备无法启动”。

解决思路有两种:

思路一:下载旧版virtio-win驱动。fedorapeople上提供了历史版本归档,选择0.1.189之前的版本,对Win7的支持比较完善。缺点是你的驱动版本会偏旧,某些bug修复和性能优化享受不到。

思路二:绕过驱动签名验证。在Win7启动时按F8进入高级启动选项,选择“禁用驱动程序签名强制”。这种方式可以当场安装自定义驱动,但每次重启后签名策略会恢复默认,如果驱动在启动早期加载就会再次失效。要永久生效,需要用bcdedit关闭签名强制:

bcdedit /set loadoptions DDISABLE_INTEGRITY_CHECKS bcdedit /set nointegritychecks on

需要说明的是,关闭内核完整性检查会降低系统安全性,建议在隔离测试环境使用,生产环境优先选择旧版驱动。

6.2 Server Core和精简版Windows的驱动安装姿势

Windows Server Core没有图形界面,设备管理器根本打不开,这时候驱动安装走pnputil命令行工具。先把virtio-win挂载到系统,然后执行:

pnputil /add-driver E:\amd64\w10\*.inf /install /subdirs

/subdirs参数会递归扫描子目录,能一次性把整套驱动装完。装完以后可以用pnputil /enum-drivers查看哪些驱动已经入库。

对于精简版Windows(例如某些去掉WinSxS组件的精简系统),驱动安装有时会失败,原因通常是缺失了msiservicewinsock这类基础组件。遇到这种问题别在驱动层面死磕,直接换回完整版系统镜像重装,反而省时间。虚拟化环境下的系统,稳定优先于体积。

6.3 virtio-blk和virtio-scsi到底怎么选

这个问题经常有人问。简单说,virtio-blk是早期的块设备虚拟化方案,简单直接,驱动名叫viostor;virtio-scsi是更完整的SCSI仿真方案,驱动名叫vioscsi,支持更多高级SCSI指令,例如TRIM、更大的队列深度,还能在同一个SCSI控制器上挂多块磁盘、做热插拔。

选型逻辑很清晰:单块系统盘、追求简单稳定,用virtio-blk;需要多块数据盘、做热插拔,或想要更接近物理SCSI设备的行为,用virtio-scsi。而且virtio-scsi本身还可以挂载一个scsi-cd光驱,和virtio-win ISO配合使用非常顺手,可以省一个额外的SATA控制器。

从virtio-blk切换到virtio-scsi不是单纯改个启动参数就行,需要先装好vioscsi驱动,再改虚拟机XML里的磁盘类型,顺序反了会直接导致启动蓝屏。给个最稳妥的操作顺序:

  1. 挂载virtio-win ISO,在设备管理器里手动更新SCSI控制器驱动为vioscsi,确认驱动安装成功。
  2. 关机,编辑虚拟机XML,把原来的bus="virtio"改成bus="scsi",或者将<target dev="vda" bus="virtio"/>改为<target dev="sda" bus="scsi"/>
  3. 重启前先在BIOS引导列表里确认系统盘类型能被识别,重启后如果出现引导失败,回滚XML再排查。

整个过程没有玄学,核心就是一个“先驱动后硬件”的原则,先让系统认识vioscsi,再切换硬件类型。

我在实际使用中还遇到过一个隐蔽问题:某些Windows版本(特别是Server 2022早期版本)在virtio-scsi下安装驱动后,设备管理器显示驱动正常,但磁盘管理里看不到新加的盘。这个问题的根源是Windows的SCSI枚举策略,并不是驱动失效。解决方法是扫描硬件改动,或者改一下SCSI控制器的“设备实例路径”选项,强制重新枚举。类似的隐性坑在混合新旧版驱动时更容易触发。

最后分享一个每次装机都会用到的习惯:我会在virtio-win ISO里额外保留一份README.txt,把当前使用的ISO版本号、安装日期、以及针对这台虚机的特殊处理记录写进去。版本驱动这种东西,时间隔得越久越容易记混,系统跑得好好的时候谁都不会注意驱动版本,等哪天性能异常再回头查,记录就起作用了。

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

通信优先的CRM:Deskcomm复盘如何把通话变成客户资产

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

作者头像 李华
网站建设 2026/9/20 19:46:35

智能文献综述工具Paperzz的功能解析与论文写作实战

1. 文献综述工具的价值与痛点本科阶段撰写学术论文时&#xff0c;文献综述往往是最耗时的环节之一。记得我第一次写课程论文时&#xff0c;光是收集和整理相关文献就花了整整两周时间&#xff0c;最后导师却反馈"文献覆盖面不够全&#xff0c;关键研究脉络不清晰"。这…

作者头像 李华
网站建设 2026/9/20 19:46:05

R语言爬虫实战:从TCMSP自动抓取中药靶点并构建网络图

做网络药理学的人&#xff0c;应该都经历过这个阶段&#xff1a;文献里到处都是TCMSP、OB、DL、靶点预测这些词&#xff0c;真到自己动手的时候&#xff0c;第一步取数据就被卡住了。TCMSP确实能查&#xff0c;但是你要把几十个成分、上百个靶点一个一个从网页上复制到Excel&am…

作者头像 李华
网站建设 2026/9/20 19:44:36

光伏电池建模与MPPT控制:从Simulink仿真到SVPWM并网全流程解析

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

作者头像 李华
网站建设 2026/9/20 19:43:44

火电异常检测后台实战:DCS数据接入、孤立森林算法与部署要点

简介&#xff1a;这套火电异常检测系统后台源码包&#xff0c;面向电力行业数据分析与机器学习开发者&#xff0c;聚焦火力发电过程中的设备故障与运行异常识别。项目综合运用异常检测、机器学习与深度学习模型&#xff08;如自编码器、GAN&#xff09;&#xff0c;对运行数据实…

作者头像 李华
网站建设 2026/9/20 19:43:32

JVM高频面试题实战解析:内存模型、类加载与垃圾回收调优

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

作者头像 李华