bigfanofloT 发表于 2017-12-9 22:26:24

【一起来玩RTOS系列】之RT-Thread 邮箱用于线程间通信

本帖最后由 bigfanofloT 于 2017-12-9 22:29 编辑

邮箱

邮箱服务是实时操作系统中一种典型的任务间通信方法,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的4字节内容(针对32位处理系统,指针的大小即为4个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如图所示,线程或中断服务例程把一封4字节长度的邮件发送到邮箱中。而一个或多个线程可以从邮箱中接收这些邮件进行处理。



RT-Thread操作系统采用的邮箱通信机制有点类似于传统意义上的管道,用于线程间通讯。非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程,中断服务,定时器向线程发送消息的有效手段。通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为0时,邮件收取过程将变成阻塞方式。所以在这类情况下,只能由线程进行邮件的收取。

RT-Thread操作系统的邮箱中可存放固定条数的邮件,邮箱容量在创建/初始化邮箱时设定,每个邮件大小为4字节。当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。

在一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择是否等待挂起或直接返回-RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送的过程。

在一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回-RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的4个字节邮件到接收线程中。

邮箱控制块


struct rt_mailbox
{
    struct rt_ipc_object parent;

    rt_uint32_t* msg_pool;            /* 邮箱缓冲区的开始地址 */
    rt_uint16_t size;                   /* 邮箱缓冲区的大小   */

    rt_uint16_t entry;                  /* 邮箱中邮件的数目   */
    rt_uint16_t in_offset, out_offset;/* 邮箱缓冲的进出指针   */
    rt_list_t suspend_sender_thread;    /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox* rt_mailbox_t;
rt_mailbox对象从rt_ipc_object中派生,由IPC容器管理。

邮箱相关接口

创建邮箱

创建邮箱对象可以调用如下的函数接口:

rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag);
创建邮箱对象时会先创建一个邮箱对象控制块,然后给邮箱分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4字节)与邮箱容量的乘积,接着初始化接收邮件和发送邮件在邮箱中的偏移量。

函数参数


      参数描述

      name邮箱名称;

      size邮箱容量;

      flag邮箱标志,它可以取如下数值:

#define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO方式*/
#define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
函数返回

创建成功返回邮箱对象的句柄;否则返回-RT_NULL。

删除邮箱

当邮箱不再被使用时,应该删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。删除邮箱的函数接口如下:

rt_err_t rt_mb_delete (rt_mailbox_t mb);
删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程获得返回值是-RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。

函数参数


      参数描述

      mb邮箱对象的句柄。

函数返回

RT_EOK

初始化邮箱

初始化邮箱跟创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。其他与创建邮箱不同的是,此处静态邮箱对象所使用的内存空间是由用户线程指定的一个缓冲区空间,用户把缓冲区的指针传递给邮箱对象控制块,其余的初始化工作与创建邮箱时相同。函数接口如下:

rt_err_t rt_mb_init(rt_mailbox_t mb, const char* name, void* msgpool,
    rt_size_t size, rt_uint8_t flag)
初始化邮箱时,该函数接口需要获得用户已经申请获得的邮箱对象控制块,缓冲区的指针,以及邮箱名称和邮箱容量。

函数参数


      参数描述

      mb邮箱对象的句柄;

      name邮箱名称;

   msgpool缓冲区指针;

      size邮箱容量;

      flag邮箱标志,它可以取如下数值:

#define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO方式*/
#define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
函数返回

RT_EOK

注: 这里的size参数指定的是邮箱的容量,即如果msgpool的字节数是N,那么邮箱容量应该是N/4。
脱离邮箱

脱离邮箱将把邮箱对象从内核对象管理器中删除。脱离邮箱使用下面的接口:

rt_err_t rt_mb_detach(rt_mailbox_t mb);
使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是- RT_ERROR ),然后将该邮箱对象从内核对象管理器中删除。

函数参数


      参数描述

      mb邮箱对象的句柄。

函数返回

RT_EOK

发送邮件

线程或者中断服务程序可以通过邮箱给其他线程发送邮件,发送邮件函数接口如下:

rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);
发送的邮件可以是32位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到-RT_EFULL 的返回值。

函数参数


      参数描述

      mb邮箱对象的句柄;

   value邮件内容。

函数返回

发送成功返回RT_EOK;如果邮箱已经满了,返回-RT_EFULL。

等待方式发送邮件

用户也可以通过如下的函数接口向指定邮箱发送邮件:

rt_err_t rt_mb_send_wait (rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout);
rt_mb_send_wait与rt_mb_send的区别在于,如果邮箱已经满了,那么发送线程将根据设定的timeout参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒返回错误码。

函数参数


      参数描述

      mb邮箱对象的句柄;

   value邮件内容;

   timeout超时时间。

函数返回

发送成功返回RT_EOK;如果设置的时间超时依然未发送成功,返回-RT_ETIMEOUT,其他情况返回-RT_ERROR。

接收邮件

只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回RT_EOK的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。接收邮件函数接口如下:

rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);
接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回-RT_ETIMEOUT。

函数参数


      参数描述

      mb邮箱对象的句柄;

   value邮件内容;

   timeout超时时间。

函数返回

成功收到返回RT_EOK,超时返回-RT_ETIMEOUT,其他返回-RT_ERROR。
使用场合

邮箱是一种简单的线程间消息传递方式,在RT-Thread操作系统的实现中能够一次传递4字节邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数(邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是4字节,所以邮箱能够用于不超过4字节的消息传递,当传送的消息长度大于这个数目时就不能再采用邮箱的方式。 最重要的是,在32位系统上4字节的内容恰好适合放置一个指针,所以邮箱也适合那种仅传递指针的情况,例如:

struct msg
{
    rt_uint8_t *data_ptr;
    rt_uint32_t data_size;
};

对于这样一个消息结构体,其中包含了指向数据的指针data_ptr和数据块长度的变量data_size。当一个线程需要把这个消息发送给另外一个线程时,可以采用如下的操作:

struct msg* msg_ptr;

msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));
msg_ptr->data_ptr = ...; /* 指向相应的数据块地址*/
msg_ptr->data_size = len; /* 数据块的长度*/
/* 发送这个消息指针给mb邮箱*/
rt_mb_send(mb, (rt_uint32_t)msg_ptr);

而在接收线程中,因为收取过来的是指针,而msg_ptr是一个新分配出来的内存块,所以在接收线程处理完毕后,需要释放相应的内存块:

struct msg* msg_ptr;
if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK)
{
    /* 在接收线程处理完毕后,需要释放相应的内存块*/
    rt_free(msg_ptr);
}

接下来在机智云Gokit智能硬件开发板上演示如何创建并使用邮箱进行线程间的通信。

注意:使用邮箱时要打开宏


/**
***********************************
* File Name          : main.c
* Description      : Main program body
***********************************
** This notice applies to any and all portions of this file
* that are not between comment pairs USER CODE BEGIN and
* USER CODE END. Other portions of this file, whether
* inserted by the user or by software development tools
* are owned by their respective copyright owners.
*
* COPYRIGHT(c) 2017 STMicroelectronics
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*   1. Redistributions of source code must retain the above copyright notice,
*      this list of conditions and the following disclaimer.
*   2. Redistributions in binary form must reproduce the above copyright notice,
*      this list of conditions and the following disclaimer in the documentation
*      and/or other materials provided with the distribution.
*   3. Neither the name of STMicroelectronics nor the names of its contributors
*      may be used to endorse or promote products derived from this software
*      without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
***********************************
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_hal.h"
#include "usart.h"
#include "gpio.h"

/* USER CODE BEGIN Includes */
#include "rtthread.h"
#include "string.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */
//重映射串口1到rt_kprintf
void rt_hw_console_output(const char *str)
{
    /* empty console output */
      char aa='\r';
          rt_enter_critical();

                while(*str!='\0')
                {
                        if(*str=='\n')
                        {
                              HAL_UART_Transmit(&huart1, (uint8_t *)&aa, 1, 10);
                        }
                              HAL_UART_Transmit(&huart1, (uint8_t *)(str++), 1, 10);
                }
               
                rt_exit_critical();
}

void rt_hw_us_delay(int us)
{
    rt_uint32_t delta;

    /* 获得延时经过的tick数 */
    us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));

    /* 获得当前时间 */
    delta = SysTick->VAL;

    /* 循环获得当前时间,直到达到指定的时间后退出循环 */
    while (delta - SysTick->VAL< us);
}

void rt_hw_ms_delay(int ms)
{
      int i=0,j=0;
      for(j=0;j<ms;j++)
      {
                for (i=0;i<2;i++)
                rt_hw_us_delay(500);
      }
}
uint32_t rt_hw_delay_Init(void)
{
#if !defined(STM32F0xx)
      uint32_t c;
      
    /* Enable TRC */
    CoreDebug->DEMCR &= ~0x01000000;
    CoreDebug->DEMCR |=0x01000000;
      
    /* Enable counter */
    DWT->CTRL &= ~0x00000001;
    DWT->CTRL |=0x00000001;
      
    /* Reset counter */
    DWT->CYCCNT = 0;
      
      /* Check if DWT has started */
      c = DWT->CYCCNT;
      
      /* 2 dummys */
      __ASM volatile ("NOP");
      __ASM volatile ("NOP");
      
      /* Return difference, if result is zero, DWT has not started */
      return (DWT->CYCCNT - c);
#else
      /* Return OK */
      return 1;
#endif
}
void rt_hw_delay_us(__IO uint32_t micros)
{
#if !defined(STM32F0xx)
      uint32_t start = DWT->CYCCNT;
      
      /* Go to number of cycles for system */
      micros *= (HAL_RCC_GetHCLKFreq() / 1000000);
      
      /* Delay till end */
      while ((DWT->CYCCNT - start) < micros);
#else
      /* Go to clock cycles */
      micros *= (SystemCoreClock / 1000000) / 5;
      
      /* Wait till done */
      while (micros--);
#endif
}
void rt_hw_delay_ms(__IO uint32_t mills)
{
      rt_hw_delay_us(1000*mills);
}

/*
* 程序清单:邮箱例程
*
* 这个程序会创建2个动态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,
* 一个线程从邮箱中收取邮件。
*/

/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;

/* 邮箱控制块 */
static struct rt_mailbox mb;
/* 用于放邮件的内存池 */
static char mb_pool;

static char mb_str1[] = "I'm a mail!";
static char mb_str2[] = "this is another mail!";

/* 线程1入口 */
static void thread1_entry(void* parameter)
{
    unsigned char* str;

    while (1)
    {
      rt_kprintf("thread1: try to recv a mail\n");

      /* 从邮箱中收取邮件 */
      if (rt_mb_recv(&mb, (rt_uint32_t*)&str, RT_WAITING_FOREVER)
                == RT_EOK)
      {
            /* 显示邮箱内容 */
            rt_kprintf("thread1: get a mail, the content:%s\n", str);

            /* 延时10个OS Tick */
            rt_thread_delay(10);
      }
    }
}

/* 线程2入口 */
static void thread2_entry(void* parameter)
{
    rt_uint8_t count;

    count = 0;
    while (1)
    {
      count ++;
      if (count & 0x1)
      {
            /* 发送mb_str1地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str1);
      }
      else
      {
            /* 发送mb_str2地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str2);
      }

      /* 延时20个OS Tick */
      rt_thread_delay(20);
    }
}


/* USER CODE END 0 */

int main(void)
{

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration----------------------------------------------------------*/

///* Reset of all peripherals, Initializes the Flash interface and the Systick. */
//HAL_Init();

///* USER CODE BEGIN Init */

///* USER CODE END Init */

///* Configure the system clock */
//SystemClock_Config();

///* USER CODE BEGIN SysInit */

///* USER CODE END SysInit */

///* Initialize all configured peripherals */
//MX_GPIO_Init();
//MX_USART1_UART_Init();

/* USER CODE BEGIN 2 */
      
    /* 初始化一个mailbox */
    rt_mb_init(&mb,
      "mbt",             /* 名称是mbt */
      &mb_pool,       /* 邮箱用到的内存池是mb_pool */
      sizeof(mb_pool)/4, /* 大小是mb_pool/4,因为每封邮件的大小是4字节 */
      RT_IPC_FLAG_FIFO); /* 采用FIFO方式进行线程等待 */

    /* 创建线程1 */
    tid1 = rt_thread_create("t1",
      thread1_entry,    /* 线程入口是thread1_entry */
      RT_NULL,         /* 入口参数是RT_NULL */
      512,
                              2,
                              20);
    if (tid1 != RT_NULL)
      rt_thread_startup(tid1);


    /* 创建线程2 */
    tid2 = rt_thread_create("t2",
      thread2_entry,   /* 线程入口是thread2_entry */
      RT_NULL,         /* 入口参数是RT_NULL */
      512,
                              2,
                        20);
    if (tid2 != RT_NULL)
      rt_thread_startup(tid2);
      
                rt_hw_delay_Init();
                        
                printf("\r\n机智云只为智能硬件而生\r\n");
                printf("Gizwits Smart Cloud for Smart Products\r\n");
                printf("链接|增值|开放|中立|安全|自有|自由|生态\r\n");
                printf("www.gizwits.com\r\n");
                printf("\r\nGokit RT-Thread Demo\r\n\r\n");
               
                return 0;
      
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
//while (1)
//{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
//               
//}
/* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;

    /**Initializes the CPU, AHB and APB busses clocks
    */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

    /**Initializes the CPU, AHB and APB busses clocks
    */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

    /**Configure the Systick interrupt time
    */
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick
    */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
* @briefThis function is executed in case of error occurrence.
* @paramNone
* @retval None
*/
void _Error_Handler(char * file, int line)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
while(1)
{
}
/* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT

/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */

}

#endif

/**
* @}
*/

/**
* @}
*/

/*********** (C) COPYRIGHT STMicroelectronics ***END OF FILE**/

串口信息:



源码下载:




页: [1]
查看完整版本: 【一起来玩RTOS系列】之RT-Thread 邮箱用于线程间通信