USB日志发送代码分析与改进

原代码打印日志存在丢失打印的情况,改进后代码几乎不会丢失打印,遂记录一下

旧代码

#include "os/os_api.h" //创建线程,系统延时
#include "system/init.h" //early_initcall

extern u32 cdc_write_data(int usb_id, u8 *buf, u32 len);

#define DATA_BUF_SIZE (200 * 1024)

static u32 lg_cnt, last_cnt;
static char lg_buf[DATA_BUF_SIZE]; //打印信息缓存值

static void usb_log_task(void *priv)
{
    os_time_dly(300); // 初始延迟 300 个时间单位,等待系统稳定

    while (1) // 进入无限循环,持续执行任务
    {
        // 检查是否有新的日志数据需要发送
        if (lg_cnt > last_cnt && last_cnt != lg_cnt)
        {
            // 检查缓冲区是否环绕(即写入指针是否超过了缓冲区大小)
            if (lg_cnt / sizeof(lg_buf) > last_cnt / sizeof(lg_buf))
            {
                // 计算第一部分数据的大小(从 last_cnt 开始到缓冲区末尾)
                size_t part1_size = sizeof(lg_buf) - last_cnt % sizeof(lg_buf);
                // 计算第二部分数据的大小(从缓冲区开头到 lg_cnt)
                size_t part2_size = lg_cnt - last_cnt - part1_size;

                // 发送第一部分数据到 CDC 接口 0 和 1
                cdc_write_data(0, (u8 *)lg_buf + (last_cnt % sizeof(lg_buf)), part1_size);
                cdc_write_data(1, (u8 *)lg_buf + (last_cnt % sizeof(lg_buf)), part1_size);

                // 发送第二部分数据到 CDC 接口 0 和 1
                cdc_write_data(0, (u8 *)lg_buf, part2_size);
                cdc_write_data(1, (u8 *)lg_buf, part2_size);
            }
            else // 非缓冲区环绕的情况
            {
                // 计算要发送的数据大小(从 last_cnt 到 lg_cnt)
                size_t data_size = lg_cnt - last_cnt;

                // 发送数据到 CDC 接口 0 和 1
                cdc_write_data(0, (u8 *)lg_buf + (last_cnt % sizeof(lg_buf)), data_size);
                cdc_write_data(1, (u8 *)lg_buf + (last_cnt % sizeof(lg_buf)), data_size);
            }
        }
        // 更新已处理的日志计数,将 last_cnt 设置为 lg_cnt
        last_cnt = lg_cnt;

        // 短暂延迟 10 个时间单位,增加检查频率
        os_time_dly(10);
    }
}

新代码

## 新代码

#include "os/os_api.h"
#include "system/init.h"
#include <string.h> // for memcpy

// 外部函数声明,用于向USB CDC接口写入数据
extern u32 cdc_write_data(int usb_id, u8 *buf, u32 len);

#define DATA_BUF_SIZE (200 * 1024)  // 定义日志缓冲区大小为200KB
#define MAX_PACKET_SIZE 64  // 定义最大数据包大小,根据USB CDC配置调整

// 声明全局变量
static volatile u32 lg_cnt = 0, last_cnt = 0;  // 用于跟踪日志写入和读取位置
static char lg_buf[DATA_BUF_SIZE];  // 日志数据缓冲区
static OS_MUTEX log_mutex;  // 互斥锁,用于保护共享资源

// USB日志任务函数
static void usb_log_task(void *priv)
{
    os_time_dly(300);  // 延迟300个时间单位,等待系统初始化

    while (1)
    {
        os_mutex_pend(&log_mutex, 0);  // 获取互斥锁

        if (lg_cnt > last_cnt)  // 检查是否有新的日志数据
        {
            u32 data_to_send = lg_cnt - last_cnt;  // 计算需要发送的数据量
            u32 start_pos = last_cnt % DATA_BUF_SIZE;  // 计算起始位置

            while (data_to_send > 0)
            {
                // 确定当前发送块的大小
                u32 chunk_size = (data_to_send > MAX_PACKET_SIZE) ? MAX_PACKET_SIZE : data_to_send;
                u32 end_pos = (start_pos + chunk_size) % DATA_BUF_SIZE;

                if (end_pos > start_pos)
                {
                    // 如果数据块未跨越缓冲区边界,直接发送
                    cdc_write_data(0, (u8 *)&lg_buf[start_pos], chunk_size);
                    cdc_write_data(1, (u8 *)&lg_buf[start_pos], chunk_size);
                }
                else
                {
                    // 如果数据块跨越了缓冲区边界,分两部分发送
                    u32 first_part = DATA_BUF_SIZE - start_pos;
                    cdc_write_data(0, (u8 *)&lg_buf[start_pos], first_part);
                    cdc_write_data(1, (u8 *)&lg_buf[start_pos], first_part);
                    cdc_write_data(0, (u8 *)lg_buf, chunk_size - first_part);
                    cdc_write_data(1, (u8 *)lg_buf, chunk_size - first_part);
                }

                // 更新位置和剩余数据量
                start_pos = end_pos;
                data_to_send -= chunk_size;
                last_cnt += chunk_size;
            }
        }

        os_mutex_post(&log_mutex);  // 释放互斥锁
        os_time_dly(5);  // 短暂延迟,避免过度占用CPU
    }
}

// 函数用于写入日志到USB缓冲区
void log_to_usb(const char *data, u32 length)
{
    os_mutex_pend(&log_mutex, 0);  // 获取互斥锁

    // 计算可用空间和实际可复制的长度
    u32 available_space = DATA_BUF_SIZE - (lg_cnt - last_cnt);
    u32 copy_length = (length > available_space) ? available_space : length;

    if (copy_length > 0)
    {
        u32 end_pos = (lg_cnt + copy_length) % DATA_BUF_SIZE;
        if (end_pos > lg_cnt % DATA_BUF_SIZE)
        {
            // 如果数据未跨越缓冲区边界,直接复制
            memcpy(&lg_buf[lg_cnt % DATA_BUF_SIZE], data, copy_length);
        }
        else
        {
            // 如果数据跨越了缓冲区边界,分两部分复制
            u32 first_part = DATA_BUF_SIZE - (lg_cnt % DATA_BUF_SIZE);
            memcpy(&lg_buf[lg_cnt % DATA_BUF_SIZE], data, first_part);
            memcpy(lg_buf, data + first_part, copy_length - first_part);
        }
        lg_cnt += copy_length;  // 更新写入计数
    }

    os_mutex_post(&log_mutex);  // 释放互斥锁
}

// USB日志系统初始化函数
static int usb_log_init(void)
{
    os_mutex_create(&log_mutex);  // 创建互斥锁
    // 创建USB日志任务,优先级为10,栈大小为1024字节
    os_task_create(usb_log_task, NULL, 10, 1024, 0, "usb_log_task");
    return 0;
}

// 使用early_initcall确保USB日志系统在系统早期就被初始化
early_initcall(usb_log_init);

原始代码问题分析:

数据丢失风险

原始代码使用了全局变量 lg_cntlast_cnt 来跟踪日志数据的写入和读取位置。这些变量未使用任何同步机制保护,在多线程环境下可能会导致数据竞争问题,进而导致日志数据丢失。

cdc_write_data 可能会被多次调用,以发送数据块。当 lg_cntlast_cnt 被其他任务或中断修改时,可能导致 last_cnt 更新不及时,导致数据未被正确发送,甚至导致数据丢失。

缓冲区环绕处理

原始代码在处理缓冲区环绕(即写入指针超出缓冲区末尾并从头开始)时,进行了复杂的计算和数据分块操作。由于这些操作没有经过严格同步,可能导致不一致性,例如重复发送或未发送部分日志数据。

频繁调用 cdc_write_data

原始代码在发送数据时,不考虑数据块大小,每次发送尽可能多的数据,可能导致频繁调用 cdc_write_data。频繁调用可能增加系统开销,尤其是在 USB 接口数据速率有限的情况下。

延迟机制

原代码每隔 10 个时间单位检查一次日志数据,可能在高负载情况下导致延迟,进一步加剧了数据丢失的风险。

新代码的改进点:

增加了互斥锁保护

新代码使用了互斥锁(OS_MUTEX log_mutex)来保护 lg_cntlast_cnt 的读写操作。通过 os_mutex_pendos_mutex_post 对共享资源进行保护,避免了多线程环境下的数据竞争问题,从而减少数据丢失的风险。

更好的缓冲区环绕处理

新代码在处理缓冲区环绕时,通过计算起始位置和结束位置,合理地将数据分为块并发送,避免了不必要的复杂计算。这不仅简化了代码逻辑,还确保了数据传输的完整性。

分块发送,减少函数调用次数

新代码引入了 MAX_PACKET_SIZE 的概念(定义为 64 字节),这是根据 USB CDC 的配置来调整的。通过将日志数据分块处理并一次性发送,可以减少 cdc_write_data 的调用次数,提高了传输效率。

优化的延迟处理

新代码在发送日志数据后使用了 os_time_dly(5) 来短暂延迟,并且相比于原来的延迟机制,新代码更加频繁地检查日志数据,这有助于在高负载环境下尽快发送日志数据,减少日志堆积和丢失的可能性。

添加了日志写入函数

新代码添加了 log_to_usb 函数,用于将外部日志数据写入到 lg_buf 中。该函数在写入数据时也使用了互斥锁保护,并考虑了缓冲区可用空间和缓冲区环绕的问题,进一步减少了数据丢失的可能性。

早期初始化

新代码通过 early_initcall(usb_log_init) 确保 USB 日志系统在系统初始化的早期阶段就被启动,从而避免系统初始化过程中的日志数据丢失。

总结

通过这些改进,新代码大幅提高了日志传输的可靠性和效率。具体而言,添加的互斥锁保护和分块发送机制显著减少了数据丢失的风险,同时优化的缓冲区处理逻辑确保了日志数据的完整传输。整体上,新代码在多任务环境下表现得更加稳定和可靠。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值