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

article/2025/7/19 8:37:52

📌往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

①📖 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

②📖嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

③📖 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

④📖 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

⑤📖 记录一场鸿蒙开发岗位面试经历~

⑥📖 持续更新中……


事件

基本概念

事件(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.

http://www.hkcw.cn/article/fnAyXxCDZp.shtml

相关文章

【鸿蒙OH 5.0】OpenHarmony标准系统方案之瑞芯微RK3568移植案例(一)

&#x1f4dd;往期推文全新看点&#xff08;文中附带最新鸿蒙全栈学习笔记&#xff09; &#x1f6a9; 鸿蒙&#xff08;HarmonyOS&#xff09;北向开发知识点记录~ &#x1f6a9; 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ &#x1f6a9; 鸿蒙应用…

【错误记录】Windows 中 DevEco Studio 真机调试无法连接设备 ( 低版本的 HarmonyOS 4.2.0 华为手机无法在 DevEco Studio 5.0.2 上真机调试 )

文章目录 一、错误记录二、问题排查三、解决方案 参考文档 : hdc&#xff08;HarmonyOS Device Connector&#xff09;文档设备连接后&#xff0c;无法识别设备的处理指导真机调试流程 一、错误记录 手机 使用的是 HarmonyOS 4.2.0 系统 ; 使用 HarmonyOS 的 hdc 工具 , 执行 …

原生鸿蒙应用市场开发者服务的技术解析:从集成到应用发布的完整体验

文章目录 引言一、鸿蒙原生应用的高效开发二、用户隐私保护&#xff1a;安全访问管理三、开发者实用工具&#xff1a;应用分析与A/B测试四、应用审核与分发&#xff1a;快速上线4.1 应用加密&#xff1a;保护代码安全4.2 自动化测试与检测前移&#xff1a;提升应用质量 五、结语…

迪士尼打架是否互殴还需细心调查 因拍照起争执

5月31日,有网友发布视频称,在上海迪士尼发生了一起冲突事件,一对情侣和一家三口发生了争执并动手。视频中可以看到双方在现场扭打,周围的人纷纷上前劝阻。6月1日,当地相关部门透露,这起事件发生在5月31日下午,地点是迪士尼疯狂动物城的一处拍照打卡点。双方因拍照问题产…

从奥运会到法网,郑钦文在“福地”取得跨年10连胜! 职业生涯首进法网八强

郑钦文在法网比赛中首次跻身八强,这是她第四次闯进大满贯八强。她在罗兰加洛斯取得了个人10连胜,也是中国选手时隔14年再次进入法网八强。比赛结束后,郑钦文躺倒在地庆祝这场历时2小时47分钟的胜利。比赛从首盘开始便陷入胶着,郑钦文与萨姆索诺娃展开了一场发球与接发球大战…

没有假球全是世仇 比赛第一,友谊第十四

“友谊第一,比赛第二”这句话在江苏省首届城市足球联赛中被玩出了新花样。“友谊第一,比赛第十四!”这样的口号让观众们捧腹大笑。这个被称为“苏超”的足球联赛最近爆火出圈,盐城现场吸引了22613名观众,网友纷纷表示这上座率堪比世界杯。“苏超”的观众数量已经超过了同期…

陈梦9岁开始领工资 妈妈管理至今

在最新一期《是女儿是妈妈》节目中,陈梦妈妈的一番话引起了网友们的广泛关注。陈梦9岁进入省队就开始领工资,而她的工资卡至今仍由妈妈保管。此外,陈梦妈妈还提到,陈梦每天的日常生活除了训练、吃饭和睡觉外,几乎没有其他时间,更没有精力去谈恋爱。陈梦是一位乒乓球世界冠…

记录下载安装sqlite3的过程

sqlite是一个数据库管理软件&#xff0c;今天用到了&#xff0c;记录下载安装的过程。 本地环境&#xff1a;Windows 10 家庭中文版。 下载网址&#xff1a;SQLite Download Page &#xff08;一&#xff09;下载下图中的两个文件&#xff1a; &#xff08;二&#xff09;自…

HarmonyOS NEXT 鸿蒙ArkTS 视频相关 视频播放、直播视频、XComponent和typeNode多方案实现画中画功能开发

一、简单的视频播放、直播播放 1. 使用meida中的avPlayer结合XComponent进行视频播放 如果是音频只需要一个路径就差不多了&#xff0c;这是音频HDI显示HDI&#xff0c;所以需要做以下几点&#xff1a; 应用从XComponent组件获取窗口SurfaceID&#xff0c;获取方式参考XCompon…

res-downloader-视频号下载,网络视频资源嗅探下载器

适用系统&#xff1a;Windows(含Win7)、macOS 和 Linux 系统 一、核心功能与特性 全平台资源支持 支持微信视频号、抖音、快手、小红书等短视频平台的无水印视频下载&#xff0c;同时兼容酷狗音乐、QQ音乐、微信小程序等音频和多媒体资源 1 8。覆盖视频、音频、图片、m3u8流媒体…

通义万相2.1:开启视频生成新时代

文章摘要&#xff1a;通义万相 2.1 是一款在人工智能视频生成领域具有里程碑意义的工具&#xff0c;它通过核心技术的升级和创新&#xff0c;为创作者提供了更强大、更智能的创作能力。本文详细介绍了通义万相 2.1 的背景、核心技术、功能特性、性能评测、用户反馈以及应用场景…

计算机视觉——基于树莓派的YOLO11模型优化与实时目标检测、跟踪及计数的实践

概述 设想一下&#xff0c;你在多地拥有多个仓库&#xff0c;要同时监控每个仓库的实时状况&#xff0c;这对于时间和精力而言&#xff0c;都构成了一项艰巨挑战。从成本和可靠性的层面考量&#xff0c;大规模部署计算设备也并非可行之策。一方面&#xff0c;大量计算设备的购…

AI赋能视频创作:蓝耘MaaS与海螺AI技术的深度融合

云边有个稻草人-CSDN博客 目录 一、蓝耘MaaS平台概述 &#xff08;1&#xff09;平台的模块化设计 &#xff08;2&#xff09;蓝耘MaaS的灵活性与扩展性 &#xff08;3&#xff09;蓝耘MaaS的安全性与隐私保护 二、海螺AI视频模型简介 &#xff08;1&#xff09;海螺AI的…

6条视频涨粉千万 心中之城回应质疑 新IP崛起之路

DY平台再次见证了一个涨粉神话。一部剧,六条视频,让账号「心中之城」在短短时间内涨粉1000万。从四月发布第一条视频至今,该账号已跃居DY热榜榜首。各大媒体纷纷报道这一现象。「心中之城」通过解说英剧《豺狼的日子》吸引了大量关注。该账号的运营者曾是电影圈知名账号毒舌…

外卖员不用办健康证了?网友争论 食品安全引热议

点外卖已成为很多人的生活习惯,而网络订餐配送过程中的食品安全问题也备受关注。去年底,四川省卫生健康委与市场监管局联合发布新规,明确外卖送餐人员及预包装食品销售从业者无须办理传统健康证即可上岗,并要求体检机构停止为外卖员提供健康证服务。这一消息受到不少外卖小…

ubuntu系统更换镜像源

目录 前言 一 查看操作系统版本 二 备份镜像源 三 镜像源站点官网 四 修改配置文件 前言 ubuntu系统默认官方源&#xff08;archive.ubuntu.com&#xff09;通常位于国外&#xff0c;国内用户访问时网络延迟高、带宽受限&#xff0c;导致下载软件速度很慢或者直接遇到更新失…

南京“以债换房”可置换月供?假 网传信息为谣言

近日,一些账号如“南京二手房零首付李经理”、“合肥瑶珺房地产代理有限公司”、“中墅地产唐广君”、“清、静”、“杨哥”、“南京免首付房产管家”等发布帖子称,南京开放了“以债换房”政策,可以将网络债务直接置换为房子的月供,无需支付首付。经南京市房产部门和人行江…

QT入门学习(一)---新建工程与、信号与槽

一: 新建QT项目 二:QT文件构成 2.1 first.pro 项目管理文件&#xff0c;下面来看代码解析 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11TARGET main# The following define makes your compiler emit warnings if you use # any Qt feature …

自己烧水喝是否比买桶装水更健康 减少微塑料摄入

水是构成人体的重要物质,对维持正常的身体活动和认知能力至关重要。尤其是在天气炎热时,及时补充水分尤为重要。在日常生活中,有人习惯自己烧水喝,也有人因担心自来水水质问题而选择桶装水或瓶装水。那么,这两种饮水方式哪种更健康呢?一项发表在《美国国家科学院院刊》上…

PCB设计教程【强化篇】——USB拓展坞PCB布线

前言 本教程基于B站Expert电子实验室的PCB设计教学的整理&#xff0c;为个人学习记录&#xff0c;旨在帮助PCB设计新手入门。所有内容仅作学习交流使用&#xff0c;无任何商业目的。若涉及侵权&#xff0c;请随时联系&#xff0c;将会立即处理 目录 前言 一、前期准备与规则…