0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

RT-Thread记录(六、IPC机制之信号量互斥量事件集)

矜辰所致 来源:矜辰所致 作者:矜辰所致 2022-06-21 10:40 次阅读
上文说到 RT-Thread 对临界区的处理方式有多种,其中已经分析了关闭调度器和屏蔽中断的方式,
本文就来学学另外的线程同步方式。

目录

前言
一、IPC机制
二、信号
2.1 信号量控制块
2.2 信号量操作
2.2.1 创建和删除
2.2.2 初始化和脱离
2.2.3 获取信号量
2.2.4 释放信号量
2.2.5 信号量控制
2.3 示例(典型停车场模型)
三、互斥量
3.1 优先级翻转
3.2 优先级继承
3.3 互斥量控制块
3.4 互斥量操作
3.2.1 创建和删除
3.2.2 初始化和脱离
3.2.3 获取互斥量
3.2.4 释放互斥量
3.5 示例(优先级继承)
四、事件集
4.1 事件集控制块
4.2 事件集操作
4.2.1 创建和删除
4.2.2 初始化和脱离
4.2.3 发送事件
4.2.4 接收事件
4.3 示例(逻辑与和逻辑或)
结语

前言

在我们专栏前面的文章中,已经学习过 RT-Thread 线程操作函数、软件定时器、临界区的保护,我们都进行了一些底层的分析,能让我们更加理解 RT-Thread 的内核,但是也不要忽略了上层的函数使用 要理解 RT-Thread 面向对象的思想,对所有的这些线程啊,定时器,包括要介绍的信号量,邮箱这些,都是以 对象 来操作,直白的说来就是 对于所有这些对象,都是以结构体的形式来表示,然后通过对这个对象结构体的操作来进行的。


本文所要介绍的内容属于 IPC机制,这些内容相对来说比较简单,我们重点在于学会如何使用以及了解他们的使用场合。

本 RT-Thread 专栏记录的开发环境:
RT-Thread记录(一、版本开发环境及配合CubeMX) + https://www.elecfans.com/d/1850333.html
RT-Thread记录(二、RT-Thread内核启动流程)+ https://www.elecfans.com/d/1850347.html
RT-Thread 内核篇系列博文链接:
RT-Thread记录(三、RT-Thread线程操作函数)+ https://www.elecfans.com/d/1850351.html
RT-Thread记录(四、RTT时钟节拍和软件定时器)+ https://www.elecfans.com/d/1850554.html
RT-Thread记录(五、RT-Thread 临界区保护) + https://www.elecfans.com/d/1850712.html


一、IPC机制

嵌入式操作系统中,运行代码主要包括线程 和 ISR,在他们的运行过程中,因为应用或者多线程模型带来的需求,有时候需要同步,有时候需要互斥,有时候也需要彼此交换数据。操作系统必须提供相应的机制来完成这些功能,这些机制统称为 线程间通信(IPC机制)。

本文所要介绍的就是关于线程同步的信号量、互斥量、事件 也属于 IPC机制。

RT-Thread 中的 IPC机制包括信号量、互斥量、事件、邮箱、消息队列。对于学习 RT-Thread ,这些IPC机制我们必须要学会灵活的使用。

为什么要说一下这个IPC机制?

我们前面说到过,RT-Thread 面向对象的思想,所有的这些 IPC 机制都被当成一个对象,都有一个结构体控制块,我们用信号量结构体来看一看:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

Kernel object有哪些,我们可以从基础内核对象结构体定义下面的代码找到:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_10,color_FFFFFF,t_70,g_se,x_16

本节说明了 RT-Thread 的 IPC 机制,同时通过 信号量的结构体控制块再一次的认识了 RT-Thread 面向对象的设计思想。

在我的 FreeRTOS 专栏中,对于FreeRTOS 的信号量,互斥量,事件集做过说明和测试。在这个部分,实际上 RT-Thread 与 FreeRTOS 是类似的,都是一样的思想。所以如果属熟悉FreeRTOS的话,这部分是简单的,我们要做的就是记录一下 对象的控制块,和操作函数,加以简单的示例测试。

二、信号量

信号量官方的说明是:信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

信号量非常灵活,可以使用的场合也很多:

  • 比如 一个典型的应用场合就是停车位模型,总共有多少个车位,就是多少个信号量,入口进入一辆车信号量-1,出口离开一辆车信号量+1。
  • 比如 两个线程之间的同步,信号量的值初始化成 0,而尝试获得该信号量的线程,一定需要等待另一个释放信号量的线程先执行完。

在 FreeRTOS 中存在二值信号量,但是 RT-Thread 中已经没有了,官方有说明:

在这里插入图片描述

信号量记住一句话基本就可以,释放一次信号量就+1,获取一次就-1,如果信号量数据为0,那么尝试获取的线程就会挂机,直到有线程释放信号量使得信号量大于0。

2.1 信号量控制块

老规矩用源码,解释看注释(使用起来也方便复制 ~ ~!):

#ifdef RT_USING_SEMAPHORE
/**
 * Semaphore structure
 * value 信号量的值,直接表明目前信号量的数量
 */
struct rt_semaphore
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint16_t          value;                         /**< value of semaphore. */
    rt_uint16_t          reserved;                      /**< reserved field */
};
/*
rt_sem_t 是指向 semaphore 结构体的指针类型
*/
typedef struct rt_semaphore *rt_sem_t;
#endif

2.2 信号量操作

2.2.1 创建和删除

同以前的线程那些一样,动态的方式,先定义一个信号量结构体的指针变量,接收创建好的句柄。

创建信号量:

/*
参数的含义:
1、name 	信号量名称
2、value 	信号量初始值
3、flag 	信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
信号量创建成功,返回信号量的控制块指针
信号量创建失败,返回RT_BULL 
*/
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)

对于最后的参数 flag,决定了当信号量不可用时(就是当信号量为0的时候),多个线程等待的排队方式。只有RT_IPC_FLAG_FIFO (先进先出)或 RT_IPC_FLAG_PRIO(优先级等待)两种 flag。

关于用哪一个,要看具体的情况,官方有特意说明:

poYBAGKxL4KAYSAVAABtsu0TwBo813.png

删除信号量:

/*
参数:
sem 	rt_sem_create() 创建的信号量对象,信号量句柄
返回值:
RT_EOK 	删除成功
*/
rt_err_t rt_sem_delete(rt_sem_t sem)

2.2.2 初始化和脱离

静态的方式,先定义一个信号量结构体,然后对他进行初始化。

初始化信号量:

/**
参数的含义:
1、sem 		信号量对象的句柄,就是开始定义的信号量结构体变量
2、name 	信号量名称
3、value 	信号量初始值
4、flag 	信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
RT_EOK 	初始化成功
 */
rt_err_t rt_sem_init(rt_sem_t    sem,
                     const char *name,
                     rt_uint32_t value,
                     rt_uint8_t  flag)

脱离信号量:

/*
参数:
sem 	信号量对象的句柄
返回值:
RT_EOK 	脱离成功
*/
rt_err_t rt_sem_detach(rt_sem_t sem);

2.2.3 获取信号量

当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1。

/**
参数:
1、sem 		信号量对象的句柄
2、time 	指定的等待时间,单位是操作系统时钟节拍(OS Tick)
返回值:
RT_EOK 			成功获得信号量
-RT_ETIMEOUT 	超时依然未获得信号量
-RT_ERROR 		其他错误
 */
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)

注意!要等待的时间是系统时钟节拍(OS Tick)。

无等待获取信号量

//就是上面获取的等待时间为0的方式
rt_err_t rt_sem_trytake(rt_sem_t sem)
{
    return rt_sem_take(sem, 0);
}

当线程申请的信号量资源实例为0时,直接返回 - RT_ETIMEOUT。

2.2.4 释放信号量

释放信号量可以使得该信号量+1,如果有线程在等待这个信号量,可以唤醒这个线程。

/**
参数:
sem 	信号量对象的句柄
返回值:
RT_EOK 	成功释放信号量
 */
rt_err_t rt_sem_release(rt_sem_t sem)

2.2.5 信号量控制

信号量控制函数,用来重置信号量,使得信号量恢复为设定的值:


/**
 * This function can get or set some extra attributions of a semaphore object.
参数:
sem 	信号量对象的句柄
cmd    信号量控制命令 ,支持命令:RT_IPC_CMD_RESET 
arg    暂时不知道
返回值:
RT_EOK 	成功释放信号量

 */
rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg)
{
    rt_ubase_t level;

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);

    if (cmd == RT_IPC_CMD_RESET)
    {
        rt_ubase_t value;

        /* get value */
        value = (rt_ubase_t)arg;
        /* disable interrupt */
        level = rt_hw_interrupt_disable();

        /* resume all waiting thread */
        rt_ipc_list_resume_all(&sem->parent.suspend_thread);

        /* set new value */
        sem->value = (rt_uint16_t)value;

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    return -RT_ERROR;
}

使用示例:

rt_err_t result;
rt_uint32_t value;

value = 10; /* 重置的值,即重置为10 */
result = rt_sem_control(sem, RT_IPC_CMD_RESET, (void*)value)

/* 重置为0 */
rt_sem_control(sem, RT_IPC_CMD_RESET, RT_NULL)

对sem重置后,会先把sem上挂起的所有任务进行唤醒(任务的error是-RT_ERROR),然后把sem的值会重新初始化成设定的值。

在官方论坛有如下说明:
在rt_sem_release后使用rt_sem_control的目的是因为在某些应用中必须rt_sem_take和rt_sem_release依次出现,而不允许rt_sem_release被连续多次调用,一旦出现这种情况会被认为是出现了异常,通过调用rt_sem_control接口来重新初始化 sem_ack恢复异常。

2.3 示例(典型停车场模型)

前面说到过,信号量非常灵活,可以使用的场合也很多,官方也有很多例子,我们这里做个典型的示例
— 停车场模型(前面用截图做解释,后面会附带源码)。

示例中,我们使用两个不同的按键来模拟车辆的进出,但是考虑到我们还没有学设备和驱动,没有添加按键驱动,所以我们用古老的方式来实现按键操作:

按键key3,代表车辆离开:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

按键key2,代表车辆进入:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

信号量的创建,初始10个车位:

pYYBAGKxL4OAAKKMAAAexk-g3H8704.png

当然不要忘了,车辆进入和车辆离开(两个按键)是需要两个线程的。

我们来看看测试效果,说明如图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_8,color_FFFFFF,t_70,g_se,x_16

注意上图测试最后的细节,虽然 one car get out! 但是打印出来的停车位还是0,可以这么理解,key3_thread_entry线程释放了信号量以后还没来得及打印,等待信号量的线程key2_thread_entry就获取到了信号量。

具体的分析需要看rt_sem_release函数源码,里面会判断是否需要值+1,以及是否需要调度:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

附上上面测试代码:

/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-02-16     RT-Thread    first version
 */

#include 
#include "main.h"
#include "usart.h"
#include "gpio.h"

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include 

static struct rt_thread led1_thread;    //led1线程
static char led1_thread_stack[256];

static rt_thread_t led2_thread = RT_NULL; //led2线程

static rt_thread_t key2_thread = RT_NULL; //

static rt_thread_t key3_thread = RT_NULL; //

rt_sem_t mysem;


static void led1_thread_entry(void *par){
    while(1){
        LED1_ON;
        rt_thread_mdelay(1000);
        LED1_OFF;
        rt_thread_mdelay(1000);
    }
}

static void led2_thread_entry(void *par){
    while(1){
        LED2_ON;
        rt_thread_mdelay(500);
        LED2_OFF;
        rt_thread_mdelay(500);
    }
}

static void key2_thread_entry(void *par){
    static rt_err_t result;
    while(1){
        if(key2_read == 0){
            rt_thread_mdelay(10); //去抖动
            if(key2_read == 0){
                result = rt_sem_take(mysem, 1000);
                if (result != RT_EOK)
                {
                    rt_kprintf("the is no parking spaces now...\r\n");
                }
                else
                {
                    rt_kprintf("one car get in!,we have %d parking spaces now...\r\n",mysem->value);
                }
                while(key2_read == 0){rt_thread_mdelay(10);}
            }
        }
        rt_thread_mdelay(1);
    }
}

static void key3_thread_entry(void *par){
    while(1){
        if(key3_read == 0){
            rt_thread_mdelay(10); //去抖动
            if(key3_read == 0){
                if(mysem->value < 10){
                    rt_sem_release(mysem);
                    rt_kprintf("one car get out!,we have %d parking spaces now...\r\n",mysem->value);
                }
                while(key3_read == 0){rt_thread_mdelay(10);} //去抖动
            }
        }
        rt_thread_mdelay(1);
    }
}
int main(void)
{
    MX_GPIO_Init();
    MX_USART1_UART_Init();


    rt_err_t rst2;
    rst2 = rt_thread_init(&led1_thread,
                        "led1_blink ",
                        led1_thread_entry,
                        RT_NULL,
                        &led1_thread_stack[0],
                        sizeof(led1_thread_stack),
                        RT_THREAD_PRIORITY_MAX -1,
                        50);

    if(rst2 == RT_EOK){
        rt_thread_startup(&led1_thread);
    }


    mysem = rt_sem_create("my_sem1", 10, RT_IPC_FLAG_FIFO);
    if(RT_NULL == mysem){
        LOG_E("create sem failed!...\n");
    }
    else LOG_D("we have 10 parking spaces now...\n");

    key2_thread = rt_thread_create("key2_control",
                                key2_thread_entry,
                                RT_NULL,
                                512,
                                RT_THREAD_PRIORITY_MAX -2,
                                50);

        /* 如果获得线程控制块,启动这个线程 */
        if (key2_thread != RT_NULL)
            rt_thread_startup(key2_thread);

     key3_thread = rt_thread_create("key3_control",
                                key3_thread_entry,
                                RT_NULL,
                                512,
                                RT_THREAD_PRIORITY_MAX -2,
                                50);

        /* 如果获得线程控制块,启动这个线程 */
        if (key3_thread != RT_NULL)
            rt_thread_startup(key3_thread);
    return RT_EOK;
}


void led2_Blink(){
    led2_thread = rt_thread_create("led2_blink",
                            led2_thread_entry,
                            RT_NULL,
                            256,
                            RT_THREAD_PRIORITY_MAX -1,
                            50);

    /* 如果获得线程控制块,启动这个线程 */
    if (led2_thread != RT_NULL)
        rt_thread_startup(led2_thread);
}

MSH_CMD_EXPORT(led2_Blink, Led2 sample);

三、互斥量

互斥量是一种特殊的二值信号量。互斥量的状态只有两种,开锁或闭锁(两种状态值)。

互斥量支持递归,持有该互斥量的线程也能够再次获得这个锁而不被挂起。自己能够再次获得互斥量。

互斥量可以解决优先级翻转问题,它能够实现优先级继承。

互斥量互斥量不能在中断服务例程中使用。

3.1 优先级翻转

优先级翻转,我以前写过:

poYBAGKxL4SAUwRIAABs3UaqWig058.png

再用官方的图加深理解:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

3.2 优先级继承

优先级继承,以前也写过:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

再用官方的图加深理解:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

需要切记的是互斥量不能在中断服务例程中使用。

3.3 互斥量控制块

#ifdef RT_USING_MUTEX
/**
 * Mutual exclusion (mutex) structure
 * parent 				继承ipc类
 * value 				互斥量的值
 * original_priority 	持有线程的原始优先级
 * hold 				持有线程的持有次数,可以多次获得
 * *owner				当前拥有互斥量的线程
 */
struct rt_mutex
{
    struct rt_ipc_object parent;              /**< inherit from ipc_object */
    rt_uint16_t          value;                /**< value of mutex */
    rt_uint8_t           original_priority;    /**< priority of last thread hold the mutex */
    rt_uint8_t           hold;                 /**< numbers of thread hold the mutex */

    struct rt_thread    *owner;               /**< current owner of mutex */
};
/* rt_mutext_t 为指向互斥量结构体的指针类型  */
typedef struct rt_mutex *rt_mutex_t;
#endif

3.4 互斥量操作

3.4.1 创建和删除

先定义一个指向互斥量结构体的指针变量,接收创建好的句柄。

创建互斥量:

/**
参数的含义:
1、name 	互斥量名称
2、flag 	该标志已经作废,无论用户选择 RT_IPC_FLAG_PRIO 还是 RT_IPC_FLAG_FIFO,
			内核均按照 RT_IPC_FLAG_PRIO 处理
返回值:
互斥量创建成功,返回互斥量的控制块指针
互斥量创建失败,返回RT_BULL 
 */
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)

删除互斥量:

/**
参数:
mutex	互斥量对象的句柄
返回值:
RT_EOK 	删除成
 */
rt_err_t rt_mutex_delete(rt_mutex_t mutex)

3.4.2 初始化和脱离

静态的方式,先定义一个互斥量结构体,然后对他进行初始化。

初始化互斥量:

/**
参数的含义:
1、mutex 互斥量对象的句柄,指向互斥量对象的内存块,开始定义的结构体
2、name 	互斥量名称
3、flag 	该标志已经作废,按照 RT_IPC_FLAG_PRIO (优先级)处理
返回值:
RT_EOK 	初始化成功
 */
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)

脱离互斥量:

/**
参数:
mutex	互斥量对象的句柄
返回值:
RT_EOK 	成功
 */
rt_err_t rt_mutex_detach(rt_mutex_t mutex)

3.4.3 获取互斥量

一个时刻一个互斥量只能被一个线程持有。

如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。

/**
参数:
1、mutex	互斥量对象的句柄
2、time 	指定的等待时间,单位是操作系统时钟节拍(OS Tick)
返回值:
RT_EOK 			成功获得互斥量
-RT_ETIMEOUT 	超时依然未获得互斥量
-RT_ERROR 		获取失败
 */
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)

3.4.4 释放互斥量

在获得互斥量后,应该尽可能的快释放互斥量。

/**
参数:
mutex 	互斥量对象的句
返回值:
RT_EOK 	成功
 */
rt_err_t rt_mutex_release(rt_mutex_t mutex)

3.5 示例(优先级继承)

互斥量做一个简单的示例,但是即便简单,也能体现出优先级继承这个机制。

示例中,我们使用两个按键,key2按键,按一次获取互斥量,再按一次释放互斥量,打印自己初始优先级,当前优先级,互斥量占有线程优先级这几个量。key3按键,按一次,获取互斥量,立马就释放,也打印几个优先级。

互斥量的创建,和两个线程的优先级:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_15,color_FFFFFF,t_70,g_se,x_16

key2操作:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

key3操作:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

测试结果说明图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

示例中为了更好的演示并没有快进快出,实际使用还是需要快进快出,除非你自己就是有这种特出需求。

还有一个细节,就是 RT-Thread 中对象的 名字,只能显示8个字符长度,长了会截断,并不影响使用。

四、事件集

事件集这部分与 FreeRTOS 基本一样。

事件集主要用于线程间的同步,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。

RT-Thread 定义的事件集有以下特点:

  • 事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
  • 事件仅用于同步,不提供数据传输功能;
  • 事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

4.1 事件集控制块

#ifdef RT_USING_EVENT
/**
 * flag defintions in event
 * 逻辑与
 * 逻辑或
 * 清除标志位
 */
#define RT_EVENT_FLAG_AND               0x01            /**< logic and */
#define RT_EVENT_FLAG_OR                0x02            /**< logic or */
#define RT_EVENT_FLAG_CLEAR             0x04            /**< clear flag */

/*
 * event structure
 * set:事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生
 */
struct rt_event
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint32_t          set;                           /**< event set */
};
/* rt_event_t 是指向事件结构体的指针类型  */
typedef struct rt_event *rt_event_t;
#endif

4.2 事件集操作

4.2.1 创建和删除

先定义一个指向事件集结构体的指针变量,接收创建好的句柄。

创建事件集:

/**
参数的含义:
1、name 	事件集的名称
2、flag 	事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO理
返回值:
事件集创建成功,返回事件集的控制块指针
事件集创建失败,返回RT_BULL 
 */
rt_event_t rt_event_create(const char *name, rt_uint8_t flag)

flag 使用哪一个,解释和信号量一样,可参考信号量创建部分说明。

删除事件集:

/**
参数:
event	事件集对象的句柄
返回值:
RT_EOK 	成功
 */
rt_err_t rt_event_delete(rt_event_t event)

4.2.2 初始化和脱离

静态的方式,先定义一个事件集结构体,然后对他进行初始化。

初始化事件集:

/**
参数的含义:
1、event	事件集对象的句柄
2、name 	事件集的名称
3、flag 	事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
RT_EOK 	初始化成功
 */
rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)

脱离事件集:

/**
参数:
event	事件集对象的句柄
返回值:
RT_EOK 	成功
 */
rt_err_t rt_event_detach(rt_event_t event)

4.2.3 发送事件

发送事件函数可以发送事件集中的一个或多个事件。

/**
参数的含义:
1、event	事件集对象的句柄
2、set		发送的一个或多个事件的标志值
返回值:
RT_EOK 	成功
 */
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)

4.2.4 接收事件

内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或“逻辑或”来选择如何激活线程。

/**
参数的含义:
1、event		事件集对象的句柄
2、set			接收线程感的事件
3、option 		接收选项,可取的值为
#define RT_EVENT_FLAG_AND               0x01       逻辑与    
#define RT_EVENT_FLAG_OR                0x02       逻辑或    
#define RT_EVENT_FLAG_CLEAR             0x04     选择清除重置事件标志位       
4、timeout		指定超时时间
5、recved		指向接收到的事件,如果不在意,可以使用 NULL
返回值:
RT_EOK 			成功
-RT_ETIMEOUT 	超时
-RT_ERROR 		错误
 */
rt_err_t rt_event_recv(rt_event_t   event,
                       rt_uint32_t  set,
                       rt_uint8_t   option,
                       rt_int32_t   timeout,
                       rt_uint32_t *recved)

4.3 示例(逻辑与和逻辑或)

事件集通过示例可以很好的理解怎么使用,我们示例中,用按钮发送事件,其他线程接收事件,进行对应的处理。

按键操作:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

线程逻辑或处理:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_19,color_FFFFFF,t_70,g_se,x_16

逻辑或测试结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_16,color_FFFFFF,t_70,g_se,x_16

线程逻辑与处理:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_19,color_FFFFFF,t_70,g_se,x_16

逻辑与测试结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_17,color_FFFFFF,t_70,g_se,x_16

结语

本文虽然只是介绍了信号量、互斥量和事件集这几个比较简单的线程同步操作,但是最终完成了后发现内容还是很多的。

洋洋洒洒这么多字,最终看下来自己还是挺满意的,希望我把该表述的都表达清楚了,希望大家多多提意见,让博主能给大家带来更好的文章。

那么下一篇的 RT-Thread 记录,就要来说说与线程通讯 有关的 邮箱、消息队列和信号内容了。

谢谢!

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • IPC
    IPC
    +关注

    关注

    3

    文章

    337

    浏览量

    51746
  • RT-Thread
    +关注

    关注

    31

    文章

    1256

    浏览量

    39795
  • 信号量
    +关注

    关注

    0

    文章

    53

    浏览量

    8301
收藏 人收藏

    评论

    相关推荐

    RT-thread内核互斥

    便于恢复。hold: 表示此互斥锁被同一线程成功take的次数,一般情况下一个线程只会take一次互斥锁,但rt-thread也允许线程重复take同一线程,此时hold的值就用来做记录
    发表于 03-06 17:23

    第15章 互斥信号量

    转rtx操作系统 本章节开始讲解RTX的另一个重要的资源共享机制---互斥信号量(Mutex,即Mutual Exclusion的缩写)。注意,建议初学者学习完上个章节的信号量后再学习
    发表于 10-06 16:40

    RT-Thread信号量删除后释放信号量跟获取信号量还是成功

    RT-Thread中创建了一个动态的信号量,运行10次这个线程后删除这个动态信号量,但是问题是10次后他再次释放信号量跟获取信号量还是成功的
    发表于 01-15 05:04

    互斥源码分析测试

    文章目录互斥源码分析测试参考资料:RTT官网文档关键字:分析RT-Thread源码、stm32、RTOS、互斥
    发表于 08-24 06:01

    浅析RT-Thread中事件的工作机制

    RT-Thread 中的事件,也就是其他 RTOS 中的事件标志组。事件也是线程(任务)间同步的一种机制。前面介绍的两种线程间同步的方式(信号量
    发表于 04-11 15:31

    【原创精选】RT-Thread征文精选技术文章合集

    RT-Thread记录(五、RT-Thread 临界区保护)RT-Thread记录
    发表于 07-26 14:56

    RT-Thread操作系统互斥的使用方法与场合介绍

    可以进入。互斥工作机制互斥信号量不同的是:拥有互斥
    发表于 08-03 11:26

    Rt-thread里面的mem.c函数保护lfree全局变量为什么用信号量

    Rt-thread 里面的mem.c函数保护lfree全局变量为什么用信号量而不是互斥信号量,用信号量保护全局变量不怕造成线程优先级翻转吗
    发表于 08-08 10:43

    RT-Thread互斥优先级问题求解

    RT Thread优先级问题,官网视频,互斥一节,明明是线程2的优先级比线程1高,但线程1会优先运行,不知是有什么机理还是Bug?经反复测试发现,将线程优先级配置到接近线程优先级的最
    发表于 12-09 15:43

    信号量互斥在使用过程中会存在这样的死锁吗?

    如果A线程已经获取了信号量互斥,但此时B线程打断了A线程,信号量互斥没有释放,并且在B线
    发表于 01-10 15:37

    Linux IPC System V 信号量

    semctl() //删除信号量 ftok()//获取key值, key值是System V IPC的标识符,成功返回key,失败返回-1设errno//同
    发表于 04-02 14:46 304次阅读

    详解互斥信号量的概念和运行

    1 、互 斥 信 号 1.1 互斥信号量的概念及其作用 互斥信号量的主要作用是对资源实现互斥
    的头像 发表于 10-22 11:57 1.1w次阅读
    详解<b class='flag-5'>互斥</b><b class='flag-5'>信号量</b>的概念和运行

    RT-Thread隐藏的宝藏completion

    completion 直接翻译过来是完成,所以我们可以称 rt_completion 为 完成。在 RT-Thread 的文档中心 中讲线程间同步时,介绍了 信号量...
    发表于 01-25 18:54 0次下载
    <b class='flag-5'>RT-Thread</b>隐藏的宝藏<b class='flag-5'>之</b>completion

    Free RTOS的互斥信号量

    二进制信号量互斥非常相似,但确实有一些细微的区别。互斥体包含优先级继承机制,而二进制信号量
    的头像 发表于 02-10 15:36 1093次阅读
    Free RTOS的<b class='flag-5'>互斥</b><b class='flag-5'>信号量</b>

    使用Linux信号量实现互斥点灯

    信号量常用于控制对共享资源的访问,有计数型信号量和二值信号量之分。初始化时信号量值大于1的,就是计数型信号量,计数型
    的头像 发表于 04-13 15:12 767次阅读
    使用Linux<b class='flag-5'>信号量</b>实现<b class='flag-5'>互斥</b>点灯