事件
事件
📚 本节导读
学习时长: 约 25 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 任务管理、信号量
事件(Event)是 OneOS 提供的一种强大的多对多同步机制。它使用 32 位掩码表示多个事件标志,支持 AND(逻辑与)和 OR(逻辑或)两种触发方式,可以在一个任务等待多个事件条件同时满足时使用。
🎯 学习目标
- 理解事件的核心概念:32 位事件标志、AND/OR 触发
- 掌握事件的发送(send)和接收(recv)操作
- 理解事件选项(AND/OR/CLEAR)的含义
- 掌握一对多和多对多同步模式
- 了解
os_event_sync同步发送的使用场景
一、事件概念
1.1 什么是事件
事件是一组 32 位的标志位,每一位代表一个独立的事件。任务可以等待一个或多个事件位被置位,也可以设置和清除事件位。
┌─────────────────────────────────────────────────────────────────┐
│ 事件模型 │
│ │
│ 事件标志位 (32 bits) │
│ ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ │
│ │31│30│29│..│ 5│ 4│ 3│ 2│ 1│ 0│ │
│ └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘ │
│ ↑ ↑ ↑ ↑ │
│ 事件4 事件3 事件2 事件1 │
│ │
│ ┌──────────┐ send(set) ┌──────────────┐ │
│ │ 任务 A │ ───────────────→ │ │ │
│ └──────────┘ 设置事件位 │ 事件对象 │ │
│ │ ┌────────┐ │ │
│ ┌──────────┐ recv() │ │ 标志集 │ │ │
│ │ 任务 B │ ───────────────→ │ └────────┘ │ │
│ └──────────┘ 等待事件位 │ ┌────────┐ │ │
│ AND/OR 触发 │ │ 等待队列│ │ │
│ │ └────────┘ │ │
│ ┌──────────┐ recv() │ │ │
│ │ 任务 C │ ───────────────→ │ │ │
│ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘1.2 事件触发逻辑
事件支持两种触发方式:
| 触发方式 | 宏 | 含义 |
|---|---|---|
| AND(逻辑与) | OS_EVENT_OPTION_AND | 等待所有感兴趣的事件位都置位后才唤醒 |
| OR(逻辑或) | OS_EVENT_OPTION_OR | 等待任意一个感兴趣的事件位置位就唤醒 |
还可以配合清除选项:
| 选项 | 宏 | 含义 |
|---|---|---|
| CLEAR | OS_EVENT_OPTION_CLEAR | 接收事件后自动清除已满足的事件位 |
1.3 事件与信号量的对比
| 特性 | 事件 | 信号量 |
|---|---|---|
| 数据维度 | 32 位(可表示 32 个独立事件) | 1 个计数器 |
| 触发方式 | AND / OR 可选 | 仅计数 > 0 |
| 一对多同步 | 支持(一个事件可唤醒多个等待者) | 不支持(一个 post 只唤醒一个等待者) |
| 多条件组合 | 支持(等待多个事件同时满足) | 不支持 |
| 中断中使用 | 仅 send 可以 | 仅 post 可以 |
1.5 事件控制块(os_event_t)字段详解
事件的内部控制块 os_event_t(即 struct os_event)定义在 kernel/source/os_prototypes.h 中:
struct os_event
{
os_list_node_t task_list_head; /* Block task list head */
os_list_node_t resource_node; /* Node in resource list */
uint32_t set; /* Event set. */
uint8_t object_inited; /* If mutex object is inited, value is OS_KOBJ_INITED */
uint8_t object_alloc_type; /* Indicates whether memory is allocated dynamically or statically,
value is OS_ALLOC_TYPE_STATIC or OS_KOBJ_ALLOC_TYPE_DYNAMIC */
uint8_t wake_type; /* The type to wake up blocking tasks, value is OS_EVENT_WAKE_TYPE_PRIO
or OS_EVENT_WAKE_TYPE_FIFO */
char name[OS_NAME_MAX + 1]; /* Mutex name */
os_spinlock_t lock; /* Spin lock used under SMP */
};
typedef struct os_event os_event_t;各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
task_list_head | os_list_node_t | 等待队列头:被该事件阻塞的任务挂在这个链表上 |
resource_node | os_list_node_t | 资源链表节点:把事件挂到事件资源链表(gs_os_event_resource_list_head),供系统统一管理 |
set | uint32_t | 事件标志集:32 位,每一位代表一个事件;1 表示已置位 |
object_inited | uint8_t | 内核对象初始化状态(OS_KOBJ_INITED / OS_KOBJ_DEINITED) |
object_alloc_type | uint8_t | 分配类型:OS_ALLOC_TYPE_STATIC(静态)/ OS_KOBJ_ALLOC_TYPE_DYNAMIC(动态) |
wake_type | uint8_t | 唤醒策略:OS_EVENT_WAKE_TYPE_PRIO(按优先级)或 OS_EVENT_WAKE_TYPE_FIFO(按等待时间) |
name | char[OS_NAME_MAX+1] | 事件名称 |
lock | os_spinlock_t | 自旋锁(仅 SMP 编译),保护事件内部操作并发安全 |
关键字段与事件核心概念的对应:
| 事件概念 | 对应字段 | 说明 |
|---|---|---|
| 32 位事件标志 | set | 每个位代表一个事件,置位/清除都通过它 |
| 等待机制 | task_list_head | 等不到事件的任务挂到等待队列阻塞 |
| AND/OR 触发 | set + 任务的 event_set/event_option | 接收时用 _k_event_flag_check() 按 option 判断 |
| 唤醒策略 | wake_type | PRIO 或 FIFO |
| 任务销毁检查 | resource_node | 事件也登记在资源链表,可查询/防重复 |
说明:用户代码不应直接访问这些内部字段,而是通过
os_event.h提供的 API 操作。此处的字段解析用于帮助理解事件的底层工作原理。
1.6 事件与多个链表的整体关系图
事件涉及两个链表,分别承载"事件自身的管理"和"哪些任务在等这个事件"两类信息:
| 链表 | 链表头 | 属于谁 | 存的内容 |
|---|---|---|---|
| 事件资源链表 | gs_os_event_resource_list_head | 事件模块(全局 1 个) | 所有已创建的事件(挂 resource_node) |
| 事件等待队列 | task_list_head(事件内字段) | 事件自己 | 等待这个事件的阻塞任务 |
整体关系图:
【链表 1】事件资源链表(全局 1 个)
gs_os_event_resource_list_head
│
▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 事件A │──►│ 事件B │──►│ 事件C │ ← 通过各事件的 resource_node 串联
└─────────┘ └─────────┘ └─────────┘
【链表 2】事件等待队列(每个事件 1 个,存于事件内)
事件C 的 task_list_head → 任务 T1 → 任务 T2 → 任务 T3
│
│ (任务 T1/T2/T3 等不到事件 C,被 os_event_recv 阻塞挂到这里)阶段一:创建后、无任务等待
事件 C(刚创建)
┌──────────────────────────────┐
│ resource_node ═══► 已挂到事件资源链表 (创建时 os_list_add_tail 挂入)
│ task_list_head ═══► 空等待队列 (还没有任务阻塞等它)
│ set ═══► 0(事件标志全空)
└──────────────────────────────┘阶段二:事件 C 被置位,且有任务 T1 等待
事件 C
┌──────────────────────────────┐
│ resource_node ═══► 仍在事件资源链表 (不变,仍被系统管理)
│ task_list_head ═══► 有任务 T1 等待 (T1 等不到事件,被 os_event_recv 阻塞)
│ set ═══► 0x00000003(事件位被 os_event_send 置位)
└──────────────────────────────┘核心关系表:
| 链表 | 属于谁 | 挂哪个节点 | 何时挂入 |
|---|---|---|---|
事件资源链表(gs_os_event_resource_list_head) | 事件模块(全局 1 个) | resource_node | 创建时(os_list_add_tail) |
事件等待队列(task_list_head) | 事件自己 | 被阻塞的任务 | 接收失败(事件未满足)时(os_event_recv) |
一句话:
resource_node让事件"被系统管理"(全局可见,可查询/防重复)。task_list_head记录"哪些任务在等这个事件"(阻塞队列,os_event_send置位时唤醒满足条件的等待者)。
说明:事件不涉及"持有者/所有权"概念(与互斥锁不同),所以只有资源链表 + 等待队列两个链表,没有"持有者链表"。
二、API 详解
以下 API 定义在 kernel/include/os_event.h 中:
#include <os_event.h>注意:使用事件需要在
oneos_config.h中启用OS_USING_EVENT宏。
2.1 事件选项宏
#define OS_EVENT_OPTION_AND 0x00000001U /* 逻辑与触发 */
#define OS_EVENT_OPTION_OR 0x00000002U /* 逻辑或触发 */
#define OS_EVENT_OPTION_CLEAR 0x00000004U /* 接收后自动清除 */
#define OS_EVENT_OPTION_MASK 0x00000007U /* 选项掩码 */2.2 静态定义
#define OS_EVENT_DEFINE(name) os_event_dummy_t name2.3 创建事件
os_event_id os_event_create(os_event_dummy_t *event_cb, const char *name);| 参数 | 说明 |
|---|---|
event_cb | 事件控制块指针。静态创建时传入 OS_EVENT_DEFINE 定义的变量地址;动态创建时传 OS_NULL |
name | 事件名称 |
| 返回值 | 成功返回事件 ID,失败返回 OS_NULL |
完整源码(kernel/source/os_event.c,与项目一致):
os_event_id os_event_create(os_event_dummy_t *event_cb, const char *name)
{
const os_event_t *iter_event;
const os_list_node_t *pos;
os_event_t *event;
uint8_t alloc_type;
os_bool_t flag;
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != event_cb);
#endif
OS_ASSERT(OS_FALSE == os_is_irq_active());
flag = OS_TRUE;
if (OS_NULL != event_cb)
{
event = OS_TYPE_CONVERT(os_event_t *, event_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
os_spin_lock(&gs_os_event_resource_list_lock);
os_list_for_each(pos, &gs_os_event_resource_list_head)
{
iter_event = os_list_entry(pos, os_event_t, resource_node);
if (iter_event == event)
{
event = OS_NULL;
os_spin_unlock(&gs_os_event_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, EVENT_TAG, "%s", (OS_KERNEL_ERR_INFO));
flag = OS_FALSE;
break;
}
}
}
#ifdef OS_USING_HEAP
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
event = (os_event_t *)OS_KERNEL_MALLOC(sizeof(os_event_t));
if (OS_NULL == event)
{
OS_KERN_LOG(KERN_ERROR, EVENT_TAG, "Malloc event memory failed");
flag = OS_FALSE;
}
}
#endif
if (OS_TRUE == flag)
{
_k_event_init(event, name, alloc_type);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_event_resource_list_lock);
}
os_list_add_tail(&gs_os_event_resource_list_head, &event->resource_node);
os_spin_unlock(&gs_os_event_resource_list_lock);
}
return OS_TYPE_CONVERT(os_event_id, event);
}分步详解:
第一步:参数校验
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != event_cb);
#endif
OS_ASSERT(OS_FALSE == os_is_irq_active());详细解释:
- 未启用堆时断言
event_cb != OS_NULL(必须提供静态控制块)。 OS_ASSERT(OS_FALSE == os_is_irq_active()):不能在中断上下文中创建事件。
第二步:静态创建(防重复检查)
if (OS_NULL != event_cb)
{
event = OS_TYPE_CONVERT(os_event_t *, event_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
os_spin_lock(&gs_os_event_resource_list_lock);
os_list_for_each(pos, &gs_os_event_resource_list_head)
{
iter_event = os_list_entry(pos, os_event_t, resource_node);
if (iter_event == event)
{
event = OS_NULL;
os_spin_unlock(&gs_os_event_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, EVENT_TAG, "%s", (OS_KERNEL_ERR_INFO));
flag = OS_FALSE;
break;
}
}
}详细解释:用户传入非空 event_cb 时用静态控制块(标记 OS_ALLOC_TYPE_STATIC),并遍历事件资源链表检查该地址是否已被占用(防重复创建)。
第三步:动态创建(分配内存)
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
event = (os_event_t *)OS_KERNEL_MALLOC(sizeof(os_event_t));
if (OS_NULL == event)
{
OS_KERN_LOG(KERN_ERROR, EVENT_TAG, "Malloc event memory failed");
flag = OS_FALSE;
}
}详细解释:用户传入 OS_NULL 时从堆分配控制块(需 OS_USING_HEAP),失败则打印日志并置 flag = OS_FALSE。
第四步:初始化并加入资源链表
if (OS_TRUE == flag)
{
_k_event_init(event, name, alloc_type);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_event_resource_list_lock);
}
os_list_add_tail(&gs_os_event_resource_list_head, &event->resource_node);
os_spin_unlock(&gs_os_event_resource_list_lock);
}
return OS_TYPE_CONVERT(os_event_id, event);详细解释:
_k_event_init():初始化事件控制块各字段(名称、分配类型等)。- 动态创建时加自旋锁后把事件登记到事件资源链表(静态创建时第二步已加过锁)。
- 返回转换后的事件 ID;失败时
event为NULL,返回OS_NULL。
使用示例(动态创建,需启用 OS_USING_HEAP):
os_event_id event_id;
event_id = os_event_create(OS_NULL, "sys_event"); /* 动态创建 */
if (event_id == OS_NULL)
{
os_kprintf("event create failed\r\n"); /* 检查返回值 */
return;
}
/* 之后可用 event_id 发送/接收事件 */2.4 销毁事件
os_err_t os_event_destroy(os_event_id event_id);| 参数 | 说明 |
|---|---|
event_id | 要销毁的事件 ID |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 销毁成功 |
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t os_event_destroy(os_event_id event_id)
{
os_bool_t need_sched;
os_event_t *event;
OS_KERNEL_INIT();
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT(OS_FALSE == os_is_irq_active());
#ifndef OS_USING_HEAP
OS_ASSERT(OS_ALLOC_TYPE_STATIC == event->object_alloc_type);
#endif
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
event->object_inited = OS_KOBJ_DEINITED;
event->set = 0U;
/* Wakeup all suspend tasks */
need_sched = k_cancel_all_blocked_task(&event->task_list_head);
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
if (OS_TRUE == need_sched)
{
OS_KERNEL_ENTER();
OS_KERNEL_EXIT_SCHED();
}
#else
if (OS_TRUE == need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
#endif
os_spin_lock(&gs_os_event_resource_list_lock);
os_list_del(&event->resource_node);
os_spin_unlock(&gs_os_event_resource_list_lock);
#ifdef OS_USING_HEAP
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == event->object_alloc_type)
{
OS_KERNEL_FREE(event);
}
#endif
return OS_SUCCESS;
}分步详解:
第一步:准备与参数校验
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT(OS_FALSE == os_is_irq_active());
#ifndef OS_USING_HEAP
OS_ASSERT(OS_ALLOC_TYPE_STATIC == event->object_alloc_type);
#endif详细解释:事件非空、已初始化(防重复销毁)、不能在中断中销毁;未启用堆时事件必须是静态分配。
第二步:标记去初始化并唤醒所有等待任务
event->object_inited = OS_KOBJ_DEINITED;
event->set = 0U;
/* Wakeup all suspend tasks */
need_sched = k_cancel_all_blocked_task(&event->task_list_head);详细解释:
object_inited = OS_KOBJ_DEINITED:标记去初始化,防止销毁过程中其他任务操作。event->set = 0U:清空所有事件标志位。k_cancel_all_blocked_task():唤醒事件等待队列上所有阻塞任务(事件支持一对多唤醒),返回值指示是否需要调度。
第三步:触发调度 + 移除资源链表 + 释放内存
if (OS_TRUE == need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
os_spin_lock(&gs_os_event_resource_list_lock);
os_list_del(&event->resource_node);
os_spin_unlock(&gs_os_event_resource_list_lock);
#ifdef OS_USING_HEAP
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == event->object_alloc_type)
{
OS_KERNEL_FREE(event);
}
#endif详细解释:有唤醒任务则触发调度;把事件从事件资源链表移除;仅动态创建的事件才 OS_KERNEL_FREE 释放堆内存。
2.5 发送事件
os_err_t os_event_send(os_event_id event_id, uint32_t set);设置事件标志位。可以在中断上下文中调用。set 参数中的每个位对应一个事件,值为 1 表示设置该事件。
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t os_event_send(os_event_id event_id, uint32_t set)
{
os_bool_t need_sched;
uint32_t recved_set_check;
os_task_t *block_task;
os_task_t *block_task_next;
os_event_t *event;
OS_KERNEL_INIT();
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT(0U != set);
need_sched = OS_FALSE;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
k_kernel_spin_lock();
#else
OS_KERNEL_ENTER();
#endif
event->set |= set;
/* Search block task list and unblock suitable tasks. */
os_list_for_each_entry_safe(block_task, block_task_next, &event->task_list_head, os_task_t, task_node)
{
recved_set_check = _k_event_flag_check(event, block_task->event_set, block_task->event_option);
if (0U != recved_set_check)
{
block_task->event_set = recved_set_check;
if (OS_TRUE == k_unblock_task(block_task))
{
need_sched = OS_TRUE;
}
}
if (0U == event->set)
{
break;
}
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
os_spin_unlock_irq(&event->lock, irq_save);
if (OS_TRUE == need_sched)
{
OS_KERNEL_ENTER();
OS_KERNEL_EXIT_SCHED();
}
#else
if (OS_TRUE == need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
#endif
return OS_SUCCESS;
}分步详解:
第一步:参数校验
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT(0U != set);详细解释:事件非空、已初始化;OS_ASSERT(0U != set) 要求 set 不能为 0(发送事件必须至少置位一个标志)。注意 os_event_send() 没有 os_is_irq_active() 断言——因为 send 不阻塞,可以在中断中调用。
第二步:置位事件标志
event->set |= set;详细解释:用位或 |= 把 set 中的标志位置位到 event->set(叠加,不会清除已有标志)。
第三步:唤醒符合条件的等待任务
os_list_for_each_entry_safe(block_task, block_task_next, &event->task_list_head, os_task_t, task_node)
{
recved_set_check = _k_event_flag_check(event, block_task->event_set, block_task->event_option);
if (0U != recved_set_check)
{
block_task->event_set = recved_set_check;
if (OS_TRUE == k_unblock_task(block_task))
{
need_sched = OS_TRUE;
}
}
if (0U == event->set)
{
break;
}
}详细解释:这段代码的核心是"发送事件 → 检查等待队列中每个任务的条件是否满足 → 满足的唤醒"。下面拆成几个要点逐个讲清。
① _k_event_flag_check() 是怎么判断"事件是否满足"的?
_k_event_flag_check(event, interested_set, option) 是判断核心(os_event.c,与项目一致):
OS_INLINE uint32_t _k_event_flag_check(os_event_t *event, uint32_t interested_set, uint32_t option)
{
uint32_t recved_set;
recved_set = 0;
if ((option & OS_EVENT_OPTION_AND) != 0U)
{
if ((event->set & interested_set) == interested_set)
{
recved_set = interested_set;
}
}
else if ((option & OS_EVENT_OPTION_OR) != 0U)
{
if ((event->set & interested_set) != 0U)
{
recved_set = interested_set & event->set;
}
}
else
{
OS_ASSERT_EX(OS_FALSE, "Check event option parameter error [%s]", _k_task_self()->name);
}
if (0U != recved_set)
{
/* Received event. */
if ((option & OS_EVENT_OPTION_CLEAR) != 0U)
{
event->set &= ~recved_set;
}
}
return recved_set;
}逐段详细解释(源码与 os_event.c 完全一致):
① 变量初始化:
uint32_t recved_set;
recved_set = 0;recved_set 是"本次实际接收到的事件位集合",初始为 0(默认什么都没收到)。
② AND 判断(option 含 OS_EVENT_OPTION_AND):
if ((option & OS_EVENT_OPTION_AND) != 0U)
{
if ((event->set & interested_set) == interested_set)
{
recved_set = interested_set;
}
}| 代码 | 含义 |
|---|---|
(option & OS_EVENT_OPTION_AND) != 0U | 先判断 option 是否指定了 AND 模式 |
(event->set & interested_set) == interested_set | AND 条件:event->set 中必须包含 interested_set 的全部位(一个都不能少) |
recved_set = interested_set | 满足时,本次接收的事件就是全部感兴趣的事件 |
③ OR 判断(option 含 OS_EVENT_OPTION_OR):
else if ((option & OS_EVENT_OPTION_OR) != 0U)
{
if ((event->set & interested_set) != 0U)
{
recved_set = interested_set & event->set;
}
}| 代码 | 含义 |
|---|---|
(option & OS_EVENT_OPTION_OR) != 0U | 判断 option 是否指定了 OR 模式 |
(event->set & interested_set) != 0U | OR 条件:event->set 与 interested_set 只要有一个共同位即可 |
recved_set = interested_set & event->set | 满足时,本次接收的事件 = 感兴趣且已置位的那些位 |
④ 非法选项断言:
else
{
OS_ASSERT_EX(OS_FALSE, "Check event option parameter error [%s]", _k_task_self()->name);
}option 既没指定 AND 也没指定 OR 时(非法),触发断言失败,并打印当前任务名便于定位。
⑤ CLEAR 处理(清除已消费的事件位):
if (0U != recved_set)
{
/* Received event. */
if ((option & OS_EVENT_OPTION_CLEAR) != 0U)
{
event->set &= ~recved_set;
}
}
return recved_set;| 代码 | 含义 |
|---|---|
0U != recved_set | 只有确实接收到了事件(recved_set 非 0)才需要处理 |
(option & OS_EVENT_OPTION_CLEAR) != 0U | 判断 option 是否指定了 CLEAR 模式(消费后清除) |
event->set &= ~recved_set | 把本次已接收的事件位从 event->set 中清除(消费掉,防止被其他任务重复接收) |
return recved_set | 返回实际接收的事件位集合 |
具体演算示例:
假设 event->set = 0b00000111(位 0、1、2 已置位),两个任务等待:
| 任务 | 感兴趣 interested_set | 选项 option | 判断过程 | 结果 recved_set |
|---|---|---|---|---|
| 任务 T1 | 0b00000101(位0、2) | AND | (0b111 & 0b101) == 0b101 ✅ | 0b101 |
| 任务 T2 | 0b00001010(位1、3) | OR | (0b111 & 0b1010) = 0b010 ≠ 0 ✅ | 0b010 |
② 为什么 event->set 会被清零?为什么 break?
关键在 _k_event_flag_check() 的 CLEAR 处理:唤醒任务后,会把该任务接收的事件位从 event->set 中清除。
初始:event->set = 0b00000111(位0、1、2 已置位)
唤醒任务 T1(AND,感兴趣位0、2):
判断通过,recved_set = 0b101
CLEAR:event->set &= ~0b101 → event->set = 0b00000010(位0、2 被消费掉)
继续遍历 T2(OR,感兴趣位1、3):
判断通过,recved_set = 0b010
CLEAR:event->set &= ~0b010 → event->set = 0b00000000(位1 也被消费)
此时 event->set == 0 → 触发 if (0U == event->set) break 提前结束循环为什么这样设计? 事件是"一对多消费"的资源——一个事件位只能被一个任务消费掉。CLEAR 防止同一个事件被多个任务重复接收。当 event->set 清零(所有事件都被消费完),就没有事件可再分给其他任务了,继续遍历没有意义,所以 break 提前退出。
③ os_list_for_each_entry_safe 为什么用"safe"版本?
os_list_for_each_entry_safe(block_task, block_task_next, &event->task_list_head, os_task_t, task_node)- 普通遍历:用一个指针
block_task依次指向每个节点。 - safe 遍历:多一个
block_task_next指针,提前保存下一个节点的地址。
为什么需要 safe? 因为循环体内 k_unblock_task(block_task) 会把任务从等待队列移除(删除节点)。如果用普通遍历,删除当前节点后,block_task 的 next 指针可能已失效(节点被移走),无法继续遍历。
safe 版本在删除前就记住了下一个节点,所以删除当前节点后仍能安全地继续遍历后面的节点。
④ 完整遍历唤醒流程(多任务示例)
os_event_send(event_id, EVENT_A) // 发送事件 A(位0)
│
▼
event->set |= 位0 // 置位 event->set = 0b...1
│
▼
遍历等待队列 event->task_list_head:
├─ T1(AND 等位0+位1):位1 不在 → 不满足,跳过
├─ T2(OR 等位0) :位0 在 → 满足!唤醒 T2,并 CLEAR 清除位0
│ → event->set 清除位0 后可能为 0 → break
└─ 若 event->set 还非 0,继续检查 T3...关键点:os_event_send() 一次可以同时唤醒多个任务(一对多)——遍历等待队列,逐个唤醒条件满足的任务,直到事件被消费完。这与信号量(一次 post 只唤醒一个)不同。
使用示例(可在中断中调用):
#define EVENT_DATA_READY (1 << 0) /* 位 0:数据就绪 */
/* 任务上下文中发送 */
os_event_send(event_id, EVENT_DATA_READY);
/* 中断上下文中发送(send 不阻塞,允许在 ISR 中调用) */
void irq_handler(void)
{
os_event_send(event_id, EVENT_DATA_READY);
}2.6 接收事件
os_err_t os_event_recv(os_event_id event_id,
uint32_t interested_set,
uint32_t option,
os_tick_t timeout,
uint32_t *recved_set);| 参数 | 说明 |
|---|---|
event_id | 事件 ID |
interested_set | 感兴趣的事件位掩码(只关心这些位) |
option | 触发选项:OS_EVENT_OPTION_AND / OS_EVENT_OPTION_OR / OS_EVENT_OPTION_CLEAR(可组合) |
timeout | 超时时间(tick 数) |
recved_set | 输出参数,返回实际接收到的事件位集合 |
| 返回值 | OS_EOK 成功;OS_ETIMEOUT 超时;OS_ERROR 错误 |
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t
os_event_recv(os_event_id event_id, uint32_t interested_set, uint32_t option, os_tick_t timeout, uint32_t *recved_set)
{
os_task_t *current_task;
uint32_t recved_set_check;
os_err_t ret;
os_event_t *event;
OS_KERNEL_INIT();
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_irq_active()));
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_irq_disabled()));
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_schedule_locked()));
OS_ASSERT((timeout < (OS_TICK_MAX / 2)) || (OS_WAIT_FOREVER == timeout));
OS_ASSERT(0U != interested_set);
ret = OS_SUCCESS;
/*Check parameter*/
if (((option & (OS_EVENT_OPTION_OR | OS_EVENT_OPTION_AND)) == (OS_EVENT_OPTION_OR | OS_EVENT_OPTION_AND)) ||
((option & (OS_EVENT_OPTION_OR | OS_EVENT_OPTION_AND)) == 0))
{
OS_KERN_LOG(KERN_ERROR, EVENT_TAG, "Check event option parameter error [%s]", k_task_self()->name);
ret = OS_INVAL;
}
if (OS_SUCCESS == ret)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
recved_set_check = _k_event_flag_check(event, interested_set, option);
if (0U != recved_set_check)
{
if (OS_NULL != (recved_set))
{
*recved_set = recved_set_check;
}
}
else
{
if (OS_NO_WAIT == timeout)
{
ret = OS_EMPTY;
}
else
{
current_task = _k_task_self();
OS_ASSERT((OS_NULL != current_task));
current_task->event_set = interested_set;
current_task->event_option = option;
/* Block current task */
if (OS_EVENT_WAKE_TYPE_PRIO == event->wake_type)
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_FALSE);
}
if (OS_SUCCESS == ret)
{
if ((OS_NULL != recved_set))
{
*recved_set = current_task->event_set;
}
}
else if (OS_TIMEOUT == ret)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
recved_set_check = _k_event_flag_check(event, interested_set, option);
if (0U != recved_set_check)
{
if ((OS_NULL != recved_set))
{
*recved_set = recved_set_check;
}
ret = OS_SUCCESS;
}
else
{
if ((OS_NULL != recved_set))
{
*recved_set = interested_set & event->set;
}
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
}
else if (OS_FAILURE == ret)
{
if ((OS_NULL != recved_set))
{
*recved_set = interested_set & event->set;
}
}
else
{
;
}
return ret;
}
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
}
return ret;
}分步详解:
第一步:参数校验(含选项合法性检查)
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_irq_active()));
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_irq_disabled()));
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_schedule_locked()));
OS_ASSERT((timeout < (OS_TICK_MAX / 2)) || (OS_WAIT_FOREVER == timeout));
OS_ASSERT(0U != interested_set);
/*Check parameter*/
if (((option & (OS_EVENT_OPTION_OR | OS_EVENT_OPTION_AND)) == (OS_EVENT_OPTION_OR | OS_EVENT_OPTION_AND)) ||
((option & (OS_EVENT_OPTION_OR | OS_EVENT_OPTION_AND)) == 0))
{
OS_KERN_LOG(KERN_ERROR, EVENT_TAG, "Check event option parameter error [%s]", k_task_self()->name);
ret = OS_INVAL;
}详细解释:
OS_ASSERT(0U != interested_set):感兴趣的事件集不能为 0。- 选项合法性检查:
option的 AND/OR 位不能同时置位((OR|AND) == (OR|AND)表示两者都设),也不能都不设(== 0)。只有明确指定 AND 或 OR 之一才合法,否则返回OS_INVAL。
第二步:立即检查是否已满足
recved_set_check = _k_event_flag_check(event, interested_set, option);
if (0U != recved_set_check)
{
if (OS_NULL != (recved_set))
{
*recved_set = recved_set_check; /* 已有事件满足,直接返回 */
}
}详细解释:先调用 _k_event_flag_check() 检查当前事件是否已满足条件(按 option 判断 AND/OR)。若已满足,直接把实际接收到的位集合写入 recved_set,不阻塞,直接返回。
第三步:未满足 —— 阻塞等待
else
{
if (OS_NO_WAIT == timeout)
{
ret = OS_EMPTY; /* 不等待,返回空 */
}
else
{
current_task = _k_task_self();
current_task->event_set = interested_set;
current_task->event_option = option;
/* Block current task */
if (OS_EVENT_WAKE_TYPE_PRIO == event->wake_type)
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_FALSE);
}
if (OS_SUCCESS == ret)
{
if ((OS_NULL != recved_set))
{
*recved_set = current_task->event_set;
}
}
else if (OS_TIMEOUT == ret)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
recved_set_check = _k_event_flag_check(event, interested_set, option);
if (0U != recved_set_check)
{
if ((OS_NULL != recved_set))
{
*recved_set = recved_set_check;
}
ret = OS_SUCCESS;
}
else
{
if ((OS_NULL != recved_set))
{
*recved_set = interested_set & event->set;
}
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
}
else if (OS_FAILURE == ret)
{
if ((OS_NULL != recved_set))
{
*recved_set = interested_set & event->set;
}
}
else
{
;
}
return ret;
}
}详细解释:
- 事件未满足时:
timeout == OS_NO_WAIT:不等待,返回OS_EMPTY(没有可用事件)。- 否则阻塞等待:把当前任务的
event_set(感兴趣位)和event_option(AND/OR/CLEAR)记录到任务控制块,然后k_block_task()把任务挂到事件等待队列event->task_list_head并让出 CPU(同其他阻塞 API 的机制)。
k_block_task()返回后(被os_event_send唤醒或超时),根据返回值:OS_SUCCESS:被唤醒拿到事件,把current_task->event_set(实际收到的事件)写入recved_set。OS_TIMEOUT/OS_FAILURE:超时或失败,返回相应错误码。
关键点:os_event_recv() 与信号量 wait 的关键区别——recv 支持 AND/OR 组合条件,通过 _k_event_flag_check() 判断"多个事件位是否同时满足";且支持 OS_EVENT_OPTION_CLEAR(接收后自动清除事件位)。
使用示例(OR 触发,任意一个事件满足即唤醒):
#define EVENT_DATA_READY (1 << 0) /* 位 0:数据就绪 */
#define EVENT_UART_IDLE (1 << 1) /* 位 1:UART 空闲 */
uint32_t recved;
os_err_t ret;
/* 等待 DATA_READY 或 UART_IDLE 任意一个满足,接收后自动清除 */
ret = os_event_recv(event_id,
EVENT_DATA_READY | EVENT_UART_IDLE, /* 感兴趣的事件 */
OS_EVENT_OPTION_OR | OS_EVENT_OPTION_CLEAR, /* OR 触发 + 自动清除 */
OS_WAIT_FOREVER, /* 永久等待 */
&recved);
if (ret == OS_EOK)
{
if (recved & EVENT_DATA_READY)
os_kprintf("data ready\r\n");
if (recved & EVENT_UART_IDLE)
os_kprintf("uart idle\r\n");
}2.7 清除事件
os_err_t os_event_clear(os_event_id event_id, uint32_t interested_clear);清除指定的事件标志位。interested_clear 中为 1 的位将被清除。
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t os_event_clear(os_event_id event_id, uint32_t interested_clear)
{
os_event_t *event;
OS_KERNEL_INIT();
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
event->set &= ~interested_clear;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
return OS_SUCCESS;
}分步详解:
| 代码 | 作用 |
|---|---|
OS_KERNEL_INIT() | 确保内核已初始化 |
OS_TYPE_CONVERT(os_event_t *, event_id) | 把事件 ID 转换为内部指针 |
OS_ASSERT(OS_NULL != event) | 事件非空 |
OS_ASSERT(OS_KOBJ_INITED == event->object_inited) | 事件必须已初始化 |
event->set &= ~interested_clear | 核心:清除 interested_clear 中为 1 的位(~interested_clear 取反后与运算) |
return OS_SUCCESS | 返回成功 |
关键点:event->set &= ~interested_clear 的位运算——interested_clear 为 1 的位被强制清零,为 0 的位保持不变。例如 event->set = 0b1111,interested_clear = 0b0011,清除后 event->set = 0b1100。
使用示例:
#define EVENT_DATA_READY (1 << 0)
os_event_clear(event_id, EVENT_DATA_READY); /* 清除"数据就绪"事件位 */2.8 获取当前事件值
os_err_t os_event_get(os_event_id event_id, uint32_t *set);获取当前所有事件标志位的值。
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t os_event_get(os_event_id event_id, uint32_t *set)
{
os_event_t *event;
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT(OS_NULL != set);
*set = event->set;
return OS_SUCCESS;
}分步详解:
| 代码 | 作用 |
|---|---|
OS_TYPE_CONVERT(os_event_t *, event_id) | 把事件 ID 转换为内部指针 |
OS_ASSERT(OS_NULL != event) | 事件非空 |
OS_ASSERT(OS_KOBJ_INITED == event->object_inited) | 事件必须已初始化 |
OS_ASSERT(OS_NULL != set) | 传出参数非空(防止写入非法地址) |
*set = event->set | 核心:把当前事件标志集写入传出参数 |
return OS_SUCCESS | 返回成功 |
关键点:set 是传出参数(out 参数),通过指针把结果"写回"给调用者。常用于查询事件当前置位状态。
使用示例:
uint32_t cur_set;
os_event_get(event_id, &cur_set);
if (cur_set & EVENT_DATA_READY)
{
os_kprintf("data ready flag is set\r\n");
}2.9 同步发送
os_err_t os_event_sync(os_event_id event_id,
uint32_t set,
uint32_t interested_set,
os_tick_t timeout,
uint32_t *recved_set);这是一个原子操作:先发送事件(set),然后等待指定的事件(interested_set)。相当于 os_event_send + os_event_recv 的原子组合。
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t os_event_sync(os_event_id event_id, uint32_t set, uint32_t interested_set, os_tick_t timeout, uint32_t *recved_set)
{
os_task_t *current_task;
os_err_t ret;
uint32_t recved_set_check;
uint32_t original_set;
uint32_t clear_bit;
os_bool_t need_sched;
os_task_t *block_task;
os_task_t *block_task_next;
os_event_t *event;
OS_KERNEL_INIT();
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_irq_active()));
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_irq_disabled()));
OS_ASSERT((OS_NO_WAIT == timeout) || (OS_NULL == k_task_self()) || (OS_FALSE == os_is_schedule_locked()));
OS_ASSERT((timeout < (OS_TICK_MAX / 2)) || (OS_WAIT_FOREVER == timeout));
OS_ASSERT(0U != interested_set);
ret = OS_SUCCESS;
recved_set_check = 0;
clear_bit = 0;
need_sched = OS_FALSE;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
event->set |= set;
original_set = event->set;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_lock();
#endif
/* Search block task list and unblock suitable tasks. */
os_list_for_each_entry_safe(block_task, block_task_next, &event->task_list_head, os_task_t, task_node)
{
if ((block_task->event_option & OS_EVENT_OPTION_AND) != 0U)
{
if ((event->set & block_task->event_set) == block_task->event_set)
{
recved_set_check = block_task->event_set;
}
}
else if ((block_task->event_option & OS_EVENT_OPTION_OR) != 0U)
{
if ((event->set & block_task->event_set) != 0U)
{
recved_set_check = block_task->event_set & event->set;
}
}
else
{
OS_ASSERT_EX(OS_FALSE, "Check event option parameter error [%s]", block_task->name);
}
if (0U != recved_set_check)
{
/* Received event. */
if ((block_task->event_option & OS_EVENT_OPTION_CLEAR) != 0U)
{
/* store clear bit in temporary varialble. */
clear_bit |= block_task->event_set;
}
block_task->event_set = recved_set_check;
if (OS_TRUE == k_unblock_task(block_task))
{
need_sched = OS_TRUE;
}
}
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
#endif
/* Now clear event bit. */
event->set &= ~clear_bit;
/* check if all bit has be set */
if ((original_set & interested_set) == interested_set)
{
/* all bit has be set , always clear event here*/
event->set &= ~interested_set;
if (recved_set != OS_NULL)
{
*recved_set = interested_set;
}
/* all bit has be set, there must be an unlocked task , schedule */
need_sched = OS_TRUE;
}
else
{
if (OS_NO_WAIT == timeout)
{
/* not all bit has be set. */
if (recved_set != OS_NULL)
{
*recved_set = interested_set & event->set;
}
ret = OS_TIMEOUT;
}
else
{
current_task = _k_task_self();
OS_ASSERT((OS_NULL != current_task));
current_task->event_set = interested_set;
current_task->event_option = OS_EVENT_OPTION_AND | OS_EVENT_OPTION_CLEAR;
/* Block current task */
if (OS_EVENT_WAKE_TYPE_PRIO == event->wake_type)
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_FALSE);
}
if (OS_SUCCESS == ret)
{
if (recved_set != OS_NULL)
{
*recved_set = current_task->event_set;
}
}
else if (OS_TIMEOUT == ret)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
if (recved_set != OS_NULL)
{
*recved_set = interested_set & event->set;
}
if ((event->set & interested_set) == interested_set)
{
event->set &= ~interested_set;
ret = OS_SUCCESS;
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
}
else if (OS_FAILURE == ret)
{
if (recved_set != OS_NULL)
{
*recved_set = interested_set & event->set;
}
}
else
{
;
}
return ret;
}
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
if (OS_TRUE == need_sched)
{
OS_KERNEL_ENTER();
OS_KERNEL_EXIT_SCHED();
}
#else
if (OS_TRUE == need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
#endif
return ret;
}分步详解(os_event_sync 核心逻辑分 3 步):
第一步:发送事件并检查等待队列
event->set |= set;
original_set = event->set;
/* Search block task list and unblock suitable tasks. */
os_list_for_each_entry_safe(block_task, block_task_next, &event->task_list_head, os_task_t, task_node)
{
if ((block_task->event_option & OS_EVENT_OPTION_AND) != 0U)
{
if ((event->set & block_task->event_set) == block_task->event_set)
{
recved_set_check = block_task->event_set;
}
}
else if ((block_task->event_option & OS_EVENT_OPTION_OR) != 0U)
{
if ((event->set & block_task->event_set) != 0U)
{
recved_set_check = block_task->event_set & event->set;
}
}
else
{
OS_ASSERT_EX(OS_FALSE, "Check event option parameter error [%s]", block_task->name);
}
if (0U != recved_set_check)
{
/* Received event. */
if ((block_task->event_option & OS_EVENT_OPTION_CLEAR) != 0U)
{
/* store clear bit in temporary varialble. */
clear_bit |= block_task->event_set;
}
block_task->event_set = recved_set_check;
if (OS_TRUE == k_unblock_task(block_task))
{
need_sched = OS_TRUE;
}
}
}详细解释(第一步逐段分析):
① 置位并保存快照:
event->set |= set;
original_set = event->set;| 代码 | 作用 |
|---|---|
event->set |= set | 把 set 中的事件位置位到 event->set(相当于先执行 os_event_send 的置位) |
original_set = event->set | 保存置位后的快照,供后续判断"发送后是否已满足等待条件"使用(因为后面 event->set 可能被 CLEAR 清除) |
② 遍历等待队列,唤醒满足条件的任务:
os_list_for_each_entry_safe(block_task, block_task_next, &event->task_list_head, os_task_t, task_node)遍历 event->task_list_head 等待队列中的每个阻塞任务。用 safe 版本是因为 k_unblock_task() 会删除当前节点,需提前保存 block_task_next。
③ 按每个任务的选项判断 AND/OR 条件:
if ((block_task->event_option & OS_EVENT_OPTION_AND) != 0U)
{
if ((event->set & block_task->event_set) == block_task->event_set)
{
recved_set_check = block_task->event_set;
}
}
else if ((block_task->event_option & OS_EVENT_OPTION_OR) != 0U)
{
if ((event->set & block_task->event_set) != 0U)
{
recved_set_check = block_task->event_set & event->set;
}
}
else
{
OS_ASSERT_EX(OS_FALSE, "Check event option parameter error [%s]", block_task->name);
}| 分支 | 判断条件 | 结果 |
|---|---|---|
| 任务的选项是 AND | (event->set & block_task->event_set) == block_task->event_set(该任务感兴趣的所有位都在) | recved_set_check = block_task->event_set(全部感兴趣事件) |
| 任务的选项是 OR | (event->set & block_task->event_set) != 0(至少一个感兴趣位在) | recved_set_check = block_task->event_set & event->set(实际已置位的那些) |
| 选项非法 | 既没 AND 也没 OR | 断言失败 |
④ 唤醒满足条件 + 累计清除位:
if (0U != recved_set_check)
{
/* Received event. */
if ((block_task->event_option & OS_EVENT_OPTION_CLEAR) != 0U)
{
/* store clear bit in temporary varialble. */
clear_bit |= block_task->event_set;
}
block_task->event_set = recved_set_check;
if (OS_TRUE == k_unblock_task(block_task))
{
need_sched = OS_TRUE;
}
}| 代码 | 作用 |
|---|---|
recved_set_check != 0 | 该任务条件满足,可以唤醒 |
clear_bit |= block_task->event_set | 把该任务消费的事件位累计到 clear_bit(暂存,稍后统一清除,避免边遍历边改 event->set 影响后续判断) |
block_task->event_set = recved_set_check | 把实际收到的事件写入该任务的 event_set(供唤醒后查询) |
k_unblock_task(block_task) | 唤醒该任务,返回 OS_TRUE 则置 need_sched |
关键点:这里用 clear_bit 临时累计而非立即清除 event->set,是为了避免在遍历过程中 event->set 被改小,导致后续任务判断时看到的事件不完整(与 os_event_send 边遍历边清除不同)。遍历结束后(第二步开头)才统一 event->set &= ~clear_bit 清除。
第二步:判断"感兴趣的事件是否全部满足"
event->set &= ~clear_bit;
if ((original_set & interested_set) == interested_set)
{
event->set &= ~interested_set;
if (recved_set != OS_NULL)
{
*recved_set = interested_set;
}
need_sched = OS_TRUE;
}详细解释(第二步逐行分析):
| 代码 | 作用 |
|---|---|
event->set &= ~clear_bit | 统一清除第一步累计的被消费事件位(clear_bit 中的位清零) |
(original_set & interested_set) == interested_set | 用发送前的快照 original_set 判断:本次 set 置位后,interested_set 的所有位是否已全部在事件中(AND 全满足) |
event->set &= ~interested_set | 全部满足则消费掉 interested_set 中的事件位 |
*recved_set = interested_set | 把 interested_set 写入传出参数(本次全部收到) |
need_sched = OS_TRUE | 有任务被唤醒,需要调度 |
关键点:这里判断的是"发送的 set 是否恰好凑齐了 interested_set"。如果凑齐了,说明发送方发送的事件正好满足了接收方的需求,无需阻塞,直接返回成功(os_event_sync 的"同步"语义:发完即达成)。
具体演算:假设 interested_set = 0b101(位0、2),set = 0b101(发送位0、2):
event->set |= set → event->set = 0b101
original_set = 0b101
(0b101 & 0b101) == 0b101 → 全满足!直接返回成功,无需等待第三步:未全满足 —— 等待或返回超时
else
{
if (OS_NO_WAIT == timeout)
{
*recved_set = interested_set & event->set;
ret = OS_TIMEOUT;
}
else
{
current_task->event_set = interested_set;
current_task->event_option = OS_EVENT_OPTION_AND | OS_EVENT_OPTION_CLEAR;
/* Block current task */
if (OS_EVENT_WAKE_TYPE_PRIO == event->wake_type)
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&event->lock, irq_save, &event->task_list_head, timeout, OS_FALSE);
}
if (OS_SUCCESS == ret)
{
if (recved_set != OS_NULL)
{
*recved_set = current_task->event_set;
}
}
else if (OS_TIMEOUT == ret)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
if (recved_set != OS_NULL)
{
*recved_set = interested_set & event->set;
}
if ((event->set & interested_set) == interested_set)
{
event->set &= ~interested_set;
ret = OS_SUCCESS;
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
}
else if (OS_FAILURE == ret)
{
if (recved_set != OS_NULL)
{
*recved_set = interested_set & event->set;
}
}
else
{
;
}
return ret;
}
}详细解释(第三步逐分支分析):
① 发送的事件不够 → timeout == OS_NO_WAIT(不等待):
if (OS_NO_WAIT == timeout)
{
*recved_set = interested_set & event->set;
ret = OS_TIMEOUT;
}发送的 set 未凑齐 interested_set,且调用方不想等待:把当前已有的事件位(interested_set & event->set)写入 recved_set,返回 OS_TIMEOUT。
② 发送的事件不够 → 阻塞等待:
else
{
current_task->event_set = interested_set;
current_task->event_option = OS_EVENT_OPTION_AND | OS_EVENT_OPTION_CLEAR;
if (OS_EVENT_WAKE_TYPE_PRIO == event->wake_type)
{
ret = k_block_task(...OS_TRUE); /* PRIO 唤醒 */
}
else
{
ret = k_block_task(...OS_FALSE); /* FIFO 唤醒 */
}调用方愿意等待:把 interested_set 记入当前任务,固定用 AND + CLEAR 选项(同步等待必须等待全部感兴趣事件,且收到后清除)。然后 k_block_task() 阻塞,等待其他任务发送补足剩余的事件。
③ 阻塞返回后的三种处理:
k_block_task 返回值 | 含义 | 处理 |
|---|---|---|
OS_SUCCESS | 被唤醒且拿到事件 | 把 current_task->event_set(实际收到的事件)写入 recved_set |
OS_TIMEOUT | 超时(延时到期) | 重新进临界区再次检查 (event->set & interested_set) == interested_set:若超时瞬间事件恰好凑齐,则消费并返回 OS_SUCCESS;否则把已有事件写入 recved_set,保持超时错误 |
OS_FAILURE | 其他失败 | 把当前已有事件(interested_set & event->set)写入 recved_set |
④ return ret:返回最终结果(OS_SUCCESS 表示同步成功,其他为超时/失败)。
关键点 —— os_event_sync 与 send + recv 的区别:
这三点是 os_event_sync 最核心的设计,下面用"任务 A 发送 REQUEST 并等待任务 B 回发 RESPONSE"这个具体场景,对照手动 send + recv 来逐个理解。
① 什么是"原子操作"?为什么"期间事件不会被其他操作打断"?
先看手动 send + recv 可能出的问题:
任务 A 手动写法:
步骤1: os_event_send(event_id, EVENT_REQUEST) → event->set 置位 REQUEST
步骤2: os_event_recv(event_id, EVENT_RESPONSE, ...)
假设任务 B 响应很快,在"步骤1 和 步骤2 之间"就回发了 RESPONSE:
事件 RESPONSE 已经被置位,但任务 A 还没开始 recv(还在步骤1、步骤2 之间)
│
▼
任务 A 开始 recv 时:RESPONSE 事件还在 event->set 里 → 没问题?
—— 实际上没问题,因为事件是"电平保持"的,置位后不会消失那么手动写法的问题在哪?问题在于两者之间可能插入其他操作,导致逻辑不一致。例如另一个任务在中间消费掉了 RESPONSE 事件:
步骤1: send(REQUEST) → event->set = REQUEST
(此时任务 C 抢先把 RESPONSE 事件消费/清除掉了)
步骤2: recv(RESPONSE) → RESPONSE 已不在 → 任务 A 被迫阻塞等待,即使响应早就回来了而 os_event_sync 把 send 和 recv 做成一个函数、一个临界区内完成**:
os_event_sync(event_id, REQUEST, RESPONSE, ...)
│
├─ ① 置位 REQUEST(相当于 send)
├─ ② 判断 RESPONSE 是否已满足
├─ ③ 满足→返回成功;不满足→在同一临界区内记录等待并阻塞
└─ 整个 ①②③ 在一个临界区内,中间不会被其他任务/中断插入"期间事件不会被其他操作打断" 的含义:整个"置位 + 判断 + 决定阻塞"在同一个临界区(OS_KERNEL_ENTER)内一次性完成,中间不会被其他任务插入操作事件。这样就不会出现手动写法中"发送和接收之间,事件被其他任务消费掉"的漏洞。
② "发送时若恰好凑齐了等待条件,直接返回成功"是什么意思?
os_event_sync 在置位完 REQUEST 后,会立刻检查"interested_set(RESPONSE)是否已经满足":
场景:任务 B 在任务 A 调用 sync 之前,就已经回发了 RESPONSE(事件已置位)
os_event_sync 执行:
① event->set |= REQUEST → event->set = REQUEST | RESPONSE(RESPONSE 早就置位了)
② 判断 (event->set & RESPONSE) == RESPONSE? → 是!RESPONSE 已满足!
③ 直接返回 OS_SUCCESS → 无需阻塞!
对比手动写法:send(REQUEST) 后还要再走一次 recv 的完整流程(虽然 recv 发现已满足也会直接返回,但代码上多了一步)关键:os_event_sync 在同一个临界区内完成了"置位 REQUEST + 判断 RESPONSE 是否已满足",如果 RESPONSE 恰好已满足(可能响应方早已置位,或本次 REQUEST 恰好补足了条件),就直接返回成功,不走 recv 的阻塞分支。
③ "等待期间固定用 AND + CLEAR"是什么意思?
当 RESPONSE 未满足、需要阻塞等待时,os_event_sync 内部自动把任务的接收选项设为 AND + CLEAR:
current_task->event_set = interested_set; /* AND:必须全部收到 */
current_task->event_option = OS_EVENT_OPTION_AND | OS_EVENT_OPTION_CLEAR; /* 固定 */| 选项 | 为什么必须如此 |
|---|---|
| AND | os_event_sync 的语义是"同步等待全部感兴趣事件凑齐"。用 AND 保证只有 interested_set 的所有位都到位,任务才被唤醒。如果用 OR,可能只收到一个事件就被唤醒,违背"同步"语义 |
| CLEAR | 收到事件后自动清除,保证"这次收到的响应不会被下一个 sync 再次读到"(事件被消费掉) |
为什么"固定"? 手动 send + recv 时,recv 的选项由调用者自己指定(可能是 OR、可能不带 CLEAR)。而 os_event_sync 作为"同步"接口,语义固定为"等待全部 + 收到即消费",所以内部强制用 AND + CLEAR,不交给调用者选择,避免调用者用错选项导致同步语义被破坏。
一句话总结:os_event_sync = "在一个临界区内完成'发送 + 判断是否已满足 + 未满足则按 AND+CLEAR 阻塞等待'",它比手动 send + recv 更安全(不被插入打断)、更简洁(发完即达直接成功)、语义更确定(固定 AND+CLEAR)。
使用示例(请求-响应模式,覆盖各种 timeout 与返回值情况):
#include <os_event.h>
#define EVENT_REQUEST (1 << 0) /* 位 0:请求事件 */
#define EVENT_RESPONSE (1 << 1) /* 位 1:响应事件 */
/*
* 场景:任务 A 向任务 B 发送 REQUEST,并同步等待 B 回发 RESPONSE。
* os_event_sync 把"发送 + 等待"做成一个原子操作。
*/
/* ① 永久等待(OS_WAIT_FOREVER):一定会等到响应,或永不返回 */
void sync_wait_forever(os_event_id event_id)
{
uint32_t recved;
os_err_t ret;
ret = os_event_sync(event_id,
EVENT_REQUEST, /* 发送 REQUEST */
EVENT_RESPONSE, /* 等待 RESPONSE */
OS_WAIT_FOREVER, /* 永久等待 */
&recved);
if (ret == OS_EOK)
{
os_kprintf("wait_forever: success, recved=0x%x\r\n", recved);
/* 此时 recved 含 EVENT_RESPONSE,说明收到了响应 */
}
else
{
os_kprintf("wait_forever: failed, ret=%d\r\n", ret);
}
}
/* ② 带超时(如 100 tick):可能成功,也可能超时 */
void sync_with_timeout(os_event_id event_id)
{
uint32_t recved;
os_err_t ret;
ret = os_event_sync(event_id,
EVENT_REQUEST,
EVENT_RESPONSE,
100, /* 超时 100 tick */
&recved);
if (ret == OS_EOK)
{
os_kprintf("timeout(100): success, recved=0x%x\r\n", recved);
/* 100 tick 内收到了 RESPONSE */
}
else if (ret == OS_TIMEOUT)
{
os_kprintf("timeout(100): timeout, already got 0x%x\r\n", recved);
/* 超时,recved 中可能是部分已到的事件 */
}
else
{
os_kprintf("timeout(100): failed, ret=%d\r\n", ret);
}
}
/* ③ 不等待(OS_NO_WAIT):立即返回,不阻塞 */
void sync_no_wait(os_event_id event_id)
{
uint32_t recved;
os_err_t ret;
ret = os_event_sync(event_id,
EVENT_REQUEST,
EVENT_RESPONSE,
OS_NO_WAIT, /* 不等待 */
&recved);
if (ret == OS_EOK)
{
os_kprintf("no_wait: success, recved=0x%x\r\n", recved);
/* 发送 REQUEST 后恰好已凑齐 RESPONSE,直接成功 */
}
else if (ret == OS_TIMEOUT)
{
os_kprintf("no_wait: not ready yet, recved=0x%x\r\n", recved);
/* RESPONSE 还没到,立即返回,recved 为当前已有的事件 */
}
}
/* ④ 组合使用:先发送,再循环等待(相当于手动拆开的 send + recv) */
void sync_manual(os_event_id event_id)
{
uint32_t recved;
os_event_send(event_id, EVENT_REQUEST); /* 先发送 REQUEST */
os_event_recv(event_id,
EVENT_RESPONSE, /* 再等待 RESPONSE */
OS_EVENT_OPTION_AND | OS_EVENT_OPTION_CLEAR,
OS_WAIT_FOREVER,
&recved);
if (recved & EVENT_RESPONSE)
{
os_kprintf("manual: got response\r\n");
}
}各种情况的返回值与 recved 内容对照:
| 场景 | timeout | 返回值 | recved 内容 |
|---|---|---|---|
发送后已凑齐 interested_set | 任意 | OS_EOK | interested_set(全部收到) |
| 未凑齐,但等待期间凑齐 | > 0 | OS_EOK | 实际收到的事件 |
| 未凑齐,等待超时 | > 0 | OS_TIMEOUT | 当前已有的事件(可能部分) |
| 未凑齐,且不等待 | OS_NO_WAIT | OS_TIMEOUT | 当前已有的事件 |
参数非法(interested_set == 0 等) | 任意 | 断言失败 | — |
说明:
os_event_sync的返回值OS_EOK表示"发送的事件 + 等待期间补充的事件已凑齐interested_set";OS_TIMEOUT表示"直到超时都没凑齐"。是否成功要看ret == OS_EOK,不要只根据recved非 0 判断。
2.10 唤醒策略
os_err_t os_event_set_wake_type(os_event_id event_id, uint8_t wake_type);| 唤醒类型 | 宏 | 含义 |
|---|---|---|
| 优先级唤醒 | OS_EVENT_WAKE_TYPE_PRIO (0x55) | 唤醒等待队列中优先级最高的任务 |
| FIFO 唤醒 | OS_EVENT_WAKE_TYPE_FIFO (0xAA) | 唤醒等待队列中等待时间最长的任务 |
完整源码(kernel/source/os_event.c,与项目一致):
os_err_t os_event_set_wake_type(os_event_id event_id, uint8_t wake_type)
{
os_err_t ret;
os_event_t *event;
OS_KERNEL_INIT();
event = OS_TYPE_CONVERT(os_event_t *, event_id);
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT((OS_EVENT_WAKE_TYPE_PRIO == wake_type) || (OS_EVENT_WAKE_TYPE_FIFO == wake_type));
ret = OS_BUSY;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&event->lock, &irq_save);
k_kernel_spin_lock();
#else
OS_KERNEL_ENTER();
#endif
if (os_list_empty(&event->task_list_head))
{
event->wake_type = wake_type;
ret = OS_SUCCESS;
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
os_spin_unlock_irq(&event->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
return ret;
}分步详解:
第一步:参数校验
OS_ASSERT(OS_NULL != event);
OS_ASSERT(OS_KOBJ_INITED == event->object_inited);
OS_ASSERT((OS_EVENT_WAKE_TYPE_PRIO == wake_type) || (OS_EVENT_WAKE_TYPE_FIFO == wake_type));详细解释:事件非空、已初始化,且 wake_type 必须是 PRIO 或 FIFO 之一。
第二步:仅在无等待任务时可修改
ret = OS_BUSY;
if (os_list_empty(&event->task_list_head))
{
event->wake_type = wake_type;
ret = OS_SUCCESS;
}详细解释:
- 等待队列为空时才能修改唤醒策略(
event->wake_type = wake_type),返回OS_SUCCESS。 - 有任务正在等待(队列非空)时返回
OS_BUSY——因为队列中的任务已按旧策略排序,中途改策略会导致排序错乱。
关键点:应在事件创建后、有任务开始等待前设置唤醒策略:
event_id = os_event_create(OS_NULL, "event");
os_event_set_wake_type(event_id, OS_EVENT_WAKE_TYPE_FIFO); /* 在无等待者时设置 */📝 本节小结
核心要点回顾
本节围绕事件机制展开,核心要点如下:
1. 事件本质
- 一个 32 位标志集(
event->set),每个位代表一个独立事件,可同时管理 32 个事件。
2. 触发条件(AND / OR)
- OR 触发:任意一个感兴趣的事件满足就唤醒(适合多事件响应)。
- AND 触发:所有感兴趣的事件都满足才唤醒(适合多条件同步)。
3. 关键 API 与行为
| API | 作用 | 关键行为 |
|---|---|---|
os_event_create | 创建事件 | 静态/动态、防重复创建 |
os_event_send | 发送事件 | 置位 + 唤醒所有满足条件的等待任务(一对多) |
os_event_recv | 接收事件 | AND/OR 判断 + CLEAR 可选清除 |
4. 特性
- CLEAR 选项:接收后自动清除已满足的事件位。
- 一对多同步:一个
os_event_send()可同时唤醒多个等待任务(遍历等待队列)。 - 同步发送
os_event_sync:原子地发送并等待响应(请求-响应模式)。
5. 中断限制
os_event_send()可在中断中调用(不阻塞)。os_event_recv()不能在中断中调用(可能阻塞)。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
在中断中调用 os_event_recv() | 禁止,recv 可能阻塞;中断中只能用 os_event_send() |
os_event_send 传 set = 0 | 断言失败,发送必须至少置位一个事件 |
option 的 AND/OR 都不设或都设 | 返回 OS_INVAL,必须明确指定 AND 或 OR 之一 |
忘记处理 os_event_recv 超时/空返回值 | 需判断 OS_EOK / OS_TIMEOUT / OS_EMPTY |
| 用事件做互斥保护 | 事件用于多条件同步,互斥保护应用互斥锁 |
| 忽略 CLEAR 选项 | 不设 CLEAR 时事件位保留,可能被后续 recv 重复消费 |
下一步
事件用于多条件同步。接下来请学习下一小节:
- 4.3.4 消息队列:了解如何在任务间传递变长数据