From 72a01aed5fd87dc79842bc16b8ffdb1a7bf2f682 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E5=AD=99=E6=98=A5=E9=98=B3?= Date: Mon, 13 Oct 2025 12:13:59 +0800 Subject: [PATCH] =?utf8?q?=E4=BC=98=E5=8C=96ppp=E4=B8=B2=E5=8F=A3=E4=B8=AD?= =?utf8?q?=E6=96=AD=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../ppp/doc/serial_test/compare_logs.py | 61 +++- code/sdk/zhihui/ppp/include/ppp_config.h | 2 +- code/sdk/zhihui/ppp/src/ppp_interrupt.c | 290 ++++++++++++------ code/sdk/zhihui/ppp/src/ppp_lwip_thread.c | 22 +- 4 files changed, 260 insertions(+), 115 deletions(-) diff --git a/code/sdk/zhihui/ppp/doc/serial_test/compare_logs.py b/code/sdk/zhihui/ppp/doc/serial_test/compare_logs.py index 903e13b7f..ad84f3134 100644 --- a/code/sdk/zhihui/ppp/doc/serial_test/compare_logs.py +++ b/code/sdk/zhihui/ppp/doc/serial_test/compare_logs.py @@ -116,30 +116,72 @@ def compare_logs(tx_data, rx_data, max_errors=100): # 显示丢失的数据范围 print(f"\n发现 {len(missing_ranges)} 个丢失区域:\n") - total_missing = 0 + # 先计算所有丢失的字节总数(包括未显示的) + total_missing = sum(range_info['count'] for range_info in missing_ranges) shown_ranges = 0 + # 计算应该显示的范围 + total_ranges = len(missing_ranges) + show_last_n = 2 # 始终显示最后N条 + + # 如果总数很少,全部显示;否则显示前面部分+最后N条 + if total_ranges <= max_errors: + # 全部显示 + ranges_to_show = list(range(total_ranges)) + else: + # 显示前面部分和最后N条 + show_first_n = max_errors - show_last_n + ranges_to_show = list(range(show_first_n)) + list(range(total_ranges - show_last_n, total_ranges)) + # 标记需要显示省略号的位置 + show_ellipsis_after = show_first_n - 1 + + # 标记是否已显示过省略提示和最后区域标题 + ellipsis_shown = False + last_section_shown = False + for i, range_info in enumerate(missing_ranges): - if shown_ranges >= max_errors: - remaining = len(missing_ranges) - shown_ranges - print(f"\n... (还有 {remaining} 个丢失区域未显示)") - break + # 检查是否需要显示这个范围 + if i not in ranges_to_show: + continue + + # 检查是否需要显示省略号(在显示最后区域之前) + if total_ranges > max_errors and not ellipsis_shown and i >= total_ranges - show_last_n: + skipped = total_ranges - max_errors + # 计算省略的字节总数 + skipped_bytes = sum(missing_ranges[j]['count'] + for j in range(show_first_n, total_ranges - show_last_n)) + print() + print("─" * 70) + print(f"⚠️ 中间省略了 {skipped} 个丢失区域 (共 {skipped_bytes} 字节)") + print("─" * 70) + print() + ellipsis_shown = True + + # 检查是否是最后几条,显示标题 + if total_ranges > max_errors and not last_section_shown and i >= total_ranges - show_last_n: + print("┌" + "─" * 68 + "┐") + print(f"│ 📌 最后 {show_last_n} 个丢失区域(始终显示)" + " " * 35 + "│") + print("└" + "─" * 68 + "┘") + print() + last_section_shown = True start_pos = range_info['start'] end_pos = range_info['end'] count = range_info['count'] data = range_info['data'] - total_missing += count shown_ranges += 1 + # 显示区域编号 + region_label = f"[区域 {i+1}/{total_ranges}]" + if count == 1: # 单个字节丢失 line, byte_val = data[0] - print(f"❌ 位置 {start_pos}: 丢失 1 字节 = 0x{byte_val:02X} (发送行{line})") + print(f"❌ {region_label} 位置 {start_pos}: 丢失 1 字节 = 0x{byte_val:02X} (发送行{line})") else: # 连续多个字节丢失 - print(f"❌ 位置 {start_pos}~{end_pos}: 丢失 {count} 字节") + print(f"❌ {region_label} 位置 {start_pos}~{end_pos}: 丢失 {count} 字节") # 显示前8个和后8个字节(如果数据较多) if count <= 16: @@ -153,11 +195,12 @@ def compare_logs(tx_data, rx_data, max_errors=100): print(f" 数据: {head} ... {tail}") # 添加空行分隔不同的范围 - if i < len(missing_ranges) - 1: + if i < len(missing_ranges) - 1 and (i+1) in ranges_to_show: print() print("-"*70) print(f"总计丢失: {total_missing} 字节") + print(f"显示区域: {shown_ranges}/{total_ranges} 个") if tx_len != rx_len: print(f"长度差异: {tx_len - rx_len} 字节") diff --git a/code/sdk/zhihui/ppp/include/ppp_config.h b/code/sdk/zhihui/ppp/include/ppp_config.h index 3ab6a1e92..02339eb7e 100644 --- a/code/sdk/zhihui/ppp/include/ppp_config.h +++ b/code/sdk/zhihui/ppp/include/ppp_config.h @@ -50,7 +50,7 @@ extern "C" { #define PPP_KEEPALIVE_INTERVAL 30 // 保活间隔(秒) /* PPP接收模式配置 */ -#ifndef SUPPORT_PPP_SERVICE_POLLING +#ifdef SUPPORT_PPP_SERVICE_POLLING #define PPP_USE_INTERRUPT_MODE 0 // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询) #elif defined(SUPPORT_PPP_SERVICE_INTERRUPT) #define PPP_USE_INTERRUPT_MODE 1 // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询) diff --git a/code/sdk/zhihui/ppp/src/ppp_interrupt.c b/code/sdk/zhihui/ppp/src/ppp_interrupt.c index 00b0ef9a0..1be783140 100644 --- a/code/sdk/zhihui/ppp/src/ppp_interrupt.c +++ b/code/sdk/zhihui/ppp/src/ppp_interrupt.c @@ -35,7 +35,7 @@ #define PPP_PING_PONG_BUFFER_SIZE 2048 // 每个buffer大小(2KB) #endif -#define PPP_PING_PONG_BUFFER_COUNT 2 // 乒乓buffer数量(固定2个) +#define PPP_PING_PONG_BUFFER_COUNT 3 // 三缓冲模式(改为3个) #define PPP_PING_PONG_TIMEOUT_MS 30 // buffer超时时间(30ms) /* PPP中断消息队列配置 */ @@ -57,11 +57,12 @@ typedef struct { volatile uint32_t write_pos; // 当前写入位置 volatile ppp_buffer_state_t state; // 缓冲区状态 volatile uint32_t last_write_time; // 最后写入时间戳(ms) + volatile uint32_t first_write_time; // 首次写入时间戳(用于统计填充时间) } ppp_ping_pong_buffer_t; /* PPP中断消息结构 - 通过消息队列传递buffer索引 */ typedef struct { - uint8_t buffer_index; // 缓冲区索引(0或1) + uint8_t buffer_index; // 缓冲区索引(0~PPP_PING_PONG_BUFFER_COUNT-1) uint16_t length; // 数据长度 } ppp_irq_msg_t; @@ -75,7 +76,7 @@ static volatile bool g_ppp_irq_task_running = false; /* 乒乓缓冲区 */ static ppp_ping_pong_buffer_t g_ping_pong_buffers[PPP_PING_PONG_BUFFER_COUNT]; -static volatile uint8_t g_current_buffer_index = 0; +static volatile uint8_t g_current_buffer_index = 0; // ⚠️ volatile不保证原子性,但单字节写入在ARM上通常是原子的 /* 全局时间戳(由定时器更新,ISR只读) */ static volatile uint32_t g_timer_timestamp_ms = 0; @@ -89,10 +90,18 @@ static ppp_interrupt_stats_t g_ppp_interrupt_stats = {0}; /* 扩展统计信息:乒乓缓冲区相关 */ typedef struct { - uint32_t buffer_full_count; // buffer满次数 - uint32_t buffer_timeout_count; // buffer超时flush次数 - uint32_t buffer_switch_count; // buffer切换次数 - uint32_t dropped_both_full; // 两个buffer都满导致丢弃的字节数 + uint32_t buffer_full_count; // buffer满次数 + uint32_t buffer_timeout_count; // buffer超时flush次数(未满但超时) + uint32_t buffer_switch_count; // buffer切换次数 + uint32_t dropped_both_full; // 两个buffer都满导致丢弃的字节数 + uint32_t switch_failed_count; // buffer切换失败次数(安全性指标) + uint32_t race_condition_detected; // 检测到的竞态条件次数(诊断) + uint32_t cross_buffer_count; // 跨buffer写入次数(数据跨两个buffer) + uint32_t timeout_unfilled_count; // 超时时未满的flush次数 + uint32_t fill_time_min_ms; // buffer填充最短时间(ms) + uint32_t fill_time_max_ms; // buffer填充最长时间(ms) + uint32_t fill_time_total_ms; // buffer填充总时间(用于计算平均值) + uint32_t fill_time_count; // buffer填充次数统计 } ppp_pingpong_stats_t; static ppp_pingpong_stats_t g_pingpong_stats = {0}; @@ -228,6 +237,7 @@ static void ppp_irq_process_thread(void *arg) // 释放buffer(标记为空闲) buffer->state = PPP_BUFFER_STATE_EMPTY; buffer->write_pos = 0; + buffer->first_write_time = 0; // 重置首次写入时间 } PPP_DEBUG_INFO("PPP IRQ process thread exited"); @@ -236,9 +246,10 @@ static void ppp_irq_process_thread(void *arg) /** * @brief 刷新指定buffer到消息队列 * @param buffer_index 缓冲区索引 + * @param is_timeout 是否因超时触发(用于统计) * @return PPP_ERR_OK 成功,其他值失败 */ -static ppp_error_t ppp_flush_buffer(uint8_t buffer_index) +static ppp_error_t ppp_flush_buffer_internal(uint8_t buffer_index, bool is_timeout) { if (buffer_index >= PPP_PING_PONG_BUFFER_COUNT) { return PPP_ERR_INVALID_PARAM; @@ -251,6 +262,32 @@ static ppp_error_t ppp_flush_buffer(uint8_t buffer_index) return PPP_ERR_OK; // 没有数据需要flush } + // 统计填充时间(从首次写入到flush) + if (buffer->first_write_time > 0) { + uint32_t current_time = ppp_get_current_time_ms(); + uint32_t fill_time = current_time - buffer->first_write_time; + + // 更新最小/最大填充时间 + if (g_pingpong_stats.fill_time_count == 0) { + g_pingpong_stats.fill_time_min_ms = fill_time; + g_pingpong_stats.fill_time_max_ms = fill_time; + } else { + if (fill_time < g_pingpong_stats.fill_time_min_ms) { + g_pingpong_stats.fill_time_min_ms = fill_time; + } + if (fill_time > g_pingpong_stats.fill_time_max_ms) { + g_pingpong_stats.fill_time_max_ms = fill_time; + } + } + g_pingpong_stats.fill_time_total_ms += fill_time; + g_pingpong_stats.fill_time_count++; + } + + // 统计超时且未满的flush + if (is_timeout && buffer->write_pos < PPP_PING_PONG_BUFFER_SIZE) { + g_pingpong_stats.timeout_unfilled_count++; + } + // 标记为FULL状态 buffer->state = PPP_BUFFER_STATE_FULL; @@ -277,6 +314,12 @@ static ppp_error_t ppp_flush_buffer(uint8_t buffer_index) return PPP_ERR_OK; } +// 便利函数:非超时flush +static ppp_error_t ppp_flush_buffer(uint8_t buffer_index) +{ + return ppp_flush_buffer_internal(buffer_index, false); +} + /** * @brief 定时器回调:检查buffer超时并flush * @note 定时器上下文,应快速返回 @@ -306,6 +349,12 @@ static void ppp_timeout_timer_callback(unsigned long arg) timer_call_count, g_ppp_interrupt_enabled, g_ppp_timer_running); } + // 每333次(约10秒)打印一次PPP丢失统计 + // 30ms * 333 ≈ 10秒 + if ((timer_call_count % 333) == 0) { + ppp_interrupt_print_stats(); + } + if (!g_ppp_interrupt_enabled || !g_ppp_timer_running) { return; } @@ -317,35 +366,54 @@ static void ppp_timeout_timer_callback(unsigned long arg) uint8_t current_idx = g_current_buffer_index; // 读取一次,避免重复读取 - // 检查非当前buffer的超时状态 - // ⚠️ 数据安全设计:定时器只处理非当前buffer,避免与ISR竞争 - // 当前buffer的超时由ISR在写入时主动检查并处理 + // ⚠️ 新增:检查所有buffer的超时状态(包括当前buffer) + // 需求4:超时时如果当前buffer未满也要flush,避免数据滞留 for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { - // 跳过当前正在写入的buffer(避免与ISR竞争,保证数据安全) - if (i == current_idx) { - continue; + // ⚠️ 竞态检测:在处理过程中再次检查current_idx是否变化 + // 如果这个buffer变成current了,需要小心处理 + uint8_t current_check = g_current_buffer_index; + bool is_current = (i == current_check); + + // 检测到current_idx在循环中变化(竞态) + if (!is_current && i == current_idx) { + g_pingpong_stats.race_condition_detected++; + continue; // 跳过,避免竞态 } ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i]; - // 只处理FILLING状态的buffer + // 只处理FILLING状态且有数据的buffer if (buffer->state == PPP_BUFFER_STATE_FILLING && buffer->write_pos > 0) { - // ⚠️ 修复:避免误判超时 - // last_write_time == 0 表示ISR刚写入数据,需要更新时间戳(不是超时) + // last_write_time == 0 表示ISR刚写入数据,需要更新时间戳 if (buffer->last_write_time == 0) { - // 第一次看到,更新时间戳,不flush buffer->last_write_time = current_time; PPP_DEBUG_DEBUG("[TIMER] Update timestamp for buffer[%u]", i); } else { - // 已经有时间戳了,检查是否真的超时 + // 检查是否超时 uint32_t elapsed = current_time - buffer->last_write_time; if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) { - // 真正超时,flush buffer - PPP_DEBUG_DEBUG("[TIMER] Flushing buffer[%u] due to timeout (%u ms)", i, elapsed); + // ⚠️ 超时了!即使未满也要flush(需求4) + PPP_DEBUG_DEBUG("[TIMER] Flushing buffer[%u] due to timeout (%u ms), %s, %u bytes", + i, elapsed, + is_current ? "CURRENT" : "non-current", + buffer->write_pos); - if (ppp_flush_buffer(i) == PPP_ERR_OK) { + // 使用internal函数,标记为超时flush + if (ppp_flush_buffer_internal(i, true) == PPP_ERR_OK) { g_pingpong_stats.buffer_timeout_count++; + + // 如果是当前buffer超时被flush,需要切换到下一个 + if (is_current) { + uint8_t next_idx = (i + 1) % PPP_PING_PONG_BUFFER_COUNT; + ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; + + if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) { + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + PPP_DEBUG_INFO("[TIMER] Switched from buffer[%u] to buffer[%u] after timeout", i, next_idx); + } + } } } } @@ -409,143 +477,141 @@ static void ppp_uart_frame_error_callback(uint32_t *err_info, uint32_t len) * 1. 写入当前buffer(memcpy,~60字节 < 1μs) * 2. buffer满了直接flush并切换(~2μs) * 3. 无malloc/free,减少内存碎片和延迟 - * 4. 总耗时目标:< 5μs(比之前的7μs更快) + * 4. ⚠️ 新增:跨buffer写入支持(需求1) + * 5. 总耗时目标:< 5μs */ void _ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error) { - // 快速路径:参数校验 + // ⚠️ 中断中禁止打印!必须极快返回 + + // 快速路径:参数校验(异常情况也不打印,只统计) if ( !buffer || length == 0 || length > 200) { // 单次中断最多200字节 - PPP_DEBUG_ERROR("ppp_uart_rx_interrupt_handler !buffer || length == 0 || length > 200 "); + // 不打印!在中断中打印会阻塞系统,只记录统计 + g_ppp_interrupt_stats.error_count++; return; } if (error) { g_ppp_interrupt_stats.error_count++; } - // 更新统计(用于诊断 - 可以在统计打印中看到) + // 更新统计 g_ppp_interrupt_stats.total_interrupts++; g_ppp_interrupt_stats.total_bytes_received += length; - // 诊断:每10次中断增加一个标记(避免频繁打印) - static volatile uint32_t irq_count_marker = 0; - if ((g_ppp_interrupt_stats.total_interrupts % 10) == 1) { - irq_count_marker = g_ppp_interrupt_stats.total_interrupts; - // 不能在中断中打印,但可以更新这个全局变量,在统计中查看 - } - - // ⚠️ 中断中禁止打印! - // 获取当前buffer uint8_t current_idx = g_current_buffer_index; ppp_ping_pong_buffer_t *current_buffer = &g_ping_pong_buffers[current_idx]; - // ⚠️ 数据安全设计:ISR负责检查当前buffer的超时 - // 定时器只处理非当前buffer,避免竞态 - // - // 检查逻辑(方案2 + 全局时间戳): - // - last_write_time == 0:ISR刚写入,定时器还没更新时间戳(不算超时) - // - last_write_time != 0:定时器已更新过时间戳,可以检查是否真的超时 - // 使用 g_timer_timestamp_ms(定时器每30ms更新一次) + // ⚠️ 检查当前buffer的超时 if (current_buffer->state == PPP_BUFFER_STATE_FILLING && current_buffer->write_pos > 0 && - current_buffer->last_write_time != 0) { // ← 只检查已有时间戳的 - // 使用全局时间戳(ISR中安全,不调用函数) + current_buffer->last_write_time != 0) { uint32_t current_time = g_timer_timestamp_ms; uint32_t elapsed = current_time - current_buffer->last_write_time; if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) { - // 当前buffer真的超时了,flush并切换 + // 超时,flush并切换 ppp_flush_buffer(current_idx); g_pingpong_stats.buffer_timeout_count++; - // 切换到下一个buffer uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; - ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; - - if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) { + if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) { g_current_buffer_index = next_idx; g_pingpong_stats.buffer_switch_count++; current_idx = next_idx; - current_buffer = next_buffer; + current_buffer = &g_ping_pong_buffers[current_idx]; } } } - // 如果当前buffer不可写(状态不对),尝试切换 + // 如果当前buffer不可写,尝试切换 if (current_buffer->state != PPP_BUFFER_STATE_EMPTY && current_buffer->state != PPP_BUFFER_STATE_FILLING) { uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; - ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; - - if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) { + if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) { g_current_buffer_index = next_idx; g_pingpong_stats.buffer_switch_count++; current_idx = next_idx; - current_buffer = next_buffer; + current_buffer = &g_ping_pong_buffers[current_idx]; } } - // 尝试写入当前buffer - uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos; - + // 初始化空buffer if (current_buffer->state == PPP_BUFFER_STATE_EMPTY) { - // buffer是空的,初始化为FILLING状态 current_buffer->state = PPP_BUFFER_STATE_FILLING; current_buffer->write_pos = 0; - available = PPP_PING_PONG_BUFFER_SIZE; // 重新计算可用空间 + current_buffer->first_write_time = g_timer_timestamp_ms; // ⚠️ 需求3:记录首次写入时间 } - if (current_buffer->state == PPP_BUFFER_STATE_FILLING && length <= available) { - // 空间足够,直接写入 - memcpy(current_buffer->data + current_buffer->write_pos, buffer, length); - current_buffer->write_pos += length; - // ⚠️ 关键修复:移除中断中的时间函数调用(可能导致死机) - // current_buffer->last_write_time = ppp_get_current_time_ms(); - current_buffer->last_write_time = 0; // 由定时器回调更新 - - // 检查是否满了 - if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) { - // buffer满,flush + // 计算当前buffer剩余空间 + uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos; + uint16_t remaining_data = length; + const uint8_t *data_ptr = (const uint8_t *)buffer; + + // ⚠️ 需求1:跨buffer写入处理 + if (current_buffer->state == PPP_BUFFER_STATE_FILLING) { + if (remaining_data <= available) { + // 情况1:数据完全放得下,直接写入 + memcpy(current_buffer->data + current_buffer->write_pos, data_ptr, remaining_data); + current_buffer->write_pos += remaining_data; + current_buffer->last_write_time = 0; // 由定时器更新 + + // 检查是否正好填满 + if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) { + ppp_flush_buffer(current_idx); + g_pingpong_stats.buffer_full_count++; + + // 切换buffer + uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; + if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) { + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + } else { + g_pingpong_stats.switch_failed_count++; + } + } + } else { + // 情况2:数据超过当前buffer剩余空间,需要跨buffer写入 + g_pingpong_stats.cross_buffer_count++; // ⚠️ 统计跨buffer次数 + + // 第1步:先填满当前buffer + if (available > 0) { + memcpy(current_buffer->data + current_buffer->write_pos, data_ptr, available); + current_buffer->write_pos += available; + data_ptr += available; + remaining_data -= available; + } + + // 第2步:flush当前buffer ppp_flush_buffer(current_idx); g_pingpong_stats.buffer_full_count++; - // 切换到另一个buffer + // 第3步:切换到下一个buffer uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; - if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) { + if (next_buffer->state == PPP_BUFFER_STATE_EMPTY && + remaining_data <= PPP_PING_PONG_BUFFER_SIZE) { + // 第4步:把剩余数据写入新buffer g_current_buffer_index = next_idx; g_pingpong_stats.buffer_switch_count++; + + next_buffer->state = PPP_BUFFER_STATE_FILLING; + next_buffer->write_pos = 0; + next_buffer->first_write_time = g_timer_timestamp_ms; // 新buffer的首次写入时间 + + memcpy(next_buffer->data, data_ptr, remaining_data); + next_buffer->write_pos = remaining_data; + next_buffer->last_write_time = 0; // 由定时器更新 + } else { + // 下一个buffer不可用,剩余数据丢弃 + g_pingpong_stats.dropped_both_full += remaining_data; + g_pingpong_stats.switch_failed_count++; } - // 如果next_buffer不是EMPTY,保持当前索引,下次数据会被丢弃 } } else { - // 当前buffer空间不足或不可用,尝试切换到另一个buffer - uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; - ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; - - if (next_buffer->state == PPP_BUFFER_STATE_EMPTY && length <= PPP_PING_PONG_BUFFER_SIZE) { - // 先flush当前buffer(如果有数据) - if (current_buffer->state == PPP_BUFFER_STATE_FILLING && current_buffer->write_pos > 0) { - ppp_flush_buffer(current_idx); - } - - // 切换到新buffer - g_current_buffer_index = next_idx; - g_pingpong_stats.buffer_switch_count++; - - // 写入新buffer - next_buffer->state = PPP_BUFFER_STATE_FILLING; - next_buffer->write_pos = 0; - memcpy(next_buffer->data, buffer, length); - next_buffer->write_pos = length; - // ⚠️ 关键修复:移除中断中的时间函数调用(可能导致死机) - // next_buffer->last_write_time = ppp_get_current_time_ms(); - next_buffer->last_write_time = 0; // 由定时器回调更新 - } else { - // 两个buffer都不可用,丢弃数据 - g_pingpong_stats.dropped_both_full += length; - } + // 当前buffer不可用,数据丢弃 + g_pingpong_stats.dropped_both_full += length; } } @@ -573,6 +639,7 @@ ppp_error_t ppp_interrupt_enable(void) g_ping_pong_buffers[i].write_pos = 0; g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY; g_ping_pong_buffers[i].last_write_time = 0; + g_ping_pong_buffers[i].first_write_time = 0; memset(g_ping_pong_buffers[i].data, 0, PPP_PING_PONG_BUFFER_SIZE); } g_current_buffer_index = 0; @@ -871,9 +938,28 @@ void ppp_interrupt_print_stats(void) // 乒乓缓冲区统计 PPP_DEBUG_WARN("Ping-Pong Buffer Statistics:"); PPP_DEBUG_WARN(" Buffer Full Count: %u", g_pingpong_stats.buffer_full_count); - PPP_DEBUG_WARN(" Buffer Timeout Count: %u", g_pingpong_stats.buffer_timeout_count); - PPP_DEBUG_WARN(" Buffer Switch Count: %u", g_pingpong_stats.buffer_switch_count); + PPP_DEBUG_WARN(" Buffer Timeout Count: %u (total)", g_pingpong_stats.buffer_timeout_count); + PPP_DEBUG_WARN(" - Timeout Unfilled: %u ⚠️ (未满即超时)", g_pingpong_stats.timeout_unfilled_count); + PPP_DEBUG_WARN(" Buffer Switch Count: %u (success)", g_pingpong_stats.buffer_switch_count); + PPP_DEBUG_WARN(" Buffer Switch Failed: %u ⚠️", g_pingpong_stats.switch_failed_count); + PPP_DEBUG_WARN(" Cross-Buffer Writes: %u 📌 (跨buffer写入)", g_pingpong_stats.cross_buffer_count); PPP_DEBUG_WARN(" Dropped (Both Full): %u bytes ⚠️", g_pingpong_stats.dropped_both_full); + + // 填充时间统计 + if (g_pingpong_stats.fill_time_count > 0) { + uint32_t avg_fill_time = g_pingpong_stats.fill_time_total_ms / g_pingpong_stats.fill_time_count; + PPP_DEBUG_WARN(" Buffer Fill Time (从首字节到flush):"); + PPP_DEBUG_WARN(" - Min: %u ms ⏱️", g_pingpong_stats.fill_time_min_ms); + PPP_DEBUG_WARN(" - Max: %u ms ⏱️", g_pingpong_stats.fill_time_max_ms); + PPP_DEBUG_WARN(" - Avg: %u ms ⏱️", avg_fill_time); + PPP_DEBUG_WARN(" - Sample Count: %u", g_pingpong_stats.fill_time_count); + } else { + PPP_DEBUG_WARN(" Buffer Fill Time: No data yet"); + } + + if (g_pingpong_stats.race_condition_detected > 0) { + PPP_DEBUG_ERROR(" Race Conditions Detected: %u 🔴", g_pingpong_stats.race_condition_detected); + } PPP_DEBUG_WARN(""); // 中断层统计 diff --git a/code/sdk/zhihui/ppp/src/ppp_lwip_thread.c b/code/sdk/zhihui/ppp/src/ppp_lwip_thread.c index d9d786a11..e4330e25a 100644 --- a/code/sdk/zhihui/ppp/src/ppp_lwip_thread.c +++ b/code/sdk/zhihui/ppp/src/ppp_lwip_thread.c @@ -76,7 +76,7 @@ static unsigned long g_ppp_msg_queue = 0; /* 乒乓缓冲区 */ static ppp_ping_pong_buffer_t g_ping_pong_buffers[PPP_PING_PONG_BUFFER_COUNT]; -static volatile uint8_t g_current_buffer_index = 0; // 当前正在填充的buffer索引 +static volatile uint8_t g_current_buffer_index = 0; // ⚠️ volatile不保证原子性,但单字节写入在ARM上通常是原子的 /* 定时器相关 */ static osal_timer g_ppp_timeout_timer; @@ -95,6 +95,8 @@ typedef struct { uint32_t lwip_error_count; // lwIP错误次数 uint32_t read_count; // UART读取次数 uint32_t read_empty_count; // UART读取为空次数 + uint32_t switch_failed_count; // buffer切换失败次数(安全性指标) + uint32_t race_condition_detected; // 检测到的竞态条件次数(诊断) } ppp_polling_stats_t; static ppp_polling_stats_t g_ppp_stats = {0}; @@ -194,6 +196,15 @@ static void ppp_timeout_timer_callback(unsigned long arg) continue; } + // ⚠️ 竞态检测:在处理过程中再次检查current_idx是否变化 + // 如果变化了,说明接收线程在我们处理期间切换了buffer + uint8_t current_check = g_current_buffer_index; + if (i == current_check && current_check != current_idx) { + // 竞态条件:我们准备处理的buffer现在变成current了 + g_ppp_stats.race_condition_detected++; + continue; // 跳过这个buffer + } + ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i]; // 只处理FILLING状态的buffer @@ -504,7 +515,8 @@ static void ppp_lwip_receive_thread(void *arg) } if (!switched) { - // 没有空闲 buffer,等待处理线程 + // ⚠️ 切换失败:没有空闲buffer,等待处理线程 + g_ppp_stats.switch_failed_count++; PPP_DEBUG_WARN("No empty buffer available, waiting..."); osal_msleep(1); } @@ -885,7 +897,11 @@ void ppp_lwip_print_discard_stats(void) PPP_DEBUG_WARN("Ping-Pong Buffer Statistics:"); PPP_DEBUG_WARN(" Buffer Full Count: %u", g_ppp_stats.buffer_full_count); PPP_DEBUG_WARN(" Buffer Timeout Count: %u", g_ppp_stats.buffer_timeout_count); - PPP_DEBUG_WARN(" Buffer Switch Count: %u", g_ppp_stats.buffer_switch_count); + PPP_DEBUG_WARN(" Buffer Switch Count: %u (success)", g_ppp_stats.buffer_switch_count); + PPP_DEBUG_WARN(" Buffer Switch Failed: %u ⚠️", g_ppp_stats.switch_failed_count); + if (g_ppp_stats.race_condition_detected > 0) { + PPP_DEBUG_ERROR(" Race Conditions Detected: %u 🔴", g_ppp_stats.race_condition_detected); + } PPP_DEBUG_WARN(""); // UART轮询统计 -- 2.17.1