RT-Thread移植到S5P4418(五):线程通信

RT-Thread实现了三种线程间通信方式,邮箱(mailbox)、消息队列(messagequeue)、和信号(signal)。

邮箱

邮箱中的每一封邮件只能容纳固定的 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_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)

  • .msg_pool 就是一个循环队列,FIFO,通过.in_offset, .out_offset记录进出位置下标。
  • .suspend_sender_thread 当邮箱满时,发送线程会挂在该链上。
发送邮件

rt_err_t rt_mb_send_wait (rt_mailbox_t mb, rt_uint32_t value)
当发送邮件时, 当且仅当邮箱还没满邮件的时候才能进行发送,如果邮箱已满,可以根据用户设定的等待时间进行等待,当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送的过程,当等待时间到了还未完成发送邮件,或者未设置等待时间,此时发送邮件失败,发送邮件的线程或者中断程序会收到一个错误码( -RT_EFULL)。如果邮件发送成功,会唤起等待接收邮件的线程。线程发送邮件可以带阻塞,但在中断中不能采用任何带阻塞的方式发送邮件。

接受邮件

rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
接收邮件时, 根据邮箱控制块中的 entry 判断队列是否有邮件,如果邮箱的邮件非空,那么可以根据 out_offset 找到最先发送到邮箱中的邮件进行接收。在接收时如果邮箱为空,如果用户设置了等待超时时间,系统会将当前线程挂起,当达到设置的超时时间,邮箱依然未收到邮件时,那么线程将被唤醒并返回-RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收线程中。如果接收成功,会唤起等待发送的线程。

工程文件
  • rtt2a9_mailbox
    线程2发送邮件,线程1等待按键事件后接收邮件消息。

消息队列

消息队列是另一种常用的线程间通讯方式,是邮箱的扩展。可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。

  • 相比于邮箱,消息队列能发送不固定长度的消息,但没有发送线程的挂起列表。
  • 支持发送紧急消息,紧急消息会直接加到对头。
消息队列控制块
struct rt_messagequeue
{
    struct rt_ipc_object parent;

    void* msg_pool;            /* 指向存放消息的缓冲区的指针 */

    rt_uint16_t msg_size;    /* 每个消息的长度 */
    rt_uint16_t max_msgs;    /* 最大能够容纳的消息数 */

    rt_uint16_t entry;        /* 队列中已有的消息数 */

    void* msg_queue_head;    /* 消息链表头 */
    void* msg_queue_tail;    /* 消息链表尾 */
    void* msg_queue_free;    /* 空闲消息链表 */
};
typedef struct rt_messagequeue* rt_mq_t;

在这里插入图片描述

创建消息队列
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要注意对齐。创建后,整个链表都是空闲块,.msg_queue_free指向表头,.msg_queue_head.msg_queue_tail指向RT_NULL

发送消息

rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size);
从空闲链表中摘下.msg_queue_free指向的空闲块,加到消息队列的尾部。如果消息队列为空,则更新.msg_queue_head.msg_queue_tail。发送紧急消息时,把消息直接加到对头。

接收消息
rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout);

通过.entry判断队列中是否有消息,没有,则挂起或直接返回。有,则从对头取出消息,拷贝数据后将内存块挂到空闲队列。
在这里插入图片描述

工程文件

线程2发送消息,消息中带一个信号量,用于线程1接收到消息后通知线程2继续发送消息。


信号

信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。
信号相关结构体:

struct siginfo
{
    rt_uint16_t si_signo;
    rt_uint16_t si_code;

    union sigval si_value;
};
typedef struct siginfo siginfo_t;

struct rt_slist_node
{
    struct rt_slist_node *next;                         /**< point to next node. */
};
typedef struct rt_slist_node rt_slist_t;                /**< Type for single list. */

struct siginfo_node
{
    siginfo_t si;       /* 信号值; 信号类型 */
    struct rt_slist_node list;      /* list.next 指向下一个siginfo_node */
};

static struct rt_mempool *_rt_siginfo_pool;

线程控制块中信号相关属性:

#if defined(RT_USING_SIGNALS)
    rt_sigset_t     sig_pending;    /**< the pending signals */
    rt_sigset_t     sig_mask;       /**< the mask bits of signal */

    void            *sig_ret;       /**< the return stack pointer from signal */
    rt_sighandler_t *sig_vectors;   /**< vectors of signal handler */
    void            *si_list;       /**< the signal infor list */
#endif
  • .sig_pending,32bit表示32中信号,置1表示相关信号发生,处于pending状态。
  • .sig_mask,屏蔽位,置1,表示对相关信号有兴趣。
  • .sig_ret,记录原堆栈的栈顶值。
  • .sig_vectors,类似异常向量表,记录着每个信号的处理函数地址。
  • .si_list,挂着未处理的siginfo_node节点
    在这里插入图片描述
信号初始化

申请一块内存池,每块内存大小为sizeof(struct siginfo_node)

int rt_system_signal_init(void){
    _rt_siginfo_pool = rt_mp_create("signal", RT_SIG_INFO_MAX, sizeof(struct siginfo_node));
    if (_rt_siginfo_pool == RT_NULL)
    {
        // LOG_E("create memory pool for signal info failed.");
        RT_ASSERT(0);
    }

    return 0;
}
为线程安装信号

rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t handler);
如果线程要处理某一信号,那么就要在线程中安装该信号。该接口根据handler修改线程的.sig_vectors表,SIG_IGN表示忽略该信号,SIG_DFL表示以系统默认的方式处理信号,或者传入用户指定的处理函数地址。成功返回安装信号前的 handler 值。

阻塞信号

void rt_signal_mask(int signo);
将线程的sig_mask相应位置0,表示对该信号没兴趣。信号发生后不会处理该信号。

解除信号阻塞

void rt_signal_unmask(int signo);
将线程的sig_mask相应位置1,表示对该信号有兴趣。在RT-Thread中信号安装时,默认是阻塞的。如果解除信号阻塞后发现该信号是挂起状态,则立刻调用void _signal_deliver(rt_thread_t tid)传递信号。

_signal_deliver的功能是:

  • 如果目标线程是挂起状态,会唤醒该线程,并设置线程的信号状态位。

  • 如果目标线程是就绪态(等着被调度),且目标线程就是当前线程,则调用void rt_thread_handle_sig(rt_bool_t clean_state);去处理信号;如果目标线程不是当前线程,则先初始化处理信号函数的上下文,然后rt_schedule()

  • signal.c: 204

发送信号

int rt_thread_kill(rt_thread_t tid, int sig);
通过此接口向目标线程发送信号。发送信号后线程控制块的信号节点指向。
在这里插入图片描述

  • 如果该信号未挂起,则挂起,然后申请siginfo_node节点,并将该节点挂在tid->si_list上。
  • 如果该信号以挂起,则更新相关信息。
  • signal.c: 346
信号处理

void rt_thread_handle_sig(rt_bool_t clean_state);

  • 首先判断信号是否挂起,且tid->sig_mask位置1。
  • 然后从tid->si_list链上摘下信号控制节点,从tid->sig_vectors表中获取处理该信号的函数地址。
  • 处理完后清除挂起标志位。
  • signal.c: 141

处理信号的时机:

  1. 线程调度时。会调用rt_thread_handle_sig(RT_TRUE);检查线程状态。
  2. 发送信号时。通过_signal_deliver(rt_thread_t tid);传送信号。
工程文件
  • 111_rtt2a9_signal
    在上一个工程(消息队列)的基础上修改,按键2给线程1发送SIGUSR1信号。线程1在接收到安装的信号后打印信号值。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值