Linux内核RCU机制的进化:从经典RCU到SRCU到Tasks RCU的场景选择
Linux内核RCU机制的进化从经典RCU到SRCU到Tasks RCU的场景选择摘要RCURead-Copy-Update是Linux内核中最重要的同步机制之一。本文深入剖析RCU机制的演进历程对比经典RCU、SRCU和Tasks RCU的技术本质、实现原理与适用场景为内核开发者和底层系统工程师提供选型决策依据。一、RCU机制的核心原理与演进背景1.1 RCU的基本概念RCURead-Copy-Update是一种适用于读多写少场景的同步机制。其核心思想可概括为读者无锁读者不需要获取任何锁即可访问共享数据写者延迟回收写者在完成数据更新后延迟回收旧数据直到所有读者完成访问宽限期Grace Period从写者完成更新到旧数据可被安全回收的时间窗口/* 经典RCU使用示例 */ struct my_data { int value; char name[64]; }; struct my_data *global_ptr; /* 读者侧 - 无任何锁开销 */ void reader(void) { struct my_data *ptr; /* 进入RCU读侧临界区 */ rcu_read_lock(); /* 安全地读取共享数据 */ ptr rcu_dereference(global_ptr); if (ptr) { printk(value%d, name%s\n, ptr-value, ptr-name); } /* 退出RCU读侧临界区 */ rcu_read_unlock(); } /* 写者侧 - 延迟回收旧数据 */ void writer(int new_value) { struct my_data *old_ptr, *new_ptr; /* 分配新数据 */ new_ptr kmalloc(sizeof(*new_ptr), GFP_KERNEL); if (!new_ptr) return; /* 复制并更新数据 */ old_ptr rcu_dereference(global_ptr); memcpy(new_ptr, old_ptr, sizeof(*new_ptr)); new_ptr-value new_value; /* 原子替换指针 */ rcu_assign_pointer(global_ptr, new_ptr); /* 等待宽限期后释放旧数据 */ synchronize_rcu(); kfree(old_ptr); }1.2 RCU演进的三大驱动力驱动力1性能优化经典RCU的synchronize_rcu()会导致写者阻塞等待所有CPU上的宽限期结束。在实时系统中这种阻塞可能导致不可接受的延迟。驱动力2场景多样化Linux内核不同子系统网络栈、文件系统、设备驱动等对RCU的需求不同。网络栈需要快速的RCU保护而某些慢速路径可以使用更宽松的保证。驱动力3可维护性随着RCU在内核中的广泛使用调试RCU相关问题变得极其困难。需要更明确的API和更好的调试支持。二、经典RCU基础设施与实现机制2.1 经典RCU的实现原理经典RCU依赖于一个关键观察在CPU上经历一次上下文切换或 quiescent state后该CPU上的所有RCU读侧临界区都已完成。/* Linux内核中RCU宽限期检测的核心逻辑简化版 */ struct rcu_state { unsigned long gp_seq; /* 宽限期序列号 */ unsigned long gp_start; /* 宽限期开始时间 */ struct rcu_node *node; /* RCU层次结构 */ }; struct rcu_data { unsigned long gp_seq; /* 本地看到的宽限期序列号 */ struct rcu_segcblist cur; /* 当前宽限期的回调 */ struct rcu_segcblist next; /* 下一个宽限期的回调 */ }; /* 检测宽限期完成的核心函数 */ static void rcu_gp_cleanup(struct rcu_state *rsp) { struct rcu_data *rdp; unsigned long gp_seq; /* 标记宽限期完成 */ gp_seq rsp-gp_seq; /* 遍历所有CPU处理完成的回调 */ for_each_possible_cpu(cpu) { rdp per_cpu_ptr(rsp-rda, cpu); /* 执行该CPU上等待的RCU回调 */ rcu_segcblist_advance(rdp-cur, gp_seq); invoke_rcu_callbacks(rdp); } /* 启动新的宽限期如果需要 */ if (need_new_gp(rsp)) { rcu_start_gp(rsp); } }2.2 宽限期的检测机制RCU通过检测所有CPU都经历了一次quiescent state静止状态来判断宽限期结束。2.3 经典RCU的局限# 经典RCU的局限性分析伪代码 class ClassicRCUAnalysis: 经典RCU局限性分析 limitations { global_grace_period: { description: 所有RCU使用者共享同一个宽限期, impact: 一个慢速使用者会延迟所有其他使用者的回收, example: 网络栈的RCU等待被文件系统的慢速操作拖慢 }, blocking_synchronize: { description: synchronize_rcu()是阻塞调用, impact: 写者线程阻塞增加延迟, mitigation: 可使用call_rcu()异步版本但编程复杂度增加 }, no_read_side_debugging: { description: 难以调试RCU读侧临界区的配对问题, impact: rcu_read_lock/unlock不匹配难以发现, consequence: 可能导致use-after-free漏洞 }, real_time_latency: { description: 宽限期检测可能增加实时延迟, impact: 不适合严格的实时系统要求, context: 某些CPU长时间不进入quiescent state } } staticmethod def demonstrate_global_gp_problem(): 演示全局宽限期问题 return 场景网络栈频繁更新路由表(使用RCU)同时文件系统缓慢遍历目录(也使用RCU) 问题网络栈的RCU宽限期必须等待文件系统的RCU读侧临界区完成 结果网络栈的性能抖动延迟不可预测 解决SRCU(Sleepable RCU)允许不同子系统使用独立的宽限期 三、SRCU可睡眠RCU的场景优化3.1 SRCU的设计目标SRCUSleepable Read-Copy-Update针对两个核心问题允许读者睡眠经典RCU的读侧临界区内不允许睡眠会导致宽限期无限延长独立宽限期不同子系统可以使用独立的RCU域避免相互干扰/* SRCU使用示例 */ #include linux/srcu.h DEFINE_SRCU(my_srcu); /* 定义SRCU域 */ struct my_data *global_ptr; /* 读者侧 - 允许睡眠 */ void srcu_reader(void) { int idx; /* 进入SRCU读侧临界区返回索引 */ idx srcu_read_lock(my_srcu); /* 可以安全地睡眠 */ struct my_data *ptr srcu_dereference(global_ptr, my_srcu); if (ptr) { msleep(100); /* 合法不会阻塞宽限期检测 */ process_data(ptr); } /* 退出SRCU读侧临界区 */ srcu_read_unlock(my_srcu, idx); } /* 写者侧 */ void srcu_writer(int new_value) { struct my_data *old_ptr, *new_ptr; new_ptr kmalloc(sizeof(*new_ptr), GFP_KERNEL); old_ptr srcu_dereference(global_ptr, my_srcu); memcpy(new_ptr, old_ptr, sizeof(*new_ptr)); new_ptr-value new_value; /* 原子替换 */ rcu_assign_pointer(global_ptr, new_ptr); /* 等待SRCU宽限期仅等待my_srcu域 */ synchronize_srcu(my_srcu); /* 安全释放 */ kfree(old_ptr); }3.2 SRCU的实现机制SRCU通过每CPU计数器来跟踪读者而非依赖上下文切换检测。/* SRCU核心数据结构简化 */ struct srcu_struct { unsigned long srcu_idx; /* 当前活跃的索引(0或1) */ struct srcu_array __percpu *sda; /* 每CPU计数器数组 */ wait_queue_head_t wq; /* 等待队列 */ }; struct srcu_array { unsigned long lock_count[2]; /* 锁计数[索引] */ unsigned long unlock_count[2]; /* 解锁计数[索引] */ }; /* SRCU读侧进入 - 核心逻辑 */ int __srcu_read_lock(struct srcu_struct *ssp) { int idx; struct srcu_array *sda this_cpu_ptr(ssp-sda); /* 获取当前索引 */ idx READ_ONCE(ssp-srcu_idx); /* 增加锁定计数这是完整的内存屏障 */ atomic_long_inc(sda-lock_count[idx]); return idx; } /* SRCU宽限期检测 */ bool srcu_readers_active_idx(struct srcu_struct *ssp, int idx) { unsigned long sum 0; int cpu; /* 汇总所有CPU的活跃读者计数 */ for_each_possible_cpu(cpu) { struct srcu_array *sda per_cpu_ptr(ssp-sda, cpu); sum atomic_long_read(sda-lock_count[idx]); sum - atomic_long_read(sda-unlock_count[idx]); } return sum 0; /* 如果还有活跃读者返回true */ }3.3 SRCU的适用场景对比特性经典RCUSRCU读者是否可睡眠不可可宽限期范围全局每域独立性能开销低中等每CPU计数器API复杂度简单较复杂需管理idx调试支持有限较好适用场景内核核心路径需要睡眠的上下文# SRCU场景选择决策树伪代码 def choose_rcu_variant(use_case: dict) - str: 根据使用场景选择RCU变体 if use_case[reader_may_sleep]: return SRCU # 读者可能睡眠必须用SRCU if use_case[subsystem_isolation_needed]: return SRCU # 需要独立的宽限期域 if use_case[is_performance_critical] and not use_case[reader_may_sleep]: return Classic RCU # 性能关键且读者不睡眠 if use_case[need_read_side_debugging]: return SRCU # SRCU有更好的调试支持 return Classic RCU # 默认选择四、Tasks RCU用户态线程的RCU保护4.1 Tasks RCU的诞生背景随着eBPF的广泛使用内核需要在用户态线程上下文中保护共享数据。经典RCU和SRCU都无法直接应用于此场景用户态线程的调度由用户态库管理内核无法直接感知其RCU临界区需要一种机制来跟踪用户态线程的grace periodTasks RCU应运而生专门用于保护涉及任务task生命周期的操作。4.2 Tasks RCU的核心原理Tasks RCU通过跟踪任务切换来判断宽限期而非CPU级别的quiescent state。/* Tasks RCU使用示例 - 常见于eBPF程序卸载场景 */ #include linux/rcupdate_wait.h /* 受Tasks RCU保护的数据 */ struct bpf_prog *active_prog; /* 卸载eBPF程序时使用Tasks RCU */ void bpf_prog_put(struct bpf_prog *prog) { /* 从活跃列表移除 */ list_del_rcu(prog-aux-list); /* 使用tasks_rcu_wait等待所有引用消失 */ tasks_rcu_wait(); /* 关键等待所有任务切换完成 */ /* 安全释放eBPF程序 */ bpf_prog_free(prog); }4.3 三种RCU机制的综合对比/* 综合对比示例 - 不同场景下的RCU选择 */ enum rcu_type { RCU_CLASSIC, RCU_SRCU, RCU_TASKS }; struct rcu_selection_guide { const char *scenario; enum rcu_type recommended; const char *reason; }; static struct rcu_selection_guide guide[] { {内核数据结构频繁读取, RCU_CLASSIC, 读侧无锁性能最优}, {读者可能睡眠(如用户态操作), RCU_SRCU, SRCU允许睡眠}, {多个子系统独立使用RCU, RCU_SRCU, 独立宽限期域互不干扰}, {eBPF程序生命周期管理, RCU_TASKS, 需要跟踪任务切换}, {网络设备驱动数据保护, RCU_CLASSIC, 性能关键读者不睡眠}, {文件系统元数据处理, RCU_SRCU, 可能睡眠需要独立域}, {内核模块卸载保护, RCU_TASKS, 确保没有任务引用模块代码}, {读侧临界区需要调试支持, RCU_SRCU, SRCU提供更丰富的调试API}, };4.4 性能特征对比五、总结与场景选择决策框架5.1 技术本质提炼本文深入剖析了Linux内核RCU机制的演进历程核心结论如下经典RCU适用于读多写少、读者不睡眠的高性能场景。依赖CPU quiescent state检测宽限期。SRCU适用于读者可能睡眠、或需要独立宽限期域的场景。通过每CPU计数器跟踪读者。Tasks RCU专为需要跟踪任务生命周期的场景设计如eBPF、模块卸载。通过任务切换判断宽限期。5.2 场景选择决策框架class RCUSelector: RCU变体选择决策框架 staticmethod def select(requirements: dict) - str: 根据需求选择RCU变体 requirements参数: - reader_may_sleep: bool # 读者是否可能睡眠 - need_isolation: bool # 是否需要宽限期隔离 - is_perf_critical: bool # 是否性能关键 - involve_user_task: bool # 是否涉及用户态任务 - need_debug_support: bool # 是否需要调试支持 # 决策规则1: 涉及用户态任务生命周期 if requirements.get(involve_user_task, False): return Tasks RCU # 决策规则2: 读者可能睡眠 if requirements.get(reader_may_sleep, False): return SRCU # 决策规则3: 需要宽限期隔离 if requirements.get(need_isolation, False): return SRCU # 决策规则4: 性能关键且读者不睡眠 if requirements.get(is_perf_critical, False): return Classic RCU # 决策规则5: 需要调试支持 if requirements.get(need_debug_support, False): return SRCU # 默认: 经典RCU return Classic RCU staticmethod def generate_example_code(rcu_type: str) - str: 生成对应RCU类型的示例代码 examples { Classic RCU: /* 经典RCU - 内核哈希表保护 */ rcu_read_lock(); entry rcu_dereference(hash_table[key]); if (entry) process(entry); rcu_read_unlock(); , SRCU: /* SRCU - 可睡眠的读侧临界区 */ idx srcu_read_lock(my_srcu); ptr srcu_dereference(data, my_srcu); msleep(100); /* 允许睡眠 */ srcu_read_unlock(my_srcu, idx); , Tasks RCU: /* Tasks RCU - eBPF程序卸载 */ list_del_rcu(prog-list); tasks_rcu_wait(); /* 等待所有任务离开 */ bpf_prog_free(prog); } return examples.get(rcu_type, /* 未知RCU类型 */)5.3 内核开发最佳实践/* RCU使用最佳实践检查清单 */ /* ✅ 正确做法 */ void correct_rcu_usage(void) { struct my_data *ptr; /* 1. 总是在rcu_read_lock/unlock之间使用rcu_dereference */ rcu_read_lock(); ptr rcu_dereference(global_ptr); if (ptr) { /* 2. 在RCU读侧临界区内不要睡眠经典RCU */ /* 3. 不要长时间持有RCU读侧锁 */ process_quickly(ptr); } rcu_read_unlock(); /* 4. 确保解锁路径被执行 */ /* 5. 使用适当的同步原语等待宽限期 */ synchronize_rcu(); /* 或 call_rcu() 异步版本 */ /* 6. 宽限期后才能释放旧数据 */ kfree(old_ptr); /* 这里old_ptr已被安全隔离 */ } /* ❌ 错误做法 */ void incorrect_rcu_usage(void) { /* 错误1: 在RCU读侧临界区外使用rcu_dereference的结果 */ rcu_read_lock(); ptr rcu_dereference(global_ptr); rcu_read_unlock(); printk(%s\n, ptr-name); /* BUG! ptr可能已被释放 */ /* 错误2: 在经典RCU读侧临界区内睡眠 */ rcu_read_lock(); ptr rcu_dereference(global_ptr); msleep(1000); /* BUG! 可能延迟宽限期检测 */ rcu_read_unlock(); /* 错误3: 忘记调用synchronize_rcu()就释放数据 */ old_ptr rcu_dereference(global_ptr); rcu_assign_pointer(global_ptr, new_ptr); kfree(old_ptr); /* BUG! 仍有读者可能访问old_ptr */ }5.4 未来演进方向生产级参考代码Linux内核源码kernel/rcu/目录rcu_preempt.c,srcutree.c,tasks.h进一步阅读Is Parallel Programming Hard, And, If So, What Can You Do About It? by Paul E. McKenneyLinux Kernel Development (3rd Edition) - Chapter 9: Kernel Synchronization Methodskernel.org/doc/Documentation/RCU/ Linux内核官方RCU文档作者钟伊人 | CSDN技术博客 | 发布日期2026年7月30日资料说明本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论不应视为行业事实。可参考 0730 资料来源索引并在发布前将具体来源贴到对应断言之后。

相关新闻

2026年7月个人IP定位5大觉醒点:从模糊到空心,你收到了几个?

2026年7月个人IP定位5大觉醒点:从模糊到空心,你收到了几个?

2026年的规则已经变了。不是慢慢变,是翻篇了。打开任何一个内容平台,相似的脸、相似的话、相似的标题。用户在刷屏中变得麻木,创作者在对比中陷入消耗。一位做了3个月小红书的朋友跟我说:"日更两个月,粉丝300。别…

2026/10/1 10:01:12 阅读更多 →
2026年降AI率软件测评:免费额度+退款承诺+适配平台横评

2026年降AI率软件测评:免费额度+退款承诺+适配平台横评

2个实测免费的降AIGC率工具,顺利通过ai率查重! AI 检测本身就没有公开 算法 ,降 AI 工具更像黑箱。如果降AI率连一次免费试用都不给,那风险太大了。万一AI率没有降下来,又不能退,少则几元多则几十。 对于学…

2026/10/13 13:13:45 阅读更多 →
HarmonyOS 5.0.0 多设备布局怎么做:手机、平板和鸿蒙电脑断点怎么避免挤成一团

HarmonyOS 5.0.0 多设备布局怎么做:手机、平板和鸿蒙电脑断点怎么避免挤成一团

HarmonyOS 5.0.0 多设备布局怎么做:手机、平板和鸿蒙电脑断点怎么避免挤成一团先说结论 这篇只讲一个点:多设备布局不是把宽度写成百分比,而是要按断点换结构。我按 HarmonyOS 5.0.0 的写法来拆,不把官方名词堆在前面,…

2026/10/5 1:28:04 阅读更多 →

最新新闻

学Simulink——储能变流器(PCS)中 PWM 整流器的恒流充放电(CC Charge / CC Discharge)仿真

学Simulink——储能变流器(PCS)中 PWM 整流器的恒流充放电(CC Charge / CC Discharge)仿真

目录 手把手教你学Simulink——储能变流器(PCS)中 PWM 整流器的恒流充放电(CC Charge / CC Discharge)仿真 一、为什么储能 PCS 要做 恒流(CC)充放电双向 PWM 整流器 二、PCS 双向控制与 CC 模式原理** 2.1 功率平衡估算(用于 idref​折算) 2.2 模式定义 三、关键…

2026/10/15 10:38:32 阅读更多 →
TaoToken 视角下的示波器使用:measure/acquire/storage/cursor 四步实测

TaoToken 视角下的示波器使用:measure/acquire/storage/cursor 四步实测

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

2026/10/15 10:38:32 阅读更多 →
学Simulink——三相 PWM 整流器在船舶岸电系统(Shore-to-Ship Power, Cold Ironing)中的并网控制仿真

学Simulink——三相 PWM 整流器在船舶岸电系统(Shore-to-Ship Power, Cold Ironing)中的并网控制仿真

目录 手把手教你学Simulink——三相 PWM 整流器在船舶岸电系统(Shore-to-Ship Power, Cold Ironing)中的并网控制仿真 一、为什么船舶岸电系统要用 三相 PWM 整流器/并网变流器 二、岸电系统典型参数(教学模型)** 三、Simulink 建模(手把手)** 3.1 Step 1️⃣ —— 港…

2026/10/15 10:38:32 阅读更多 →
PP-HumanSeg Lite + NCNN C++ 部署实战:精准版本对齐与边缘落地

PP-HumanSeg Lite + NCNN C++ 部署实战:精准版本对齐与边缘落地

简介:本资源是一套面向嵌入式与移动端开发者的轻量级人像分割部署方案,聚焦PP-HumanSeg Lite模型在NCNN框架下的C端高效落地,适用于视频会议背景虚化、AR换装、实时抠图等低延迟场景,适合具备C基础和一定CV部署经验的中高级开发者…

2026/10/15 10:38:32 阅读更多 →
OpenGL正背面剔除与深度测试:原理、配置与穿模问题排查

OpenGL正背面剔除与深度测试:原理、配置与穿模问题排查

1. 从一个“穿模”现象说起:为什么需要正背面剔除与深度测试但凡做过3D渲染的人,几乎都遇到过这样的场景:一个封闭的立方体或者角色模型,旋转到某个角度后,本该被挡住的背面却透了出来,或者两个物体交叠的地…

2026/10/15 10:38:32 阅读更多 →
企业WordPress定制开发:核心价值与选型指南

企业WordPress定制开发:核心价值与选型指南

1. 为什么企业需要专业的WordPress定制开发服务 在数字化浪潮席卷各行各业的当下,企业官网已从简单的"电子名片"升级为集品牌展示、客户服务和业务转化于一体的核心平台。WordPress作为全球占比43%的内容管理系统,凭借其开源特性、丰富的插件生…

2026/10/15 10:37:31 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →