尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Linux VM内核参数调优:内存管理、脏页回写与OOM实践
看到Linux VM 内核参数调优这个标题我第一反应是很多人会把它当成虚拟机调优点进来——毕竟 VM 这两个字母太有迷惑性了。但只要你在运维或者后端一线待过翻过/proc/sys/vm/这个目录就知道这里的 VM 指的是内核里的Virtual Memory 子系统也就是内存管理这一整块页框分配、页面回收、脏页回写、超额分配、NUMA 水位线。这些东西平时安静得像不存在可一旦配歪表现出来就是业务间歇性卡顿、写入延迟毛刺、日志里莫名出现 OOM、监控上 swap 曲线突然抬头。我这些年处理过的内存类故障大概有一半最终能追到/proc/sys/vm/下面某几个数字上而另一半则是被错误地归咎于它。这篇就把这套参数一次讲透每个参数到底在管什么、默认值为什么是那个值、什么场景该动、动了之后怎么验证以及我踩过的那些改完反而更糟的坑。1. 先把VM这个词掰开/proc/sys/vm 管的是内存不是虚拟机标题里的 VM 歧义必须先澄清不然讨论会一路跑偏。/proc/sys/vm/是内核内存管理子系统对外暴露的调参入口和虚拟化技术没有半点关系。你可以把它理解成内存这套供水系统的总阀门面板哪些水先放、放多少、蓄水池留多少余量、overflow 的时候是溢出来还是直接断供全在这里定。1.1 四个工作面匿名页、文件页、回收器与超额分配内核内存管理的日常其实就围着四件事转所有vm.*参数最终都落在其中之一。第一件事是匿名页的取舍也就是进程 malloc 出来、没有对应磁盘文件的那部分内存。当物理内存吃紧内核要决定是把它换到 swap 上还是把文件缓存丢掉。vm.swappiness就是调这个天平的。第二件事是文件页的回收节奏也就是 page cache 和 dentry/inode 缓存。缓存留着能加速读占着又不能无限涨vm.vfs_cache_pressure在这一层踩油门。第三件事是脏页回写。write()返回成功不代表数据落到盘上它只是进了 page cache。什么时候把这些脏页刷下去、一次刷多少、刷的时候会不会阻塞业务进程由dirty_ratio那一组四个参数说了算。这一组是我见过最容易配出事故的地方后面单独拆。第四件事是超额分配与水位线。进程申请内存时内核要不要立刻答应、答应到什么程度、物理内存低到什么水位开始紧急回收对应overcommit_memory和min_free_kbytes这一族。这四条线互相咬合。你把 swappiness 压到极低文件缓存回收压力就会上升你把 dirty_ratio 调小写吞吐可能掉你把 min_free_kbytes 拉高可用内存直接少一块。所以调参从来不是把某个值改成网上说的那个数而是先确认你机器的瓶颈在哪条线上。1.2 改了没生效参数落盘的三种写法与容器里的坑这一步看着基础但我见过太多改完参数重启就回去了的情况。临时看值sysctl vm.swappiness cat /proc/sys/vm/swappiness临时改值两种写法等价直接写/proc下的文件只是少了进程开销sysctl -w vm.swappiness10 echo 10 /proc/sys/vm/swappiness要持久化别往/etc/sysctl.conf里堆那个文件在多数量产系统上已经属于历史遗留。正确做法是丢一个独立文件进/etc/sysctl.d/按用途命名方便审计和回滚# /etc/sysctl.d/90-memory-tuning.conf vm.swappiness 10 vm.dirty_background_bytes 536870912 vm.dirty_bytes 1610612736 vm.overcommit_memory 0然后sysctl --system让它生效。注意加载顺序是按文件名字典序90-这种前缀能保证在系统自带的99-sysctl.conf之前生效避免被覆盖。注意dirty_bytes和dirty_ratio是互斥的。你设了dirty_bytes内核会自动把dirty_ratio置 0反过来也一样。所以别两个都写写了也只有一个起作用还容易让人误判当前生效值。真正的坑在容器里。/proc/sys/vm/下绝大多数参数没有做命名空间隔离你在容器内执行sysctl -w vm.swappiness0改的是宿主机全局的值容器一销毁这个改动还留在宿主机上下一次别的业务起来就莫名其妙受影响。加固过的运行时会拦住一部分写操作但拦不住的那部分就是隐患。我的做法很干脆内存类参数只在宿主机层面统一配置容器内一律不碰。如果某个业务确实需要独立的内存行为用 cgroup 的memory.max、memory.swap.max去约束别去动vm.*。2. swappiness被背了最多次锅的那个参数只要一台机器 swap 使用量涨了第一反应永远是把 swappiness 调低。这个动作我做过也做错过。要讲清楚它得先扔掉一个流传很广的误解。2.1 它算的是回收成本不是用不用 swapvm.swappiness的取值区间是 0 到 100较新内核放宽到 200默认 60。它不是swap 使用百分比也不是内存压力到多少才换出。内核在计算该回收哪些页时会用这个值作为一个权重值越高越倾向于把匿名页换出去、把文件缓存留下来值越低越倾向于丢掉 page cache、保住匿名页。换句话说它调节的是两类页面的回收代价比值。匿名页换出要写磁盘、换回来要读磁盘代价高文件页丢掉就行需要时再从盘上读代价相对可控。swappiness 高意味着内核认为swap 设备的 IO 比重新读文件便宜于是更愿意动匿名页。这个判断在 swap 放在本地 SSD 时是合理的在 swap 放在网络存储或者和老磁盘抢带宽时就完全不合理。还有一个必须纠正的说法老内核上swappiness0并不等于禁用 swap。它只是把倾向压到最低当内存压力足够大、文件缓存已经无可回收时匿名页照样会被换出。真正想在 cgroup 层面禁用 swap靠的是memory.swap.max0想在全局层面禁用是swapoff -a而不是把 swappiness 设 0。2.2 把 60 改成 1 之前先看你机器上 swap 到底在干什么判断依据不是 swap 用了多少而是swap 的换入换出速率。si和so长期是 0swap 占用了几个 G 也完全没事那只是早期内存紧张时留下的冷页躺在那里不影响性能。反过来si/so每秒几十 MB 地持续跑哪怕 swap 只用了 200MB也说明系统在反复抖动这比单纯用了 swap严重得多。vmstat 1 5 # 关注 si so 两列持续非 0 就是真在换页 sar -W 1 5 # pswpin/s 和 pswpout/s趋势比瞬时值更有意义一个很典型的场景某台 64G 内存的业务机跑着一个常驻 40G 的缓存型服务。监控报警swap 使用率 12%值班同学直接把 swappiness 从 60 改成 1。结果第二天开始文件缓存被大量丢弃服务的读请求命中率从 96% 掉到 78%后端存储压力翻倍接口 P99 反而涨了。原因很简单那台机器上 swap 里躺着的是启动阶段留下的冷页si/so一直是零本来什么事都没有把 swappiness 压到 1 之后内核在内存稍有压力时更倾向于丢 page cache 而不是碰匿名页于是把真正有用的缓存给扔了。2.3 三个真实场景的取值建议含反例我给不出一个通用最优值但可以按场景分档这些取值都是我在实际环境里跑过、观察过至少两周的。场景特征swappiness 建议理由数据库、缓存服务内存基本占满swap 在本地 SSD10保住匿名页缓冲池不给内核太多换出倾向同时保留极端情况下的兜底能力通用应用服务器内存有一定余量读写混合30~60接近默认让内核自己平衡不必强行干预桌面/交互式机器希望切窗口不卡60~100匿名页换出后切回来体验比重新读文件差这里反而不该压低明确不需要 swap 的机器直接swapoff或用memory.swap.max0别指望 swappiness 干这事还有一类特殊情况内存远大于工作集的机器比如 128G 内存跑一个实际只用 20G 的服务。这种机器上 swappiness 改不改基本没差别因为压根不会触发回收。花时间调它不如去看看透明大页和 NUMA 绑定。我个人的经验是凡是内存使用率长期低于 60%的机器vm.*这一族参数基本都不用动动了也测不出差异纯属给自己找心理安慰。3. 脏页回写的四道闸门配错一组参数业务直接卡成 PPT如果说 swappiness 是被误解最多的那脏页这一组就是最容易出事故的。我处理过的写入延迟毛刺十个里有六七个能追到这里。3.1 从 write() 返回到磁盘落地中间经过什么write()把数据拷进 page cache 就返回了这页被标记为 dirty。之后有三条路把它刷下去第一条是定时回写线程按vm.dirty_writeback_centisecs默认 500即 5 秒周期性醒来把超过vm.dirty_expire_centisecs默认 3000即 30 秒还没落盘的脏页刷下去。这两个参数的单位是 0.01 秒写配置时经常有人当成秒写成dirty_expire_centisecs 30实际变成 0.3 秒回写频率暴涨磁盘 IO 直接被打满。第二条是后台阈值触发脏页占到vm.dirty_background_ratio默认 10%时内核启动后台回写但不阻塞写入进程。这是温和档。第三条是同步阻塞脏页占到vm.dirty_ratio默认 20%时任何继续写脏页的进程都会被balance_dirty_pages()拦下来自己参与回写并且睡眠直到脏页降下去。这就是业务侧看到的突然卡一下。所以 20% 这个默认值意味着什么在 8G 内存的小机器上是 1.6G 脏页封顶还算合理在一台 256G 内存的机器上就是51G 脏页。想象一下51G 脏数据堆在内存里后台回写根本追不上然后某个瞬间所有写进程同时被拉去同步回写磁盘队列打到几百接口延迟从 5ms 跳到 800ms。这就是大内存机器上内存越大反而越卡的经典原因。3.2 大内存机器为什么必须换成按字节数配置比例配置的问题在于它随内存线性放大而磁盘带宽不会随内存变大。一台 256G 内存配 SATA SSD 的机器按默认值算出 51G 的同步阈值这个数字毫无工程意义。解法是换成绝对量vm.dirty_background_bytes和vm.dirty_bytes。经验取值跟你的磁盘持续写带宽挂钩我一般按后台阈值约等于 2~5 秒的持续写带宽来估假设盘持续写 500MB/s取 3 秒 → 后台阈值 1.5G 左右同步阈值取它的 3 倍左右。落到配置上vm.dirty_background_bytes 1610612736 # 1.5G vm.dirty_bytes 4294967296 # 4G保守一点的机器机械盘、网络存储就往下压后台 256~512MB同步 1~1.5G。压太低的代价是回写太频繁磁盘一直在小批量刷反而浪费 IO 调度合并的机会。一个容易忽略的细节这两个是全局阈值不是单进程的。一个疯狂写的进程可以把全局脏页推高然后所有写进程一起被限流。所以在多业务混布的机器上光调内核参数不够还得配合 cgroup 的memory.max把写压力圈在单个业务里。3.3 快盘、慢盘、机械盘的分档取值与验证方法我把常见的存储类型整理成一张表可以直接拿去当起点但务必用你自己的压测数据校准别照抄。存储类型dirty_background_bytesdirty_bytes备注NVMe SSD持续 2GB/s2G6G回写带宽充裕阈值可以放高减少同步阻塞SATA SSD持续 500MB/s512M1536M最常见的档位机械盘阵列持续 200MB/s128M512M阈值压小避免一次刷太多把队列打满网络块存储64M~128M256M~512M延迟高宁可小批量多批次验证方式不能只看平均值必须看毛刺。我的标准动作是# 一边压测写入一边看脏页和阻塞情况 grep -E Dirty|Writeback /proc/meminfo iostat -dx 1真正有诊断价值的是/proc/vmstat里的nr_dirty_threshold和nr_dirty_background_threshold它们告诉你当前内核实际在用的两个阈值是多少页换算一下就知道你的配置有没有生效grep -E nr_dirty /proc/vmstat如果nr_dirty_threshold还是几十万页乘 4KB 就是几个 G 到十几个 G说明你的dirty_bytes压根没生效——八成是dirty_ratio和dirty_bytes同时写了或者配置文件加载顺序被覆盖了。4. overcommit 与 OOM内存能不能先答应下来这一组参数决定了内核对内存申请的态度。默认情况下内核是个先答应了再说的角色因为绝大多数进程申请的内存并不会立刻全部用到实际分配和实际使用之间有巨大缺口。这个策略叫启发式超额分配。4.1 模式 0、1、2 的判定逻辑与 CommitLimit 的算法vm.overcommit_memory三个取值0启发式默认内核按一套经验规则判断这次申请是否离谱明显超出物理内存加 swap 就拒绝否则答应。绝大多数机器用这个就行它有兜底也不会太激进。1总是答应不管申请多少都给直到真正写内存时才因为找不到页触发 OOM。听起来危险但对某些特定进程是刚需。2从不超额严格按CommitLimit来超过就返回 ENOMEM。模式 2 下的CommitLimit算法是swap 总量 物理内存 × vm.overcommit_ratio / 100其中overcommit_ratio默认 50。也就是说一台 64G 内存加 8G swap 的机器CommitLimit 是 8 32 40G。看着挺宽裕但要注意这个额度是全系统累计的虚拟内存承诺总量不是单进程的。Java 应用、Go 程序、带大量 mmap 的服务虚拟地址空间动辄几十 G很容易在物理内存还很空闲的时候就撞到 CommitLimit报出让人摸不着头脑的 Cannot allocate memory。我见过一个典型案例某台机器把overcommit_memory设成 2理由是为了提高稳定性防止 OOM。结果跑在上面的一个程序在 fork 子进程时失败因为 fork 需要按父进程地址空间大小做承诺哪怕用了写时复制。日志里只有一句内存分配失败排查了半天才发现是内核参数拦的。4.2 那些必须设成 1 的进程与它的代价有几类进程确实需要overcommit_memory 1这不是玄学是它们的运行模型决定的。后台派生型进程像某些缓存服务在持久化时会 fork 一个子进程来处理fork 瞬间子进程的地址空间和父进程一样大但实际共享物理页。如果机器物理内存已经用了大半按模式 0 的启发式规则就会判定这次申请太离谱直接拒绝日志里出现那句著名的 Background save may fail under low memory condition 提示。这类场景官方的建议就是把vm.overcommit_memory设成 1。大规模稀疏分配的运行时某些语言的运行时和 JVM 的保留堆策略会预留大块虚拟地址空间实际提交的只是其中一部分。模式 2 下这种预留会被当成真实占用直接卡死。代价也很明确你放弃了最后一道防线。模式 0 至少会在分配阶段拦住明显不合理的申请模式 1 则一路放行一旦所有进程真的把申请的内存写满唯一的结果就是 OOM killer 出场随机杀进程。所以设成 1 的前提是机器的内存监控必须到位得有告警并且你能接受偶发的进程被杀。4.3 OOM 发生后去哪里找证据OOM 一旦发生先别急着调参数先把现场证据捞出来。内核会把 OOM 事件写在系统日志里重点看三段内容触发时的内存全局概况、被杀进程的内存占用、以及所有进程的内存排行。排查时我会顺着这几个方向看dmesg -T | grep -i -A 20 out of memory被杀的是不是最大的那个进程如果是可能只是它太能吃属于容量规划问题不是参数问题。total-vm和rss的比例是多少total-vm很大而rss很小说明虚拟地址空间虚高这种情况下严格超额分配模式 2反而会误伤。有没有多个进程在同一秒被杀如果确实需要保住某个核心进程oom_score_adj比调内核参数更精准。把它调到 -1000 就是基本免疫 OOM调到 1000 就是优先被牺牲。这属于进程级策略比全局改overcommit_memory风险小得多echo -500 /proc/pid/oom_score_adj至于vm.panic_on_oom默认 0 是对的。设成 1 让内核在 OOM 时直接重启这招只在机器必须自动恢复而不是带病运行的场景下用比如无状态的计算节点。数据库和有状态服务上千万别开。5. 水位线与 min_free_kbytes一条设错就全网告警的线这一组参数的特殊之处在于它一旦设错症状会非常统一可用内存明明还有不少但系统就是开始疯狂回收、负载飙高。5.1 三个水位线是怎么算出来的内核给每个内存管理区zone画了三条线min、low、high。空闲页低于high时后台回收线程kswapd开始干活这是温和回收。低于low回收力度加大同时开始考虑唤醒直接回收。低于min所有分配都要走直接回收申请内存的进程自己停下来去回收页这就是那种系统看起来没跑什么但所有操作都卡住的状态。三条线的基准是vm.min_free_kbytes。较新内核里min 就是它折算成的页数按 zone 大小分摊low 和 high 则是 min 加上一段间隔间隔取min 折算页数和zone 总页数 ×vm.watermark_scale_factor/ 10000中的较大值。默认watermark_scale_factor是 10也就是千分之一。旧一些的内核直接按 min 的 1.25 倍和 1.5 倍来算。min_free_kbytes的默认值由内核按可用内存开方推算大致是4 × sqrt(内存KB)并且上下限被夹在 128KB 到 64MB 之间。所以一台 64G 的机器默认值大约是 32MB。这个量级在小机器上够用但在大内存、NUMA、突发分配的场景下偏小容易频繁触到直接回收。5.2 调大调小的两个极端事故调太大的事故曾经有人听说留点余量更安全在 128G 的机器上把min_free_kbytes设成 8G。结果是每个 zone 的 min 水位都被抬高可用内存凭空少了一大块kswapd 常年不停地在回收CPU 的 sys 占比直接上到 15%而且因为回收压力大page cache 几乎留不住读性能下降明显。更麻烦的是这种配置下内存使用率看着永远上不去容量规划全乱。调太小的事故反过来把min_free_kbytes压到很小看起来多了点可用内存实际上损失的是内核的应急能力。突发的大块分配比如某个进程瞬间 mmap 几 G会直接冲到 min 水位以下触发直接回收然后所有申请内存的进程一起排队。表现出来就是接口在某个时刻集体超时vmstat里wa飙高、free掉得飞快。我的经验取值4G 以下内存保持默认16G 到 128G 之间设到 1% 左右但不要低于 256MB再大的机器按 2G 到 4G 这个量级考虑同时配合watermark_scale_factor一起调。5.3 watermark_scale_factor 与 NUMA 的 zone_reclaim_modevm.watermark_scale_factor是新内核里比min_free_kbytes更好用的旋钮。它直接决定 low 和 high 距离 min 有多远也就是留给后台回收线程的反应时间。默认 10千分之一在内存大的机器上这个间隔很小kswapd 还没反应过来水位就破了。把它的值调到 50 到 150 之间即 0.5% 到 1.5%相当于给回收线程更长的提前量能显著减少直接回收的发生概率。再说vm.zone_reclaim_mode这个参数只在 NUMA 机器上有意义。默认 0表示本节点内存不够时优先去别的节点借而不是就地回收本节点的缓存。如果你的业务对内存访问延迟敏感可以设成 4让内核优先在本地节点回收页避免跨节点访问带来的延迟放大。但要注意开了它就等于主动放弃一部分 page cache如果是读缓存型应用命中率会掉。所以我的建议是除非你明确知道自己在 NUMA 绑核并且测出了跨节点访问的延迟差异否则保持 0。6. 两个容易被忽略的隐形上限vfs_cache_pressure 与 max_map_count这两个参数不像前面那些动不动就影响全局但它们造成的故障特别有欺骗性——一个是缓存莫名其妙没了一个是明明内存够却申请不到地址空间。6.1 dentry/inode 缓存回收的油门vm.vfs_cache_pressure默认 100控制内核回收 dentry 和 inode 缓存的积极程度。值越大越积极回收值越小越倾向保留。这一块缓存对海量小文件场景极其关键如果目录树有几百上千万个文件每次遍历都要重建 dentry元数据操作会变成磁盘随机读性能断崖式下跌。有人把它设成 0想让内核永远别回收这些缓存。这个做法在内存充足的机器上短时间看不出问题但等于告诉内核这块内存不许动一旦系统压力上来内核就只剩匿名页和文件页两个选项回收压力全压到那边反而更容易触发直接回收甚至 OOM。我一般只在一种情况下动它明确观察到小文件元数据操作慢且slabtop显示 dentry/inode 占用很大但增长不快。这时候把值降到 50 左右是温和的做法能延长缓存寿命又不至于失控。slabtop -s c -o | head -20 # 看 dentry 和 inode_cache 两行的大小6.2 max_map_count 撞墙时的典型报错vm.max_map_count限制单个进程能拥有的内存映射区域数量默认 65530。这个数在普通应用上绰绰有余但对 mmap 密集的程序来说远远不够——搜索引擎类组件、部分 JVM 配置、某些数据库的索引文件访问方式都可能产生几十万甚至上百万个映射。撞墙时的报错非常明确就是那句很多人第一次见会懵的Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory. Out of Memory Error (os_linux.cpp)关键点在于这个报错和物理内存没关系free -g看着还有几十 G 空闲程序就是起不来。判断方法很简单看进程的映射数wc -l /proc/pid/maps如果这个数字逼近 65530就是撞限了。解法就是调高vm.max_map_count 262144提示这个参数同样没有命名空间隔离容器内改会作用到宿主机。容器化部署时要么在宿主机统一调高要么在启动参数里显式声明需要的能力。我倾向于前者一次配好省得每个镜像都操心。7. 调参的工程方法基线、单变量、可回滚参数本身讲完了但真正决定调优成败的其实是方法。我见过太多一次性改了八个参数然后性能变好了但没人知道是哪个起的作用下次出问题也不知道该回滚谁的操作。7.1 观测工具组合与关键指标调参前先建立基线这是不可跳过的一步。我固定用这几样东西覆盖内存、回收、回写三个维度关注点指标来源关键字段整体内存构成/proc/meminfoMemAvailable、Cached、Dirty、Writeback、SUnreclaim换页活动vmstat 1si、so、wa回收压力/proc/vmstatpgscan_direct、pgsteal_direct、allocstall回写行为/proc/vmstatnr_dirty、nr_writeback、nr_dirty_threshold内核缓存占用slabtopdentry、inode_cache、kmalloc-*其中pgscan_direct和allocstall这两个字段最容易被忽略但它们恰恰是直接回收发生次数的直接证据。调优的核心目标之一就是把这俩压到接近 0。如果调完之后它们还在涨说明你的参数没打中要害。MemAvailable比MemFree更有参考价值它是内核算出来的在不触发换页的情况下还能给新应用多少内存包含了大部分可回收的缓存。容量规划看这个数别盯着MemFree自己吓自己。7.2 一次只动一个参数一份可直接抄的记录表我的规矩是一次只动一个参数观察窗口不少于 30 分钟跨过至少一次业务高峰。同时把每次改动记下来格式固定方便回溯。下面这张表可以直接拿去当模板日期参数原值新值预期效果实际观测是否保留D1vm.dirty_bytes比例默认4294967296消除写延迟毛刺P99 从 820ms 降到 45ms保留D2vm.watermark_scale_factor10100减少直接回收allocstall 从 1200/s 降到 5/s保留D3vm.swappiness6010减少换出si/so 本来就是 0无变化回滚第三行是真实会发生的情况改了一个参数观测不到任何变化。那就老老实实回滚别留着。参数越少出问题时排查面越小。还有一点优先级排序很重要。我的顺序是先解决脏页问题影响最直接再看水位线影响最广泛然后才是 swappiness大多数场景影响其实不大最后考虑vfs_cache_pressure和max_map_count这种专项参数。上来就调 swappiness 是最典型的抓错主要矛盾。7.3 回滚与灰度所有参数改动都必须能在一分钟内回滚。做法很简单配置文件进版本管理改动前先备份当前生效值。sysctl -a /tmp/sysctl-baseline-$(date %F).txt更稳妥的是先灰度到一台机器观察一个完整业务周期至少包含一次日终批量或者一次流量高峰确认无异常再推全量。我碰到过一次dirty_bytes调小之后日终批量任务的写入吞吐从 400MB/s 掉到 180MB/s——平时的在线流量完全看不出来问题只有大批量顺序写的时候才暴露。这种问题不过一个业务周期根本发现不了。关于切换的时机也说一句内存类参数改动基本都不需要重启sysctl --system立即生效但对已经在跑的进程影响是渐进的比如脏页阈值变了正在积累的脏页要等下一轮回写才按新阈值走。所以改完之后别急着看结果给它几分钟稳定。最后再分享一个我自己的判断标准如果一个参数你改完之后说不出它影响了哪条链路、哪几个观测指标应该有变化那就别改。内存调优不是把网上抄来的数字填进去而是先定位瓶颈在哪条线上再用参数去推那条线。大部分线上机器的/proc/sys/vm/保持默认就挺好真正需要动的往往是那台内存特别大、存储特别慢、或者业务特别怪的机器。
RELATED

相关推荐

【跟踪】跟踪篇(4)--对于MOT16 Benchmark的理解

【跟踪】跟踪篇(4)--对于MOT16 Benchmark的理解

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

📅 2026/10/23 4:01:39
STM32F103与AT24C02的I2C读写实战:时序、硬件与调试避坑指南

STM32F103与AT24C02的I2C读写实战:时序、硬件与调试避坑指南

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

📅 2026/10/23 4:01:39
YOLOv6工业视觉检测优化与部署实战:从能检出到敢上线

YOLOv6工业视觉检测优化与部署实战:从能检出到敢上线

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

📅 2026/10/23 3:56:38
MORE NEWS

更多资讯

📰

FairScan Scan-to-PDF Intent开发指南:让你的应用调用隐私优先的扫描器

【免费下载链接】FairScan An Android app to scan your documents — simple and respectful 项目地址: https://gitcode.com/gh_mirrors/fa/FairScan 点击查看 免费下载 FairScan 是一个开源的 Android 文档扫描应用,主打「简单、尊重用户」&#xff…

📰

30分钟刷好平衡车FOC固件:hoverboard-firmware-hack-FOC 实战指南

30分钟刷好平衡车FOC固件:hoverboard-firmware-hack-FOC 实战指南 【免费下载链接】hoverboard-firmware-hack-FOC With Field Oriented Control (FOC) 项目地址: https://gitcode.com/GitHub_Trending/ho/hoverboard-firmware-hack-FOC 那台嗡嗡响、起步发愣…

📰

WoWee路线图前瞻:从大灾变支持到全新MMO计划,这个开源实验将走向何方

【免费下载链接】WoWee World of Warcraft Engine Experiment - a custom opensource client. 项目地址: https://gitcode.com/gh_mirrors/wow/WoWee 点击查看 免费下载 WoWee 是一个基于 C20 与 Vulkan 1.3 渲染器的开源魔兽世界客户端,目前已支持 Van…

📰

手把手教程:从零到平衡车 FOC 电机控制,一次讲透 hoverboard-firmware-hack-FOC

手把手教程:从零到平衡车 FOC 电机控制,一次讲透 hoverboard-firmware-hack-FOC 【免费下载链接】hoverboard-firmware-hack-FOC With Field Oriented Control (FOC) 项目地址: https://gitcode.com/GitHub_Trending/ho/hoverboard-firmware-hack-FOC …

📰

Rufus 制作 Win11 安装盘教程:3 步绕过 TPM 2.0

Rufus 制作 Win11 安装盘教程:3 步绕过 TPM 2.0 【免费下载链接】rufus The Reliable USB Formatting Utility 项目地址: https://gitcode.com/GitHub_Trending/ru/rufus Rufus 是一款单文件绿色 U 盘格式化工具,最大本事是把 Windows 11 安装器里…

📰

从0.5.x迁移到0.6:Unreal_mcp单一unreal工具革命完全指南与直接调用移除处理

【免费下载链接】Unreal_mcp A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine through the native C Automation Bridge plugin. Built with TypeScript and C. 项目地址: https://gitcode.com/gh_mirro…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬