目录
1.概念
2.工作原理
3.事件API
3.1静态事件
3.1.1 静态事件的创建
3.1.2 静态事件的删除
3.2动态事件
3.2.1 动态事件的创建
3.2.2 动态事件的删除
3.3 事件的发送和接收
3.3.1 事件的发送
3.3.2 事件的接收
4.C语言位操作(事件标志位置1)
4.1 左移
4.2 左移赋值运算符
4.3 右移
5.实际使用代码
事件常用于线程间同步,在操作系统中,可以实现利用事件一对一,一对多或者多对多的同步,即一个线程可以根据一个事件触发,或多个事件同时发生才触发,同理,一个事件也可以同时触发多个线程,或多个事件触发多个线程。
多个事件的集合称为事件集,常用一个32位无符号整型变量表示,变量的每一位代表一个事件,线程通过“逻辑与” 和“逻辑或”与一个事件或多个事件关联,逻辑或称为独立型同步,逻辑与称为关联型同步。
ps:事件只能进行线程间同步,不能进行线程间通信。
我们常用全局变量作为标志位来进行线程间同步,但这一做法在实时操作系统中,会造成代码可读性差,代码管理困难的问题,因此使用事件,他比信号量使用范围广,适用情况灵活,可一对一,一对多和多对多。
每个线程对于事件都有三个对应的标志位,逻辑与(RT_EVENT_FLAG_AND
),逻辑或(RT_EVENT_FLAG_OR
)和清除标记(RT_EVENT_FLAG_CLEAR)。
若线程的事件标志中第一位和第三位被置为1,事件信息标志位被置为逻辑或,则代表此时事件1和事件3任何一个发生都会触发线程的运行:
若线程事件信息标志位被置为逻辑与,则代表此时事件1和事件3必须同时发生才会触发线程的运行。
如果信息标志位还设置了清除标记位,则代表线程触发运行后会将事件标志清0,如果没有设置,则线程运行结束后事件1和事件3仍为原来的置1状态。
事件与线程,信号量,互斥量类似,对应API仍然分为静态/动态事件创建,删除,事件的发送和接收。
rt_event_init (rt_event_t event,
const char * name,
rt_uint8_t flag
)
参数为指向事件的指针,事件集名称和标志,标志为两种,先进先出模式和优先级模式。两种模式的含义前面有提到,不再赘述。
创建时需要首先创建一个事件集指针(static struct rt_event event),然后对该指针取地址即可创建事件集。如下:
/* 事件控制块 */
static struct rt_event event;
rt_err_t result ;
/* 初始化事件对象 */
result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); //创建静态事件
rt_event_detach ( rt_event_t event )
静态事件的删除,参数为静态事件的指针。注意使用时取地址(&event)。
rt_event_create (const char * name,
rt_uint8_t flag
)
动态事件的创建,函数参数为事件名称,以及事件标志。标志同上。使用时需要创建信号量对象,如下:
rt_err_t result ;
/* 初始化事件对象 */
result = rt_event_create("event", RT_IPC_FLAG_FIFO);
rt_event_delete(rt_event_t event)
函数参数为动态事件指针。
使用该函数接口时,通过参数 set 指定的事件标志来设定 event 事件集对象的事件标志值, 然后遍历等待在event事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前 event对象事件标志值匹配,如果有,则唤醒该线程。
————RT-Thread官方手册
rt_event_send (rt_event_t event,
rt_uint32_t set
)
使用时,event事件集对象句柄参数,需要取地址才可正常使用,set标志值由以下创建并发送:
#define EVENT_FLAG3 (1 << 3) //事件三标志位置1
#define EVENT_FLAG5 (1 << 5) //事件五标志位置1
rt_event_send(&event, EVENT_FLAG3); //发送事件标志位
rt_thread_mdelay(200);
rt_event_send(&event, EVENT_FLAG5); //发送事件标志位
rt_thread_mdelay(200);
rt_event_send(&event, EVENT_FLAG3); //发送事件标志位
当用户调用这个接口时,系统首先根据set参数和接收选项option来判断它要接收的事件是否发生, 如果已经发生,则根据参数option上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应 标志位,然后返回(其中recved参数返回收到的事件);如果没有发生,则把等待的set和option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间 超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不 等待,而直接返回-RT_ETIMEOUT。
————RT-Thread官方手册
rt_event_recv ( rt_event_t event,
rt_uint32_t set,
rt_uint8_t option,
rt_int32_t timeout,
rt_uint32_t * recved
)
函数参数为指向线程的指针,触发线程的一个或者多个事件,多个事件则用 "I"分割开来,接收选项则选择“逻辑或”或者“逻辑与”,指定超时时间(RT_WAITING_FOREVER为永久等待),然后指向收到的事件,可以设置为RT_NULL。以下为实际使用示例:
#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)
static struct rt_event event; /* 事件控制块 */
rt_uint32_t e; //创建收到的事件事件
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
// 事件指针event,事件3和5,逻辑与,清除事件标志位,永久等待方式,收到的事件
{
rt_kprintf("thread1: AND recv event 0x%x\n", e);
}
事件在使用中需要将对应标志位置1,线程才可根据事件标志位响应事件。而将标志位置1的方法最常用到的为c语言中的位操作。
左移运算符将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值丢失,用0填充空出的位置。
(1000 0110) << 2 //将数值左移两位
结果为
(0001 1000)
将所选量移动相应位数,再将结果赋值给该变量
int val = 1; //0000 0001
val <<= 2; //将val左移两位
结果为
0000 0100
右移运算符将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。
对于无符号类型,用0填充空出的位置,对于有符号类型,结果取决于机器。
int val = 16; //0001 0000
val >>= 3; //右移赋值运算符,与左移赋值相同
结果为:
0000 0010
/*
* 程序清单:事件例程
*
* 程序会初始化2个线程及初始化一个静态事件对象
* 一个线程等待于事件对象上,以接收事件;
* 一个线程发送事件 (事件3/事件5)
*
* 现象:接收线程一直等待,事件3发生,接收事件运行第一个if判断,再次等待,事件3和5同时发生,运行第二个if判断
*/
#include
#define EVENT_FLAG3 (1 << 3) //事件三标志位置1
#define EVENT_FLAG5 (1 << 5) //事件五标志位置1
/*创建事件对象句柄*/
static struct rt_event event;
static void thread1_recv_event(void *param)
{
rt_uint32_t e;
/* 事件句柄event,响应事件3和5,逻辑或,事件标志位清0,永久等待模式,返回事件e */
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
rt_kprintf("thread1: or receive event : 0x%x\n", e);
}
rt_kprintf("thread1: delay 1s receive next \n");
rt_thread_mdelay(1000);
/*事件句柄event,响应事件3和5,逻辑与,事件标志位清0,永久等待模式,返回事件e*/
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
rt_kprintf("thread1: and receive event : 0x%x\n", e);
}
rt_kprintf("thread1: end.\n");
}
static void thread2_send_event(void *param)
{
rt_kprintf("thread2: send event three\n");
rt_event_send(&event, EVENT_FLAG3);
rt_thread_mdelay(200);
rt_kprintf("thread2: send event five\n");
rt_event_send(&event, EVENT_FLAG5);
rt_thread_mdelay(200);
rt_kprintf("thread2: send event three\n");
rt_event_send(&event, EVENT_FLAG3);
rt_kprintf("thread2: end\n");
}
static rt_thread_t rec=RT_NULL;
/* 线程1入口参数*/
static rt_thread_t send=RT_NULL;
/*线程2入口参数*/
int main(void)
{
rt_err_t result ;
/* 创建事件对象*/
result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
{
rt_kprintf("event create failed\n");
return -1;
}
rec= rt_thread_create("thread1",
thread1_recv_event,
RT_NULL,
256,
6,
5);
rt_thread_startup(rec);
send=rt_thread_create("thread2",
thread2_send_event,
RT_NULL,
256,
7,
5);
rt_thread_startup(send);
return 0;
}
/* 导入msh命令行中 */
MSH_CMD_EXPORT(main, event sample);
现象:
接收线程一直等待,事件3发生,接收事件运行第一个if判断,再次等待,事件3和5同时发生,运行第二个if判断。