HarmonyOS内核开发(事件互斥锁消息队列)

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HarmonyOS内核开发(事件互斥锁消息队列)相关的知识,希望对你有一定的参考价值。

HarmonyOS内核开发

事件

基本概念

事件是一种实现任务间通信的机制,可用于实现任务间的同步,但事件通信只能是事件类型的通信,无数据传输。一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。事件集合用32位无符号整型变量来表示,每一位代表一个事件。
多任务环境下,任务之间往往需要同步操作。事件可以提供一对多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发;多对多同步模型:多个任务等待多个事件的触发。
任务可以通过创建事件控制块来实现对事件的触发和等待操作。LiteOS的事件仅用于任务间的同步。

运作机制

读事件时,可以根据入参事件掩码类型uwEventMask读取事件的单个或者多个事件类型。事件读取成功后,如果设置LOS_WAITMODE_CLR会清除已读取到的事件类型,反之不会清除已读到的事件类型,需显式清除。可以通过入参选择读取模式,读取事件掩码类型中所有事件还是读
取事件掩码类型中任意事件。写事件时,对指定事件写入指定的事件类型,可以一次同时写多个事件类型。写事件会触发任务调度。清除事件时,根据入参事件和待清除的事件类型,对事件对应位进行清0操作。

事件接口

接口名功能描述
osEventFlagsId_t osEventFlagsNew(const osEventFlagsAttr_t *attr)创建事件标记对象
uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags)设置事件标记
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout)等待事件标记触发
osStatus_t osEventFlagsDelete(osEventFlagsId_t ef_id)删除事件标记对象

osEventFlagsNew()

osEventFlagsId_t osEventFlagsNew(const osEventFlagsAttr_t *attr)

描述:

osEventFlagsNew函数创建了一个新的事件标志对象,用于跨线程发送事件,并返回事件标志对象标识符的指针,或者在出现错误时返回NULL。可以在RTOS启动(调用 osKernelStart)之前安全地调用该函数,但不能在内核初始化 (调用 osKernelInitialize)之前调用该函数。

注意 :不能在中断服务调用该函数

参数:

名字描述
attr事件标志属性;空:默认值.

osEventFlagsSet()

uint32_t osEventFlagsSet(osEventFlagsId_t ef_id,uint32_t flags)

描述:
osEventFlagsSet函数在一个由参数ef_id指定的事件标记对象中设置由参数flags指定的事件标记。

注意 :不能在中断服务调用该函数

参数:

名字描述
ef_id事件标志由osEventFlagsNew获得的ID.
flags指定设置的标志.

osEventFlagsWait()

uint32_t osEventFlagsWait(osEventFlagsId_t ef_id,uint32_t flags,uint32_t options,uint32_t timeout)

描述:
osEventFlagsWait函数挂起当前运行线程,直到设置了由参数ef_id指定的事件对象中的任何或所有由参数flags指定的事件标志。当这些事件标志被设置,函数立即返回。否则,线程将被置于阻塞状态。

注意 :如果参数timeout设置为0,可以从中断服务例程调用

参数:

名字描述
ef_id事件标志由osEventFlagsNew获得的ID.
flags指定要等待的标志.
options指定标记选项.
timeout超时时间,0表示不超时

事件案例

业务代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

#define FLAGS_MSK1 0x00000001U

osEventFlagsId_t evt_id; // event flags id

/***** 发送事件 *****/
void Thread_EventSender(void *argument)
{
  (void)argument;
  while (1)
  {
    osEventFlagsSet(evt_id, FLAGS_MSK1);

    //suspend thread
    osThreadYield();

    osDelay(100);
  }
}

/***** 接收事件 *****/
void Thread_EventReceiver(void *argument)
{
  (void)argument;
  uint32_t flags;

  while (1)
  {
    flags = osEventFlagsWait(evt_id, FLAGS_MSK1, osFlagsWaitAny, osWaitForever);
    printf("Receive Flags is %d\\n", flags);
  }
}

/***** 创建事件 *****/
static void Event_example(void)
{
  evt_id = osEventFlagsNew(NULL);
  if (evt_id == NULL)
  {
    printf("Falied to create EventFlags!\\n");
  }

  osThreadAttr_t attr;

  attr.attr_bits = 0U;
  attr.cb_mem = NULL;
  attr.cb_size = 0U;
  attr.stack_mem = NULL;
  attr.stack_size = 1024 * 4;
  attr.priority = 25;

  attr.name = "Thread_EventSender";
  if (osThreadNew(Thread_EventSender, NULL, &attr) == NULL)
  {
    printf("Falied to create Thread_EventSender!\\n");
  }
  attr.name = "Thread_EventReceiver";
  if (osThreadNew(Thread_EventReceiver, NULL, &attr) == NULL)
  {
    printf("Falied to create Thread_EventReceiver!\\n");
  }
}

APP_FEATURE_INIT(Event_example);

烧录并查看

事件案例扩展

上面我们实现了监听一个事件,如何监听多个事件呢?
修改业务代码


#define FLAGS_MSK1 0x00000001U
//新增事件
#define FLAGS_MSK2 0x00000002U
#define FLAGS_MSK3 0x00000003U
.....
void Thread_EventReceiver(void *argument)
{
  (void)argument;
  uint32_t flags;

  while (1)
  {
    //通过|增加两个事件,并把事件连接符改为ALL
    flags = osEventFlagsWait(evt_id, FLAGS_MSK1|FLAGS_MSK2|FLAGS_MSK3, osFlagsWaitAll, osWaitForever);
    printf("Receive Flags is %d\\n", flags);
  }
}

此时重新烧录,Thread_EventReceiver函数没有输出,这是因为没有同时收到FLAGS_MSK1|FLAGS_MSK2|FLAGS_MSK3这三个事件,函数永远阻塞到了这里。

再次修改

void Thread_EventSender(void *argument)
{
  (void)argument;
  while (1)
  {
    osEventFlagsSet(evt_id, FLAGS_MSK1);
    //新增两个事件
    osEventFlagsSet(evt_id, FLAGS_MSK2);
    osEventFlagsSet(evt_id, FLAGS_MSK3);

    //suspend thread
    osThreadYield();

    osDelay(100);
  }
}

令Thread_EventSender函数每次传输三个事件。
烧录并查看

可以看到函数重新开始输出了并且flags变成了3,这是因为监听了三个事件。

互斥锁

概念

1、互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理。
2、任意时刻互斥锁的状态只有两种:开锁或闭锁。
3、当有任务持有时,互斥锁处于闭锁状态,这个任务获得该互斥锁的所有权。
4、当该任务释放时,该互斥锁被开锁,任务失去该互斥锁的所有权。
5、当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。
6、多任务环境下往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。
另外,互斥锁可以解决信号量存在的优先级翻转问题。

运作原理

多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的,需要任务进行独占式处理。互斥锁怎样来避免这种冲突呢?
用互斥锁处理非共享资源的同步访问时,如果有任务访问该资源,则互斥锁为加锁状态。此时其他任务如果想访问这个公共资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个公共资源,保证了公共资源操作的完整性。

互斥锁接口

接口名功能描述
osMutexId_t osMutexNew(const osMutexAttr_t *attr)创建互斥锁
osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout)获取互斥锁
osStatus_t osMutexRelease(osMutexId_t mutex_id)释放互斥锁
osStatus_t osMutexDelete(osMutexId_t mutex_id)删除互斥锁

osMutexNew()

osMutexId_t osMutexNew(const osMutexAttr_t *attr)

描述:

函数osMutexNew创建并初始化一个新的互斥锁对象,并返回指向互斥锁对象标识符的指针,如果出现错误则返回NULL可以在RTOS启动(调用 osKernelStart)之前安全地调用该函数,但不能在内核初始化 (调用 osKernelInitialize)之前调用该函数。

注意 :不能在中断服务调用该函数

参数:

名字描述
attr互斥对象的属性.

osMutexAcquire()

osStatus_t osMutexAcquire(osMutexId_t mutex_id,uint32_t timeout)

描述:
函数osMutexAcquire一直等待,直到参数mutex_id指定的互斥对象可用为止。如果没有其他线程获得互斥锁,该函数立即返回并阻塞互斥锁对象。

注意 :不能在中断服务调用该函数

参数:

名字描述
mutex_id通过osMutexNew获得互斥锁ID.
timeout超时值.

osMutexRelease()

osStatus_t osMutexRelease(osMutexId_t mutex_id)

描述:
函数osMutexRelease释放一个由参数mutex_id指定的互斥量。当前等待这个互斥锁的其他线程将被置于就绪状态。

注意 :不能从中断服务例程调用此函数。

参数:

名字描述
mutex_id通过osMutexNew获得互斥锁ID.

互斥锁案例

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

osMutexId_t mutex_id;

void HighPrioThread(void)
{
  // wait 1s until start actual work
  osDelay(100U);

  while (1)
  {
    // try to acquire mutex
    osMutexAcquire(mutex_id, osWaitForever);

    printf("HighPrioThread is runing.\\r\\n");
    osDelay(300U);
    osMutexRelease(mutex_id);
  }
}

void MidPrioThread(void)
{
  // wait 1s until start actual work
  osDelay(100U);

  while (1)
  {
    printf("MidPrioThread is runing.\\r\\n");
    osDelay(100);
  }
}

void LowPrioThread(void)
{
  while (1)
  {
    osMutexAcquire(mutex_id, osWaitForever);
    printf("LowPrioThread is runing.\\r\\n");

    // block mutex for 3s
    osDelay(300U);
    osMutexRelease(mutex_id);
  }
}

void Mutex_example(void)
{
  osThreadAttr_t attr;

  attr.attr_bits = 0U;
  attr.cb_mem = NULL;
  attr.cb_size = 0U;
  attr.stack_mem = NULL;
  attr.stack_size = 1024 * 4;

  attr.name = "HighPrioThread";
  attr.priority = 24;
  if (osThreadNew((osThreadFunc_t)HighPrioThread, NULL, &attr) == NULL)
  {
    printf("Falied to create HighPrioThread!\\n");
  }
  attr.name = "MidPrioThread";
  attr.priority = 25;
  if (osThreadNew((osThreadFunc_t)MidPrioThread, NULL, &attr) == NULL)
  {
    printf("Falied to create MidPrioThread!\\n");
  }
  attr.name = "LowPrioThread";
  attr.priority = 26;
  if (osThreadNew((osThreadFunc_t)LowPrioThread, NULL, &attr) == NULL)
  {
    printf("Falied to create LowPrioThread!\\n");
  }
  mutex_id = osMutexNew(NULL);
  if (mutex_id == NULL)
  {
    printf("Falied to create Mutex!\\n");
  }
}
APP_FEATURE_INIT(Mutex_example);


可以看到低优先级进程拿到互斥锁后,高优先级进程也无法访问互斥锁。

互斥锁案例扩展

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

osMutexId_t mutex_id;

void HighPrioThread(void)
{
  // wait 1s until start actual work
  osDelay(100U);
  //设置变量存储获取互斥锁状态
  osStatus_t status;
  while (1)
  {
    // try to acquire mutex
    status=osMutexAcquire(mutex_id, osWaitForever);
    if(status != osOK){
      printf("acquire mutex failed\\r\\n");
    }else{
      printf("acquire mutex success\\r\\n");
    }

    printf("HighPrioThread is runing.\\r\\n");
    osDelay(300U);
    osMutexRelease(mutex_id);
  }
}

void MidPrioThread(void)
{
  // wait 1s until start actual work
  osDelay(100U);

  while (1)
  {
    printf("MidPrioThread is runing.\\r\\n");
    osDelay(100);
  }
}

void LowPrioThread(void)
{
  osStatus_t status;
  while (1)
  {
    status=osMutexAcquire(mutex_id, osWaitForever);
     if(status != osOK){
      printf("acquire mutex failed\\r\\n");
    }else{
      printf("acquire mutex success\\r\\n");
    }
    printf("LowPrioThread is runing.\\r\\n");

    // block mutex for 3s
    osDelay(300U);
    osMutexRelease(mutex_id);
  }
}

void Mutex_example(void)
{
  osThreadAttr_t attr;

  attr.attr_bits = 0U;
  attr.cb_mem = NULL;
  attr.cb_size = 0U;
  attr.stack_mem = NULL;
  attr.stack_size = 1024 * 4;

  attr.name = "HighPrioThread";
  attr.priority = 24;
  if (osThreadNew((osThreadFunc_t)HighPrioThread, NULL, &attr) == NULL)
  {
    printf("Falied to create HighPrioThread!\\n");
  }
  attr.name = "MidPrioThread";
  attr.priority = 25;
  if (osThreadNew((osThreadFunc_t)MidPrioThread, NULL, &attr) == NULL)
  {
    printf("Falied to create MidPrioThread!\\n");
  }
  attr.name = "LowPrioThread";
  attr.priority = 26;
  if (osThreadNew((osThreadFunc_t)LowPrioThread, NULL, &attr) == NULL)
  {
    printf("Falied to create LowPrioThread!\\n");
  }
  mutex_id = osMutexNew(NULL);
  if (mutex_id == NULL)
  {
    printf("Falied to create Mutex!\\n");
  }
  //创建后立即删除
  osStatus_t status;
  status=osMutexDelete(mutex_id);
  if(status!=osOK){
    printf("delete mutex failed\\r\\n");
  }else{
    printf("delete mutex success\\r\\n");
  }
  //重复删除
  status=osMutexDelete(mutex_id);
  if(status!=osOK){
    printf("delete mutex failed\\r\\n");
  }else{
    printf("delete mutex success\\r\\n");
  }
}
APP_FEATURE_INIT(Mutex_example);



可以看到当互斥锁被删除后,重复删除,获取,释放等操作均会失败。

消息队列

概念

消息队列,是一种常用于任务间通信的数据结构,实现了接收来自任务或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在自己空间。任务能够从队列里面读取消息,当队列中的消息是空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。
用户在处理业务时,消息队列提供了异步处理机制,允许将一个消息放入队列,但并不立即处理它,同时队列还能起到缓冲消息作用。
LiteOS中使用队列数据结构实现任务异步通信工作,具有如下特性:
1、消息以先进先出方式排队,支持异步读写工作方式。
2、读队列和写队列都支持超时机制。
3、发送消息类型由通信双方约定,可以允许不同长度(不超过队列节点最大值)消息。
4、一个任务能够从任意一个消息队列接收和发送消息。
5、多个任务能够从同一个消息队列接收和发送消息。
6、当队列使用结束后,如果是动态申请的内存,需要通过释放内存函数回收。

运作机制

创建队列时,根据用户传入队列长度和消息节点大小来开辟相应的内存空间以供该队列使用,返回队列ID。
在队列控制块中维护一个消息头节点位置Head和一个消息尾节点位置Tail来表示当前队列中消息存储情况。Head表示队列中被占用消息的起始位置。Tail表示队列中被空闲消息的起始位置。刚创建时Head和Tail均指向队列起始位置。
写队列时,根据Tail找到被占用消息节点末尾的空闲节点作为数据写入对象。
读队列时,根据Head找到最先写入队列中的消息节点进行读取。
删除队列时,根据传入的队列ID寻找到对应的队列,把队列状态置为未使用,释放原队列所占的空间,对应的队列控制头置为初始状态。

接口名功能描述
osMessageQueueId_t osMessageQueueNew(uint32_t msg_count,uint32_t msg_size,const osMessageQueueAttr_t *attr)创建消息队列
osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id,const void *msg_ptr,uint8_t msg_prio,uint32_t timeout)发送消息
osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id,void *msg_ptr,uint8_t *msg_prio,uint32_t timeout)获取消息
osStatus_t osMessageQueueDelete(osMessageQueueId_t mq_id)删除消息队列

osMessageQueueNew()

osMessageQueueId_t osMessageQueueNew(uint32_t msg_count,uint32_t msg_size,const osMessageQueueAttr_t *attr)

描述:

函数osMessageQueueNew创建并初始化一个消息队列对象。该函数返回消息队列对象标识符,如果出现错误则返回NULL,可以在RTOS启动(调用 osKernelStart)之前安全地调用该函数,也可以在内核初始化 (调用 osKernelInitialize)之前调用该函数。

注意 :不能在中断服务调用该函数

参数:

名字描述
msg_count队列中的最大消息数.
msg_size最大消息大小(以字节为单位).
attr消息队列属性;空:默认值.

osMessageQueuePut()

osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id,const void *msg_ptr,uint8_t msg_prio,uint32_t timeout)	

描述:
函数osMessageQueuePut将msg_ptr指向的消息放入参数mq_id指定的消息队列中。

注意 :如果参数timeout设置为0,可以从中断服务例程调用

参数:

名字描述
mq_id由osMessageQueueNew获得的消息队列ID.
msg_ptr要发送的消息.
msg_prio指优先级.
timeout超时值.

osMessageQueueGet()

osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id,void *msg_ptr,uint8_t *msg_prio,uint32_t 	timeout)

描述:
函数osMessageQueueGet从参数mq_id指定的消息队列中检索消息,并将其保存到参数msg_ptr所指向的缓冲区中。

注意 :如果参数timeout设置为0,可以从中断服务例程调用。

参数:

名字描述
mq_id由osMessageQueueNew获得的消息队列ID.
msg_ptr指针指向队列中获取消息的缓冲区指针.
msg_prio指优先级.
timeout超时值.

消息队列案例

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"


//number of Message Queue Objects
#define MSGQUEUE_OBJECTS 16

typedef struct
{
  //object data type
  char *Buf基于条件变量的消息队列

Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁信号量事件队列生产者消费者模型

第1章 简介

HI3861学习笔记——HarmonyOS(CMSIS-RTOS2)互斥锁

消息队列

关于互斥锁,条件变量的内核源码解析