携手创造,共同成长!这是我参加「日新方案 8 月更文应战」的第7天,点击检查活动概况

讲完了线程同步的机制,咱们要开端线程通讯的学习,
线程通讯中的邮箱音讯行列也属于 RT-Thread 的IPC机制。

前言

与上篇文章的介绍的信号量、互斥量和事情集,邮箱、音讯行列相同为 RT-Thread IPC机制。可是信号量它们属于线程同步机制,并不能在线程之间传递音讯,咱们本文介绍的 邮箱、音讯行列便是完成线程间音讯传递的机制。

相关于上一篇文章的内容,线程通讯的学习会相对杂乱些,由于涉及到音讯的传递,音讯在实际项目中的可能存在多种不同的状况,所以 邮箱和音讯行列的运用场景和方法是关键,尤其是音讯行列。根本上实际项目中的一切音讯类型都能够运用音讯行列的方法。音讯行列运用于串口通信我会独自用一篇博文来阐明,本文先做根底介绍和根本示例的讲解。

本 RT-Thread 专栏记载的开发环境:

RT-Thread记载(一、RT-Thread 版别、RT-Thread Studio开发环境 及 合作CubeMX开发快速上手)

RT-Thread记载(二、RT-Thread内核发动流程 — 发动文件和源码剖析)

RT-Thread 内核篇系列博文链接:

RT-Thread记载(三、RT-Thread 线程操作函数及线程办理与FreeRTOS的比较)

RT-Thread记载(四、RT-Thread 时钟节拍和软件定时器)

RT-Thread记载(五、RT-Thread 临界区保护)

RT-Thread记载(六、IPC机制之信号量、互斥量和事情集)

一、邮箱

RT-Thread 中的邮件是线程、中止服务、定时器向线程发送音讯的有效手法(中止和定时器需求非堵塞方法,不能等候发送,也不能接纳)。

邮箱中的每一封邮件只能包容固定的 4 字节内容(32位内核正好能够传递一个指针)。

邮箱特点 RAM空间占用少,功率较高。

RT-Thread 有点相似 FreeRTOS 的使命通知,相同的只能传递4个字节内容。 可是 FreeRTOS 的使命通知是属于使命自己的,每个使命有且只要一个通知, 而 RT-Thread 的邮箱由邮箱操控块统一办理,新建一个邮箱,能够包含多封邮件(每封4个字节)。

1.1 邮箱操控块

老规矩用源码,解释看注释(运用起来也方便复制 ~ ~!)

#ifdef RT_USING_MAILBOX
/**
 * mailbox structure
 */
struct rt_mailbox
{
    struct rt_ipc_object parent;             /**< inherit from ipc_object */
    rt_ubase_t          *msg_pool;           /**< 邮箱缓冲区的开端地址  */
    rt_uint16_t          size;               /**< 邮箱缓冲区的巨细      */
    rt_uint16_t          entry;              /**< 邮箱中邮件的数目 */
    rt_uint16_t          in_offset;          /**< 邮箱缓冲的进口指针 */
    rt_uint16_t          out_offset;         /**< 邮箱缓冲的出口指针 */
    rt_list_t            suspend_sender_thread;   /**< 发送线程的挂起等候行列 */
};
typedef struct rt_mailbox *rt_mailbox_t;
#endif

1.2 邮箱操作

1.2.1 创立和删去

同以前的线程那些相同,动态的方法,先界说一个邮箱结构体的指针变量,接纳创立好的句柄。

创立邮箱:

/**
参数的意义:
1、name 		邮箱称号
2、size			邮箱容量(便是多少封邮件,4的倍数)
3、flag 		邮箱标志,它能够取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
回来值:
RT_NULL 		创立失利
邮箱目标的句柄 	创立成功 
 */
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)

最后的 flag 和信号量相同建议 RT_IPC_FLAG_PRIO

RT-Thread记录(七、IPC机制之邮箱、消息队列)

删去邮箱:

/**
参数的意义:
mb 	邮箱目标的句柄
回来
RT_EOK 	成功
 */
rt_err_t rt_mb_delete(rt_mailbox_t mb)

1.2.2 初始化和脱离

静态的方法,先界说一个邮箱结构体,然后对他进行初始化。

这儿要留意,还要界说一个数组,用来做邮箱的内存空间,和静态初始化线程相同。

初始化邮箱:

/**
参数意义:
1、mb	 	邮箱目标的句柄,需求取自界说的结构体地址
2、name	 	邮箱称号
3、msgpool 	缓冲区指针(用户自界说的数组的地址,第一个数组元素的地址)
4、size 	邮箱容量(便是数组的巨细/4)
5、flag 	邮箱标志,它能够取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
回来
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 	邮箱目标的句柄
回来
RT_EOK 	成功
 */
rt_err_t rt_mb_detach(rt_mailbox_t mb)

1.2.3 发送邮件

在 RT-Thread 中发送邮件分为 有无等候方法发送邮件,以及发送紧迫邮件。

在我建的工程版别中,并没有发送紧迫邮件函数了,这儿按照工程源码来阐明,就不介绍发送紧迫邮件的函数了,在一般的 STM32 运用中,个人认为紧迫邮件有没有都没有影响!

无等候方法适用于一切的线程和中止,等候方法不能用于中止中!

无等候发送邮件:

/**
参数:
1、mb	 	邮箱目标的句柄
2、value 	邮件内容
回来
RT_EOK 		发送成功
-RT_EFULL 	邮箱已经满了
看函数原型,其实便是把等候方法发送的时刻改成了0
 */
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
{
    return rt_mb_send_wait(mb, value, 0);
}

无等候发送其实便是运用等候方法发送邮件,等候时刻为0:。

等候方法发送邮件:

/**
参数:
1、mb 		邮箱目标的句柄
2、value 	邮件内容
3、timeout 	超时时刻
回来:
RT_EOK 			发送成功
-RT_ETIMEOUT 	超时
-RT_ERROR 		失利,回来过错
 */
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                         rt_ubase_t   value,
                         rt_int32_t   timeout)

1.2.4 接纳邮件

接纳邮件时,除了指定接纳邮件的邮箱句柄,并指定接纳到的邮件寄存方位(需求有一个变量来保存接纳到的数据)。

/**
参数意义:
1、mb 		邮箱目标的句柄,从哪个邮件操控块取邮件
2、value 	邮件内容,需求用一个变量保存
3、timeout 	超时时刻
回来值:
RT_EOK 	接纳成功
-RT_ETIMEOUT 	超时
-RT_ERROR 	失利,回来过错
 */
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)

1.3 示例(指针传递)

2个示例,第一个是正常的音讯传递,第二个是与邮箱创立个数有关的引导示例。

1.3.1 邮箱音讯传递

前面说到过,邮箱中的每一封邮件只能包容固定的 4 字节内容,可是4字节能够传递指针,咱们别离做个简略的演示。

示例中,咱们运用两个不同的按键来发送邮件,经过一个事情来接纳邮件,并打印收到的邮件内容。

按键key3,发送4字节的内容,按键Key2,发送一个字符串指针:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

邮件创立:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

在接纳线程中,咱们打印出接纳到的数值:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

测验结果,两个按键按下,线程不仅能收到直接传过来的4字节数据,还能经过传递的指针发送一个字符串:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

1.3.2 邮箱个数示例

在上面的比如中,咱们开端创立的邮箱巨细就一个,咱们测验下,假如没有线程接纳,是不是就会打印邮箱满的音讯,咱们把线程接纳邮箱代码注释掉,其他仍是和前面测验相同:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

咱们再来改一下,运用一个按键测验一下这个 size 是字节呢,仍是直接是邮件个数,直接看图阐明:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

在静态初始化邮件时分,咱们需求留意咱们拓荒的空间巨细,需求是4的倍数,咱们一般都是用数组除以4直接表明邮箱的size巨细,如下:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

RT-Thread 是经过操控块来办理这些IPC机制,在实际测验中,为了加深对某个目标的了解,比如这儿的邮箱,能够直接打印出邮箱的参数来检查当前邮箱的状况。学会测验!!!

RT-Thread记录(七、IPC机制之邮箱、消息队列)

二、音讯行列

音讯行列能够接纳来自线程或中止服务例程中不固定长度的音讯,并把音讯缓存在自己的内存空间中。

音讯行列和邮箱的区别是长度并不限定在 4 个字节以内,可是假如假如把音讯行列的每条音讯的最大字节规定在4个字节以内,那么音讯行列就和邮箱相同了。

典型运用,运用串口接纳不定长数据(后期会独自有博文介绍音讯行列在串口接纳上的运用)。

2.1 音讯行列操控块

音讯行列操控块的这些特点,咱们等会用示例来打印出来看,加深一下对这些特点的知道。

#ifdef RT_USING_MESSAGEQUEUE
/**
 * message queue structure
 */
struct rt_messagequeue
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */
    void                *msg_pool;                      /**< 音讯行列的开端地址 */
    rt_uint16_t          msg_size;                      /**< 每个音讯长度 */
    rt_uint16_t          max_msgs;                      /**< 最大的音讯数量 */
    rt_uint16_t          entry;                         /**< 已经有的音讯数 */
    void                *msg_queue_head;                /**< list head 链表头 */
    void                *msg_queue_tail;                /**< list tail 链表尾*/
    void                *msg_queue_free;                /**< 闲暇音讯链表 */
    rt_list_t            suspend_sender_thread;         /**< 挂起的发送线程 */
};
typedef struct rt_messagequeue *rt_mq_t;
#endif

2.2 音讯行列操作

2.2.1 创立和删去

先界说一个邮箱结构体的指针变量,接纳创立好的句柄。

创立音讯行列:

/**
参数:
1、name 		音讯行列的称号
2、msg_size 	音讯行列中一条音讯的最大长度,单位字节
3、max_msgs 	音讯行列的最大个数
4、flag 		音讯行列选用的等候方法,它能够取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
回来:
RT_EOK 				发送成功
音讯行列目标的句柄 	成功
RT_NULL 			失利
 */
rt_mq_t rt_mq_create(const char *name,
                     rt_size_t   msg_size,
                     rt_size_t   max_msgs,
                     rt_uint8_t  flag)

留意!msg_size 单位是字节,在32位体系中 RT-Thread 默认#define RT_ALIGN_SIZE 4 ,所以假如 msg_size 不是4字节对齐,体系会自动补全。

比如用户界说为9,那么体系会自动把音讯行列巨细设置为 12,界说为1,设置为4。

还有flag的运用,仍然得留意一下,和邮箱信号量等相同,留意实时性问题。

删去音讯行列:

/**
参数
mq 		音讯行列目标的句柄
回来
RT_EOK 	成功
 */
rt_err_t rt_mq_delete(rt_mq_t mq)

2.2.2 初始化和脱离

静态的方法,先界说一个音讯行列结构体,然后对他进行初始化。

初始化音讯行列:

/**
参数:
1、mq 			音讯行列目标的句柄,需求取自界说的结构体地址
2、name 		称号
3、msgpool 		寄存音讯的地址
4、msg_size 	音讯行列中一条音讯的最大长度,单位字节
5、pool_size 	寄存音讯的缓冲区巨细
6、flag 		音讯行列选用的等候方法,
回来:
RT_EOK 	成功
 */
rt_err_t rt_mq_init(rt_mq_t     mq,
                    const char *name,
                    void       *msgpool,
                    rt_size_t   msg_size,
                    rt_size_t   pool_size,
                    rt_uint8_t  flag)

脱离音讯行列:

/**
参数:
mq 		音讯行列目标的句柄
回来:
RT_EOK 	成功
 */
rt_err_t rt_mq_detach(rt_mq_t mq)

2.2.3 发送音讯

和邮件相同,在 RT-Thread 中发送邮件分为 有无等候方法发送,以及紧迫音讯发送。

无等候方法适用于一切的线程和中止,等候方法不能用于中止中!

无等候发送音讯:

/**
看函数原型,其实便是把等候方法发送的时刻改成了0
参数:
1、mq 		音讯行列目标的句柄
2、buffer 	音讯内容
3、size 	音讯巨细
回来:
RT_EOK 		成功
-RT_EFULL 	音讯行列已满
-RT_ERROR 	失利,表明发送的音讯长度大于音讯行列中音讯的最大长度
 */
rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)
{
    return rt_mq_send_wait(mq, buffer, size, 0);
}

等候方法发送邮件:

/**
除了最后多一个时刻,其他参数,和上面无等候方法相同
timeout 	超时时刻(时钟节拍)
*/
rt_err_t rt_mq_send_wait(rt_mq_t     mq,
                         const void *buffer,
                         rt_size_t   size,
                         rt_int32_t  timeout)

发送紧迫音讯:

/**
参数:
1、mq 		音讯行列目标的句柄
2、buffer 	音讯内容
3、size 	音讯巨细
回来:
RT_EOK 		成功
-RT_EFULL 	音讯行列已满
-RT_ERROR 	失利
 */
rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size)

2.2.4 接纳音讯

接纳音讯时,接纳者需指定存储音讯的音讯行列目标句柄,并且指定一个内存缓冲区,接纳到的音讯内容将被复制到该缓冲区里。

/**
参数:
mq 				音讯行列目标的句柄
buffer 			音讯内容
size 			音讯巨细
timeout 		指定的超时时刻
回来:
RT_EOK 			成功收到
-RT_ETIMEOUT 	超时
-RT_ERROR 		失利,回来过错
 */
rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout)

2.3 音讯行列原理简析

音讯行列操控块:

要了解 音讯行列 的原理,就得从他初始化的状况开端说起:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

发送音讯,其实一切的步骤都是在rt_mq_send_wait函数中的,再次着重,学会看源码!

关键的几个当地阐明一下:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

当然这儿没有特意的阐明等候时刻问题,由于发送和接纳都能够堵塞等候,这儿不是要了解的重点。

RT-Thread记录(七、IPC机制之邮箱、消息队列)

发送完完成今后假如发现有线程在等候音讯行列,会发生一次调度:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

接纳音讯,其实相似,能够自己检查源码,试着剖析。

关于上述过程的了解,我独自写了个比如,结合比如去了解上面的步骤,更加直观!请看下面 了解音讯行列原理示例。

2.4 示例(音讯行列原理了解)

2个示例,第一个为了更加直观的了解音讯行列原理,第二个是简略的音讯传递。

关于典型的串口接纳不定长度数据的示例,我会独自运用一篇文章来介绍。

2.4.1 了解音讯行列原理

咱们在上面 《2.3 音讯行列原理简析》 剖析了一下音讯行列的原理,咱们再来经过一个比如直观的加深一下了解。

新建一个音讯行列(留意新建时分的参数):

RT-Thread记录(七、IPC机制之邮箱、消息队列)

咱们2个按键,经过Key2按键发送音讯:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

经过 Key3 打印 音讯行列 对应的状况值:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

咱们测验的时分,经过调查音讯行列初始化今后的状况,然后每次发送今后调查 head,tail,free的改变状况,加深咱们对音讯行列的了解:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

经过上面的示例再去了解音讯行列的原理,就很直观了,假如有音讯接纳,调查地址的改变,相同的能够剖分出接纳音讯时分的原理。

2.4.2 音讯传递

音讯传递相对来说,就简略多了,直接在上面的根底上,新建一个使命接纳音讯(由于没有做长度辨认,这儿没有做解析):

RT-Thread记录(七、IPC机制之邮箱、消息队列)

仍是经过上面的Key2按键发送音讯:

RT-Thread记录(七、IPC机制之邮箱、消息队列)

结语

本文尽管只介绍了2个IPC机制,可是在项目中,它们的运用无处不在。

音讯行列的运用在咱们实际运用中,是很重要的,串口通信接纳数据便是运用音讯行列来完成。关于音讯行列的串口运用,我会独自开一片博文来总结。

本文针对音讯行列的完成原理给出了很好的示例,仍是那句话,学会多看源码,多动手测验!

博主会用心写好每一篇博文,期望我们支撑!谢谢!