第二十一章 事件(第二部分)
五、事件函数
1、事件控制块
块包含了一个 32 位的 set 变量,其变量的各个位表示一个事件,每一位代表一个事件的发生,利用逻辑或、逻辑与等实现不同事件的不同唤醒处理
1 struct rt_event {
2 struct rt_ipc_object parent;
3
4 rt_uint32_t set; /**< 事件标志位 */
5 };
6 typedef struct rt_event *rt_event_t; /* rt_event_t 是指向事件结构体的指针 */
2、 事件创建函数 rt_event_create()
创建成功返回事件句柄;创建失败返回 RT_NULL
1 rt_event_t rt_event_create(const char *name, rt_uint8_t flag) (1)
**name :事件的名称,由用户自己定义。flag :事件阻塞唤醒模式。 **
2 {
3 rt_event_t event; (2) ** 创建一个事件控制块。 **
4
5 RT_DEBUG_NOT_IN_INTERRUPT;
6
7 /* 分配对象 */
8 event = (rt_event_t)rt_object_allocate(RT_Object_Class_Event, name);
9 if (event == RT_NULL) (3)
**分配事件对象,调用 rt_object_allocate()函数将从对象系统分配对
象,为创建的事件分配一个事件的对象,并且命名对象名称**
10 return event;
11
12 /* 设置阻塞唤醒的模式 */
13 event->parent.parent.flag = flag; (4)
**:设置事件的阻塞唤醒模式**
14
15 /* 初始化事件对象 */
16 rt_ipc_object_init(&(event->parent)); (5) **(5):初始化事件内核对象**
17
18 /* 事件集合清零 */
19 event->set = 0; (6)
**事件集合清零,因为现在是创建事件,还没有事件发生,所以事件集合中所有位都为 0**
20
21 return event; (7) **创建成功返回事件对象的句柄,创建失败返回 RT_NULL。**
22 }
23 RTM_EXPORT(rt_event_create);
(4):设置事件的阻塞唤醒模式
3、 事件删除函数 rt_event_delete()
在很多场合,某些事件只用一次,那就需要进行事件删除。
1 rt_err_t rt_event_delete(rt_event_t event) (1)
**event 是我们自己定义的事件句柄,根据事件句柄进行删除操作。 **
2 {
3 /* 事件句柄检查 */
4 RT_ASSERT(event != RT_NULL); (2)
**:检查事件句柄 event 是否有效,如果它是未定义或者未创建的事件
句柄,那么是无法进行删除操作的。 **
5
6 RT_DEBUG_NOT_IN_INTERRUPT;
7
8 /* 恢复所有阻塞在此事件的线程 */
9 rt_ipc_list_resume_all(&(event->parent.suspend_thread)); (3) **将所有因为访问此事件的而阻塞的
线程从阻塞态中唤醒,所有被唤醒的线程的返回值是-RT_ERROR**
10
11 /* 删除事件对象 */
12 rt_object_delete(&(event->parent.parent)); (4)
**(4):删除事件对象,释放事件对象占用的内存资源。 **
13
14 return RT_EOK; (5)**删除成功返回 RT_EOK。 **
15 }
16 RTM_EXPORT(rt_event_delete);
(3):在删除的时候,应先确认所有的线程都无需再次使用这个事件,并且所有线程都没被此事件阻塞时候才进行删除,否则删除之后线程需要再次使用此事件的话那也会发生错误。
例子:
1 /* 定义事件控制块(句柄) */
2 static rt_event_t test_event = RT_NULL;
3 rt_err_t uwRet = RT_EOK;
4 /* 删除一个事件 */
5 uwRet = rt_event_delete(test_event);
6 if (RT_EOK == uwRet)
7 rt_kprintf("事件删除成功!\n\n");
4、 事件发送函数 rt_event_send()
通过参数 set 指定的事件标志来设定事件的标志位,然后遍历等待在 event 事件对象上的等待线程链表,判断是否有线程的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该线程。
😗
设置定义的事件标志位为1,并看看有没有线程在等待这个事件,有的话就唤醒该线程
1 rt_err_t rt_event_send(rt_event_t event, (1)
**事件发送操作的事件句柄**
2 rt_uint32_t set) (2)
**set:设置事件集合中的具体事件**
3 {
4 struct rt_list_node *n;
5 struct rt_thread *thread;
6 register rt_ubase_t level;
7 register rt_base_t status;
8 rt_bool_t need_schedule;
9
10 /* 事件对象检查 */
11 RT_ASSERT(event != RT_NULL); (3)
**检查事件句柄 event 是否有效,如果它是未定义或者未创建的事件
句柄,那么是无法进行发送事件操作的。 **
12 if (set == 0)
13 return -RT_ERROR;
14
15 need_schedule = RT_FALSE; (4)
**need_schedule 用于记录是否进行线程调度,默认不进行线程调度。 **
16 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(event->parent.parent)));
17
18 /* 关中断 */
19 level = rt_hw_interrupt_disable();
20
21 /* 设置事件 */
22 event->set |= set; (5) **设置事件发生的标志位,利用‘|’操作即保证不干扰其他事件位
又能同时对多个事件位一次性标记,即使是多次向线程发送同一事件(如果线程还未来得及
读走),也等效于只发送一次。 **
23
24 if (!rt_list_isempty(&event->parent.suspend_thread)) { (6)
**果当前有线程因为等待某个事件进入阻塞态,则在阻塞列表中
搜索线程,并且执行(7)-(18), **
25 /* 搜索线程列表以恢复线程 */
26 n = event->parent.suspend_thread.next;
27 while (n != &(event->parent.suspend_thread)) {
28 /* 找到要恢复的线程 */
29 thread = rt_list_entry(n, struct rt_thread, tlist); (7) **从等待的线程中获取对应的线程控制块。 **
30
31 status = -RT_ERROR;
32 if (thread->event_info & RT_EVENT_FLAG_AND) { (8)
**如果线程等待事件的模式是 RT_EVENT_FLAG_AND(逻辑与),
需要等待的事件都发生时才动作。 **
33 if ((thread->event_set & event->set)
34 == thread->event_set) { (9)
**判断线程等待的事件是否都发生了,如果事件激活要求与事件标志
值匹配,则唤醒事件。**
35 /* 收到了一个 AND */
36 status = RT_EOK; (10)
**当等待的事件都发生的时候,进行标记 status 动作,表示事件已经等待到了。**
37 }
38 } else if (thread->event_info & RT_EVENT_FLAG_OR) {
(11) **如果线程等待事件的模式是 RT_EVENT_FLAG_OR(逻辑或),
那么线程等待的所有事件标记中只要有一个或多个事件发生了就表示事件已发生,可以唤
醒线程。 **
39 if (thread->event_set & event->set) {
40 /* 保存收到的事件集 */
41 thread->event_set = thread->event_set & event->set; (12) **:保存收到的事件**
42
43 /* 收到一个 OR */
44 status = RT_EOK; (13)
**当等待的事件发生的时候,进行标记 status 动作,表示事件已经等待到了**
45 }
46 }
47
48 /* 将节点移动到下一个节点 */
49 n = n->next; (14)
**将节点移动到下一个节点,因为这是搜索所有等待的线程。**
50
51 /* 条件满足,恢复线程 */
52 if (status == RT_EOK) { (15)
**当等待的事件发生的时候,条件满足,需要恢复线程。 **
53 /* 清除事件标志位 */
54 if (thread->event_info & RT_EVENT_FLAG_CLEAR)
(16) **如果在接收中设置了 RT_EVENT_FLAG_CLEAR,那么在线程被
唤醒的时候,系统会进行事件标志位的清除操作,防止一直响应事件。采用 event->set &=
~thread->event_set 操作仅仅是清除对应事件标志位,不影响其他事件标志位。 **
55 event->set &= ~thread->event_set;
56
57 /* 恢复线程 */
58 rt_thread_resume(thread); (17) **恢复阻塞的线程。 **
59
60 /* 需要进行线程调度 */
61 need_schedule = RT_TRUE; (18) **:标记一下 need_schedule 表示需要进行线程调度。 **
62 }
63 }
64 }
65
66 /* 开中断 */
67 rt_hw_interrupt_enable(level);
68
69 /* 发起一次线程调度 */
70 if (need_schedule == RT_TRUE)
71 rt_schedule(); (19) **发起一次线程调度。 **
72
73 return RT_EOK;
74 }
75 RTM_EXPORT(rt_event_send);
(12):保存收到的事件,因为在接收事件函数的时候,这
个 值 是 要 用 来 进 行 判 断 的 , 假 设 有 一 个 线 程 等 待 接 收 3 个 事 件 , 采 用RT_EVENT_FLAG_OR(逻辑或)的方式等待接收,那么有其中一个事件发生,该线程就会解除阻塞,但是我们假如没保存收到的事件的话,就不知道是哪个事件发生
例子:
比如我们要记录一个事件的发生,这个事件在事件集合的位置是 bit0,当
它还未发生的时候,那么事件集合 bit0 的值也是 0,当它发生的时候,我们往事件集合bit0 中写入这个事件,也就是 0x01,那这就表示事件已经发生了,一般操作我们都是用宏定义来实现 #define EVENT (0x01 << x), “<< x”表示写入事件集合的bit x
1 #define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0
2 #define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1
3 static void send_thread_entry(void* parameter)
4 {
5 /* 线程都是一个无限循环,不能返回 */
6 while (1) {//如果 KEY2 被单击
7 if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
8 rt_kprintf ( "KEY1 被单击\n" );
9 /* 发送一个事件 1 */
10 rt_event_send(test_event,KEY1_EVENT);
11 }
12 //如果 KEY2 被单击
13 if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
14 rt_kprintf ( "KEY2 被单击\n" );
15 /* 发送一个事件 2 */
16 rt_event_send(test_event,KEY2_EVENT);
17 }
18 rt_thread_delay(20); //每 20ms 扫描一次
19 }
20 }
5、 事件接受函数 rt_event_recv()
1 rt_err_t rt_event_recv(rt_event_t event, (1) **event:事件发送操作的事件句柄**
2 rt_uint32_t set, (2)**:set:事件集合中的事件标志**
3 rt_uint8_t option, (3)
** option : 接 收 选 项 , 有 RT_EVENT_FLAG_AND 、
RT_EVENT_FLAG_OR,可以与 RT_EVENT_FLAG_CLEAR 通过“|”按位或操作符连接
使用。**
4 rt_int32_t timeout, (4) **timeout 是设置等待的超时时间。 **
5 rt_uint32_t *recved) (5) **recved 用于保存接收到的事件标志结果,用户通过它的值判断是否
成功接收到事件**
6 {
7 struct rt_thread *thread;
8 register rt_ubase_t level;
9 register rt_base_t status;
10
11 RT_DEBUG_IN_THREAD_CONTEXT;
12
13 /* 检查事件句柄 */
14 RT_ASSERT(event != RT_NULL);
(6)**检查事件句柄 event 是否有效,如果它是未定义或者未创建的事件
句柄,那么是无法接收事件的。 **
15 if (set == 0)
16 return -RT_ERROR;
17
18 /* 初始化状态 */
19 status = -RT_ERROR;
20 /* 获取当前线程 */
21 thread = rt_thread_self(); (7) **:获取当前线程信息,即获取调用接收事件的线程。 **
22 /* 重置线程错误码 */
23 thread->error = RT_EOK;
24
25 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(event->parent.parent)));
26
27 /* 关中断 */
28 level = rt_hw_interrupt_disable();
29
30 /* 检查事件接收选项&检查事件集合 */
31 if (option & RT_EVENT_FLAG_AND) { (8)
**指定的 option 接收选项是 RT_EVENT_FLAG_AND,那么判
断事件集合里面的信息与线程感兴趣的信息是否全部吻合,如果满足条件则标记接收成功。 **
32 if ((event->set & set) == set)
33 status = RT_EOK;
34 } else if (option & RT_EVENT_FLAG_OR) { (9)
**如果指定的 option 接收选项是 RT_EVENT_FLAG_OR,那么判断
事件集合里面的信息与线程感兴趣的信息是否有吻合的部分(有其中一个满足即可),如
果满足条件则标记接收成功。 **
35 if (event->set & set)
36 status = RT_EOK;
37 } else {
38 /* 应设置 RT_EVENT_FLAG_AND 或 RT_EVENT_FLAG_OR */
39 RT_ASSERT(0); (10)
**其 他 情 况 , 接 收 选 项 应 设 置 RT_EVENT_FLAG_AND 或
RT_EVENT_FLAG_OR,他们无法同时使用,也不能不使用。 **
40 }
41
42 if (status == RT_EOK) {
43 /* 返回接收的事件 */
44 if (recved)
45 *recved = (event->set & set); (11) **满足接收事件的条件,则返回接收的事件,读取 recved 即可知道
接收到了哪个事件。**
46
47 /* 接收事件清除 */
48 if (option & RT_EVENT_FLAG_CLEAR) (12)
**如果指定的 option 接收选项选择了 RT_EVENT_FLAG_CLEAR,
在接收完成的时候会清除对应的事件集合的标志位。 **
49 event->set &= ~set;
50 } else if (timeout == 0) { (13)
**如果 timeout= 0,那么接收不到事件就不等待,直接返回-
RT_ETIMEOUT 错误码。 **
51 /* 不等待 */
52 thread->error = -RT_ETIMEOUT;
53 } else {
54 /* 设置线程事件信息 */
55 thread->event_set = set; (14)
timeout 不为 0,需要等待,那么需要配置线程接收事件的信息,
event_set 与 event_info 在线程控制块中有定义,event_set 表示当前线程等待哪些感兴趣的
事件,event_info 表示事件接收选项 option。
56 thread->event_info = option;
57
58 /* 将线程添加到阻塞列表中 */
59 rt_ipc_list_suspend(&(event->parent.suspend_thread), (15)**将等待的线程添加到阻塞列表中。**
60 thread,
61 event->parent.parent.flag);
62
63 /* 如果有等待超时,则启动线程计时器 */
64 if (timeout > 0) {
65 /* 重置线程超时时间并且启动定时器 */
66 rt_timer_control(&(thread->thread_timer), (16) **根据 timeout 的值重置线程超时时间。 **
67 RT_TIMER_CTRL_SET_TIME,
68 &timeout);
69 rt_timer_start(&(thread->thread_timer)); (17) **启动定时器开始计时。**
70 }
71
72 /* 开中断 */
73 rt_hw_interrupt_enable(level);
74
75 /* 发起一次线程调度 */
76 rt_schedule(); (18)**发起一次线程调度。 **
77
78 if (thread->error != RT_EOK) {
79 /* 返回错误代码 */
80 return thread->error; (19)**:返回错误代码。**
81 }
82
83 /* 接收一个事件,失能中断 */
84 level = rt_hw_interrupt_disable();
85
86 /* 返回接收到的事件 */
87 if (recved)
88 *recved = thread->event_set; (20) **返回接收到的事件**
89 }
90
91 /* 开中断 */
92 rt_hw_interrupt_enable(level);
93
94 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(event->parent.parent)));
95
96 return thread->error; (21) **返回接收成功结果**
97 }
98 RTM_EXPORT(rt_event_recv);
(14):timeout 不为 0,需要等待,那么需要配置线程接收事件的信息,
event_set 与 event_info 在线程控制块中有定义,
- event_set 表示当前线程等待哪些感兴趣的事件,
- event_info 表示事件接收选项 option。
当用户调用这个接口时,
- 系统首先根据 set 参数和接收选项来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否清除事件的相应标志位,其中 recved 参数用于保存收到的事件;
- 如果事件没有发生,则把线程感兴趣的事件和接收选项填写到线程控制块中,然后把线程挂起在此事件对象的阻塞列表上,直到事件发生或等待时间超时
- 例子
1 static void receive_thread_entry(void* parameter)
2 {
3 rt_uint32_t recved;
4 /* 线程都是一个无限循环,不能返回 */
5 while (1) {
6 /* 等待接收事件标志 */
7 rt_event_recv(test_event, /* 事件对象句柄 */
8 KEY1_EVENT|KEY2_EVENT, /* 接收线程感兴趣的事件 */
9 RT_EVENT_FLAG_AND|RT_EVENT_FLAG_CLEAR,/* 接收选项 */
10 RT_WAITING_FOREVER, /* 指定超时事件,一直等 */
11 &recved); /* 指向接收到的事件 */
12 if (recved == (KEY1_EVENT|KEY2_EVENT)) { /* 如果接收完成并且正确 */
13 rt_kprintf ( "Key1 与 Key2 都按下\n");
14 LED1_TOGGLE; //LED1 反转
15 } else
16 rt_kprintf ( "事件错误!\n");
17 }
18 }
19