FreeRTOS计数信号量深度解析:从队列原理到事件计数与资源池管理实战
1. 从“资源管理”到“任务同步”计数信号量的核心定位在嵌入式实时操作系统RTOS的开发中任务间的协调与资源共享是永恒的主题。FreeRTOS作为一款轻量、高效且应用广泛的开源RTOS提供了丰富的同步与通信机制其中信号量Semaphore是基石之一。今天我们不谈二值信号量也不谈互斥量就聚焦于一个看似简单、实则功能强大的工具——计数信号量Counting Semaphore。很多刚接触FreeRTOS的朋友容易把计数信号量简单地理解为“一个可以计数的二值信号量”。这种理解虽然直观但会严重限制其应用潜力甚至可能导致设计上的误区。在我经手的多个涉及数据流处理、事件批量响应或资源池管理的项目中计数信号量往往是简化架构、提升系统健壮性的关键。它解决的不仅仅是“有”或“无”的二元状态问题而是对“数量”这一维度的精确管理。想象一个经典场景一个生产者任务比如从传感器读取数据和一个消费者任务比如处理数据。如果生产者速度偶尔快于消费者使用二值信号量一次生产就会唤醒消费者如果消费者没处理完下一次生产的数据就可能被覆盖或丢失。而计数信号量则像一个有深度的仓库生产者每生产一个“物品”数据就往仓库里放一个令牌消费者每处理一个就从仓库取走一个令牌。仓库里令牌的数量直观地反映了待处理数据的积压量。这不仅仅是同步更是对系统负载的量化可视化和缓冲。因此理解计数信号量不能止步于API调用。我们需要深入其内核机制厘清它与二值信号量、队列的本质区别并掌握其在事件计数、资源池管理等高阶场景下的应用模式。这正是本文希望带你厘清的核心。2. 计数信号量的内部机制不止是一个计数器要用好一个工具必须了解它的工作原理。FreeRTOS的计数信号量在源码层面是基于其核心的队列Queue机制实现的。这一点非常关键因为它决定了计数信号量的许多行为特性。2.1 基于队列的实现原理在semphr.h中信号量句柄SemaphoreHandle_t实际上就是队列句柄QueueHandle_t的别名。当你调用xSemaphoreCreateCounting()创建一个最大计数值为uxMaxCount、初始计数值为uxInitialCount的计数信号量时FreeRTOS在底层创建了一个特殊的队列。这个队列具有以下特征队列长度uxLength等于你指定的最大计数值uxMaxCount。队列项大小uxItemSize为0。这意味着这个队列不用于存储实际的数据只用于“计数”。队列中的每一项你可以理解为一个“空”的令牌。初始状态队列初始时就有uxInitialCount个“空”项令牌。xSemaphoreGive()相当于向这个队列发送一个“空”消息如果队列未满而xSemaphoreTake()相当于从队列接收一个“空”消息如果队列非空。这种设计非常巧妙复用性复用了队列成熟的阻塞、超时、中断安全等机制无需为信号量单独实现一套复杂的任务调度逻辑。阻塞行为当一个任务尝试Take一个信号量而当前计数值为0时该任务会被放入该信号量队列的等待接收任务列表中进入阻塞状态。这与等待队列消息的行为一致。优先级继承注意计数信号量本身不具备优先级继承机制。这是它与互斥量Mutex的核心区别之一。互斥量用于保护临界资源需要防止优先级反转所以有优先级继承。而计数信号量主要用于同步和计数不涉及“所有权”概念因此没有优先级继承。如果你用计数信号量来管理一组实质上是互斥访问的资源比如缓冲区需要额外小心优先级反转问题。2.2 关键API的行为剖析与陷阱理解了底层是队列就能解释一些API的微妙行为。xSemaphoreCreateCounting(uxMaxCount, uxInitialCount)uxMaxCount信号量能达到的最大值。它直接决定了底层队列的长度。这个值在创建后无法更改。设计时必须根据应用场景的峰值负载合理设定。设得太小可能导致Give操作因队列满而失败返回pdFALSE设得太大则会浪费内存每个队列项虽不存数据但仍有管理开销。uxInitialCount信号量的初始值。它表示系统初始化后立即可用的“资源”数量。xSemaphoreGive(xSemaphore)/xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)本质向底层队列发送一个消息。由于消息大小为0所以不拷贝数据只增加队列中的项目数即信号量计数值。成功条件队列未满即当前计数值 uxMaxCount。唤醒机制如果此时有任务正在等待Take这个信号量即阻塞在接收队列消息上那么Give操作会唤醒等待任务中优先级最高的那个。如果是GiveFromISR并且唤醒了优先级更高的任务则pxHigherPriorityTaskWoken会被设置为pdTRUE提示中断退出后可能需要执行一次上下文切换。常见陷阱在中断服务程序ISR中必须使用GiveFromISR版本。使用普通的Give在大多数端口上会导致断言assert失败。此外不要假设Give一定会成功。在高并发或设计不当时Give可能因为计数已达上限而失败你的代码应该处理这种返回状态。xSemaphoreTake(xSemaphore, xBlockTime)/xSemaphoreTakeFromISR(xSemaphore, pxHigherPriorityTaskWoken)本质从底层队列接收一个消息。同样不拷贝数据只减少队列中的项目数。成功条件队列非空即当前计数值 0。如果为空任务会根据xBlockTime参数选择阻塞等待或直接返回pdFALSE。阻塞行为阻塞时任务状态变为eBlocked并被挂入该信号量的等待接收列表。其阻塞时间由系统节拍Tick管理。中断中的限制TakeFromISR是不带阻塞超时的版本。它只在当前计数值大于0时成功否则立即返回pdFALSE。在ISR中绝不能进行可能导致阻塞的操作。注意uxSemaphoreGetCount()函数可以获取当前的信号量计数值。但请注意这是一个“快照”在多任务环境下你刚获取这个值可能另一个任务就通过Give或Take改变了它。因此它通常用于监控和调试而非用于做严谨的逻辑判断比如“如果计数大于5则...”这种判断在并发下是不安全的。3. 计数信号量的两大经典应用场景与实战理论之后我们来点实际的。计数信号量的应用可以归纳为两大类每一类都有其独特的模式和注意事项。3.1 场景一事件计数Event Counting这是最直观的应用。某个事件如定时器溢出、外部中断、消息到达发生一次就Give一次信号量。一个或多个处理任务通过Take信号量来获知事件发生的次数并进行相应次数的处理。实战案例高频数据采集与批量处理假设我们有一个ADC通过DMA以1kHz的频率采集数据每采集完100个点即100ms触发一次DMA半满/全满中断。我们希望在主循环中批量处理这100个数据而不是在中断中处理。// 定义 SemaphoreHandle_t xAdcDataReadySemaphore; #define ADC_BUFFER_SIZE 200 uint16_t adcBuffer[ADC_BUFFER_SIZE]; // DMA双缓冲 // 在初始化中创建计数信号量初始为0最大计数值设一个足够大的数比如10 void System_Init(void) { xAdcDataReadySemaphore xSemaphoreCreateCounting(10, 0); // ... 其他初始化配置ADC和DMA ... } // DMA传输完成中断服务程序 void DMA1_Channel1_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken pdFALSE; if (DMA_GetITStatus(DMA1_IT_TC1)) { DMA_ClearITPendingBit(DMA1_IT_TC1); // 通知任务ADC数据已就绪例如后100个点 xSemaphoreGiveFromISR(xAdcDataReadySemaphore, xHigherPriorityTaskWoken); } // 可能还有半满中断... portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } // 数据处理任务 void vDataProcessTask(void *pvParameters) { while (1) { // 等待数据就绪信号 if (xSemaphoreTake(xAdcDataReadySemaphore, portMAX_DELAY) pdTRUE) { // 信号量获取成功代表有至少一批数据就绪 // 但这里有个关键点中断可能比任务处理得快。 // 如果数据处理很耗时DMA中断可能连续触发多次信号量计数会累积。 // 这恰恰是我们想要的它告诉我们有多少批数据在“排队”。 UBaseType_t uxCount uxSemaphoreGetCount(xAdcDataReadySemaphore); // 我们可以选择一次处理一批也可以根据uxCount决定是否要加速处理或丢弃一些数据 process_adc_buffer(adcBuffer[100]); // 处理一批数据 // 注意我们只Take了一次但中断可能Give了多次所以信号量计数还保留着uxCount-1。 // 任务循环会继续Take直到处理完所有积压的数据。 } } }这个案例的精髓中断发生的频率是固定的但任务处理的时间可能不确定。计数信号量天然地缓冲了这种速度差异。如果任务偶尔处理慢了信号量计数会增加但数据不会丢失只要DMA缓冲区设计合理不会覆盖未处理的数据。你可以通过查询uxSemaphoreGetCount()来监控数据积压情况实现简单的负载监控。3.2 场景二资源池管理Managing a Pool of Resources这是计数信号量更强大的一种用法。将信号量的计数值初始化为可用资源的数量如空闲内存块数、可用网络连接数、空闲硬件外设实例数。任务要使用资源前必须先Take一个信号量获取访问权使用完毕后Give回信号量释放资源。实战案例管理一个静态内存缓冲区池假设我们有5个固定大小的内存缓冲区Buffer供多个任务临时存储数据。必须确保一个缓冲区同时只被一个任务使用。#define POOL_SIZE 5 static uint8_t bufferPool[POOL_SIZE][BUFFER_SIZE]; static SemaphoreHandle_t xBufferSemaphore; static QueueHandle_t xFreeBufferIndexQueue; // 用于存放空闲缓冲区的索引 void BufferPool_Init(void) { // 创建计数信号量初始值等于资源总数5 xBufferSemaphore xSemaphoreCreateCounting(POOL_SIZE, POOL_SIZE); // 创建一个队列用于存放空闲缓冲区的索引号0~4 xFreeBufferIndexQueue xQueueCreate(POOL_SIZE, sizeof(int)); for (int i 0; i POOL_SIZE; i) { xQueueSend(xFreeBufferIndexQueue, i, 0); // 初始化将所有索引放入队列 } } // 申请一个缓冲区 int acquire_buffer(uint8_t **ppBuffer, TickType_t xTicksToWait) { int bufferIndex -1; // 第一步获取访问资源池的“资格”一个信号量令牌 if (xSemaphoreTake(xBufferSemaphore, xTicksToWait) ! pdTRUE) { return -1; // 超时没有可用资源 } // 第二步从空闲队列中取出一个具体的缓冲区索引 // 由于我们已经拿到了信号量所以这个队列操作应该很快成功除非有bug if (xQueueReceive(xFreeBufferIndexQueue, bufferIndex, 0) ! pdTRUE) { // 这不应该发生信号量计数和实际资源不同步说明有逻辑错误。 xSemaphoreGive(xBufferSemaphore); // 归还信号量避免死锁 configASSERT(0); // 在调试版本触发断言 return -1; } *ppBuffer bufferPool[bufferIndex]; return bufferIndex; // 返回索引用于后续释放 } // 释放一个缓冲区 void release_buffer(int bufferIndex) { if (bufferIndex 0 || bufferIndex POOL_SIZE) { return; // 非法索引 } // 第一步将缓冲区索引归还到空闲队列 xQueueSendToBack(xFreeBufferIndexQueue, bufferIndex, 0); // 第二步归还资源池的“资格”信号量令牌 xSemaphoreGive(xSemaphore); } // 任务中使用示例 void vSomeTask(void *pvParameters) { uint8_t *pMyBuffer NULL; int myBufferIndex -1; while (1) { // 申请缓冲区等待最多100个Tick myBufferIndex acquire_buffer(pMyBuffer, 100); if (myBufferIndex 0) { // 成功获取缓冲区进行数据操作... memcpy(pMyBuffer, someData, DATA_SIZE); // ... 处理数据 ... // 操作完成后必须释放 release_buffer(myBufferIndex); myBufferIndex -1; // 防止误用 } else { // 获取缓冲区失败可能是池已耗尽可以重试、报错或等待 vTaskDelay(pdMS_TO_TICKS(10)); } } }这个设计模式的优点资源访问串行化计数信号量控制了访问资源池的并发数确保不会超额分配。资源具体分配队列xFreeBufferIndexQueue负责管理具体的、可区分的资源实例。信号量只关心“有多少个可用”队列关心“哪几个可用”。灵活性你可以很容易地扩展这个模式比如实现不同优先级的资源申请或者在释放资源时进行清理工作。重要提醒在这种模式下计数信号量只控制资源的数量不控制对资源本身的互斥访问。如果两个任务拿到了不同的缓冲区不同的bufferIndex它们可以并行操作因为操作的是不同的内存块。但如果多个任务需要读写同一个缓冲区你还需要在缓冲区层面使用互斥量Mutex进行保护。计数信号量在这里的角色是“资源池管理员”而不是“资源锁”。4. 常见误区、调试技巧与进阶思考即使理解了原理和应用在实际项目中围绕计数信号量仍有不少坑。4.1 误区一将计数信号量误用作互斥量这是最常见的错误。互斥量有所有权概念和优先级继承用于保护临界区。计数信号量没有所有权一个任务Take后可以由另一个完全无关的任务Give。如果你用计数信号量初始值为1来保护一个共享变量SemaphoreHandle_t xSem xSemaphoreCreateCounting(1, 1); // 看起来像二值信号量 void TaskA(void) { xSemaphoreTake(xSem, portMAX_DELAY); // 访问共享资源 xSemaphoreGive(xSem); // TaskA释放 } void TaskB(void) { // 错误TaskB没有Take却直接Give这会破坏保护 xSemaphoreGive(xSem); }TaskB的非法Give会使信号量计数大于1导致多个任务可能同时进入临界区。而互斥量会记录持有者非持有者释放会触发断言错误。结论保护独占式访问的共享资源请使用互斥量xSemaphoreCreateMutex()。4.2 误区二忽略Give/Take的返回值xSemaphoreGive和xSemaphoreTake都可能失败。Give失败通常因为计数已达到最大值uxMaxCount。这可能意味着消费者处理太慢或者uxMaxCount设置不合理。需要检查系统设计。Take失败在指定超时时间内未获取到信号量。这可能是生产者出了问题或者系统死锁。在生产代码中务必检查这些返回值并实现适当的错误处理如重试、告警、系统复位等。4.3 调试技巧利用uxSemaphoreGetCount()进行系统状态监控在调试复杂系统时可以将关键计数信号量的当前值通过调试接口如串口、SEGGER SystemView、FreeRTOSTrace实时输出。这能帮你识别瓶颈如果某个资源池的信号量计数长时间为0说明该资源是瓶颈。发现死锁如果信号量计数卡在某个非预期值比如应该是0或最大值却卡在中间可能暗示有任务异常终止未释放资源或者Give/Take不匹配。验证流程在事件计数场景下观察信号量计数是否在预期范围内波动可以验证生产-消费链是否顺畅。4.4 进阶思考计数信号量与队列的抉择两者都能用于任务间通信如何选择需要传递数据本身用队列xQueueCreate。队列拷贝数据发送和接收的是数据实体。只需要通知事件发生或管理资源数量且事件/资源不可区分或无需区分用计数信号量。它更轻量不拷贝数据且计数值本身承载了“有多少个”的信息。混合场景有时需要同时传递数据和计数。一种模式是“队列计数信号量”生产者将数据放入队列同时Give一个信号量消费者Take信号量然后从队列读数据。这确保了通知和数据的同步。FreeRTOS的流缓冲区Stream Buffer或消息缓冲区Message Buffer在某种程度上是这种模式的封装。4.5 性能与内存考量内存每个计数信号量会占用一个队列结构体的内存。对于资源极度紧张的系统如果只需要二值信号量使用xSemaphoreCreateBinary()会更节省内存它使用更精简的实现。速度Give和Take操作涉及任务调度和可能的上下文切换在性能关键路径上需谨慎使用。在中断服务程序中务必使用FromISR版本。uxMaxCount的设置不要随意设置一个非常大的值。它决定了底层队列的等待任务列表数组的大小虽然每个等待任务只是链表节点但队列结构本身有开销。根据实际最大并发需求设定一个合理的值即可。计数信号量是FreeRTOS同步原语中一颗灵活多面的齿轮。它看似简单但将其置于正确的应用场景事件计数、资源池并避开常见的误区误用作互斥量、忽略返回值就能极大地提升多任务系统的清晰度、健壮性和可维护性。下次设计任务协作时不妨先问问自己我这里需要管理的是“有/无”还是一个“数量”如果是后者计数信号量很可能就是你要找的答案。

相关新闻

Dijkstra算法与优先队列结合的性能优化实践

Dijkstra算法与优先队列结合的性能优化实践

1. Dijkstra算法与优先队列的完美结合第一次看到Dijkstra算法和优先队列放在一起时,我脑海中浮现的是快递分拣中心的场景。想象一下,传统的Dijkstra就像人工分拣员挨个检查包裹,而优先队列则像自动分拣机,能立即识别出最优先处理的…

2026/10/15 16:29:57 阅读更多 →
刷屏全网的胡楚靓 AI 工作台!零代码手把手复刻(附100套专属提示词)

刷屏全网的胡楚靓 AI 工作台!零代码手把手复刻(附100套专属提示词)

最近,胡楚靓的AI个人工作台在各个社媒刷爆了,全是打卡截图! 评论区里考研党、销售、宝妈、各行打工人都在晒自己搭的专属工作台截图,评论区直接成了大型交作业现场。 也有很多人只看热闹,也没有自己搭。 我完整拆解了全网爆火的版…

2026/10/15 12:22:18 阅读更多 →
高并发下的 MySQL 锁治理:从行锁、间隙锁、死锁到 MDL 雪崩的生产级实战

高并发下的 MySQL 锁治理:从行锁、间隙锁、死锁到 MDL 雪崩的生产级实战

高并发下的 MySQL 锁治理:从行锁、间隙锁、死锁到 MDL 雪崩的生产级实战 文章定位:不是罗列锁名词,而是建立一套从“SQL 如何加锁”到“生产事故如何止损”的完整方法论。 导读 高并发系统中的数据库锁问题,通常不是“某一条 SQL 很慢”这么简单。 一次锁事故往往会沿着下…

2026/10/12 5:38:06 阅读更多 →

最新新闻

ARM嵌入式PPP拨号一键编译包:pppd+chat静态构建与AT兼容修复

ARM嵌入式PPP拨号一键编译包:pppd+chat静态构建与AT兼容修复

简介:本资源是面向嵌入式Linux开发者与ARM平台运维人员的pppd与chat自动化编译实践套件,聚焦解决在ARM架构设备(如工业网关、物联网终端)上交叉编译PPP协议栈组件并适配3G模组的典型工程难题。压缩包共12个文件,含3个S…

2026/10/15 16:29:42 阅读更多 →
变频器EMC滤波与屏蔽接地工程实践:从共模干扰排查到整改闭环

变频器EMC滤波与屏蔽接地工程实践:从共模干扰排查到整改闭环

1. 变频器干扰问题的现场画像与排查起点干了十几年电气工程,我越来越觉得低压变频器的EMC问题像一场“慢性病”——它不会让设备立刻停机,但会持续制造麻烦:PLC模拟量通道跳字、称重传感器读数漂移、编码器脉冲丢失、触摸屏偶尔死机、通信总线…

2026/10/15 16:29:42 阅读更多 →
元模型驱动的开发模式与软件开发智能体的认知负担(短文版)

元模型驱动的开发模式与软件开发智能体的认知负担(短文版)

元模型驱动的开发模式与软件开发智能体的认知负担(短文版)摘要元模型驱动开发通过分层抽象与模型自动转换,将业务语义从底层技术实现解耦,已广泛应用于企业软件与工业数字化工程。随着大模型驱动的软件开发智能体逐步落地&#xf…

2026/10/15 16:29:42 阅读更多 →
低压变频器EMC整改实战:滤波器选型、屏蔽接地与现场案例

低压变频器EMC整改实战:滤波器选型、屏蔽接地与现场案例

1. 变频器EMC问题的工程背景与核心痛点1.1 为什么低压变频器是车间里的"电磁污染源"干了十几年电气调试,我越来越觉得低压变频器是个让人又爱又恨的东西。爱它是因为调速节能、控制精准,恨它是因为它天生就是个强电磁骚扰源。变频器内部的核心…

2026/10/15 16:29:42 阅读更多 →
C++条件变量详解:从wait原理到生产者消费者实战

C++条件变量详解:从wait原理到生产者消费者实战

如果你已经用std::mutex写过几段多线程代码,大概率会撞上一个特别拧巴的场景:线程 A 在等一个条件成立,而这个条件什么时候成立只有线程 B 知道,B 又不会提前告诉你。第一反应是轮询——循环里查一下状态,睡一会儿&…

2026/10/15 16:29:42 阅读更多 →
STM32+华为云IoT+Qt:智能家居全栈开发实战

STM32+华为云IoT+Qt:智能家居全栈开发实战

1. 项目缘起与整体架构拆解智能家居这个词已经被说烂了,但真正自己动手从零搭一套完整系统的人并不多。大部分人要么停留在用现成模组点个灯的阶段,要么卡在设备上云这一步就放弃了。我当初做这个项目的出发点很简单:家里有几台老式空调和加湿…

2026/10/15 16:28:41 阅读更多 →

日新闻

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/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

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

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

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

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

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

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 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 阅读更多 →