携手创造,共同成长!这是我参加「日新方案 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
:
删去邮箱:
/**
参数的意义:
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,发送一个字符串指针:
邮件创立:
在接纳线程中,咱们打印出接纳到的数值:
测验结果,两个按键按下,线程不仅能收到直接传过来的4字节数据,还能经过传递的指针发送一个字符串:
1.3.2 邮箱个数示例
在上面的比如中,咱们开端创立的邮箱巨细就一个,咱们测验下,假如没有线程接纳,是不是就会打印邮箱满的音讯,咱们把线程接纳邮箱代码注释掉,其他仍是和前面测验相同:
咱们再来改一下,运用一个按键测验一下这个 size 是字节呢,仍是直接是邮件个数,直接看图阐明:
在静态初始化邮件时分,咱们需求留意咱们拓荒的空间巨细,需求是4的倍数,咱们一般都是用数组除以4直接表明邮箱的size
巨细,如下:
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_mq_send_wait
函数中的,再次着重,学会看源码!
关键的几个当地阐明一下:
当然这儿没有特意的阐明等候时刻问题,由于发送和接纳都能够堵塞等候,这儿不是要了解的重点。
发送完完成今后假如发现有线程在等候音讯行列,会发生一次调度:
接纳音讯,其实相似,能够自己检查源码,试着剖析。
关于上述过程的了解,我独自写了个比如,结合比如去了解上面的步骤,更加直观!请看下面 了解音讯行列原理示例。
2.4 示例(音讯行列原理了解)
2个示例,第一个为了更加直观的了解音讯行列原理,第二个是简略的音讯传递。
关于典型的串口接纳不定长度数据的示例,我会独自运用一篇文章来介绍。
2.4.1 了解音讯行列原理
咱们在上面 《2.3 音讯行列原理简析》 剖析了一下音讯行列的原理,咱们再来经过一个比如直观的加深一下了解。
新建一个音讯行列(留意新建时分的参数):
咱们2个按键,经过Key2按键发送音讯:
经过 Key3 打印 音讯行列 对应的状况值:
咱们测验的时分,经过调查音讯行列初始化今后的状况,然后每次发送今后调查 head,tail,free的改变状况,加深咱们对音讯行列的了解:
经过上面的示例再去了解音讯行列的原理,就很直观了,假如有音讯接纳,调查地址的改变,相同的能够剖分出接纳音讯时分的原理。
2.4.2 音讯传递
音讯传递相对来说,就简略多了,直接在上面的根底上,新建一个使命接纳音讯(由于没有做长度辨认,这儿没有做解析):
仍是经过上面的Key2按键发送音讯:
结语
本文尽管只介绍了2个IPC机制,可是在项目中,它们的运用无处不在。
音讯行列的运用在咱们实际运用中,是很重要的,串口通信接纳数据便是运用音讯行列来完成。关于音讯行列的串口运用,我会独自开一片博文来总结。
本文针对音讯行列的完成原理给出了很好的示例,仍是那句话,学会多看源码,多动手测验!
博主会用心写好每一篇博文,期望我们支撑!谢谢!