八卦阵 发表于 2024-11-12 07:06:29

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

往期推文全新看点:



[*] 嵌入式开发适不恰当做鸿蒙南向开发?看完这篇你就了解了~
[*] 学鸿蒙开发的优劣势,你清楚吗?发起你了解一下!
[*] 鸿蒙(HarmonyOS)北向开发知识点纪录~
[*] 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
[*] 鸿蒙应用开发与鸿蒙系统开发哪个更有远景?
[*] 对于大前端开发来说,转鸿蒙开发毕竟是福还是祸?
[*] 鸿蒙岗位需求突增!移动端、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 轻量系统事件运作原理图
https://img-blog.csdnimg.cn/img_convert/ff752ca3bcd5b51e13b563d7c34fdb1d.webp?x-oss-process=image/format,png
接口说明

功能分类接口名描述事件检测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事件控制块烧毁。 开发流程

事件的典型开发流程:

[*] 初始化事件控制块
[*] 阻塞读事件控制块
[*] 写入相干事件
[*] 阻塞使命被唤醒,读取事件并检查是否满足要求
[*] 处理事件控制块
[*] 事件控制块烧毁
   说明:


[*] 举行事件读写使用时,事件的第25bit(0x02U << 24)为保留bit位,不可以举行位设置。
[*] 对同一事件反复写入,算作一次写入。
编程实例

实例描述

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

[*] 在使命ExampleEvent创建使命EventReadTask,其中使命EventReadTask优先级高于ExampleEvent。
[*] 在使命EventReadTask中读事件0x00000001,阻塞,发生使命切换,执行使命ExampleEvent。
[*] 在使命ExampleEvent写事件0x00000001,发生使命切换,执行使命EventReadTask。
[*] EventReadTask得以执行,直到使命结束。
[*] 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 轻量系统互斥锁运作示意图
https://img-blog.csdnimg.cn/img_convert/3ceaa53ec5936d629a9482c5d8a4fb7a.webp?x-oss-process=image/format,png
接口说明

表1 互斥锁模块接口
功能分类接口描述互斥锁的创建和删除LOS_MuxCreate:创建互斥锁。LOS_MuxDelete:删除指定的互斥锁。互斥锁的申请和释放LOS_MuxPend:申请指定的互斥锁。
LOS_MuxPost:释放指定的互斥锁。 开发流程

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

[*] 创建互斥锁LOS_MuxCreate。
[*] 申请互斥锁LOS_MuxPend。 申请模式有三种:无阻塞模式、永世阻塞模式、定时阻塞模式。


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

[*] 释放互斥锁LOS_MuxPost。

[*]假如有使命阻塞于指定互斥锁,则唤醒被阻塞使掷中优先级高的,该使命进入就绪态,并举行使命调理;
[*]假如没有使命阻塞于指定互斥锁,则互斥锁释放成功。

[*] 删除互斥锁LOS_MuxDelete。
   说明:


[*] 互斥锁支持嵌套,即申请该互斥锁的使命与已经持有该互斥锁的使命为同一个使命时会以为申请成功,按申请次数对应的去释放该锁即可。
[*] 互斥锁不能在制止服务程序中使用。
[*] LiteOS-M内核作为及时使用系统需要包管使命调理的及时性,只管制止使命的长时间阻塞,因此在得到互斥锁之后,应该尽快释放互斥锁。
[*] 持有互斥锁的过程中,不得再调用LOS_TaskPriSet等接口更改持有互斥锁使命的优先级。
编程实例

实例描述

本实例实现如下游程。

[*] 使命ExampleMutex创建一个互斥锁,锁使命调理,创建两个使命ExampleMutexTask1、ExampleMutexTask2。ExampleMutexTask2优先级高于ExampleMutexTask1,解锁使命调理。
[*] ExampleMutexTask2被调理,以永世阻塞模式申请互斥锁,并成功获取到该互斥锁,然后使命休眠100Tick,ExampleMutexTask2挂起,ExampleMutexTask1被唤醒。
[*] ExampleMutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被ExampleMutexTask2持有,ExampleMutexTask1挂起。10Tick超时时间到达后,ExampleMutexTask1被唤醒,以永世阻塞模式申请互斥锁,因互斥锁仍被ExampleMutexTask2持有,ExampleMutexTask1挂起。
[*] 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 getmutex, 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 getmutex, 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 getmutex, wait forever.
task2 get mutex g_testMux and suspend 100 ticks.
task1 try to getmutex, 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;         /* 数组下标0的元素表示队列中可读消息数,                              
                                                                    数组下标1的元素表示队列中可写消息数 */
    LOS_DL_LIST readWriteList;            /* 读取或写入消息的任务等待链表,
                                                                       下标0:读取链表,下标1:写入链表 */
    LOS_DL_LIST memList;                                         /* 内存块链表 */
} LosQueueCB;
每个队列控制块中都含有队列状态,表示该队列的使用环境:


[*]OS_QUEUE_UNUSED:队列未被使用。
[*]OS_QUEUE_INUSED:队列被使用中。
队列运作原理



[*]创建队列时,创建队列成功会返回队列ID。
[*]在队列控制块中维护着一个消息头节点位置Head和一个消息尾节点位置Tail,用于表示当前队列中消息的存储环境。Head表示队列中被占用的消息节点的起始位置。Tail表示被占用的消息节点的结束位置,也是空闲消息节点的起始位置。队列刚创建时,Head和Tail均指向队列起始位置。
[*]写队列时,根据readWriteableCnt判断队列是否可以写入,不能对已满(readWriteableCnt为0)队列举行写使用。写队列支持两种写入方式:向队列尾节点写入,也可以向队列头节点写入。尾节点写入时,根据Tail找到起始空闲消息节点作为数据写入对象,假如Tail已经指向队列尾部则接纳回卷方式。头节点写入时,将Head的前一个节点作为数据写入对象,假如Head指向队列起始位置则接纳回卷方式。
[*]读队列时,根据readWriteableCnt判断队列是否有消息需要读取,对全部空闲(readWriteableCnt为0)队列举行读使用会引起使命挂起。假如队列可以读取消息,则根据Head找到最先写入队列的消息节点举行读取。假如Head已经指向队列尾部则接纳回卷方式。
[*]删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态,并释放队列所占内存。
图1 队列读写数据使用示意图
https://img-blog.csdnimg.cn/img_convert/1259e8147c1904f300e40da6474d1f79.webp?x-oss-process=image/format,png
上图对读写队列做了示意,图中只画了尾节点写入方式,没有画头节点写入,但是两者是雷同的。
接口说明

功能分类接口描述创建/删除消息队列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、队列长度、消息节点大小、头节点、尾节点、可读节点数量、可写节点数量、等待读使用的使命、等待写使用的使命。 开发流程


[*]用LOS_QueueCreate创建队列。创建成功后,可以得到队列ID。
[*]通过LOS_QueueWrite大概LOS_QueueWriteCopy写队列。
[*]通过LOS_QueueRead大概LOS_QueueReadCopy读队列。
[*]通过LOS_QueueInfoGet获取队列信息。
[*]通过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通过读队列接口吸收消息。

[*]通过LOS_TaskCreate创建使命1和使命2。
[*]通过LOS_QueueCreate创建一个消息队列。
[*]在使命1 SendEntry中发送消息。
[*]在使命2 RecvEntry中吸收消息。
[*]通过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 50

VOID 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 = {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 轻量系统信号量运作示意图
https://img-blog.csdnimg.cn/img_convert/628d8e21c688b6b74358fd8d5e826282.webp?x-oss-process=image/format,png
接口说明

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


[*] 创建信号量LOS_SemCreate,若要创建二值信号量则调用LOS_BinarySemCreate。
[*] 申请信号量LOS_SemPend。
[*] 释放信号量LOS_SemPost。
[*] 删除信号量LOS_SemDelete。
   说明: 由于制止不能被阻塞,因此不能在制止中使用阻塞模式申请信号量。
编程实例

实例描述

本实例实现如下功能:

[*] 测试使命ExampleSem创建一个信号量,锁使命调理。创建两个使命ExampleSemTask1和ExampleSemTask2, ExampleSemTask2优先级高于ExampleSemTask1。两个使掷中申请同一信号量,解锁使命调理后两使命阻塞,测试使命ExampleSem释放信号量。
[*] ExampleSemTask2得到信号量,被调理,然后使命休眠20Tick,ExampleSemTask2耽误,ExampleSemTask1被唤醒。
[*] ExampleSemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被ExampleSemTask2持有,ExampleSemTask1挂起,10Tick后仍未得到信号量,ExampleSemTask1被唤醒,试图以永世阻塞模式申请信号量,ExampleSemTask1挂起。
[*] 20Tick后ExampleSemTask2唤醒, 释放信号量后,ExampleSemTask1得到信号量被调理运行,最后释放信号量。
[*] 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)全栈开发技能的学习路线,包含了鸿蒙开发必把握的核心知识要点,内容有(OpenHarmony多媒体技能、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技能知识点。
https://i-blog.csdnimg.cn/direct/535cbb5bd3ea4b55aabf2fd1c8bd4e4f.png#pic_center
《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.根本概念
2.构建第一个ArkTS应用
3.……
https://i-blog.csdnimg.cn/direct/4728f3dfb9ce4c7e836c5edb22e1d798.png
鸿蒙开发面试真题(含参考答案):

https://i-blog.csdnimg.cn/direct/ca0c55f25839464aa3c9c632793f4a9e.png
《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview



[*]搭建开发环境
[*]Windows 开发环境的搭建
[*]Ubuntu 开发环境搭建
[*]Linux 与 Windows 之间的文件共享
[*]……
[*]系统架构分析
[*]构建子系统
[*]启动流程
[*]子系统
[*]分布式使命调理子系统
[*]分布式通讯子系统
[*]驱动子系统
[*]……
https://img-blog.csdnimg.cn/img_convert/b2a7c0f56b33ef83d8199a6e53e443f2.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: OpenHarmony(鸿蒙南向开发)——轻量系统内核(LiteOS-M)【内核通讯机制