OpenHarmony(鸿蒙南向开发)——轻量系统内核(LiteOS-M)【内核通信机制】

往期知识点记录:

  • 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
  • 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
  • 持续更新中……

事件

基本概念

事件(Event)是一种任务间的通信机制,可用于任务间的同步操作。事件的特点是:

  • 任务间的事件同步,可以一对多,也可以多对多。一对多表示一个任务可以等待多个事件,多对多表示多个任务可以等待多个事件。但是一次写事件最多触发一个任务从阻塞中醒来。

  • 事件读超时机制。

  • 只做任务间同步,不传输具体数据。

提供了事件初始化、事件读写、事件清零、事件销毁等接口。

运行机制

事件控制块

由事件初始化函数配置的一个结构体,在事件读写等操作时作为参数传入,用于标识不同的事件,控制块数据结构如下:

typedef struct tagEvent {UINT32 uwEventID;        /* 事件集合,表示已经处理(写入和清零)的事件集合 */LOS_DL_LIST stEventList; /* 等待特定事件的任务链表 */
} EVENT_CB_S, *PEVENT_CB_S;

事件运作原理

事件初始化:创建一个事件控制块,该控制块维护一个已处理的事件集合,以及等待特定事件的任务链表。

写事件:会向事件控制块写入指定的事件,事件控制块更新事件集合,并遍历任务链表,根据任务等待具体条件满足情况决定是否唤醒相关任务。

读事件:如果读取的事件已存在时,会直接同步返回。其他情况会根据超时时间以及事件触发情况,来决定返回时机:等待的事件条件在超时时间耗尽之前到达,阻塞任务会被直接唤醒,否则超时时间耗尽该任务才会被唤醒。

读事件条件满足与否取决于入参eventMask和mode,eventMask即需要关注的事件类型掩码。mode是具体处理方式,分以下三种情况:

  • LOS_WAITMODE_AND:逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。

  • LOS_WAITMODE_OR:逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。

  • LOS_WAITMODE_CLR:这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。

事件清零:根据指定掩码,去对事件控制块的事件集合进行清零操作。当掩码为0时,表示将事件集合全部清零。当掩码为0xffff时,表示不清除任何事件,保持事件集合原状。

事件销毁:销毁指定的事件控制块。

图1 轻量系统事件运作原理图

接口说明

功能分类接口名描述
事件检测LOS_EventPoll根据eventID,eventMask(事件掩码),mode(事件读取模式),检查用户期待的事件是否发生。
须知:
当mode含LOS_WAITMODE_CLR,且用户期待的事件发生时,此时eventID中满足要求的事件会被清零,这种情况下eventID既是入参也是出参。其他情况eventID只作为入参。
初始化LOS_EventInit事件控制块初始化。
事件读LOS_EventRead读事件(等待事件),任务会根据timeOut(单位:tick)进行阻塞等待;
未读取到事件时,返回值为0;
正常读取到事件时,返回正值(事件发生的集合);
其他情况返回特定错误码。
事件写LOS_EventWrite写一个特定的事件到事件控制块。
事件清除LOS_EventClear根据events掩码,清除事件控制块中的事件。
事件销毁LOS_EventDestroy事件控制块销毁。

开发流程

事件的典型开发流程:

  1. 初始化事件控制块

  2. 阻塞读事件控制块

  3. 写入相关事件

  4. 阻塞任务被唤醒,读取事件并检查是否满足要求

  5. 处理事件控制块

  6. 事件控制块销毁

说明:

  • 进行事件读写操作时,事件的第25bit(0x02U << 24)为保留bit位,不可以进行位设置。

  • 对同一事件反复写入,算作一次写入。

编程实例

实例描述

示例中,任务ExampleEvent创建一个任务EventReadTask,EventReadTask读事件阻塞,ExampleEvent向该任务写事件。可以通过示例日志中打印的先后顺序理解事件操作时伴随的任务切换。

  1. 在任务ExampleEvent创建任务EventReadTask,其中任务EventReadTask优先级高于ExampleEvent。

  2. 在任务EventReadTask中读事件0x00000001,阻塞,发生任务切换,执行任务ExampleEvent。

  3. 在任务ExampleEvent写事件0x00000001,发生任务切换,执行任务EventReadTask。

  4. EventReadTask得以执行,直到任务结束。

  5. ExampleEvent得以执行,直到任务结束。

示例代码

示例代码如下:

本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleEvent。

#include "los_event.h"
#include "los_task.h"/* 事件控制结构体 */
EVENT_CB_S g_exampleEvent;/* 等待的事件类型 */
#define EVENT_WAIT 0x00000001/* 等待超时时间 */
#define EVENT_TIMEOUT 100/* 用例任务入口函数 */
VOID EventReadTask(VOID)
{UINT32 ret;UINT32 event;/* 超时等待方式读事件,超时时间为100 ticks, 若100 ticks后未读取到指定事件,读事件超时,任务直接唤醒 */printf("Example_Event wait event 0x%x \n", EVENT_WAIT);event = LOS_EventRead(&g_exampleEvent, EVENT_WAIT, LOS_WAITMODE_AND, EVENT_TIMEOUT);if (event == EVENT_WAIT) {printf("Example_Event, read event :0x%x\n", event);} else {printf("Example_Event, read event timeout\n");}
}UINT32 ExampleEvent(VOID)
{UINT32 ret;UINT32 taskId;TSK_INIT_PARAM_S taskParam = { 0 };/* 事件初始化 */ret = LOS_EventInit(&g_exampleEvent);if (ret != LOS_OK) {printf("init event failed .\n");return LOS_NOK;}/* 创建任务 */taskParam.pfnTaskEntry = (TSK_ENTRY_FUNC)EventReadTask;taskParam.pcName       = "EventReadTask";taskParam.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;taskParam.usTaskPrio   = 3;ret = LOS_TaskCreate(&taskId, &taskParam);if (ret != LOS_OK) {printf("task create failed.\n");return LOS_NOK;}/* 写事件 */printf("Example_TaskEntry write event.\n");ret = LOS_EventWrite(&g_exampleEvent, EVENT_WAIT);if (ret != LOS_OK) {printf("event write failed.\n");return LOS_NOK;}/* 清标志位 */printf("EventMask:%d\n", g_exampleEvent.uwEventID);LOS_EventClear(&g_exampleEvent, ~g_exampleEvent.uwEventID);printf("EventMask:%d\n", g_exampleEvent.uwEventID);/* 删除事件 */ret = LOS_EventDestroy(&g_exampleEvent);if (ret != LOS_OK) {printf("destory event failed .\n");return LOS_NOK;}return LOS_OK;
}

结果验证

编译运行得到的结果为:

Example_Event wait event 0x1
Example_TaskEntry write event.
Example_Event, read event :0x1
EventMask:1
EventMask:0

互斥锁

基本概念

互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理。

任意时刻互斥锁的状态只有两种,开锁或闭锁。当任务持有互斥锁时,该互斥锁处于闭锁状态,这个任务获得该互斥锁的所有权。当该任务释放互斥锁时,该互斥锁被开锁,任务失去该互斥锁的所有权。当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。

多任务环境下往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。另外互斥锁可以解决信号量存在的优先级翻转问题。

运行机制

多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的,需要任务进行独占式处理。互斥锁怎样来避免这种冲突呢?

用互斥锁处理非共享资源的同步访问时,如果有任务访问该资源,则互斥锁为加锁状态。此时其他任务如果想访问这个公共资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个公共资源,保证了公共资源操作的完整性。

图1 轻量系统互斥锁运作示意图

接口说明

表1 互斥锁模块接口

功能分类接口描述
互斥锁的创建和删除LOS_MuxCreate:创建互斥锁。
LOS_MuxDelete:删除指定的互斥锁。
互斥锁的申请和释放LOS_MuxPend:申请指定的互斥锁。
LOS_MuxPost:释放指定的互斥锁。

开发流程

互斥锁典型场景的开发流程:

  1. 创建互斥锁LOS_MuxCreate。

  2. 申请互斥锁LOS_MuxPend。 申请模式有三种:无阻塞模式、永久阻塞模式、定时阻塞模式。

  • 无阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有任务持有,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功。否则直接返回并继续运行当前任务,不会产生阻塞。
  • 永久阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有被占用,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。
  • 定时阻塞模式:任务需要申请互斥锁,若该互斥锁当前没有被占用,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后,指定时间超时前有其他任务释放该互斥锁,或者用户指定时间超时后,阻塞任务才会重新得以执行。
  1. 释放互斥锁LOS_MuxPost。

    • 如果有任务阻塞于指定互斥锁,则唤醒被阻塞任务中优先级高的,该任务进入就绪态,并进行任务调度;
    • 如果没有任务阻塞于指定互斥锁,则互斥锁释放成功。
  2. 删除互斥锁LOS_MuxDelete。

说明:

  • 互斥锁支持嵌套,即申请该互斥锁的任务与已经持有该互斥锁的任务为同一个任务时会认为申请成功,按申请次数对应的去释放该锁即可。

  • 互斥锁不能在中断服务程序中使用。

  • LiteOS-M内核作为实时操作系统需要保证任务调度的实时性,尽量避免任务的长时间阻塞,因此在获得互斥锁之后,应该尽快释放互斥锁。

  • 持有互斥锁的过程中,不得再调用LOS_TaskPriSet等接口更改持有互斥锁任务的优先级。

编程实例

实例描述

本实例实现如下流程。

  1. 任务ExampleMutex创建一个互斥锁,锁任务调度,创建两个任务ExampleMutexTask1、ExampleMutexTask2。ExampleMutexTask2优先级高于ExampleMutexTask1,解锁任务调度。

  2. ExampleMutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,ExampleMutexTask2挂起,ExampleMutexTask1被唤醒。

  3. ExampleMutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被ExampleMutexTask2持有,ExampleMutexTask1挂起。10Tick超时时间到达后,ExampleMutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被ExampleMutexTask2持有,ExampleMutexTask1挂起。

  4. 100Tick休眠时间到达后,ExampleMutexTask2被唤醒, 释放互斥锁,唤醒ExampleMutexTask1。ExampleMutexTask1成功获取到互斥锁后,释放并删除互斥锁。

示例代码

示例代码如下:

本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleMutex。

#include "los_mux.h"/* 互斥锁句柄 */
UINT32 g_testMux;VOID ExampleMutexTask1(VOID)
{UINT32 ret;printf("task1 try to get  mutex, wait 10 ticks.\n");/* 申请互斥锁 */ret = LOS_MuxPend(g_testMux, 10);if (ret == LOS_OK) {printf("task1 get mutex g_testMux.\n");/* 释放互斥锁,这个分支正常不应该进来 */LOS_MuxPost(g_testMux);LOS_MuxDelete(g_testMux);return;}if (ret == LOS_ERRNO_MUX_TIMEOUT ) {printf("task1 timeout and try to get mutex, wait forever.\n");/* 申请互斥锁 */ret = LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);if (ret == LOS_OK) {printf("task1 wait forever, get mutex g_testMux.\n");/* 释放互斥锁 */LOS_MuxPost(g_testMux);/* 删除互斥锁 */LOS_MuxDelete(g_testMux);printf("task1 post and delete mutex g_testMux.\n");return;}}return;
}VOID ExampleMutexTask2(VOID)
{printf("task2 try to get  mutex, wait forever.\n");/* 申请互斥锁 */(VOID)LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);printf("task2 get mutex g_testMux and suspend 100 ticks.\n");/* 任务休眠100Ticks */LOS_TaskDelay(100);printf("task2 resumed and post the g_testMux\n");/* 释放互斥锁 */LOS_MuxPost(g_testMux);return;
}UINT32 ExampleMutex(VOID)
{UINT32 ret;TSK_INIT_PARAM_S task1 = { 0 };TSK_INIT_PARAM_S task2 = { 0 };UINT32 taskId01;UINT32 taskId02;/* 创建互斥锁 */LOS_MuxCreate(&g_testMux);/* 锁任务调度 */LOS_TaskLock();/* 创建任务1 */task1.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleMutexTask1;task1.pcName       = "MutexTsk1";task1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;task1.usTaskPrio   = 5;ret = LOS_TaskCreate(&taskId01, &task1);if (ret != LOS_OK) {printf("task1 create failed.\n");return LOS_NOK;}/* 创建任务2 */task2.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleMutexTask2;task2.pcName       = "MutexTsk2";task2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;task2.usTaskPrio   = 4;ret = LOS_TaskCreate(&taskId02, &task2);if (ret != LOS_OK) {printf("task2 create failed.\n");return LOS_NOK;}/* 解锁任务调度 */LOS_TaskUnlock();return LOS_OK;
}

结果验证

编译运行得到的结果为:

task2 try to get  mutex, wait forever.
task2 get mutex g_testMux and suspend 100 ticks.
task1 try to get  mutex, wait 10 ticks.
task1 timeout and try to get mutex, wait forever.
task2 resumed and post the g_testMux
task1 wait forever, get mutex g_testMux.
task1 post and delete mutex g_testMux.

消息队列

基本概念

消息队列又称队列,是一种任务间通信的机制。消息队列接收来自任务或中断的不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中。

任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。

可以通过调整读队列和写队列的超时时间来调整读写接口的阻塞模式,如果将读队列和写队列的超时时间设置为0,就不会挂起任务,接口会直接返回,这就是非阻塞模式。反之,如果将读队列和写队列的超时时间设置为大于0的时间,就会以阻塞模式运行。

消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用,可以使用队列实现任务异步通信,队列具有如下特性:

  • 消息以先进先出的方式排队,支持异步读写。
  • 读队列和写队列都支持超时机制。
  • 每读取一条消息,就会将该消息节点设置为空闲。
  • 发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息。
  • 一个任务能够从任意一个消息队列接收和发送消息。
  • 多个任务能够从同一个消息队列接收和发送消息。
  • 创建普通队列时所需的队列空间,由系统自行动态申请内存。
  • 创建静态队列时所需的队列空间,由用户传入。这块空间在队列删除之后也由用户去释放。

运行机制

队列控制块

队列会在初始化时给分配一个属于自己的控制块,控制块包含了队列的名称、状态等信息。删除队列时会释放该控制块。

队列控制块数据结构如下:

typedef struct 
{UINT8       *queue;                          		/* 队列消息内存空间的指针 */UINT8 		*queueName								/* 队列名称 */UINT16      queueState;                      		/* 队列状态 */UINT16      queueLen;                        		/* 队列中消息节点个数,即队列长度 */UINT16      queueSize;                       		/* 消息节点大小 */UINT16      queueID;                         		/* 队列ID */UINT16      queueHead;                       		/* 消息头节点位置(数组下标)*/UINT16      queueTail;                       		/* 消息尾节点位置(数组下标)*/UINT16      readWriteableCnt[OS_READWRITE_LEN]; 	/* 数组下标0的元素表示队列中可读消息数,                              数组下标1的元素表示队列中可写消息数 */LOS_DL_LIST readWriteList[OS_READWRITE_LEN];    	/* 读取或写入消息的任务等待链表, 下标0:读取链表,下标1:写入链表 */LOS_DL_LIST memList;                         		/* 内存块链表 */
} LosQueueCB;

每个队列控制块中都含有队列状态,表示该队列的使用情况:

  • OS_QUEUE_UNUSED:队列未被使用。
  • OS_QUEUE_INUSED:队列被使用中。

队列运作原理

  • 创建队列时,创建队列成功会返回队列ID。
  • 在队列控制块中维护着一个消息头节点位置Head和一个消息尾节点位置Tail,用于表示当前队列中消息的存储情况。Head表示队列中被占用的消息节点的起始位置。Tail表示被占用的消息节点的结束位置,也是空闲消息节点的起始位置。队列刚创建时,Head和Tail均指向队列起始位置。
  • 写队列时,根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)队列进行写操作。写队列支持两种写入方式:向队列尾节点写入,也可以向队列头节点写入。尾节点写入时,根据Tail找到起始空闲消息节点作为数据写入对象,如果Tail已经指向队列尾部则采用回卷方式。头节点写入时,将Head的前一个节点作为数据写入对象,如果Head指向队列起始位置则采用回卷方式。
  • 读队列时,根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)队列进行读操作会引起任务挂起。如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。如果Head已经指向队列尾部则采用回卷方式。
  • 删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态,并释放队列所占内存。

图1 队列读写数据操作示意图

上图对读写队列做了示意,图中只画了尾节点写入方式,没有画头节点写入,但是两者是类似的。

接口说明

功能分类接口描述
创建/删除消息队列LOS_QueueCreate:创建一个消息队列,由系统动态申请队列空间。
LOS_QueueCreateStatic:创建一个消息队列,由用户传入队列空间。
LOS_QueueDelete:根据队列ID删除一个指定队列,静态消息队列删除后,队列空间需要用例自行处理。
读/写队列(不带拷贝)LOS_QueueRead:读取指定队列头节点中的数据(队列节点中的数据实际上是一个地址)。
LOS_QueueWrite:向指定队列尾节点中写入入参bufferAddr的值(即buffer的地址)。
LOS_QueueWriteHead:向指定队列头节点中写入入参bufferAddr的值(即buffer的地址)。
读/写队列(带拷贝)LOS_QueueReadCopy:读取指定队列头节点中的数据。
LOS_QueueWriteCopy:向指定队列尾节点中写入入参bufferAddr中保存的数据。
LOS_QueueWriteHeadCopy:向指定队列头节点中写入入参bufferAddr中保存的数据。
获取队列信息LOS_QueueInfoGet:获取指定队列的信息,包括队列ID、队列长度、消息节点大小、头节点、尾节点、可读节点数量、可写节点数量、等待读操作的任务、等待写操作的任务。

开发流程

  1. 用LOS_QueueCreate创建队列。创建成功后,可以得到队列ID。
  2. 通过LOS_QueueWrite或者LOS_QueueWriteCopy写队列。
  3. 通过LOS_QueueRead或者LOS_QueueReadCopy读队列。
  4. 通过LOS_QueueInfoGet获取队列信息。
  5. 通过LOS_QueueDelete删除队列。

说明:

  • 系统支持的最大队列数是指:整个系统的队列资源总个数,而非用户能使用的个数。例如:系统软件定时器多占用一个队列资源,那么用户能使用的队列资源就会减少一个。

  • 创建队列时传入的队列名和flags暂时未使用,作为以后的预留参数。

  • 队列接口函数中的入参timeOut是相对时间。

  • LOS_QueueReadCopy和LOS_QueueWriteCopy及LOS_QueueWriteHeadCopy是一组接口,LOS_QueueRead和LOS_QueueWrite及LOS_QueueWriteHead是一组接口,每组接口需要配套使用。

  • 鉴于LOS_QueueWrite和LOS_QueueWriteHead和LOS_QueueRead这组接口实际操作的是数据地址,用户必须保证调用LOS_QueueRead获取到的指针所指向的内存区域在读队列期间没有被异常修改或释放,否则可能导致不可预知的后果。

  • LOS_QueueReadCopy接口的读取长度如果小于消息实际长度,消息将被截断。

  • 鉴于LOS_QueueWrite和LOS_QueueWriteHead和LOS_QueueRead这组接口实际操作的是数据地址,也就意味着实际写和读的消息长度仅仅是一个指针数据,因此用户使用这组接口之前,需确保创建队列时的消息节点大小,为一个指针的长度,避免不必要的浪费和读取失败。

编程实例

实例描述

创建一个队列,两个任务。任务1调用写队列接口发送消息,任务2通过读队列接口接收消息。

  1. 通过LOS_TaskCreate创建任务1和任务2。
  2. 通过LOS_QueueCreate创建一个消息队列。
  3. 在任务1 SendEntry中发送消息。
  4. 在任务2 RecvEntry中接收消息。
  5. 通过LOS_QueueDelete删除队列。

示例代码

示例代码如下:

本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleQueue。

#include "los_task.h"
#include "los_queue.h"STATIC UINT32 g_queue;
#define BUFFER_LEN 50VOID SendEntry(VOID)
{UINT32 ret = 0;CHAR abuf[] = "test message";UINT32 len = sizeof(abuf);ret = LOS_QueueWriteCopy(g_queue, abuf, len, 0);if (ret != LOS_OK) {printf("send message failure, error: %x\n", ret);}
}VOID RecvEntry(VOID)
{UINT32 ret = 0;CHAR readBuf[BUFFER_LEN] = {0};UINT32 readLen = BUFFER_LEN;/* 休眠1s */usleep(1000000);ret = LOS_QueueReadCopy(g_queue, readBuf, &readLen, 0);if (ret != LOS_OK) {printf("recv message failure, error: %x\n", ret);}printf("recv message: %s.\n", readBuf);ret = LOS_QueueDelete(g_queue);if (ret != LOS_OK) {printf("delete the queue failure, error: %x\n", ret);}printf("delete the queue success.\n");
}UINT32 ExampleQueue(VOID)
{printf("start queue example.\n");UINT32 ret = 0;UINT32 task1;UINT32 task2;TSK_INIT_PARAM_S taskParam1 = { 0 };TSK_INIT_PARAM_S taskParam2 = { 0 };LOS_TaskLock();taskParam1.pfnTaskEntry = (TSK_ENTRY_FUNC)SendEntry;taskParam1.usTaskPrio = 9;taskParam1.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;taskParam1.pcName = "SendQueue";ret = LOS_TaskCreate(&task1, &taskParam1);if(ret != LOS_OK) {printf("create task1 failed, error: %x\n", ret);return ret;}taskParam2.pfnTaskEntry = (TSK_ENTRY_FUNC)RecvEntry;taskParam2.usTaskPrio = 10;taskParam2.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;taskParam2.pcName = "RecvQueue";ret = LOS_TaskCreate(&task2, &taskParam2);if(ret != LOS_OK) {printf("create task2 failed, error: %x\n", ret);return ret;}ret = LOS_QueueCreate("queue", 5, &g_queue, 0, 50);if(ret != LOS_OK) {printf("create queue failure, error: %x\n", ret);}printf("create the queue success.\n");LOS_TaskUnlock();return ret;
}

结果验证

编译运行得到的结果为:

start queue example.
create the queue success.
recv message: test message.
delete the queue success.

信号量

基本概念

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。

一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:

  • 0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。

  • 正值,表示该信号量当前可被获取。

信号量可用于同步或者互斥。以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:

  • 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。

  • 用作同步时,初始信号量计数值为0。任务1因获取不到信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。

运行机制

信号量控制块

/*** 信号量控制块数据结构*/
typedef struct {UINT16            semStat;          /* 信号量状态 */UINT16            semType;          /* 信号量类型 */UINT16            semCount;         /* 信号量计数 */UINT16            semId;            /* 信号量索引号 */LOS_DL_LIST       semList;          /* 用于插入阻塞于信号量的任务 */
} LosSemCB;

信号量运作原理

信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过LOSCFG_BASE_IPC_SEM_LIMIT宏实现,按产品实际需要设定),并把所有信号量初始化成未使用,加入到未使用链表中供系统使用。

信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。

信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。

信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。

信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。

信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。当访问资源的任务数达到该资源允许的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。

图1 轻量系统信号量运作示意图

接口说明

功能分类接口描述
创建/删除信号量LOS_SemCreate:创建信号量,返回信号量ID。
LOS_BinarySemCreate:创建二值信号量,其计数值最大为1。
LOS_SemDelete:删除指定的信号量。
申请/释放信号量LOS_SemPend:申请指定的信号量,并设置超时时间。
LOS_SemPost:释放指定的信号量。

开发流程

  1. 创建信号量LOS_SemCreate,若要创建二值信号量则调用LOS_BinarySemCreate。

  2. 申请信号量LOS_SemPend。

  3. 释放信号量LOS_SemPost。

  4. 删除信号量LOS_SemDelete。

说明: 由于中断不能被阻塞,因此不能在中断中使用阻塞模式申请信号量。

编程实例

实例描述

本实例实现如下功能:

  1. 测试任务ExampleSem创建一个信号量,锁任务调度。创建两个任务ExampleSemTask1和ExampleSemTask2, ExampleSemTask2优先级高于ExampleSemTask1。两个任务中申请同一信号量,解锁任务调度后两任务阻塞,测试任务ExampleSem释放信号量。

  2. ExampleSemTask2得到信号量,被调度,然后任务休眠20Tick,ExampleSemTask2延迟,ExampleSemTask1被唤醒。

  3. ExampleSemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被ExampleSemTask2持有,ExampleSemTask1挂起,10Tick后仍未得到信号量,ExampleSemTask1被唤醒,试图以永久阻塞模式申请信号量,ExampleSemTask1挂起。

  4. 20Tick后ExampleSemTask2唤醒, 释放信号量后,ExampleSemTask1得到信号量被调度运行,最后释放信号量。

  5. ExampleSemTask1执行完,400Tick后任务ExampleSem被唤醒,执行删除信号量。

示例代码

示例代码如下:

本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleSem。

#include "los_sem.h"/* 信号量结构体id */
static UINT32 g_semId;VOID ExampleSemTask1(VOID)
{UINT32 ret;printf("ExampleSemTask1 try get sem g_semId, timeout 10 ticks.\n");/* 定时阻塞模式申请信号量,定时时间为10ticks */ret = LOS_SemPend(g_semId, 10);/* 申请到信号量 */if (ret == LOS_OK) {LOS_SemPost(g_semId);return;}/* 定时时间到,未申请到信号量 */if (ret == LOS_ERRNO_SEM_TIMEOUT) {printf("ExampleSemTask1 timeout and try get sem g_semId wait forever.\n");/*永久阻塞模式申请信号量*/ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);printf("ExampleSemTask1 wait_forever and get sem g_semId.\n");if (ret == LOS_OK) {LOS_SemPost(g_semId);return;}}
}VOID ExampleSemTask2(VOID)
{UINT32 ret;printf("ExampleSemTask2 try get sem g_semId wait forever.\n");/* 永久阻塞模式申请信号量 */ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);if (ret == LOS_OK) {printf("ExampleSemTask2 get sem g_semId and then delay 20 ticks.\n");}/* 任务休眠20 ticks */LOS_TaskDelay(20);printf("ExampleSemTask2 post sem g_semId.\n");/* 释放信号量 */LOS_SemPost(g_semId);return;
}UINT32 ExampleSem(VOID)
{UINT32 ret;TSK_INIT_PARAM_S task1 = { 0 };TSK_INIT_PARAM_S task2 = { 0 };UINT32 taskId1;UINT32 taskId2;/* 创建信号量 */LOS_SemCreate(0, &g_semId);/* 锁任务调度 */LOS_TaskLock();/* 创建任务1 */task1.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleSemTask1;task1.pcName       = "TestTask1";task1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;task1.usTaskPrio   = 5;ret = LOS_TaskCreate(&taskId1, &task1);if (ret != LOS_OK) {printf("task1 create failed.\n");return LOS_NOK;}/* 创建任务2 */task2.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleSemTask2;task2.pcName       = "TestTask2";task2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;task2.usTaskPrio   = 4;ret = LOS_TaskCreate(&taskId2, &task2);if (ret != LOS_OK) {printf("task2 create failed.\n");return LOS_NOK;}/* 解锁任务调度 */LOS_TaskUnlock();ret = LOS_SemPost(g_semId);/* 任务休眠400 ticks */LOS_TaskDelay(400);/* 删除信号量 */LOS_SemDelete(g_semId);return LOS_OK;
}

结果验证

编译运行得到的结果为:

ExampleSemTask2 try get sem g_semId wait forever.
ExampleSemTask1 try get sem g_semId, timeout 10 ticks.
ExampleSemTask2 get sem g_semId and then delay 20 ticks.
ExampleSemTask1 timeout and try get sem g_semId wait forever.
ExampleSemTask2 post sem g_semId.
ExampleSemTask1 wait_forever and get sem g_semId.

最后

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

鸿蒙开发面试真题(含参考答案):

在这里插入图片描述

《OpenHarmony源码解析》:

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1558601.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

NVIDIA 多实例 GPU

单个 GPU 中包含七个独立实例。 文章目录 前言一、优势1. 扩展 GPU 的使用范围2. 优化 GPU 利用率3. 运行同步工作负载二、 技术原理1. 根据需要调配和配置实例2. 安全、并行运行工作负载三、Blackwell GPU 中的 MIG四、为 IT 和开发运营而打造1. 从数据中心部署到边缘2. 利用容…

【一分钟学C++】指针和引用

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~ 公众号&#xff1a; C学习与探索 | 个人主页&#xff1a; rainInSunny | 个人专栏&#xff1a; Learn OpenGL In Qt 文章目录 指针普通指针函数指针注意事项 引用左值引用右值引用注意事项 指针和引用区别 指针 普通指针 指针是一个…

宝兰德加入中国交通运输协会信息专业委员会,携手共绘交通行业信息化新篇章

近日&#xff0c;中国交通运输协会信息专业委员会&#xff08;以下简称信专委&#xff09;第四届会员代表大会暨第四届一次理事会扩大会议在北京成功举行。宝兰德受邀出席会议&#xff0c;会议总结了第三届理事会的工作&#xff0c;修改了信专委工作规则&#xff0c;选举产生了…

Android Camera系列(四):TextureView+OpenGL ES+Camera

别人贪婪时我恐惧&#xff0c;别人恐惧时我贪婪 Android Camera系列&#xff08;一&#xff09;&#xff1a;SurfaceViewCamera Android Camera系列&#xff08;二&#xff09;&#xff1a;TextureViewCamera Android Camera系列&#xff08;三&#xff09;&#xff1a;GLSur…

一键生成PPT在线使用的保姆级教程:告别加班就靠它

已经过完24年所有的法定节假日的你&#xff0c;上班状态还好吗&#xff1f; 小编人倒是挺飘忽的&#xff0c;就那种人在工位&#xff0c;魂仍在青青大草原的感觉&#xff0c;都是牛马却失去了自由奔跑的权利...... 尤其是还要面对节前一堆没完成的工作&#xff0c;手动完成不…

Day-1 java入门

什么是JAVA? Java是美国的sun 公司(Stanford University Network)在1995年推出的一门计算机高级编程语言。 sun公司于2009年被Oracle(甲骨文)公司收购。 普遍认同Java的联合创始人之一:詹姆斯 高斯林 (James Gosling)为ava之父。 JAVA三大技术平台 Java SE(Java Standa…

大模型 + 在线运行 + ISRealsoft ⇒ 编程运行验证一体化

最近在撰写《计算机的错误计算》系列内容。其中用到三种在线工具&#xff0c;分别是大语言模型、在线运行软件以及 ISRealsoft 在线软件。 系列主要介绍各种软硬件关于数值计算的误差或错误计算。比如&#xff0c;各种数学库 math 中涉及的函数的误差&#xff0c;或算术表达式&…

用伪代码Prompt让LLM进行图推理,生成更精准内容

最近有研究发现&#xff0c;当LLM面对结构化数据&#xff0c;特别是图数据时&#xff0c;LLM的表现却不尽如人意。这几天&#xff0c;来自希腊和法国的研究团队提出了一种创新方法——利用伪代码提示来增强LLM的图推理能力。我基于这项研究先写了一个伪代码的SYSYTEM PROMPT运行…

【智能算法应用】正切搜索算法求解二维路径规划问题

摘要 本文提出了基于正切搜索算法的二维路径规划方法&#xff0c;用于解决包含障碍物的复杂路径规划问题。通过在二维平面中建立障碍物模型和路径目标点&#xff0c;利用正切搜索算法进行路径搜索&#xff0c;找出从起点到终点的最优路径。实验结果显示&#xff0c;该算法在不…

5. PH47 代码框架软件开发环境搭建

5.1. 概述 PH47 软件开发环境搭建比较简单&#xff0c;但毫无疑问非常重要。主要涉及到 stm32 编译链接工具 Keil&#xff1b;代码编辑器 Visual Studio 或者 VS code。 若需要更进一步进行 PH47 框架在不同 stm32 芯片间移植&#xff0c;那么还需要Stm32CubeMx 初始化代码生成…

Python 如何使用 SQLAlchemy 进行复杂查询

Python 如何使用 SQLAlchemy 进行复杂查询 一、引言 SQLAlchemy 是 Python 生态系统中非常流行的数据库处理库&#xff0c;它提供了一种高效、简洁的方式与数据库进行交互。SQLAlchemy 是一个功能强大的数据库工具&#xff0c;支持结构化查询语言&#xff08;SQL&#xff09;…

小白入门《大模型应用开发极简入门》学习成为善用 AI 的人!

《大模型应用开发极简入门&#xff1a;基于 GPT-4 和 ChatGPT》这本书旨在为读者提供一个从零开始&#xff0c;快速掌握大语言模型&#xff08;LLM&#xff09;开发的入门指南&#xff0c;特别是基于 GPT-4 和 ChatGPT 的应用开发。书中内容涵盖了大模型的基础概念、架构原理、…

PCL 计算点云包围盒

目录 一、概述二、代码三、结果 一、概述 PCL中计算点云包围盒的简单使用案例 二、代码 moment_of_inertia.cpp #include <vector> #include <thread>#include <pcl/features/moment_of_inertia_estimation.h> #include <pcl/io/pcd_io.h> #include…

使用java分别输出二叉树的深度遍历和广度遍历

代码功能 这段Java代码定义了一个二叉树&#xff0c;并实现了两种遍历方法&#xff1a;深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&#xff08;BFS&#xff09;。通过DFS&#xff0c;代码从根节点开始&#xff0c;优先访问子节点&#xff0c;直至最深的节点&…

常用的十款文件加密软件分享|2024办公文件怎么加密?赶快码住!

在现代办公环境中&#xff0c;数据安全和隐私保护变得尤为重要&#xff0c;尤其是随着远程办公、跨平台协作的普及&#xff0c;文件的加密需求大大增加。为了保障敏感信息的安全性&#xff0c;选择合适的加密软件成为必不可少的一步。本文将为大家推荐2024年常用的十款文件加密…

‌视频画面添加滚动字幕剪辑:提升观众体验的创意技巧

在视频制作中&#xff0c;字幕不仅是传达信息的重要工具&#xff0c;也是提升观众体验的关键元素。本文将探讨如何在视频画面中添加滚动字幕剪辑&#xff0c;以提升观众的观看体验。 1打开软件&#xff0c;在功能栏里切换到“任务剪辑”版块上 2添加原视频导入到表格里&#x…

简单花20分钟学会top 命令手册 (linux上的任务管理器)

1. 介绍 top 是一个常用的 Linux 命令行工具&#xff0c;用于实时监视系统资源和进程的运行情况。用户可以通过 top 命令查看系统的 CPU 使用率、内存占用情况、进程列表等重要信息&#xff0c;帮助快速了解系统运行状态并进行性能监控。该工具可以认为相当于windows上的任务管…

探索Theine:Python中的AI缓存新贵

文章目录 探索Theine&#xff1a;Python中的AI缓存新贵背景&#xff1a;为何选择Theine&#xff1f;Theine是什么&#xff1f;如何安装Theine&#xff1f;简单的库函数使用方法场景应用场景一&#xff1a;Web应用缓存场景二&#xff1a;分布式系统中的数据共享场景三&#xff1…

【DFDT】DFDT: An End-to-End DeepFake Detection Framework Using Vision Transformer

文章目录 DFDT: An End-to-End DeepFake Detection Framework Using Vision Transformerkey points贡献方法补丁提取和嵌入基于注意力的补丁选择多流transformer块多尺度分类器实验DFDT: An End-to-End DeepFake Detection Framework Using Vision Transformer 会议/期刊:App…

Java 函数式编程(1 万字)

此笔记来自于B站黑马程序员 good Java 历史版本及其优势 函数式编程, Stream API 一.函数伊始函数、函数对象 函数对象 行为参数法 延迟执行 a-lambda b-方法引用 复习小测 Math::random () -> Math.random()Math::sqrt (double number) -> Math.sqrt(number)Student:…