Zephyr RTOS 中用于同步多路复用 I/O 的核心机制k_poll 相关函数用法

目录

概述

[1 k_poll 相关函数介绍](#1 k_poll 相关函数介绍)

[1.1 核心函数列表](#1.1 核心函数列表)

[1.2 关键数据结构](#1.2 关键数据结构)

[1.3 注意事项](#1.3 注意事项)

[1.4 调试技巧](#1.4 调试技巧)

[2 函数详解与示例](#2 函数详解与示例)

[2.1 k_poll_event_init() - 初始化轮询事件](#2.1 k_poll_event_init() - 初始化轮询事件)

[2.2 k_poll() - 核心轮询函数](#2.2 k_poll() - 核心轮询函数)

[2.3 k_poll_signal_raise() - 触发信号(可在 ISR 中调用)](#2.3 k_poll_signal_raise() - 触发信号(可在 ISR 中调用))

[3 完整使用示例](#3 完整使用示例)


概述

k_poll 是 Zephyr RTOS 中用于同步多路复用 I/O 的核心机制。它允许单个线程同时等待多个不同类型的事件(如信号量、消息队列、信号等),并在任一事件就绪时被唤醒,是实现高效事件驱动架构的关键。k_poll 是构建高效、响应式系统的强大工具。通过合理使用这些函数,可以创建出能够同时处理多个事件源而无需复杂线程管理的嵌入式应用。

1 k_poll 相关函数介绍

1.1 核心函数列表

类别 函数 核心作用
核心轮询 k_poll() 主函数。挂起线程,等待一组事件中的任意一个或多个就绪。
事件初始化 k_poll_event_init() 初始化一个轮询事件结构体。这是最常用的初始化方法。
K_POLL_EVENT_INITIALIZER() 用于静态初始化轮询事件结构体的宏。
信号管理 k_poll_signal_init() 初始化一个轮询信号对象。
k_poll_signal_raise() 触发/发出一个轮询信号(可从任何上下文调用,包括 ISR)。
k_poll_signal_check() 检查信号是否已被触发,并获取其结果,非阻塞
k_poll_signal_reset() 将信号状态重置为未触发。
事件状态检查 k_poll_event_get_state() 获取轮询事件的当前状态。
外部事件 k_poll_event_raise() 当使用 K_POLL_TYPE_IGNORE 类型事件时,手动将其状态标记为就绪。

1.2 关键数据结构

1) 轮询事件 (struct k_poll_event)

cpp 复制代码
struct k_poll_event {
    sys_dnode_t _node;                // 内部链表节点
    enum k_poll_types type: 8;        // 事件类型
    enum k_poll_states state: 8;      // 事件状态
    unsigned int mode: 8;             // 模式(通常为 K_POLL_MODE_NOTIFY_ONLY)
    unsigned int unused: 8;
    union {
        struct k_sem *sem;            // 当 type 为 K_POLL_TYPE_SEM 时
        struct k_fifo *fifo;          // 当 type 为 K_POLL_TYPE_FIFO 时
        struct k_queue *queue;        // 当 type 为 K_POLL_TYPE_QUEUE 时
        struct k_poll_signal *signal; // 当 type 为 K_POLL_TYPE_SIGNAL 时
        void *obj;                    // 通用指针(如用于 K_POLL_TYPE_IGNORE)
    };
    void *tag; // 用户自定义标签,可用于传递上下文
};

2) 轮询信号 (struct k_poll_signal)

cpp 复制代码
struct k_poll_signal {
    sys_dlist_t poll_events; // 内部链表,连接等待此信号的事件
    unsigned int signaled: 1; // 信号是否已触发(1=已触发)
    int result;              // 信号触发时传递的结果值
    void *user_data;         // 信号触发时传递的用户数据
};

1.3 注意事项

  1. 事件状态必须手动复位k_poll() 返回后,必须 将已触发事件的 state 字段重置为 K_POLL_STATE_NOT_READY,然后才能再次用于轮询。这是最常见的错误来源。

  2. ISR 中的使用限制k_poll() 不能 在中断服务例程中调用(它是阻塞的)。但 k_poll_signal_raise() 可以在 ISR 中调用。

  3. 性能优化

    • 将高频事件放在数组前面,因为 k_poll() 会顺序检查。

    • 减少轮询事件数量,只监视必要的事件。

    • 对于实时性要求高的场景,使用较短的超时时间。

  4. 内存管理

    • 通过 FIFO/队列传递动态分配的消息时,确保在处理完毕后释放内存。

    • tag 字段可用于传递消息的元数据或内存指针。

  5. 信号量的特殊处理 :对于信号量事件,k_poll() 只检查是否可用,不会自动获取 。必须手动调用 k_sem_take() 来消费信号量。

  6. 超时处理 :合理设置超时时间。K_FOREVER 适用于必须等待的场景;K_NO_WAIT 用于检查当前状态;具体超时用于需要定期执行其他任务的场景。

  7. 错误处理 :始终检查 k_poll() 的返回值,处理超时和错误情况。

1.4 调试技巧

  1. 启用调试 :设置 CONFIG_POLL_EVENT_DEBUG=y 可以获取更多调试信息。

  2. 使用标签 :利用 tag 字段标识不同事件,便于调试时识别。

  3. 状态检查 :使用 k_poll_event_get_state() 在调试时检查事件状态。

  4. 信号检查 :使用 k_poll_signal_check() 非阻塞地检查信号状态。

2 函数详解与示例

2.1 k_poll_event_init() - 初始化轮询事件

1) 函数原型

cpp 复制代码
void k_poll_event_init(struct k_poll_event *event,
                       enum k_poll_types type,
                       int mode,
                       void *obj);

2) 示例

cpp 复制代码
struct k_sem my_sem;
struct k_poll_event events[2];

// 初始化一个信号量事件
k_poll_event_init(&events[0],
                  K_POLL_TYPE_SEM,
                  K_POLL_MODE_NOTIFY_ONLY,
                  &my_sem);

// 初始化一个 FIFO 事件
k_poll_event_init(&events[1],
                  K_POLL_TYPE_FIFO,
                  K_POLL_MODE_NOTIFY_ONLY,
                  &my_fifo);

2.2 k_poll() - 核心轮询函数

1) 函数原型

cpp 复制代码
int k_poll(struct k_poll_event *events,
           int num_events,
           k_timeout_t timeout);

参数说明

  • events:轮询事件数组。

  • num_events:事件数量。

  • timeout:超时时间(K_NO_WAIT, K_FOREVER, 或具体时长如 K_MSEC(100))。

2) 示例

cpp 复制代码
// 等待事件,最多阻塞 1 秒
int rc = k_poll(events, ARRAY_SIZE(events), K_MSEC(1000));

if (rc == 0) {
    // 至少有一个事件就绪
} else if (rc == -EAGAIN) {
    // 超时,无事件就绪
} else {
    // 错误发生
}

2.3 k_poll_signal_raise() - 触发信号(可在 ISR 中调用)

1) 函数原型

cpp 复制代码
void k_poll_signal_raise(struct k_poll_signal *sig,
                         int result,
                         void *user_data);

2) 示例

cpp 复制代码
struct k_poll_signal my_signal;

// 在中断或线程中触发信号
void isr_callback(void) {
    k_poll_signal_raise(&my_signal, 42, (void *)0xDEADBEEF);
}

3 完整使用示例

以下是一个综合示例,展示如何同时等待信号量和 FIFO:

cpp 复制代码
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

/* 定义事件源 */
static struct k_sem data_ready;
static struct k_fifo data_fifo;

/* 定义轮询事件数组 */
static struct k_poll_event events[2];

/* 消息结构 */
struct data_msg {
    void *fifo_reserved;
    int value;
};

/* 事件等待线程 */
void event_thread(void *p1, void *p2, void *p3) {
    int rc;
    struct data_msg *msg;

    /* 初始化轮询事件 */
    k_poll_event_init(&events[0],
                      K_POLL_TYPE_SEM,
                      K_POLL_MODE_NOTIFY_ONLY,
                      &data_ready);

    k_poll_event_init(&events[1],
                      K_POLL_TYPE_FIFO,
                      K_POLL_MODE_NOTIFY_ONLY,
                      &data_fifo);

    printk("事件线程启动,开始轮询...\n");

    while (1) {
        /* 等待事件发生 */
        rc = k_poll(events, ARRAY_SIZE(events), K_FOREVER);

        if (rc != 0) {
            printk("k_poll 错误: %d\n", rc);
            break;
        }

        /* 处理信号量事件 */
        if (events[0].state == K_POLL_STATE_SEM_AVAILABLE) {
            printk("信号量就绪\n");
            k_sem_take(&data_ready, K_NO_WAIT);
            events[0].state = K_POLL_STATE_NOT_READY; // 必须手动复位
        }

        /* 处理 FIFO 事件 */
        if (events[1].state == K_POLL_STATE_FIFO_DATA_AVAILABLE) {
            msg = k_fifo_get(&data_fifo, K_NO_WAIT);
            if (msg != NULL) {
                printk("收到 FIFO 消息: %d\n", msg->value);
                k_free(msg);
            }
            events[1].state = K_POLL_STATE_NOT_READY; // 必须手动复位
        }
    }
}

/* 生产者线程 */
void producer_thread(void *p1, void *p2, void *p3) {
    int count = 0;

    while (1) {
        k_sleep(K_MSEC(2000));

        /* 每隔一次发送不同类型的事件 */
        if (count++ % 2 == 0) {
            printk("释放信号量\n");
            k_sem_give(&data_ready);
        } else {
            /* 发送 FIFO 消息 */
            struct data_msg *msg = k_malloc(sizeof(*msg));
            if (msg != NULL) {
                msg->value = count;
                printk("发送 FIFO 消息: %d\n", msg->value);
                k_fifo_put(&data_fifo, msg);
            }
        }
    }
}

/* 主函数 */
void main(void) {
    /* 初始化内核对象 */
    k_sem_init(&data_ready, 0, 1);
    k_fifo_init(&data_fifo);

    /* 创建线程 */
    k_thread_create(&event_thr, event_stack,
                    K_THREAD_STACK_SIZEOF(event_stack),
                    event_thread,
                    NULL, NULL, NULL,
                    5, 0, K_NO_WAIT);

    k_thread_create(&prod_thr, prod_stack,
                    K_THREAD_STACK_SIZEOF(prod_stack),
                    producer_thread,
                    NULL, NULL, NULL,
                    6, 0, K_NO_WAIT);

    printk("k_poll 示例启动\n");
}

配置选项

prj.conf 文件中:

cpp 复制代码
# 增加最大事件数(默认 10)
CONFIG_POLL_MAX_EVENTS=20

# 启用调试支持
CONFIG_POLL_EVENT_DEBUG=y

# 确保信号支持已启用
CONFIG_POLL_SIGNAL=y

# 如果使用消息队列
CONFIG_POLL_QUEUE=y
相关推荐
mftang4 天前
Zephyr RTOS 中k_mutex(互斥锁)功能介绍
互斥锁·zephyr·zephyr rtos·k_mutex
mftang9 天前
Zephyr RTOS 中timing 相关函数功能介绍
时间管理·zephyr rtos
mftang21 天前
Zephyr RTOS 中k_cpu_idle 函数功能介绍
zephyr rtos·k_cpu_idle
mftang1 个月前
Zephyr RTOS 下 bt_le_scan_start 函数详解
被动扫描·zephyr rtos·主动扫描
背包旅行码农1 年前
Ubuntu2404 下搭建 Zephyr 开发环境
zephyr rtos