Linux NVMe IO路径深度解析:从请求提交到中断完成的性能优化

跑了很多年存储性能测试,我一直觉得,聊NVMe设备的IO过程,比单纯比跑分有意思得多。一个普通的4K随机读,从应用程序发起,到操作系统层层剥皮,再到PCIe总线上那个小小的门铃寄存器被敲响,最后从SSD返回一个中断,这中间每一步都有牵一发动全身的连锁反应。很多人手里捏着很漂亮的硬件,却总是卡在IO延迟不稳定、IOPS上不去这种怪圈里,根本原因是压根没有把 Linux nvme设备IO过程 整条链路看透。这篇文章就是把我自己这些年排查性能问题时梳理出来的完整链路,按一条真实请求的路径走一遍,同时把每一步容易踩的坑和可以优化的点落出来。

1. 从一次最普通的读开始:syscall 往里走的每一步

1.1 读请求不是一上来就进 NVMe 驱动的

很多初学者有个误区:以为read()直接就把命令丢给SSD了。实际上在Linux里,一个 read 要经过好几层“衙门”。如果把设备以块设备方式挂载,比如/dev/nvme0n1直连,不带文件系统,那么调用链大致是:

read() -> VFS -> blkdev_direct_IO -> 通用块层 -> blk-mq -> NVMe驱动 -> PCIe -> 设备

如果走文件系统,中间还会插进页缓存、文件系统布局换算这些环节。也就是说,你看到的应用层延迟,不光是SSD的功劳,有一大部分是软件路径贡献的。我做过一个很直观的对比:同一批NVMe盘,用O_DIRECT绕过页缓存跑4K随机读,和走页缓存跑,软件CPU开销能差出好几倍。这不代表固态盘性能变了,而是代表IO在软件栈里绕的路不一样了。

1.2 bio 诞生:一个 IO 在内存里的第一副骨架

走到通用块层之后,每个IO会被封装成一个struct bio。你可以把它理解成一张“送货单”,上面记录着:

  • 要读写的起始块号(bi_iter.bi_sector)
  • 长度和方向(读还是写)
  • 指向数据缓冲区的bio_vec数组
  • 所在的块设备(bi_bdev)

在内核新版代码里,bio还会带上优先级、控制信息,甚至直接携带REQ_POLLED这种轮询标志。为什么说它是骨架?因为后面的NVMe命令,就是从bio里把地址和长度拆出来再拼装的。如果bio里的bio_vec指向了多个分散的页面,那么后续构造PRP或者SGL时就要做额外的事情,这也是“IO过程”里第一个容易被忽视的性能拐点。

2. 命令上总线的那一下:SQ、门铃与 PRP 的纠缠

2.1 blk-mq 到底在帮什么忙

老式块设备用的是单队列模型,所有CPU的IO请求都往一个队列里塞,锁竞争重、扩展性差。NVMe天然支持多队列,所以Linux直接引入blk-mq机制来匹配。

每个CPU会有一个软件队列(Software Queue),里面可以放待处理的请求;每个NVMe硬件队列(Hardware Queue)则对应一组中断和一块门铃内存。IO提交的时候,blk_mq_submit_bio()会根据CPU亲和性和映射关系,把请求派发到合适的硬件队列。如果启用了IO调度器,还会在中间过一遍合并/排序逻辑,但现代NVMe性能测试场景下,我建议直接设成none,减少这层带来的额外遍历开销。

在blk-mq分配请求的时候,每个request结构体会带有一段DMA映射信息。NVMe驱动的queue_rq回调会被调用,传入一个request,此时这个 request 是“通用块层请求”,还不是“NVMe命令”。

2.2 从 request 到 nvme_rw_command

NVMe驱动要做的事,就是把request里的信息翻译成一张控制器认识的命令表。核心是nvme_setup_cmd(),它会填充struct nvme_rw_command。关键字段有:

  • opcode:操作码,读是nvme_cmd_read,写是nvme_cmd_write
  • command_id:命令编号,用于完成时匹配请求
  • nsid:命名空间ID
  • slba:起始逻辑块地址
  • nlb:需要传输的块数减1
  • prp1/prp2:物理区域页指针,用来告诉SSD数据在哪里

这里面最让我觉得有意思的是command_id。一个NVMe队列的深度是有限的,比如设备的队列深度是512,那么就同时最多有512条未完成命令。完成队列里返回来的command_id,配合队列索引,就能在中断处理时迅速定位到对应的request。Linux的blk-mq用tag机制直接实现这种索引,所以不需要在完成路径上做复杂的查找。

2.3 PRP 和 SGL:数据地址表示的两套方案

NVMe规范里,数据地址可以用两种方式表示:PRP(Physical Region Page)和 SGL(Scatter Gather List)。

PRP是一种固定大小的条目,一般大小为8字节,指向一个物理页。如果IO跨越多个物理页面,需要构造多个PRP,并通过prp1、prp2串起来。关键在于,PRP要求数据页按系统页大小对齐,如果不想对齐,就得用更复杂的PRP列表。Linux驱动在早期版本里对PRP的处理花了很多精力,专门处理一个请求跨越两个PRP页、但页内偏移不一致的情况,这块代码写起来十分繁琐,也容易出边界bug。

SGL则是更现代的方式:一个描述符里既可以指一整段内存,也可以指分散的小段。因为NVMe设备通常支持SGL,所以Linux在新版本里更多倾向于在合适场景使用SGL,减少生成大量PRP条目的开销。我自己跑大块顺序IO时,明显感受到SGL的优势:一个很大的段可能只用一两个描述符就能描述完,而PRP可能需要几十个条目,DMA映射时的计算量完全不在一个量级。

2.4 门铃:真正把请求踢出去的动作

命令填入主机内存里的 Submission Queue(SQ)之后,还需要告诉控制器“有活干了”。这个动作就是写门铃(Doorbell)。

NVMe控制器的寄存器会被映射到主机地址空间,驱动通过writel()写入SQ Tail doorbell地址,代表“SQ里新的有效命令到这儿为止”。这里有个细节:写doorbell不是简简单单一store,它需要显式保证之前的命令数据已经写入内存并可见。因此驱动在写门铃前后,通常会有比较强的内存屏障,防止CPU乱序导致控制器读到没准备好的SQ Entry。

在性能视角上,门铃写导致MMIO开销其实很小,但每一跳都需要跨总线,如果竞态频繁,软件排队时间长,门铃写入次数会变多。高IOPS场景下,一个队列每秒可能要写几十万次门铃,这部分微小的开销就会被放大。所以内核里有“门铃合并”机制:当多个请求在极短时间内在同一队列提交时,驱动会把它们的SQ Tail先写到本地变量,攒到一点批次后一次性更新门铃,从而减少MMIO次数。这也是为什么很多NVMe深度调优里会看到“batch doorbell”相关的描述。

3. 完成通知与中断消散:CQ 在驱动侧被吃掉的完整路径

3.1 控制器写完数据之后发生了什么事

当SSD处理完命令,会把命令完成状态写到一个叫 Completion Queue(CQ)的位置。CQ同样位于主机内存,由驱动分配、控制器写。完成条目(CQ Entry)中最重要的两个信息是:当前命令的command_id和状态字段。驱动器写完CQ Entry后,会向CPU发起MSI-X中断。

这里有个让不少人困惑的点:为什么完成数据不用另外发总线消息?因为CQ是共享内存,控制器会通过DMA把结果写进主机内存,中断只是一个“提醒信号”。所以真正的完成数据是靠读内存拿到的,而不是在中断里解析“数据包”。

3.2 nvme_irq 里发生了什么

中断触发后,对应CPU会进入NVMe驱动的中断处理函数。处理逻辑大致是:

  1. 记录并判断这是哪个队列的中断(MSI-X每个队列有独立中断号)。
  2. 更新CQ头指针,遍历从上次位置到当前cq_tail之前的那些CQ条目。
  3. 对每个完成条目,根据command_id取出request,把状态和残差信息填报进请求,然后调nvme_complete_rq()。
  4. 最终由块层完成bio,唤醒等待IO的用户线程。

仔细想想,每个完成条目本身就是一个“已完成协议”的最小通知单位。读内存遍历CQ条目这一步,比想象中容易产生性能差异:如果CQ缓存行和当前CPU不在一个NUMA节点,遍历时的缓存缺失会造成延迟上升。所以把NVMe中断亲和性和运行IO的CPU尽量安排在同一个NUMA节点,收益非常明显。

3.3 内存序:完成路径里最容易翻车的地方

驱动读CQ条目时,要小心顺序问题。控制器写CQ Entry的顺序,和它发中断的顺序是有协议的,驱动必须保证自己是按序看到这些条目的。因此,在读取CQ条目并遍历前,通常需要dma_rmb()之类的读屏障,防止CPU或编译器把后面的读取乱序提前,读到还未被控制器写完的完成条目。

处理完CQ条目之后,驱动还要写CQ Head doorbell寄存器,告诉控制器“这些完成条目我已经消费了,buffer可以复用了”。这里又需要写屏障,确保CQ内存的读取操作全部完成后再更新doorbell,不然控制器看到新的head doorbell后,可能立刻把旧条目覆盖掉,而驱动的遍历逻辑还没走完,就会读到互相覆盖的错误数据。

这种内存序问题平时不会暴露,但在某些高并发、多核争抢、编译器优化开了O2的情况下偶发异常。排查的时候不看汇编几乎很难想到。我提这个是想强调:NVMe驱动的IO路径不是“发命令、等中断”那么简单,每一环都要保证严格的顺序一致性。

3.4 超时与重置:当完成永远不来

正常流程中,命令提交后总会在几十到几百微秒内完成。但如果控制器卡死、命令进入异常状态,驱动不能无限等下去。Linux里有nvme_timeout机制,超时时间默认可以通过nvme_core.timeout设置。

超时后驱动会尝试停止该队列、取消这个request,如果多个命令超时,甚至可能触发控制器重置。这个重置过程会把整个IO路径所有队列重建一遍,期间对应用来说就是“一大段延迟毛刺”。我们在生产级排查中,看到大量超时一般会先怀疑是该queue的MSI-X中断丢失,或者CPU被长时间关中断卡死,其次是检查NVMe设备固件异常。中断丢失时,由于完成中断没触发,即便CQ已经在内存里,驱动也感知不到——这正好解释了为什么某些偶发高延迟场景下,SSD统计显示已处理完,但应用始终等不到响应。

4. 让 IO 过程变快的三个实践方向:队列、中断与轮询

4.1 队列深度:不是越大越好

NVMe的SQ深度理论上每队列可以到65535,但在Linux下的实际深度受驱动配置和内存影响。队列深度直接影响能同时在途的命令数。深度太小,SSD内部多通道无法占满,IOPS上不去;深度太大,软件遍历、硬件调度开销都上来,延迟反而变高。

我建议用fio做一条阶梯测试:固定线程数,从iodepth=1、2、4、8、16、32、64逐步往上测,记录平均延迟和99分位延迟。对于高端企业级NVMe盘,通常是iodepth=32到64之间某个点达到最佳平衡。如果只关心延迟且是低并发场景,iodepth=1反而能测出设备真实单IO延迟,因为这个深度下的排队效应最小。

4.2 中断与 CPU 亲和性是一套组合拳

MSI-X 的特点是每个队列一个中断号,可以把不同队列的中断分散到不同CPU。但Linux发行版默认的irqbalance守护进程可能会频繁迁移中断,造成缓存失效。我一般建议在NVMe性能测试机上关闭irqbalance,手动设置各中断号的smp_affinity。

实际操作的时候,先通过cat /proc/interrupts找到形如nvme0q0、nvme0q1、nvme0q2的中断号,然后用echo "cpu_mask" > /proc/irq/<irq>/smp_affinity把每个中断绑定到指定CPU。绑核之后,再看iostat或者直接测fio,延迟毛刺通常会明显减少。如果跑的IO线程本身也在不同CPU上,需要确保线程所在CPU和该队列中断所在CPU尽量一致,或者至少在同一NUMA节点,否则DMA完成后的cache miss时间会被显著放大。

4.3 轮询模式:当中断本身成了瓶颈

中断的好处是可异步唤醒CPU,但在极高IOPS、极低延迟的场景下,每一次中断的上下文切换开销也变得碍事。NVMe支持在部分队列上关闭中断,用轮询方式读取CQ。Linux的io_uring可以通过IORING_SETUP_IOPOLL开启轮询,NVMe驱动也有专门的poll队列。

轮询模式下,提交IO的用户线程会在等待阶段反复检查CQ是否有完成条目,省去了中断、唤醒、调度这几层开销。实测能看到单队列延时曲线变得更稳定。这不是玄学,本质上是把“异步等待”改成了“同步自旋等待”。但代价是CPU占用率会上升,因为线程在等待期间不睡觉,一直占着一个核。所以轮询模式比较适合延迟敏感、CPU核数充足的场景,比如高频量化交易里的低延迟存储,或者对单IO延迟极其敏感的消息队列持久化。普通高吞吐、大规模服务场景,还是让中断去异步通知更划算。

5. 一次延迟排查:把前面所有机制串起来看

5.1 现象:4K随机读平均延迟前面还行,99分位偶尔飙到几毫秒

我在一套测试环境里遇到过这种情况:处理器和NVMe盘看起来都不差,跑4K随机读,平均延迟大概80多微秒,但99分位延迟偶尔跳到2到5毫秒。最初怀疑是盘的问题,但单队列单线程低深度测试时,延迟又很稳定。说明问题大概率出在软件IO路径上。

我采取的排查顺序如下:

层次手段判断依据
设备健康nvme smart-log看温度、百分比、错误计数健康状态正常的话基本排除盘体问题
队列映射cat /sys/block/nvme0n1/mq/*/cpu_list看每条队列映射到哪些CPU如果队列映射全部压在CPU0附近,说明分配不均衡
中断分布cat /proc/interrupts看各NVMe中断在哪个CPU上处理大量中断只被单一CPU处理时会成为瓶颈
调度器状态cat /sys/block/nvme0n1/queue/scheduler确认是否为nonemq-deadline在部分随机场景会引入额外逻辑但收益有限
NUMA一致性lspci -vvv查看设备PCIe位置,用numactl --hardware查看CPU内存拓扑IO线程的内存分配要和NVMe所在socket一致

5.2 问题定位:中断迁移和跨NUMA

最终定位出两个主要因素。第一个是irqbalance在测试期间反复迁移中断,导致部分请求的CQ内存读取跨越NUMA节点,延迟抖动变大。关掉irqbalance,手动把NVMe中断分别绑到不同的物理核上,抖动立刻下降。第二个因素是fio线程没做NUMA绑定,内存被分配到非设备所在NUMA节点,导致PRP列表指向的内存页面和设备DMA访问不在同一NUMA区域。用numactl --membind把测试进程的内存绑定到设备所在socket,延迟又有明显改善。

这两步调整之后,99分位延迟降到了平均值的1.5倍以内,软件开销占比大幅下降。整个过程其实就是把前面章节里的机制反过来用:队列、中断、内存序、DMA位置,每一个都在延迟统计里留下痕迹。

5.3 复现和验证的思路

排查完,别急着宣告结束。我会做一组对照测试:调整前后各跑一次fio,固定相同的--iodepth=32 --numjobs=8 --direct=1 --ioengine=libaio参数,记录avg、p99、cpu usage。这样能量化每个改动带来的收益,也方便后续再遇到类似问题时回溯。

如果问题能稳定复现,还可以用perf抓一下软中断和硬中断的占比,看看nvme_irq的耗时是否异常。正常4K随机读场景下,单次中断处理耗时应该控制在微秒级。如果发现某个CPU上nvme_irq占比奇高,那基本就是中断分布不均衡,需要回到队列映射上找原因。

6. 写在配置文件和内核参数里的经验

6.1 IO调度器选 none 不是偷懒

对NVMe设备而言,none调度器不代表不做任何事,只代表把块层排序和合并的逻辑最小化。PCIe NVMe盘的随机IO能力已经很强,固件内部本来就有高级调度,反而内核的调度器若不懂底层NAND特性,可能会把原本相邻的IO重新打乱。尤其是在压测极限性能时,用none能把软件干扰降到最低。如果是珍视服务质量的多租户环境,可以考虑mq-deadline或bfq,但必须仔细验证延迟,不能无脑选默认。

6.2 关于内核参数和驱动选项

常见可调内核算项有:

  • nvme_core.io_timeout:每个命令的超时时间,默认值较大;调低可以更快发现卡死命令,但太激进会误杀慢IO。
  • nvme_core.poll_queues:创建独立轮询队列的数量,需要在模块加载时设定,使用io_uring轮询时记得留足够队列。
  • blk_mq相关参数:例如queue_depth与nr_hw_queues的配比,通常不需要手动改,但在条件极端时可作为临时验证手段。

需要注意,很多参数是模块级全局的,修改后影响所有NVMe设备。改之前先备份原值,并且最好在测试机而不是生产机上试。我踩过最惨的一次是盲目把io_timeout调到 100ms,结果高负载下偶发命令耗时超过100ms,导致大量超时重置,整个存储服务雪崩。后来才知道,性能调优要基于实测分布来定阈值,而不是拍脑袋设一个“激进值”。

6.3 小技巧:观察每队列的中断处理是否均衡

除了cat /proc/interrupts,还可以用watch -d cat /proc/interrupts持续观察几秒,看不同队列中断计数增长速度是否一致。如果某个队列的计数增速是其他队列的几十倍,说明请求分发不均衡,常见原因是应用只跑在少数几个CPU上,或者IO线程数量和硬件队列数不匹配。

硬件队列数通常与PCIe MSI-X中断向量数一致,常见有8、16、32个队列。使用nvme list或lspci能看到设备支持的中断向量数。测试时,建议把线程数设置为硬件队列数的整数倍,例如32个硬件队列就至少开32个IO线程,再逐步往上加,观察拐点。

6.4 一些实际验证后的结论

基于这些年的实践,我总结了几条个人经验:

  • 对NVMe来说,软件栈的CPU开销在整个延迟中占比很高。4K随机读下,设备物理执行部分可能只占一半时间,另外一半都花在系统调用、DMA映射、中断处理和块层流转上。
  • 使用io_uring配合registered buffers和IORING_SETUP_IOPOLL,可以把每IO的软件开销压缩到极致。但前提是设备和驱动都支持poll,且应用能接受CPU自旋。
  • 多队列不是越多越好,关键是请求映射是否均衡。如果某个队列深度打满而其他队列空闲,延迟照样会升高。
  • 中断和IO线程之间不要跨NUMA。跨NUMA的DMA访问带宽和内存在路径上都会慢一大截,这是很多主板默认拓扑下的隐藏雷区。
  • 排查延迟问题,先拆时间:软件路径耗时多少、设备完成耗时多少。可以用bpf或perf在blk_mq_submit_bio到nvme_irq之间打点,把两者拆开看。能拆时间,你就能用数据说话,而不是靠猜。

最后再分享一个小技巧:需要看某个IO请求实际走的哪条硬件队列时,可以开启块层的IO跟踪,配合nvc或者简单的trace-cmd抓到nvme_setup_cmd和nvme_complete_rq两个事件,看看同一个请求的提交和完成是否发生在同一CPU上。如果频繁跨CPU,说明你的中断绑核或者线程调度配置还有优化空间。把这些细节吃透,Linux nvme设备IO过程在你眼里就不再是一堆黑盒,而是一条一条可以量化的路。

相关新闻

多任务学习驱动智能家居统一架构:控制与推荐融合

多任务学习驱动智能家居统一架构:控制与推荐融合

智能家居这几年越来越普及&#xff0c;但真正用起来总有一种“各管各”的割裂感。你在手机App里设置好回家模式&#xff0c;灯光自动打开&#xff0c;窗帘拉上&#xff0c;空调设到26度——这是“设备控制”。冰箱里的食材快过期了&#xff0c;系统在屏幕角落提醒你做晚餐&…

2026/10/20 18:21:00 阅读更多 →
构建HuggingFace图像-文本数据集:从Schema设计到Hub推送全流程

构建HuggingFace图像-文本数据集:从Schema设计到Hub推送全流程

做多模态模型训练的人&#xff0c;早晚都要跟 HuggingFace 图像-文本数据集打交道。这个标题看起来像是个文档整理任务&#xff0c;实际上它涵盖了从数据采集、清洗、格式设计&#xff0c;到用 datasets 库构建 Dataset、推到 Hub 供团队或社区使用的整套流程。我最早接触到这个…

2026/10/20 18:21:00 阅读更多 →
HuggingFace图像-文本数据集构建全流程:从本地图片到Hub流式加载

HuggingFace图像-文本数据集构建全流程:从本地图片到Hub流式加载

搞多模态训练或者给图文检索、图像生成做数据准备的时候&#xff0c;第一步往往不是写模型&#xff0c;而是先把图像-文本数据集理顺。接触 HuggingFace 生态的同学应该深有体会&#xff1a;datasets库加载数据集非常方便&#xff0c;但很多人到了“我想把自己手里的图片和 cap…

2026/10/20 18:21:00 阅读更多 →

最新新闻

CANFD记录仪如何破解新能源车偶发故障排查难题

CANFD记录仪如何破解新能源车偶发故障排查难题

1. 先从一次海外售后支援说起&#xff1a;偶发故障为什么难缠做国产新能源出海配套这几年&#xff0c;我接过最多的需求不是“帮我们做个新功能”&#xff0c;而是“帮我们查一个偶发故障”。这类故障有个共同点&#xff1a;客户报障时描述得很清楚——没动力、亮故障灯、偶发抖…

2026/10/20 19:02:01 阅读更多 →
REA房产经纪人全攻略:从带看谈单到风险控制,手把手教你站稳这行

REA房产经纪人全攻略:从带看谈单到风险控制,手把手教你站稳这行

REA&#xff0c;三个字母&#xff0c;如果不在这一行里泡过几年&#xff0c;可能很多人会以为是某个软件版本号或者游戏职业缩写&#xff0c;但在我们这行&#xff0c;它就是"房产经纪人"的标准代号。我见过不少人抱着"卖房子不就是陪客户看看房"的想法入行…

2026/10/20 19:02:01 阅读更多 →
消息传递接口死锁检测:基于队列的模拟算法与工程实现解析

消息传递接口死锁检测:基于队列的模拟算法与工程实现解析

1. 题目到底在说什么 这道题&#xff0c;是最短路径问题的一个经典变体&#xff0c;题意很直白&#xff0c;但实现起来有不少小坑。我们需要在一个无向图中&#xff0c;给定起点&#xff0c;找到所有其他点到起点的最短路径长度&#xff0c;并且要求输出这些路径本身&#xff0…

2026/10/20 19:02:01 阅读更多 →
非定常气动力与颤振计算:偶极子格网法和p-k法工程实践

非定常气动力与颤振计算:偶极子格网法和p-k法工程实践

简介&#xff1a;压缩包“飞行器机翼非定常气动力计算及颤振计算程序.7z”是一份面向航空航天专业研究生、工程师及高年级本科生的轻量级MATLAB代码资源&#xff0c;重点解决飞行器机翼在非定常流动环境下的气动力响应计算与颤振边界预测问题。资源包内仅含1个程序文件&#xf…

2026/10/20 19:02:01 阅读更多 →
深入理解.NET Async/Await原理:状态机、同步上下文与性能开销

深入理解.NET Async/Await原理:状态机、同步上下文与性能开销

“每日一题&#xff1a;.NET中的Async/Await原理是什么”——这题我前后被面试官问过至少三次&#xff0c;也在组内分享时讲过两轮。我越来越觉得&#xff0c;这个题目最值钱的地方不在“语法长什么样”&#xff0c;而在于它能把线程模型、编译器机制、上下文调度和性能开销串成…

2026/10/20 19:02:01 阅读更多 →
字节开源Agent手册:可运行源码拆解调度循环与工具注册

字节开源Agent手册:可运行源码拆解调度循环与工具注册

简介&#xff1a;这份资源是字节跳动开源的Agent实践手册配套可运行源码包&#xff0c;面向企业内部业务与技术团队、AI应用开发者及希望将智能体技术落地到实际业务中的技术人员。手册系统总结了字节在飞书、抖音、教育等多条业务线落地Agent的方法、工具与规范&#xff0c;涵…

2026/10/20 19:01:00 阅读更多 →

日新闻

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 阅读更多 →