RT-thread内核之邮箱

一、邮箱控制块:在include/rtdef.h中

#ifdef RT_USING_MAILBOX
/**
 * mailbox structure
 */
struct rt_mailbox
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */                //继承自IPC对象 

    rt_uint32_t         *msg_pool;                      /**< start address of message buffer */        //消息缓冲地址  

    rt_uint16_t          size;                          /**< size of message pool */                   //可存放的消息最大条数 

    rt_uint16_t          entry;                         /**< index of messages in msg_pool */          //当前邮箱中存放的消息条数
    rt_uint16_t          in_offset;                     /**< input offset of the message buffer */     //消息存入的偏移位置
    rt_uint16_t          out_offset;                    /**< output offset of the message buffer */    //消息取出时的偏移位置

    rt_list_t            suspend_sender_thread;         /**< sender thread suspended on this mailbox *///邮箱发送线程挂起链表(当没有取走时,发送线程会被挂起)
};
typedef struct rt_mailbox *rt_mailbox_t;
#endif

二、邮箱相关接口:在src/ipc.c中

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

删除邮箱:
rt_err_t rt_mb_delete(rt_mailbox_t mb);
删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程获得返回值是-RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。
初始化邮箱:
rt_err_t rt_mb_init(rt_mailbox_t mb,             //邮箱对象的句柄
                    const char  *name,           //邮箱名称
                    void        *msgpool,        //缓冲区指针
                    rt_size_t    size,           //邮箱容量
                    rt_uint8_t   flag);          //邮箱标志(FIFO/PRIO)
与创建邮箱不同的是,此处静态邮箱对象所使用的内存空间是由用户线程指定的一个缓冲区空间,用户把缓冲区的指针传递给邮箱对象控制块,其余的初始化工作与创建邮箱时相同。
注: 这里的size参数指定的是邮箱的容量,即如果msgpool的字节数是N,那么邮箱容量应该是N/4

脱离邮箱:
rt_err_t rt_mb_detach(rt_mailbox_t mb);
使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是-RT_ERROR ),然后将该邮箱对象从内核对象管理器中删除。
等待方式发送邮件:
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参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这是发送线程将被唤醒返回错误码。

发送邮件:
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value);
此函数与rt_mb_send_wait(mb, value, 0)等价。发送的邮件可以是32位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到-RT_EFULL 的返回值。发送函数在邮箱满的时候会挂起当前所处的发送线程。

接收邮件:
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout);
只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回RT_EOK的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回-RT_ETIMEOUT。函数的整个流程为:(1)接收邮件函数会判断当前邮箱是否为空且时间参数为0, 如果是则直接返回,然后进行一个while循环,在这个while循环中的判断条件是邮箱为空,为什么要这么做呢?因此程序将在这个 while循环内一直等待邮件的到达,只有邮件到达才会跳出这个循环(如果等待超时则直接函数返回了)。(2)进入while循环后,程序再次判断当前的等待时间参数是否为0,如果是则直接返回。接着挂起当前线程,再次判断时间参数是否大于0,这里主要是扫除timeout=RT_WAITING_FOREVER的情况,因为RT_WAITING_FOREVER宏定义为-1.在if语句内记录当前的时间点,然后设置一定时器并开启,接着重新调度。在重新调度后,系统可能切换到其它线程,假设一段时间内,系统再次切换回来,原因可能有多种,1:邮箱被脱离,此时当前线程thread->error=-RT_ERROR;2 定时器时间到达,但是邮件还未到达,此时thread->error=-RT_ETIMEOUT;3:邮件到达,本线程在发送邮件函数中被唤醒(注:发送邮件函数中只是唤醒第一条等待邮件的线程),此时,thread->error还是保持原值RT_EOK不变;4:其它原因假设一段时间后线程切换回来,此时error的值也一直保持原样RT_EOK不变.因此,在重新调度了线程之后,才会有一个if语句通过判断thread->error的值是否为RT_EOK来判断当前线程是否被发送邮件函数唤醒。如果不是,则直接返回错误。(3)接下来,按原则上说,当前线程一定是被发送邮件函数唤醒,因此,当前一定会存在接收的邮件,但是接下来的几行代码却是再次判断时间参数大于0的情况下,计算还剩余多多时间,然后返回到while循环接着循环。其原因为判断邮箱中是否真正存在邮件的唯一标准是while循环的判断条件,即邮箱内的接收信件条数不能为空,或为空,则判断循环,或不为空,则自然不会进行到while循环中了。但如果这时发现原来邮箱还是为空,那么当前线程则应该继续等待了,此时就应该计算出下一次循环中需要等待的剩下时间,好让下一循环进行精确等待这段时间。(4)接下来就是取出接收到的邮件,并更新邮箱的进出口偏移位置及邮箱中的邮件数减1,这样操作过后,不要忘记邮箱内可能还保留着因之前邮箱空间不中而挂起的发送线程,这个时候由于读取邮件操作,那么肯定是至少有一个空出的位置,那么是时候唤醒可能挂起的发送线程了(如果存在的话)。最后重新调度一下。
控制邮箱:
rt_err_t rt_mb_control(rt_mailbox_t mb, rt_uint8_t cmd, void *arg);
只支持RT_IPC_CMD_RESET这一命令,表示复位邮箱(重新初始化邮箱)。

 三、小结

邮箱相关源码主要是在发送与接收上。发送时,由于当前邮箱可能空间已满,放不下要发送的邮件,此时,不得不挂起当前发送线程(如果存在时间参数的话),只要在接收函数读取出一条邮件时才会唤醒它。同理,如果当前邮箱为空,则接收函数会挂起当前的接收线程,直到有新的邮件到达(在发送函数中唤醒接收线程)或等待超时。

另外需要注意地是,rt-thread操作系统支持多个发送线程和多个接收线程,多个发送线程倒还好,倒是多个接收线程就不太好控制了,一般这种情况也不会用的,如果真的需要这种情况,那么多个接收线程就得好好控制了,因为,到底是哪个接收线程接收到邮件还不好说。

时间: 2024-08-02 09:01:02

RT-thread内核之邮箱的相关文章

RT Thread学习历程(1):串口乱码问题

因为学习实时系统,最近接触到RT Thread. 把RT Thread官网上的示例代码烧录到STM32的板子上之后,在串口软件上接收到的全是乱码,一开始以为是串口软件的问题,换了2个软件之后情况都一样,最后发现是晶振的问题,我用的是STM32F407VGT6,晶振要设为8MHz,代码相应的设置晶振的部分也要修改.

STM32 + RT Thread OS 串口通讯

1.   创建项目 a)   禁用Finsh和console b)   默认情况下,项目文件包含了finsh,它使用COM1来通讯,另外,console输出(rt_kprintf)也使用了COM1.因此,在运行scons命令生成项目文件之前,修改rtconfig.h,禁用这两项.(下图L65, L70) c)   生成项目文件 运行scons --target=mdk4 –s 打开生成的项目文件,可以看到,文件组finsh已经不再被包含进来了. d)   创建echo.c 新建一个C文件echo

RT thread 设备驱动之串口设备

本文以stm32f4xx平台介绍串口驱动,主要目的是: 1.RTT中如何编写中断处理程序 2.如何编写RTT设备驱动接口代码 3.了解串行设备的常见处理机制 所涉及的主要源码文件有:usart.c,usart.h,serial.c,serial.h 一.RTT的设备驱动程序概述 编写uart的驱动程序,首先需要了解RTT的设备框架,这里以usart的驱动来具体分析RTT的IO设备管理.注:参考<RTT实时操作系统编程指南> I/O设备管理一章. 我们可以将USART的硬件驱动分成两个部分,如下

向linux内核中添加外部中断驱动模块

本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内核中添加外部中断驱动模块.7.完整驱动程序代码.linux的内核版本为linux2.6.32.2. 一.linux模块的框架以及混杂设备相关知识 1.内核模块的框架如下图所示,其中module_init()(图中有误,不是modules_init)只有在使用insmod命令手动加载模块时才会被调用,

实验六分析Linux内核创建一个新进程的过程

王康 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 1,进程的描述 操作系统三大功能:进程管理(核心),内存管理,文件系统 1,进程控制块PCB--task_struct 也叫进程描述符,为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息. struct task_struct数据结构很庞大:可以看到state进程状态,stack内核堆栈,

Linux内核设计与实现 读书笔记

第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的task_struct连在一起组成了一个双向链表 3. 2.6内核的内核栈底放的是thread_info结构,其中有指向task_struct的指针: 4. current宏可以找到当前进程的task_struct:X86是通过先找到thread_info结构,而PPC是有专门的寄存器存当前task_s

RT-Thread--内存管理

内存管理的功能特点 RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法.总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况: 第一种是针对小内存块的分配管理(小内存管理算法): 第二种是针对大内存块的分配管理(slab 管理算法): 第三种是针对多内存堆的分配情况(memheap 管理算法) 内存堆管理 内存堆管理用于管理一段连续的内存空间,如下图所示,RT-Thread 将 “ZI 段结尾处” 到内

LwIP学习笔记——STM32 ENC28J60移植与入门

0.前言 去年(2013年)的整理了LwIP相关代码,并在STM32上"裸奔"成功.一直没有时间深入整理,在这里借博文整理总结.LwIP的移植过程细节很多,博文也不可能一一详解个别部分只能点到为止. [本文要点] [1]不带操作系统的LwIP移植,LwIP版本为1.4.1. [2]MCU为STM32F103VE,网卡为ENC28J60. [3]移植过程重点描述ethernetif.c和LwIP宏配置等. [4]一个简单的TCP echo例子. [5]力求简单,没有DHCP功能,甚至没有

多线程的两种实现方式

java中多线程可以采用两种方式实现,分别是继承Thread类重写run方法和实现Runnable接口重写run方法. 继承Thread类重写run方法举例如下: /* 需求:在主线程之外继承Thread类创建两独立线程,分别打印1至50. */ class ThreadTest extends Thread{ public void run(){ for(int i = 1; i <= 50; i++) System.out.println(Thread.currentThread().get