Zephyr sem

news/2024/11/24 7:32:17/

文章目录

  • 简介
    • 互斥
    • 同步
  • 数据结构
  • 信号量初始化
    • Z_SEM_INITIALIZER
    • int k_sem_init (struct k_sem *sem, unsigned int initial_count, unsigned int limit)
  • 获取信号量
    • int k_sem_take(struct k_sem *sem, k_timeout_t timeout)
  • 释放信号量
    • void k_sem_give(struct k_sem *sem)
  • 获取计数值
    • unsigned int k_sem_count_get(struct k_sem *sem)
  • 重置信号量
    • void k_sem_reset(struct k_sem *sem)
  • 示例

简介

  • 在 Zephyr 中,信号量(semaphore)是一种用于实现任务之间同步和互斥的同步原语。

互斥

  • 互斥(Mutual Exclusion):当多个任务需要访问共享资源时,信号量可以确保在同一时刻只有一个任务能够访问该资源。这可以防止多个任务同时修改共享资源,从而避免数据不一致和竞态条件的发生。
    • 例如,假设有两个任务 A 和 B 都需要访问一个共享的数据结构。通过使用信号量,我们可以确保在任务 A 访问数据结构时,任务 B 会被阻塞,直到任务 A 完成对数据结构的访问。这样就可以避免任务 A 和任务 B 同时修改数据结构,导致数据不一致的问题。

同步

  • 同步(Synchronization):信号量还可以用于协调多个任务之间的执行顺序。当一个任务需要等待另一个任务完成某个操作时,可以使用信号量来实现这种等待关系。
    • 例如,假设有两个任务 A 和 B,任务 A 需要等待任务 B 完成某个操作后才能继续执行。通过使用信号量,我们可以让任务 A 在等待信号量时阻塞,直到任务 B 完成操作并释放信号量。这样就实现了任务 A 和任务 B 之间的同步。

数据结构

struct k_sem {_wait_q_t wait_q;unsigned int count;unsigned int limit;_POLL_EVENT;SYS_PORT_TRACING_TRACKING_FIELD(k_sem)
};
  • wait_q 等待队列,当线程调用 k_sem_take 函数时,如果当前计数值为 0 且等待时间不为 0,调用线程将会被添加到等待队列中。
  • count 信号量当前计数值。
  • limit 信号量最大计数值。
  • 除此之外,如果使能了CONFIG_POLL,还会在每个对象中增加一个双向链表,调用 k_poll 时,如果当前count为0,等待时间不为0,会把调用者加入到链表中进入等待,直到超时或者信号量不为 0 时被唤醒。
#ifdef CONFIG_POLL
#define _POLL_EVENT_OBJ_INIT(obj) \.poll_events = SYS_DLIST_STATIC_INIT(&obj.poll_events),
#define _POLL_EVENT sys_dlist_t poll_events
#else
#define _POLL_EVENT_OBJ_INIT(obj)
#define _POLL_EVENT
#endif

信号量初始化

  • 信号量的初始化包含三个步骤:
    • 初始化等待队列
    • 设置信号量初始值
    • 设置信号量最大值
  • 需要注意的是,初始值必须小于等于最大值,最大值不能是0。

Z_SEM_INITIALIZER

#define Z_SEM_INITIALIZER(obj, initial_count, count_limit) \{ \.wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \.count = initial_count, \.limit = count_limit, \_POLL_EVENT_OBJ_INIT(obj) \}

int k_sem_init (struct k_sem *sem, unsigned int initial_count, unsigned int limit)

int z_impl_k_sem_init(struct k_sem *sem, unsigned int initial_count,unsigned int limit)
{/** Limit cannot be zero and count cannot be greater than limit*/CHECKIF(limit == 0U || limit > K_SEM_MAX_LIMIT || initial_count > limit) {SYS_PORT_TRACING_OBJ_FUNC(k_sem, init, sem, -EINVAL);return -EINVAL;}sem->count = initial_count;sem->limit = limit;SYS_PORT_TRACING_OBJ_FUNC(k_sem, init, sem, 0);z_waitq_init(&sem->wait_q);
#if defined(CONFIG_POLL)sys_dlist_init(&sem->poll_events);
#endifz_object_init(sem);return 0;
}

获取信号量

int k_sem_take(struct k_sem *sem, k_timeout_t timeout)

int z_impl_k_sem_take(struct k_sem *sem, k_timeout_t timeout)
{int ret = 0;__ASSERT(((arch_is_in_isr() == false) ||K_TIMEOUT_EQ(timeout, K_NO_WAIT)), "");k_spinlock_key_t key = k_spin_lock(&lock);SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_sem, take, sem, timeout);/* 信号量大于0则直接退出并返回0 */if (likely(sem->count > 0U)) {sem->count--;k_spin_unlock(&lock, key);ret = 0;goto out;}/* 信号量等于0,且timeout为0,退出并返回等待超时 */if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {k_spin_unlock(&lock, key);ret = -EBUSY;goto out;}SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_sem, take, sem, timeout);/* 否则刮起当前线程 */ret = z_pend_curr(&lock, key, &sem->wait_q, timeout);out:SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_sem, take, sem, timeout, ret);return ret;
}

释放信号量

void k_sem_give(struct k_sem *sem)

void z_impl_k_sem_give(struct k_sem *sem)
{k_spinlock_key_t key = k_spin_lock(&lock);struct k_thread *thread;SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_sem, give, sem);thread = z_unpend_first_thread(&sem->wait_q);/* 等待队列中存在等待线程时则直接唤醒等待线程* 如果不存在则需要将技术值加1,如果 poll_events 中不为空,* 需要发送信号唤醒 k_poll 调用者, 然后通过k_sem_take 获取信号量。*/if (thread != NULL) {arch_thread_return_value_set(thread, 0);z_ready_thread(thread);} else {sem->count += (sem->count != sem->limit) ? 1U : 0U;handle_poll_events(sem);}z_reschedule(&lock, key);SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_sem, give, sem);
}

获取计数值

unsigned int k_sem_count_get(struct k_sem *sem)

static inline unsigned int z_impl_k_sem_count_get(struct k_sem *sem)
{return sem->count;
}
  • count是一个与总线宽度相同的变量,CPU可以用一条指令获取到计数值,本身是一个原子操作,且没有其他数据与其有关,因此可以直接读取该值获得当前计数值。

重置信号量

void k_sem_reset(struct k_sem *sem)

void z_impl_k_sem_reset(struct k_sem *sem)
{struct k_thread *thread;k_spinlock_key_t key = k_spin_lock(&lock);/* 将等待队列中所有线程唤醒并设置返回值为 -EAGAIN */while (true) {thread = z_unpend_first_thread(&sem->wait_q);if (thread == NULL) {break;}arch_thread_return_value_set(thread, -EAGAIN);z_ready_thread(thread);}/* 同时需要将信号量的值清0 */sem->count = 0;SYS_PORT_TRACING_OBJ_FUNC(k_sem, reset, sem);/* 在开启POLL功能之后,如果调用 k_poll 时信号量为 0 且需要进入等待,* 调用线程并不会被添加到 wait_q 中,* 而是将调用者所使用的 k_poll_event 对象地址放入 sem 中的双向链表 poll_events 中,* 随后切换上下文并进入休眠,直到等待超时或者收到 K_POLL_STATE_SEM_AVAILABLE 信号后,* 会将该线程唤醒,然后再通过 k_sem_take 获取信号量。* 除此之外如果在 k_poll 等待过程中,有线程调用了 k_sem_reset,* 也需要将那些因调用 k_poll 进入等待的线程唤醒,避免因此导致出错。* handle_poll_events 函数则用于发送 K_POLL_STATE_SEM_AVAILABLE 信号。*/handle_poll_events(sem);z_reschedule(&lock, key);
}
  • k_sem_reset 用于重置信号量的技术值,重置时需要将等待的线程唤醒,唤醒分为以下两种情况:
    • 被挂起的线程调用的是k_sem_take,该线程会被添加到 wait_q 中,唤醒即将所有队列中的线程取出并设置为就绪态。
    • 被挂起的线程使用了poll机制,以等待多个IPC事件到来,在这种情况下调用线程不会添加到 wait_q 中,而是将调用者所使用的 k_poll_event 对象放入 poll_events 队列中,如果需要将等待线程唤醒,则需要向 poll_events 中的 k_poll_event 对象发送信号。

示例

在 Zephyr 中,信号量的实现是基于内核对象 k_sem。以下是一个简单的信号量使用示例:

#include <zephyr.h>
#include <kernel.h>
#include <misc/printk.h>K_SEM_DEFINE(my_sem, 0, 1); // 定义一个信号量,初始值为0,最大值为1void taskA(void)
{k_sem_take(&my_sem, K_FOREVER); // 等待信号量printk("Task A is running\n");k_sem_give(&my_sem); // 释放信号量
}void taskB(void)
{printk("Task B is running\n");k_sem_give(&my_sem); // 释放信号量
}void main(void)
{k_thread_spawn(taskA_stack, STACK_SIZE, taskA, NULL, NULL, NULL, PRIORITY, 0, K_NO_WAIT);k_thread_spawn(taskB_stack, STACK_SIZE, taskB, NULL, NULL, NULL, PRIORITY, 0, K_NO_WAIT);
}

在这个示例中,我们定义了一个信号量 my_sem,并在两个任务 taskA 和 taskB 之间使用它来实现同步。任务 A 需要等待任务 B 完成后才能执行。通过使用信号量,我们可以确保任务 A 和任务 B 按照预期的顺序执行。


http://www.ppmy.cn/news/99454.html

相关文章

Java学习笔记20——常用API

常用API 常用APIMath类Math的常用方法 System类System类常用方法 Object类Object类常用方法 Arrays类Arrays常用方法 基本类型包装类Integer类的概述和使用int和String的相互转换自动装箱和拆箱 日期类Date类Date类的常用方法 SimpleDateFormat类SimpleDateFormat的构造方法Sim…

Java输入输出流

目录 一、数据流概念 1.输入输出的概念​ 2.流的概念 3.流的操作 二、常用的流分类 三、文件输入输出流 1.FileReader和FileWriter 2.FileInputStream和FileOutStream 四、复制文件 一、数据流概念 1.输入输出的概念​ 输入输出技术用于处理设备之间的数据传输&#x…

【数据库复习】第六章 关系数据理论 1

关系模式的设计 按照一定的原则从数量众多而又相互关联的数据中&#xff0c;构造出一组既能较好地反映现实世界&#xff0c;而又有良好的操作性能的关系模式 ●冗余度高 ●修改困难 ●插入问题 ●删除问题 ★产生问题的原因 属性间约束关系&#xff08;即数据间的依赖关系&…

dpdk ip分片报文重组处理

dpdk ip报文重组及分片API及处理逻辑介绍 DPDK的分片和重组实现零拷贝&#xff0c;详细介绍可以参阅DPDK分片与重组用户手则 相关数据结构 /** Fragmented packet to reassemble.* First two entries in the frags[] array are for the last and first fragments.*/ struct …

Doris----Rollup表分析及案例实现

ROLLUP 在多维分析中是“上卷”的意思&#xff0c;即将数据按某种指定的粒度进行进一步聚合。 之前的聚合模型: 用户id数据插入时间城市年龄性别最后一次访问的时间该用户的总消费额该用户的最大停留时长该用户的最小停留时长100002017/10/2北京1002017/10/02 08:00:00651521…

GPT-4发布!ChatGPT大升级!太太太太强了!

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 我新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 一觉醒来&#xff0c;万众期待的GPT-4&#xff0c;它来了&#xff01; OpenAI老板Sam Altman直接开门见山地介绍说&#xff1a…

问chartgpt:python的编程规则?

一.问题描述 python的编程规则&#xff1f; 二.ChatGPT的解决方法【实测可行】 &#xff08;一&#xff09;Python编程有一些基本规则需要遵守&#xff0c;这些规则包括&#xff1a; 缩进&#xff1a;Python使用缩进来表示代码块&#xff0c;因此请确保使用一致的缩进方式&a…

RT-Thread 学习笔记:memheap 死机问题的分析与解决

验证环境 NUCLEO-L476RG 开发板&#xff0c;板载 STM32L476RGT6&#xff08;96K SARM1 32K SRAM2&#xff09; Win10 64 位 Keil MDK 5.36 RT-Thread 5.0.1 版本&#xff08;2023-05-28 master 主线&#xff09; bsp : bsp\stm32\stm32l476-st-nucleo 功能描述 最近在研…