RTOS址与值 void*任务传参的坑 走void*万能转的天坑

任务1 app_dpd0.c

cpp 复制代码
static uint32_t workIndex = 0;

rt_thread_create("sleepApp", sleep_manager_thread,  &workIndex, 4096, 12, 10);

static void sleep_manager_thread(void *param){

    uint32_t *index = (uint32_t *)param;

         rt_kprintf("[*index=%d] sleep_manager_thread get sem \n",*index);

}

典型代码如上 传递参数给任务 任务拿着*index使用 表现正常正确

任务2 \app_modbus.c

cpp 复制代码
static int modbus_app_start(void){

    extern uint32_t workIndex_get(void);

    uint32_t workIndex = workIndex_get();

     

    rt_thread_t tid = rt_thread_create("busApp",thread_modbus_entry,(void *)workIndex,1024,20,10);

    if (tid )   rt_thread_startup(tid);

    return 0;

}



static void thread_modbus_entry(void *param) {

    uint32_t index = (uint32_t)param;

    rt_kprintf("index=%d\n", index);

}

典型代码如上 传递参数给任务 任务拿着index使用 表现正常正确

坑在哪里?

上面两份代码都是成功了 其中第二份代码很容易翻车!

void* 做万能转 核心绘图如上

A模块把参数传递给B 需要AB提前约定统一void*转成啥样的结构体!所以是一个氧化-还原反应 AB心知肚明即可! 而嵌入式尤其喜欢传递地址简简单单地址4个字节就可以拿过去任意转化 非常妙呀!天坑也就在这里 隐含条件是地址上的值是不容更改的!

可以想象一下:

A操作的时候地址0X11223344的值是0XAABBCCDD

此时A把地址0X11223344打包成void*传递给B

而后面B收到 开始把0X11223344按照约定换成值 的时候

物理上地址0X11223344的值不再维持0XAABBCCDD

那么传递就失效了 甚至是灾难!

演示 任务2 \app_modbus.c的 bug写法

Bug 根因:把 workIndex 这个局部变量的地址传给了线程

为什么外面是 4,任务里不是 4?

  1. workIndex

    是 modbus_app_start 的局部变量 ,存放在栈上。

  2. modbus_app_start

    执行到 return 0 返回后,这个栈帧就被回收/覆盖了。

  3. 线程 busApp 是异步 启动的,它真正运行 thread_modbus_entry 的时间点晚于 modbus_app_start 返回。

  4. 等你线程拿到 param(即 &workIndex)去解引用时,这块栈内存早被其他函数调用覆盖,读到的自然是垃圾值(不是 4)。

这正是 RT-Thread/FreeRtos嵌入式多线程开发里最常见的坑之一:把局部变量地址传给新线程。

正确做法(三种)

小结

1>址与值 必须要清晰的认知

2>void* 做万能转

2.1需要AB提前约定统一void*转成啥样的结构体

2.2 隐含条件是地址上的值不容更改

FreeRtos在创建任务时如何正确传递参数

FreeRTOS 里 xTaskCreate(..., void *pvParameters, ...) 的第四个参数本质是 一个 void* 值 ,内核只会把这个"指针值/整数值"原样交给任务函数,不会帮你拷贝它指向的数据。所以"怎么传才对"核心就一句话:

如果你传的是"地址",那个地址指向的对象必须活得比任务使用它的时间更长。


1️⃣ 传整数 / 枚举 / 小标识(最安全)

不需要取地址,直接把整数强转成 void*:

cpp 复制代码
void vLedTask(void *pvParameters)
{
    int led_id = (int)pvParameters;

    for (;;)
    {
        // 用 led_id
    }
}

xTaskCreate(vLedTask, "LED", 256, (void *)1, 1, NULL);
xTaskCreate(vLedTask, "LED", 256, (void *)2, 1, NULL);

✅ 没有指针、没有生命周期问题

⚠️ 只能传能放进指针的整数(通常没问题)


2️⃣ 传结构体 / 配置参数(最常见坑)

❌ 错误:传局部变量地址

cpp 复制代码
void CreateTask(void)
{
    TaskConfig cfg = { .baud = 115200, .mode = 1 };

    xTaskCreate(vTask, "T", 256, &cfg, 1, NULL);
    // cfg 在函数返回后就失效
}

任务什么时候跑是不确定的,等它读 cfg 时,栈已经被覆盖 → 野指针 / 乱数据。


✅ 正确做法 1:用 static / 全局变量

cpp 复制代码
static TaskConfig cfg1 = { .baud = 115200, .mode = 1 };

xTaskCreate(vTask, "T", 256, &cfg1, 1, NULL);

任务里:

cpp 复制代码
void vTask(void *pv)
{
    TaskConfig *cfg = (TaskConfig *)pv;
    for (;;) { }
}

✅ 生命周期 = 整个程序

✅ 最适合"每个任务一份固定配置"


✅ 正确做法 2:堆里分配(动态参数)

cpp 复制代码
TaskConfig *cfg = pvPortMalloc(sizeof(TaskConfig));
cfg->baud = 115200;

xTaskCreate(vTask, "T", 256, cfg, 1, NULL);

任务里用完后释放:

cpp 复制代码
void vTask(void *pv)
{
    TaskConfig *cfg = (TaskConfig *)pv;

    // 使用 cfg

    vPortFree(cfg);   // 用完就释放
    vTaskDelete(NULL);
}

⚠️ 一定要保证:

  • 任务启动前对象存在
  • 任务不再用之前别释放
  • 谁分配谁释放(或规则明确)

✅ 正确做法 3:如果参数是"只读常量"

cpp 复制代码
const TaskConfig cfg = { .baud = 115200 };

xTaskCreate(vTask, "T", 256, (void *)&cfg, 1, NULL);

✅ 可以

⚠️ 但 const 不等于"不是栈变量",下面这种还是错的:

cpp 复制代码
void f(void) {
    const int x = 5;
    xTaskCreate(t, "T", 256, (void*)&x, 1, NULL); // ❌
}

3️⃣ 传句柄(队列 / 信号量 / 定时器)

句柄本身通常就已经是指针:

cpp 复制代码
QueueHandle_t q = xQueueCreate(10, sizeof(int));

xTaskCreate(vProducer, "prod", 256, q, 1, NULL);

任务里:

cpp 复制代码
void vProducer(void *pv)
{
    QueueHandle_t q = (QueueHandle_t)pv;
}

✅ 正确

因为队列本体在 FreeRTOS 堆里,句柄值一直有效


4️⃣ main() 里传参的特殊坑(STM32 / Cortex-M 很常见)

cpp 复制代码
int main(void)
{
    int x = 10;
    xTaskCreate(t, "T", 256, &x, 1, NULL);
    vTaskStartScheduler();
}

看起来没问题,但:

  • main() 的栈 = MSP
  • 调度器启动后,MSP 被拿去当 中断栈
  • main 栈帧随时被覆盖

所以 不要在 main / 普通函数里传局部变量地址。


5️⃣ 推荐实践总结

想传的内容 推荐方式
ID / 索引 / 模式 (void *)id
固定配置结构体 static / 全局
动态参数 pvPortMalloc + 任务内 vPortFree
队列 / 信号量句柄 直接传句柄值
局部变量地址 ❌ 禁止

6️⃣ 一句话记忆

**FreeRTOS 只拷贝 void* 这个值,不拷贝它指向的东西。**​

指针参数指向的对象,必须"活得够久"。

相关推荐
天吾cc11 小时前
FreeRTOS任务创建和删除
freertos
天吾cc19 小时前
FreeRTOS中断管理
freertos
天吾cc19 小时前
FreeRTOS任务挂起和恢复
freertos
中文英文-我选中文5 天前
8.2 学习探索
freertos
qq_4017004110 天前
FreeRTOS 堆内存管理源码剖析:释放一块内存,空闲块数量反而变少了?
单片机·freertos
List<String> error_P12 天前
使用FreeRTOS搭建工程项目(一)
freertos
@嵌入式扫地僧20 天前
RTOS 面试实战:任务状态切换原理
freertos·rtos·时间片轮转·任务状态切换·抢占式调度
WWJA王文举1 个月前
Bootloader、App 和升级分区之间是什么关系?
stm32·freertos·嵌入式开发·在线升级
怀民民民1 个月前
嵌入式软件学习路线与项目实践记录:STM32、FreeRTOS、Linux 与 OpenMV
stm32·freertos·嵌入式软件·嵌入式linux·openmv