• CMSIS_OS中osMailPut 和 osMessagePut 的问题


    1. 背景

    为了屏蔽不同OS之间的差别,ARM公司开发了一套OS接口--CMSIS_OS。

    在使用STM32 cube生成的free rtos工程中,遇到一些问题。

    问题1:osMessageGet 和 osMessagePut 发送和接收队列(结构体,数组等)。

    问题2:osMailGet 和 osMailPut发送和接收队列(结构体,数组等)。

    2. 问题分析( osMessagePut 和osMessageGet 为例)

    osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
    {
      portBASE_TYPE taskWoken = pdFALSE;
      TickType_t ticks;
      
      ticks = millisec / portTICK_PERIOD_MS;
      if (ticks == 0) {
        ticks = 1;
      }
      
      if (inHandlerMode()) {
        if (xQueueSendFromISR(queue_id, &info, &taskWoken) != pdTRUE) {
          return osErrorOS;
        }
        portEND_SWITCHING_ISR(taskWoken);
      }
      else {
        if (xQueueSend(queue_id, &info, ticks) != pdTRUE) {
          return osErrorOS;
        }
      }
      
      return osOK;
    }

    发送函数本质是调用xQueueSendFromISR 和xQueueSend。这个函数调用

    BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )

    继续调用

    ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

    最终调用

    ( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );

    所以本质就是memcpy,把用户需要发送的数据拷贝到队列的内部内存保存。

    osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)接口上,传入的只能是一个数据或指针,所以传入数组或结构体时候,就必须以指针方式传入。

    对于osMessageGet ,通用关键的也是memcpy,不过包装后,有一点非常特殊。

    和free rtos接口的调用时经过xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken)。

    那么传入的产生就是一个指针,而后面的memcpy会往这个指针里拷贝内容。

    所以如果创建的消息长度大于4个字节,指针自加,就会把队列里面的数据拷贝到栈空间的某一个未知的位置。

    osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec)
    {
      portBASE_TYPE taskWoken;
      TickType_t ticks;
      osEvent event;
      
      event.def.message_id = queue_id;
      event.value.v = 0;
      
      if (queue_id == NULL) {
        event.status = osErrorParameter;
        return event;
      }
      
      taskWoken = pdFALSE;
      
      ticks = 0;
      if (millisec == osWaitForever) {
        ticks = portMAX_DELAY;
      }
      else if (millisec != 0) {
        ticks = millisec / portTICK_PERIOD_MS;
        if (ticks == 0) {
          ticks = 1;
        }
      }
      
      if (inHandlerMode()) {
        if (xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken) == pdTRUE) {
          /* We have mail */
          event.status = osEventMessage;
        }
        else {
          event.status = osOK;
        }
        portEND_SWITCHING_ISR(taskWoken);
      }
      else {
        if (xQueueReceive(queue_id, &event.value.v, ticks) == pdTRUE) {
          /* We have mail */
          event.status = osEventMessage;
        }
        else {
          event.status = (ticks == 0) ? osOK : osEventTimeout;
        }
      }
      
      return event;
    }

    那么问题出现了,结构体或数组,必须以指针的方式传入,然后指针方式读出。

    若生产者比消费者频率高,如何保证一包数据没有被接收前不能在发送端覆盖了。

    本来这个工作free rtos已经做了,但是经过这么一封装之后,free rtos保护的只是一个指针而已。

    实际的发送端,怎么保证源数据空间不被新数据覆盖,就需要用户考虑了。

    好在cmsis考虑了这个情况。例子来源 https://os.mbed.com/handbook/CMSIS-RTOS

    typedef struct {
        float    voltage;   /* AD result of measured voltage */
        float    current;   /* AD result of measured current */
        uint32_t counter;   /* A counter value               */
    } message_t;
     
    osPoolDef(mpool, 16, message_t);
    osPoolId  mpool;
     
    osMessageQDef(queue, 16, message_t);
    osMessageQId  queue;
     
    void send_thread (void const *args) {
        uint32_t i = 0;
        while (true) {
            i++; // fake data update
            message_t *message = (message_t*)osPoolAlloc(mpool);
            message->voltage = (i * 0.1) * 33; 
            message->current = (i * 0.1) * 11;
            message->counter = i;
            osMessagePut(queue, (uint32_t)message, osWaitForever);
            osDelay(1000);
        }
    }
     
    osThreadDef(send_thread, osPriorityNormal, DEFAULT_STACK_SIZE);
     
    int main (void) {
        mpool = osPoolCreate(osPool(mpool));
        queue = osMessageCreate(osMessageQ(queue), NULL);
        
        osThreadCreate(osThread(send_thread), NULL);
        
        while (true) {
            osEvent evt = osMessageGet(queue, osWaitForever);
            if (evt.status == osEventMessage) {
                message_t *message = (message_t*)evt.value.p;
                printf("
    Voltage: %.2f V
    
    "   , message->voltage);
                printf("Current: %.2f A
    
    "     , message->current);
                printf("Number of cycles: %u
    
    ", message->counter);
                
                osPoolFree(mpool, message);
            }
        }
    }

    发送端:osPoolAlloc,从pool中申请空间。也就是说队列不在保存数据,只保存数据指针。

    如果有N份数据,原来是N份保存在队列中,现在队列中只申请N个指针的空间。

    发送数据时候,从pool中拿到一个空间,保存数据,然后发送指针给messageQ。

    接收时候:从队列中取到指针的地址,然后使用数据。因为此数据在pool有空间,并且不会被覆盖,所以不需要在做拷贝到本地变量中。

    数据使用完成之后,就可以释放空间,就是osPoolFree。

    问题在哪里:

    1)看发送,先osPoolAlloc,后osMessagePut。如果队列空间不够,在osMessagePut是可以设置阻塞时间的。

    而osPoolAlloc没有阻塞时间,那么就会直接返回错误,上面的例子是官方的例子,就没有考虑申请空间失败的情况。

    如果返回了错误,那么后面的osMessagePut超时机制就是空架子了,反正执行不到这里。

    2)看接收,osMessageGet之后,队列计数就会减1,以便于发生端数据入队。

    如果有数据等待入队,那么这里不会入队,因为靠osPoolFree释放pool后才会入队。所以时序要求高的应用下需要特别注意。

    3)上面分析的同样的问题,申请队列时候,不能用sizeof队列大小作为itemSize。

    上面的官方例子有bug,osMessageQDef(queue, 16, message_t); 这样申请会导致接收队列时候,free rtos按照itemSize大小拷贝内容到evt.value.p地址。

    正确的方法是osMessageQDef(queue, 16, uing32_t)。

    实际上这种用法下,已经不需要向队列拷贝具体的内容了,对列发数据前申请空间,收数据后释放空间。

    osMailGet 和 osMailPut的问题和上面的messageQ一样,另外,还发现一个更严重的问题。

    osMailPut没有做超时时间,实现里面调用的是xQueueSend(queue_id->handle, &mail, 0)。

    本以为发送队列满的时候靠申请数据空间来做超时等待,查看源码发现根本没有。

    osStatus osMailPut (osMailQId queue_id, void *mail)
    {
      portBASE_TYPE taskWoken;
      
      
      if (queue_id == NULL) {
        return osErrorParameter;
      }
      
      taskWoken = pdFALSE;
      
      if (inHandlerMode()) {
        if (xQueueSendFromISR(queue_id->handle, &mail, &taskWoken) != pdTRUE) {
          return osErrorOS;
        }
        portEND_SWITCHING_ISR(taskWoken);
      }
      else {
        if (xQueueSend(queue_id->handle, &mail, 0) != pdTRUE) { 
          return osErrorOS;
        }
      }
      
      return osOK;
    }

    因为在调用put之前,会先申请数据空间。而osMailAlloc调用osPoolAlloc,根本没有使用超时参数millisec。

    void *osMailAlloc (osMailQId queue_id, uint32_t millisec)
    {
      (void) millisec;
      void *p;
      
      
      if (queue_id == NULL) {
        return NULL;
      }
      
      p = osPoolAlloc(queue_id->pool);
      
      return p;
    }

    3. 其他问题

    osMessagePut可以通过传递复杂数据结构的指针,来实现任务间通信。

    不过这些数据结构应该是动态分配的,传入参数是pool空间内存的指针,不是任务中的局部变量和全局变量的指针,否则就hardfault。

    调用是 message_t *message = (message_t*)osPoolAlloc(mpool);  和osMessagePut(queue, (uint32_t)message, osWaitForever);

    入口是osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec);

    内部调用是xQueueSend(queue_id, &info, ticks) != pdTRUE) 

    这里调用关系复杂,使用局部变量和全局变量的指针频频出错,原因还未知。

    4. 附录

    期待ST出面解决,Will come back to you soon.. 

  • 相关阅读:
    Entity Framework开源了
    动态执行超过4000个字符的SQL
    微软针对Access提供了免费的SQL Server移植工具——SSMA
    网络采集库NCrawler
    使用WinSCP软件在windows和Linux中进行文件传输
    Open Source C# (Mono Compatible) Library for Sending Push Notifications To iOS (iPhone/iPad APNS), Android (C2DM), Windows Phone Apps
    Mono 2.11.3 发布包含微软的 Entity Framework
    谷歌发布 AngularJS 1.0,允许扩展HTML语法
    Accord.NET Framework
    CentOS源码编译安装Nginx和tcp_proxy module
  • 原文地址:https://www.cnblogs.com/pingwen/p/8416608.html
Copyright © 2020-2023  润新知