前言:

2025/8/26 21 14分 开启,看了rtos训练营群里有人问了一下关于printf在多线程中,数据打印次序不对,比如task1 打印"hello word" 但是打印到“hello” 跳转新任务 ,新任务打印 “dashuaibi”,结果就变成了“hello dashuaibi”,遂开始记录。先记录一个。

 以后在补充

RTOS中关于串口资源的管理(其他以此类推)

问题描述:

rtos训练营群里有人问了一下关于printf在多线程中,数据打印次序不对,比如task1 打印"hello word" 但是打印到“hello” 跳转新任务 ,新任务打印 “dashuaibi”,结果就变成了“hello dashuaibi

标准库自己写发送没有这机制

image

HAL里面有设置总线忙:所以不必担心。

 

解决方式:

1.使用信号量或者锁标识资源(优先信号量),每次操作串口之前都去获取信号量或者锁(休眠),使用完在释放掉控制权(其他函数就休眠,直到有资源)

设计:

typedef struct Uart_source{

  /* 变量 */

      串口号

      串口信号量

      使用者

      发送完成标志

  /* 操作函数 */

      获取串口:打印信息,谁正在使用

      发送

      接收

      放弃串口:谁放弃了

}Uart_source;

在HAL层

2.printf函数增加一段,格式化数据之后通过邮箱发送给串口发送线程,由串口线程统一发送

模拟:

// =================================================================================
// Section 1: 头文件和宏定义 (Header & Macros)
// =================================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>

// --- 配置宏 ---
#define UART_QUEUE_SIZE 20 // 消息队列容量
#define UART_MSG_MAX_LEN 256 // 单条消息最大长度
#define NUM_SENSOR_THREADS 2 // 传感器线程数量

// =================================================================================
// Section 2: 数据结构定义 (Data Structures)
// =================================================================================

/**
* @brief UART消息结构体
* 定义了通过队列传递的消息格式
*/
typedef struct {
char data[UART_MSG_MAX_LEN]; // 消息内容
char source[32]; // 消息来源标识
int priority; // 优先级 (本示例中未使用,为未来扩展保留)
} UartMsg_t;

/**
* @brief 线程安全的消息队列 (环形缓冲区)
* 使用互斥锁(mutex)和条件变量(condition variable)保证线程安全
*/
typedef struct {
UartMsg_t buffer[UART_QUEUE_SIZE]; // 环形缓冲区
int head; // 队列头指针
int tail; // 队列尾指针
int count; // 当前元素数量
int shutdown; // 关闭标志
pthread_mutex_t mutex; // 互斥锁,保护队列结构
pthread_cond_t not_empty; // 条件变量,当队列不为空时触发
pthread_cond_t not_full; // 条件变量,当队列不满时触发
} UartQueue_t;


// =================================================================================
// Section 3: 消息队列核心实现 (Queue Core Implementation)
// =================================================================================

// 全局唯一的UART消息队列实例
static UartQueue_t g_uartQueue;

/**
* @brief 初始化消息队列
*/
int queue_init(UartQueue_t *q) {
q->head = 0;
q->tail = 0;
q->count = 0;
q->shutdown = 0;

if (pthread_mutex_init(&q->mutex, NULL) != 0) return -1;
if (pthread_cond_init(&q->not_empty, NULL) != 0) {
pthread_mutex_destroy(&q->mutex);
return -1;
}
if (pthread_cond_init(&q->not_full, NULL) != 0) {
pthread_mutex_destroy(&q->mutex);
pthread_cond_destroy(&q->not_empty);
return -1;
}
return 0;
}

/**
* @brief 销毁消息队列,并唤醒所有等待的线程
*/
void queue_destroy(UartQueue_t *q) {
pthread_mutex_lock(&q->mutex);
q->shutdown = 1;
// 广播信号,唤醒所有可能在等待的生产者和消费者线程,让他们得以退出
pthread_cond_broadcast(&q->not_empty);
pthread_cond_broadcast(&q->not_full);
pthread_mutex_unlock(&q->mutex);

// 销毁资源
pthread_mutex_destroy(&q->mutex);
pthread_cond_destroy(&q->not_empty);
pthread_cond_destroy(&q->not_full);
}

/**
* @brief 发送消息到队列 (生产者)
* @param timeout_ms 超时时间(毫秒)。>0:超时等待; 0:不等待; <0:无限等待
* @return 0 on success, -1 on failure (full or timeout)
*/
int queue_send(UartQueue_t *q, const UartMsg_t *msg, int timeout_ms) {
pthread_mutex_lock(&q->mutex);

// 1. 检查队列是否已满
while (q->count >= UART_QUEUE_SIZE && !q->shutdown) {
if (timeout_ms == 0) { // 非阻塞模式
pthread_mutex_unlock(&q->mutex);
return -1;
} else if (timeout_ms > 0) { // 超时等待
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000L;
if (ts.tv_nsec >= 1000000000L) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000L;
}
int ret = pthread_cond_timedwait(&q->not_full, &q->mutex, &ts);
if (ret == ETIMEDOUT) {
pthread_mutex_unlock(&q->mutex);
return -1;
}
} else { // 无限等待
pthread_cond_wait(&q->not_full, &q->mutex);
}
}

// 2. 如果队列已关闭,则直接返回
if (q->shutdown) {
pthread_mutex_unlock(&q->mutex);
return -1;
}

// 3. 将消息放入队列
q->buffer[q->tail] = *msg;
q->tail = (q->tail + 1) % UART_QUEUE_SIZE;
q->count++;

// 4. 发送信号,通知可能在等待的消费者线程
pthread_cond_signal(&q->not_empty);
pthread_mutex_unlock(&q->mutex);

return 0;
}

/**
* @brief 从队列接收消息 (消费者)
* @return 0 on success, -1 on failure (queue is shutdown and empty)
*/
int queue_receive(UartQueue_t *q, UartMsg_t *msg) {
pthread_mutex_lock(&q->mutex);

// 1. 等待队列不为空
while (q->count == 0 && !q->shutdown) {
pthread_cond_wait(&q->not_empty, &q->mutex);
}

// 2. 如果队列已关闭且为空,则这是退出的信号
if (q->shutdown && q->count == 0) {
pthread_mutex_unlock(&q->mutex);
return -1;
}

// 3. 从队列中取出消息
*msg = q->buffer[q->head];
q->head = (q->head + 1) % UART_QUEUE_SIZE;
q->count--;

// 4. 发送信号,通知可能在等待的生产者线程
pthread_cond_signal(&q->not_full);
pthread_mutex_unlock(&q->mutex);

return 0;
}


// =================================================================================
// Section 4: UART日志系统封装 (UART Logging System)
// =================================================================================

/**
* @brief 模拟底层的UART发送函数 (替代HAL_UART_Transmit)
*/
void uart_transmit_raw(const char *data) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
// 模拟串口发送的耗时
usleep(10000); // 10ms
// 在终端打印,模拟真实发送
printf("[%ld.%03ld] UART_TX: %s", ts.tv_sec % 1000, ts.tv_nsec / 1000000, data);
fflush(stdout);
}

/**
* @brief 统一的、线程安全的日志打印接口
* @param source 消息来源标识
* @param fmt 格式化字符串
*/
void uart_printf(const char *source, const char *fmt, ...) {
UartMsg_t msg;

// 格式化消息内容
va_list args;
va_start(args, fmt);
vsnprintf(msg.data, sizeof(msg.data), fmt, args);
va_end(args);

// 设置消息元数据
strncpy(msg.source, source, sizeof(msg.source) - 1);
msg.source[sizeof(msg.source) - 1] = '\0';
msg.priority = 0;

// 将消息发送到队列,设置1秒超时
if (queue_send(&g_uartQueue, &msg, 1000) != 0) {
fprintf(stderr, "\n[ERROR] UART queue is full. Message from '%s' was dropped!\n", source);
}
}


// =================================================================================
// Section 5: 业务逻辑线程 (Worker Threads - Producers)
// =================================================================================

void *sensor_task(void *arg) {
int thread_id = *(int*)arg;
printf("[THREAD_START] Sensor Task %d\n", thread_id);
for (int i = 0; i < 5; ++i) {
usleep( (800 + rand() % 400) * 1000 ); // 0.8s ~ 1.2s
float temp = 20.0 + (rand() % 100) / 10.0;
float humidity = 40.0 + (rand() % 400) / 10.0;
uart_printf("SENSOR", "Temperature: %.1f C, Humidity: %.1f %% [Sensor-%d#%d]\n", temp, humidity, thread_id, i + 1);
}
printf("[THREAD_EXIT] Sensor Task %d\n", thread_id);
return NULL;
}

void *system_task(void *arg) {
printf("[THREAD_START] System Status Task\n");
for (int i = 0; i < 8; ++i) {
usleep(1200000); // 1.2s
int cpu = 10 + rand() % 80;
int mem = 20 + rand() % 60;
uart_printf("SYSTEM", "CPU: %d%%, Mem: %d%%, Uptime: %ds [#%d]\n", cpu, mem, i * 12, i + 1);
}
printf("[THREAD_EXIT] System Status Task\n");
return NULL;
}

void *error_task(void *arg) {
const char *errors[] = { "WARNING: Low battery", "INFO: Connection established", "ERROR: Comm timeout", "DEBUG: Checksum ok" };
printf("[THREAD_START] Error Report Task\n");
for (int i = 0; i < 6; ++i) {
usleep(1500000); // 1.5s
uart_printf("REPORT", "%s [Report#%d]\n", errors[rand() % 4], i + 1);
}
printf("[THREAD_EXIT] Error Report Task\n");
return NULL;
}


// =================================================================================
// Section 6: 消费者线程 (Consumer Thread)
// =================================================================================

/**
* @brief UART发送任务:唯一的消费者
* 负责从队列中取出消息,并串行化地通过物理UART发送出去
*/
void *uart_task(void *arg) {
UartMsg_t msg;
int msg_count = 0;
printf("[THREAD_START] UART Consumer Task\n");

while (1) {
// 从队列中阻塞式地获取一条消息
if (queue_receive(&g_uartQueue, &msg) == 0) {
msg_count++;
// 可以在此统一添加时间戳、序号等信息
char final_output[UART_MSG_MAX_LEN + 64];
snprintf(final_output, sizeof(final_output), "[%s#%d] %s", msg.source, msg_count, msg.data);

// 调用底层发送函数
uart_transmit_raw(final_output);
} else {
// 接收失败,说明队列已关闭且为空,是时候退出了
break;
}
}

printf("[THREAD_EXIT] UART Consumer Task\n");
return NULL;
}


// =================================================================================
// Section 7: 主函数 (Main Function)
// =================================================================================

void print_intro() {
printf("========================================================\n");
printf(" Multi-Threaded UART Logging Demo\n");
printf("--------------------------------------------------------\n");
printf(" Architecture: Producer-Consumer Model\n");
printf(" - Multiple worker threads (producers) generate logs.\n");
printf(" - Logs are sent to a thread-safe message queue.\n");
printf(" - A single UART thread (consumer) serializes output.\n");
printf("========================================================\n\n");
}

int main() {
pthread_t uart_thread;
pthread_t sensor_threads[NUM_SENSOR_THREADS];
pthread_t system_thread, error_thread;
int sensor_ids[NUM_SENSOR_THREADS];

srand(time(NULL));
print_intro();

// 1. 初始化
if (queue_init(&g_uartQueue) != 0) {
fprintf(stderr, "Fatal: Failed to initialize UART queue\n");
return EXIT_FAILURE;
}

// 2. 创建所有线程
// 消费者线程
pthread_create(&uart_thread, NULL, uart_task, NULL);
// 生产者线程
for (int i = 0; i < NUM_SENSOR_THREADS; ++i) {
sensor_ids[i] = i + 1;
pthread_create(&sensor_threads[i], NULL, sensor_task, &sensor_ids[i]);
}
pthread_create(&system_thread, NULL, system_task, NULL);
pthread_create(&error_thread, NULL, error_task, NULL);

// 3. 等待所有生产者线程完成工作
printf("\n[MAIN] All worker threads created. Waiting for them to finish...\n");
for (int i = 0; i < NUM_SENSOR_THREADS; ++i) {
pthread_join(sensor_threads[i], NULL);
}
pthread_join(system_thread, NULL);
pthread_join(error_thread, NULL);

// 4. 所有生产者已退出,优雅地关闭系统
printf("\n[MAIN] All worker threads have finished.\n");
printf("[MAIN] Notifying UART task to shut down after processing remaining messages...\n");

// 等待一小段时间,确保最后几条消息能被成功发送到队列
usleep(100 * 1000);

// 销毁队列,这将通知消费者线程退出
queue_destroy(&g_uartQueue);

// 等待消费者线程处理完队列中剩余的消息并完全退出
pthread_join(uart_thread, NULL);

printf("\n========================================================\n");
printf(" Program Finished\n");
printf("========================================================\n");

return EXIT_SUCCESS;
}

 

image

 rt-thread的rk_printf就是这样处理的