【rtthread内核系列】第三篇:线程间通信

一、线程间通信

在裸机中使用全局变量进行功能间的通信,rtthread提供了三个工具用于线程间的通信。

  • 邮箱:
  • 消息队列
  • 信号

二、邮箱

2.1 邮箱概念

邮箱中的每一份邮件内容为4字节,在32位系统中刚好为一个指针的大小。rtthread将邮箱抽象成rt_mailbox。

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;

2.2 邮箱api

创建邮箱有动态和静态两种方式: rt_mb_create、rt_mb_init,与之对应的删除邮箱的方式为:rt_mb_delete和rt_mb_detach。在创建邮箱之后,可以使用发送和接收api:rt_mb_send和rt_mb_recv发送和接收邮件,rt_mb_send在邮箱为满时会返回 -RT_EFULL,增强版的rt_mb_send_wait在邮箱满时会挂起等待。

//创建一个邮箱
/*
name:邮箱名称
size:邮箱容量
flag:标志
返回:邮箱句柄
*/
rt_mailbox_t rt_mb_create (const char* name, 
                                    rt_size_t size, 
                                    rt_uint8_t flag);

//删除邮箱:当在删除时有线程挂起在这个邮箱时,会唤醒这些线程,线程返回-RT_ERROR.
/*
mb:邮箱句柄
*/
rt_err_t rt_mb_delete (rt_mailbox_t mb);

//初始化邮箱
/*
mb:邮箱句柄
name:邮箱名称
msgpool:缓冲区
size:邮箱容量
flag:标志:可取RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
*/
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_err_t rt_mb_detach(rt_mailbox_t mb);

//发送邮件
/*
mb:邮箱句柄
value:发送的内容:32位任意值或者指针
*/
rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);

//等待方式发送邮件
/*
mb:邮箱句柄
value:发送内容
timeout:超时时间
*/
rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
                                rt_uint32_t value,
                                rt_int32_t timeout);
                                
//接收邮件
/*
mb:邮箱句柄
value:接收数据缓冲区
timeout:超时时间
*/
rt_err_t rt_mb_recv (rt_mailbox_t mb, 
                            rt_uint32_t* value, 
                            rt_int32_t timeout);

2.3 邮箱示例

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-08-24     yangjie      the first version
 */

/*
 * 程序清单:邮箱例程
 *
 * 这个程序会创建2个动态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,
 * 一个线程往邮箱中收取邮件。
 */
#include <rtthread.h>

#define THREAD_PRIORITY      10
#define THREAD_TIMESLICE     5

/* 邮箱控制块 */
static struct rt_mailbox mb;
/* 用于放邮件的内存池 */
static char mb_pool[128];

static char mb_str1[] = "I'm a mail!";
static char mb_str2[] = "this is another mail!";
static char mb_str3[] = "over";

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;

/* 线程1入口 */
static void thread1_entry(void *parameter)
{
    
    
    char *str;

    while (1)
    {
    
    
        rt_kprintf("thread1: try to recv a mail\n");

        /* 从邮箱中收取邮件 */
        if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
        {
    
    
            rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str);
            if (str == mb_str3)
                break;

            /* 延时100ms */
            rt_thread_mdelay(100);
        }
    }
    /* 执行邮箱对象脱离 */
    rt_mb_detach(&mb);
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

/* 线程2入口 */
static void thread2_entry(void *parameter)
{
    
    
    rt_uint8_t count;

    count = 0;
    while (count < 10)
    {
    
    
        count ++;
        if (count & 0x1)
        {
    
    
            /* 发送mb_str1地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str1);
        }
        else
        {
    
    
            /* 发送mb_str2地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str2);
        }

        /* 延时200ms */
        rt_thread_mdelay(200);
    }

    /* 发送邮件告诉线程1,线程2已经运行结束 */
    rt_mb_send(&mb, (rt_uint32_t)&mb_str3);
}

int mailbox_sample(void)
{
    
    
    rt_err_t result;

    /* 初始化一个mailbox */
    result = rt_mb_init(&mb,
                        "mbt",                      /* 名称是mbt */
                        &mb_pool[0],                /* 邮箱用到的内存池是mb_pool */
                        sizeof(mb_pool) / 4,        /* 邮箱中的邮件数目,因为一封邮件占4字节 */
                        RT_IPC_FLAG_FIFO);          /* 采用FIFO方式进行线程等待 */
    if (result != RT_EOK)
    {
    
    
        rt_kprintf("init mailbox failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mailbox_sample, mailbox sample);

三 消息队列

3.1 消息队列概念

消息队列用于发送不固定长度的消息,线程获得的消息是最先进入消息队列的消息。rtthread将消息队列抽象成rt_messagequeue。

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; /* 空 闲 消 息 链 表 */
    rt_list_t suspend_sender_thread; /* 发 送 线 程 的 挂 起 等 待 队 列 */
};
typedef struct rt_messagequeue* rt_mq_t;

3.2 消息队列api

//创建消息队列
/*
name:消息队列的名称
msg_size:消息的最大长度,单位:字节
max_msgs:最大消息个数
flag:标志:可取RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO
返回:消息队列句柄
*/
rt_mq_t rt_mq_create(const char* name,
                            rt_size_t msg_size,
                            rt_size_t max_msgs, rt_uint8_t flag);

//删除消息队列
/*
mq:消息队列句柄
*/
rt_err_t rt_mq_delete(rt_mq_t mq);

//初始化消息队列
/*
name:消息队列名称
msgpool:缓冲区
msg_size:最大消息大小
pool_size:缓冲区大小
flag:标志
*/
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_err_t rt_mq_detach(rt_mq_t mq);

//发送消息
/*
mq:消息队列句柄
buffer:发送缓冲区
size:发送消息的大小
*/
rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);

//等待方式发送消息
/*
mq:消息队列句柄
buffer:发送缓冲区
size:发送消息大小
timeout:等待超时时间
*/
rt_err_t rt_mq_send_wait(rt_mq_t mq,
                                const void *buffer,
                                rt_size_t size,
                                rt_int32_t timeout);

//发送紧急消息,消息放在队首,接收线程将第一个接收到这条消息
/*
mq:消息队列句柄
buffer:发送缓冲区
size:发送消息大小
*/
rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size);

//接收消息
/*
mq:消息队列句柄
buffer:接收缓冲区
size:接收消息大小
timeout:接收超时时间
*/
rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer,
                        rt_size_t size, rt_int32_t timeout);

3.3 消息队列示例

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-08-24     yangjie      the first version
 */

/*
 * 程序清单:消息队列例程
 *
 * 这个程序会创建2个动态线程,一个线程会从消息队列中收取消息;一个线程会定时给消
 * 息队列发送 普通消息和紧急消息。
 */
#include <rtthread.h>

#define THREAD_PRIORITY      25
#define THREAD_TIMESLICE     5

/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;

/* 线程1入口函数 */
static void thread1_entry(void *parameter)
{
    
    
    char buf = 0;
    rt_uint8_t cnt = 0;

    while (1)
    {
    
    
        /* 从消息队列中接收消息 */
        if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK)
        {
    
    
            rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf);
            if (cnt == 19)
            {
    
    
                break;
            }
        }
        /* 延时50ms */
        cnt++;
        rt_thread_mdelay(50);
    }
    rt_kprintf("thread1: detach mq \n");
    rt_mq_detach(&mq);
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

/* 线程2入口 */
static void thread2_entry(void *parameter)
{
    
    
    int result;
    char buf = 'A';
    rt_uint8_t cnt = 0;

    while (1)
    {
    
    
        if (cnt == 8)
        {
    
    
            /* 发送紧急消息到消息队列中 */
            result = rt_mq_urgent(&mq, &buf, 1);
            if (result != RT_EOK)
            {
    
    
                rt_kprintf("rt_mq_urgent ERR\n");
            }
            else
            {
    
    
                rt_kprintf("thread2: send urgent message - %c\n", buf);
            }
        }
        else if (cnt >= 20)/* 发送20次消息之后退出 */
        {
    
    
            rt_kprintf("message queue stop send, thread2 quit\n");
            break;
        }
        else
        {
    
    
            /* 发送消息到消息队列中 */
            result = rt_mq_send(&mq, &buf, 1);
            if (result != RT_EOK)
            {
    
    
                rt_kprintf("rt_mq_send ERR\n");
            }

            rt_kprintf("thread2: send message - %c\n", buf);
        }
        buf++;
        cnt++;
        /* 延时5ms */
        rt_thread_mdelay(5);
    }
}

/* 消息队列示例的初始化 */
int msgq_sample(void)
{
    
    
    rt_err_t result;

    /* 初始化消息队列 */
    result = rt_mq_init(&mq,
                        "mqt",
                        &msg_pool[0],               /* 内存池指向msg_pool */
                        1,                          /* 每个消息的大小是 1 字节 */
                        sizeof(msg_pool),           /* 内存池的大小是msg_pool的大小 */
                        RT_IPC_FLAG_FIFO);          /* 如果有多个线程等待,按照先来先得到的方法分配消息 */

    if (result != RT_EOK)
    {
    
    
        rt_kprintf("init message queue failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(msgq_sample, msgq sample);

四 信号

4.1 信号概念

信号本质是软中断, 用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。

4.2 信号api

//安装信号
/*
signo:信号值,可取SIGUSR1,SIGUSR2
handle:信号处理方式:1类似于中断的操作、 SIG_IGN:忽略某个信号; SIG_DFL:默认的信号处理方式
*/
rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t[] handler);

//信号阻塞,信号传递不到安装该信号的线程。
/*
signo:信号值
*/
void rt_signal_mask(int signo);

//接触信号阻塞
void rt_signal_unmask(int signo);

//发送信号
/*
tid:线程句柄
sig:信号值
*/
int rt_thread_kill(rt_thread_t tid, int sig);

//等待信号
/*
set:信号
si:存储信号的指针
timeout:超时时间
*/
int rt_signal_wait(const rt_sigset_t *set,
                        rt_siginfo_t[] *si, 
                        rt_int32_t timeout);

4.3 信号示例

本示例采用软中断的方式处理信号,示例先创建的线程1安装了SIGUSR1信号,当示例发送信号时,线程1执行软中断程序。

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-08-24     yangjie      the first version
 */

/*
 * 程序清单:信号例程
 *
 * 这个例子会创建一个线程,线程安装信号,然后给这个线程发送信号。
 *
 */
#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5

static rt_thread_t tid1 = RT_NULL;

/* 线程1的信号处理函数 */
void thread1_signal_handler(int sig)
{
    
    
    rt_kprintf("thread1 received signal %d\n", sig);
}

/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
{
    
    
    int cnt = 0;

    /* 安装信号 */
    rt_signal_install(SIGUSR1, thread1_signal_handler);
    rt_signal_unmask(SIGUSR1);

    /* 运行10次 */
    while (cnt < 10)
    {
    
    
        /* 线程1采用低优先级运行,一直打印计数值 */
        rt_kprintf("thread1 count : %d\n", cnt);

        cnt++;
        rt_thread_mdelay(100);
    }
}

/* 信号示例的初始化 */
int signal_sample(void)
{
    
    
    /* 创建线程1 */
    tid1 = rt_thread_create("thread1",
                            thread1_entry, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);

    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    rt_thread_mdelay(300);

    /* 发送信号 SIGUSR1 给线程1 */
    rt_thread_kill(tid1, SIGUSR1);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(signal_sample, signal sample);

猜你喜欢

转载自blog.csdn.net/weixin_43810563/article/details/116722034