RT-thread内核之邮箱
时间:10-02
整理:3721RD
点击:
一、邮箱控制块:在include/rtdef.h中
创建邮箱:
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这一命令,表示复位邮箱(重新初始化邮箱)。
- #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
创建邮箱:
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操作系统支持多个发送线程和多个接收线程,多个发送线程倒还好,倒是多个接收线程就不太好控制了,一般这种情况也不会用的,如果真的需要这种情况,那么多个接收线程就得好好控制了,因为,到底是哪个接收线程接收到邮件还不好说。