Skip to content

Latest commit

 

History

History
6891 lines (5779 loc) · 434 KB

Huawei_LiteOS_Kernel_Developer_Guide_zh.md

File metadata and controls

6891 lines (5779 loc) · 434 KB

Huawei LiteOS Kernel开发指南

目 录

1.知识共享许可协议说明

您可以自由地:

分享 在任何媒介以任何形式复制、发行本文档

演绎 修改、转换或以本文档为基础进行创作

只要你遵守许可协议条款,许可人就无法收回你的这些权利

惟须遵守下列条件:

署名 您必须提供适当的证书,提供一个链接到许可证,并指示是否作出更改。您可以以任何合理的方式这样做,但不是以任何方式表明,许可方赞同您或您的使用。

非商业性使用 您不得将本文档用于商业目的。

相同方式共享 如果您的修改、转换,或以本文档为基础进行创作,仅得依本素材的授权条款来散布您的贡献作品。

没有附加限制 您不能增设法律条款或科技措施,来限制别人依授权条款本已许可的作为。

声明:

当您使用本素材中属于公众领域的元素,或当法律有例外或限制条款允许您的使用,则您不需要遵守本授权条款。

未提供保证。本授权条款未必能完全提供您预期用途所需要的所有许可。例如:形象权、隐私权、著作人格权等其他权利,可能限制您如何使用本素材。

须知:
为了方便用户理解,这是协议的概述。可以访问网址https://creativecommons.org/licenses/by-nc-sa/3.0/legalcode了解完整协议内容。

2.前言

目的

本文档介绍Huawei LiteOS的体系结构,并介绍如何进行开发和调试。

读者对象

本文档主要适用于Huawei LiteOS的开发者,主要适用于以下对象:

  • 物联网端侧软件开发工程师
  • 物联网架构设计师

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号 说明
用于警示紧急的危险情形,若不避免,将会导致人员死亡或严重的人身伤害
用于警示潜在的危险情形,若不避免,可能会导致人员死亡或严重的人身伤害
用于警示潜在的危险情形,若不避免,可能会导致中度或轻微的人身伤害
用于传递设备或环境安全警示信息,若不避免,可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果,“注意”不涉及人身伤害
“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息

3.概述

Huawei LiteOS是轻量级的实时操作系统,具备轻量级、低功耗、快速启动、组件丰富等关键能力。

内核架构

图 1 Huawei LiteOS Kernel的基本框架图

Huawei LiteOS基础内核包括不可裁剪的极小内核和可裁剪的其他模块。极小内核包含任务管理、内存管理、中断管理、异常管理和系统时钟。可裁剪的模块包括信号量、互斥锁、队列管理、事件管理、软件定时器等。 Huawei LiteOS支持 UP(单核)与 SMP(多核)模式,即支持在单核或者多核的环境上运行。

Lincense

Huawei LiteOS Kernel遵循BSD-3开源许可协议。

各模块简介

任务管理

提供任务的创建、删除、延迟、挂起、恢复等功能,以及锁定和解锁任务调度。支持任务按优先级高低的抢占调度以及同优先级时间片轮转调度。

内存管理

  • 提供静态内存和动态内存两种算法,支持内存申请、释放。目前支持的内存管理算法有固定大小的BOX算法、动态申请的bestfit算法和bestfit_little算法。
  • 提供内存统计、内存越界检测功能。

硬件相关

提供中断管理、异常管理、系统时钟等功能。

  • 中断管理:提供中断的创建、删除、使能、禁止、请求位的清除功能。
  • 异常管理:系统运行过程中发生异常后,跳转到异常处理模块,打印当前发生异常的函数调用栈信息,或者保存当前系统状态。
  • Tick :Tick是操作系统调度的基本时间单位,对应的时长由每秒Tick数决定,由用户配置。

IPC通信

提供消息队列、事件、信号量和互斥锁功能。

  • 消息队列:支持消息队列的创建、删除、发送和接收功能。
  • 事件:支持读事件和写事件功能。
  • 信号量:支持信号量的创建、删除、申请和释放功能。
  • 互斥锁:支持互斥锁的创建、删除、申请和释放功能。

软件定时器

软件定时器提供了定时器的创建、删除、启动、停止功能。

自旋锁

多核场景下,支持自旋锁的初始化、申请、释放功能。

低功耗

  • Run-stop:即休眠唤醒,是Huawei LiteOS提供的保存系统现场镜像以及从系统现场镜像中恢复运行的机制。
  • Tickless:Tickless机制通过计算下一次有意义的时钟中断的时间,来减少不必要的时钟中断,从而降低系统功耗。打开Tickless功能后,系统会在CPU空闲时启动Tickless机制。

维测

  • CPU占用率:可以获取系统或者指定任务的CPU占用率。
  • Trace事件跟踪:实时获取事件发生的上下文,并写入缓冲区。支持自定义缓冲区,跟踪指定模块的事件,开启/停止Trace,清除/输出trace缓冲区数据等。
  • LMS:实时检测内存操作合法性,LMS能够检测的内存问题包括缓冲区溢出(buffer overflow),释放后使用(use after free),多重释放(double free)和释放野指针(wild pointer)。
  • Shell:Huawei LiteOS Shell使用串口接收用户输入的命令,通过命令的方式调用、执行相应的应用程序。Huawei LiteOS Shell支持常用的基本调试功能,同时支持用户添加自定义命令。

C++支持

Huawei LiteOS支持部分STL特性、异常和RTTI特性,其他特性由编译器支持。

使用约束

  • Huawei LiteOS提供一套自有OS接口,同时也支持POSIX和CMSIS接口。请勿混用这些接口。混用接口可能导致不可预知的错误,例如:用POSIX接口申请信号量,但用Huawei LiteOS接口释放信号量。
  • 开发驱动程序只能用Huawei LiteOS的接口,上层APP建议用POSIX接口。

任务

概述

基本概念

从系统角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。

Huawei LiteOS的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。Huawei LiteOS的任务模块具有如下特性:

  • 支持多任务。
  • 一个任务表示一个线程。
  • 抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
  • 相同优先级任务支持时间片轮转调度方式。
  • 共有32个优先级[0-31],最高优先级为0,最低优先级为31。

任务相关概念

任务状态

Huawei LiteOS系统中的任务有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。

任务状态通常分为以下四种:

  • 就绪(Ready):该任务在就绪队列中,只等待CPU。
  • 运行(Running):该任务正在执行。
  • 阻塞(Blocked):该任务不在就绪队列中。包含任务被挂起(suspend状态)、任务被延时(delay状态)、任务正在等待信号量、读写队列或者等待事件等。
  • 退出态(Dead):该任务运行结束,等待系统回收资源。

任务状态迁移

图 1 任务状态示意图

任务状态迁移说明:

  • 就绪态→运行态

    任务创建后进入就绪态,发生任务切换时,就绪队列中最高优先级的任务被执行,从而进入运行态,但此刻该任务依旧在就绪队列中。

  • 运行态→阻塞态

    正在运行的任务发生阻塞(挂起、延时、读信号量等)时,该任务会从就绪队列中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪队列中最高优先级任务。

  • 阻塞态→就绪态(阻塞态→运行态)

    阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪队列,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,该任务由就绪态变成运行态。

  • 就绪态→阻塞态

    任务也有可能在就绪态时被阻塞(挂起),此时任务状态由就绪态变为阻塞态,该任务从就绪队列中删除,不会参与任务调度,直到该任务被恢复。

  • 运行态→就绪态

    有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪队列中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪队列中。

  • 运行态→退出态

    运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出状态以及Invalid状态。例如,任务运行结束但是没有自删除,对外呈现的就是Invalid状态,即退出态。

  • 阻塞态→退出态

    阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。

任务ID

任务ID,在任务创建时通过参数返回给用户,是任务的重要标识。系统中的ID号是唯一的。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。

任务优先级

优先级表示任务执行的优先顺序。任务的优先级决定了在发生任务切换时即将要执行的任务,就绪队列中最高优先级的任务将得到执行。

任务入口函数

新任务得到调度后将执行的函数。该函数由用户实现,在任务创建时,通过任务创建结构体设置。

任务栈

每个任务都拥有一个独立的栈空间,我们称为任务栈。栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。

任务上下文

任务在运行过程中使用的一些资源,如寄存器等,称为任务上下文。当这个任务挂起时,其他任务继续执行,可能会修改寄存器等资源中的值。如果任务切换时没有保存任务上下文,可能会导致任务恢复后出现未知错误。

因此,Huawei LiteOS在任务切换时会将切出任务的任务上下文信息,保存在自身的任务栈中,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行挂起时被打断的代码。

任务控制块TCB

每个任务都含有一个任务控制块(TCB)。TCB包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息。TCB可以反映出每个任务运行情况。

任务切换

任务切换包含获取就绪队列中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作。

运作机制

用户创建任务时,系统会初始化任务栈,预置上下文。此外,系统还会将“任务入口函数”地址放在相应位置。这样在任务第一次启动进入运行态时,将会执行“任务入口函数”。

开发指导

使用场景

任务创建后,内核可以执行锁任务调度,解锁任务调度,挂起,恢复,延时等操作,同时也可以设置任务优先级,获取任务优先级。

功能

Huawei LiteOS 的任务管理模块提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

创建和删除任务

LOS_TaskCreateOnly

创建任务,并使该任务进入suspend状态,不对该任务进行调度。如果需要调度,可以调用LOS_TaskResume使该任务进入ready状态

LOS_TaskCreate

创建任务,并使该任务进入ready状态,如果就绪队列中没有更高优先级的任务,则运行该任务

LOS_TaskCreateOnlyStatic

创建任务,任务栈由用户传入,并使该任务进入suspend状态,不对该任务进行调度。如果需要调度,可以调用LOS_TaskResume使该任务进入ready状态

LOS_TaskCreateStatic

创建任务,任务栈由用户传入,并使该任务进入ready状态,如果就绪队列中没有更高优先级的任务,则运行该任务

LOS_TaskDelete

删除指定的任务

控制任务状态

LOS_TaskResume

恢复挂起的任务,使该任务进入ready状态

LOS_TaskSuspend

挂起指定的任务,然后切换任务

LOS_TaskDelay

任务延时等待,释放CPU,等待时间到期后该任务会重新进入ready状态

LOS_TaskYield

当前任务释放CPU,并将其移到具有相同优先级的就绪任务队列的末尾

控制任务调度

LOS_TaskLock

锁任务调度,但任务仍可被中断打断

LOS_TaskUnlock

解锁任务调度

控制任务优先级

LOS_CurTaskPriSet

设置当前任务的优先级

LOS_TaskPriSet

设置指定任务的优先级

LOS_TaskPriGet

获取指定任务的优先级

设置任务亲和性

LOS_TaskCpuAffiSet

设置指定任务的运行cpu集合(该函数仅在SMP模式下支持)

回收任务栈资源

LOS_TaskResRecycle

回收所有待回收的任务栈资源

获取任务信息

LOS_CurTaskIDGet

获取当前任务的ID

LOS_TaskInfoGet

获取指定任务的信息,包括任务状态、优先级、任务栈大小、栈顶指针SP、任务入口函数、已使用的任务栈大小等

LOS_TaskCpuAffiGet

获取指定任务的运行cpu集合(该函数仅在SMP模式下支持)

任务信息维测

LOS_TaskSwitchHookReg

注册任务上下文切换的钩子函数。只有开启LOSCFG_BASE_CORE_TSK_MONITOR宏开关后,这个钩子函数才会在任务发生上下文切换时被调用

须知:

  • 可以通过make menuconfig配置LOSCFG_KERNEL_SMP使能多核模式。在SMP的子菜单中,还可以设置核的数量、使能多任务的核间同步、使能函数跨核调用。
  • 在多核模式下,创建任务时可以传入usCpuAffiMask来配置任务的CPU亲和性,该标志位采用1bit-1core的对应方式,详细可见TSK_INIT_PARAM_S结构体。
  • 各个任务的任务栈大小,在创建任务时可以进行针对性的设置,若设置为0,则使用默认任务栈大小(LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE)作为任务栈大小。

TASK状态

Huawei LiteOS任务的大多数状态由内核维护,唯有自删除状态对用户可见,需要用户在创建任务时传入:

定义

实际数值

描述

LOS_TASK_STATUS_DETACHED

0x0100

任务是自删除的

用户在调用创建任务接口时,可以将创建任务的TSK_INIT_PARAM_S参数的uwResved域设置为LOS_TASK_STATUS_DETACHED,即自删除状态,设置成自删除状态的任务会在运行完成后执行自删除操作。

须知: 自删除状态受LOSCFG_COMPAT_POSIX开关影响。

  • LOSCFG_COMPAT_POSIX打开,只有将任务状态设置为LOS_TASK_STATUS_DETACHED才能实现自删除,否则任务完成时不会自删除。
  • LOSCFG_COMPAT_POSIX关闭,任务完成时都会自删除,不管TSK_INIT_PARAM_S参数的uwResved域是否设置为LOS_TASK_STATUS_DETACHED。

TASK错误码

创建任务、删除任务、挂起任务、恢复任务、延时任务等操作存在失败的可能,失败时会返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_TSK_NO_MEMORY

0x03000200

内存空间不足

增大动态内存空间,有两种方式可以实现:

  • 设置更大的系统动态内存池,配置项为OS_SYS_MEM_SIZE
  • 释放一部分动态内存

如果错误发生在LiteOS启动过程中的任务初始化,还可以通过减少系统支持的最大任务数来解决;如果错误发生在任务创建过程中,也可以减小任务栈大小来解决

2

LOS_ERRNO_TSK_PTR_NULL

0x02000201

传递给任务创建接口的任务参数initParam为空指针,或者传递给任务信息获取的接口的参数为空指针

确保传入的参数不为空指针

3

LOS_ERRNO_TSK_STKSZ_NOT_ALIGN

0x02000202

暂不使用该错误码

-

4

LOS_ERRNO_TSK_PRIOR_ERROR

0x02000203

创建任务或者设置任务优先级时,传入的优先级参数不正确

检查任务优先级,必须在[0, 31]的范围内

5

LOS_ERRNO_TSK_ENTRY_NULL

0x02000204

创建任务时传入的任务入口函数为空指针

定义任务入口函数

6

LOS_ERRNO_TSK_NAME_EMPTY

0x02000205

创建任务时传入的任务名为空指针

设置任务名

7

LOS_ERRNO_TSK_STKSZ_TOO_SMALL

0x02000206

创建任务时传入的任务栈太小

增大任务的任务栈大小使之不小于系统设置最小任务栈大小(配置项为LOS_TASK_MIN_STACK_SIZE)

8

LOS_ERRNO_TSK_ID_INVALID

0x02000207

无效的任务ID

检查任务ID

9

LOS_ERRNO_TSK_ALREADY_SUSPENDED

0x02000208

挂起任务时,发现任务已经被挂起

等待这个任务被恢复后,再去尝试挂起这个任务

10

LOS_ERRNO_TSK_NOT_SUSPENDED

0x02000209

恢复任务时,发现任务未被挂起

挂起这个任务后,再去尝试恢复这个任务

11

LOS_ERRNO_TSK_NOT_CREATED

0x0200020a

任务未被创建

创建这个任务,这个错误可能会发生在以下操作中:

  • 删除任务
  • 恢复/挂起任务
  • 设置指定任务的优先级
  • 获取指定任务的信息
  • 设置指定任务的运行cpu集合

12

LOS_ERRNO_TSK_DELETE_LOCKED

0x0300020b

删除任务时,任务处于锁定状态

解锁任务之后再删除任务

13

LOS_ERRNO_TSK_MSG_NONZERO

0x0200020c

暂不使用该错误码

-

14

LOS_ERRNO_TSK_DELAY_IN_INT

0x0300020d

中断期间,进行任务延时

等待退出中断后再进行延时操作

15

LOS_ERRNO_TSK_DELAY_IN_LOCK

0x0200020e

在任务锁定状态下,延时该任务

解锁任务之后再延时任务

16

LOS_ERRNO_TSK_YIELD_IN_LOCK

0x0200020f

在任务锁定状态下,进行Yield操作

任务解锁后再进行Yield操作

17

LOS_ERRNO_TSK_YIELD_NOT_ENOUGH_TASK

0x02000210

执行Yield操作时,发现具有相同优先级的就绪任务队列中没有其他任务

增加与当前任务具有相同优先级的任务数

18

LOS_ERRNO_TSK_TCB_UNAVAILABLE

0x02000211

创建任务时,发现没有空闲的任务控制块可以使用

调用LOS_TaskResRecyle接口回收空闲的任务控制块,如果回收后依然创建失败,再增加系统的任务控制块数量

19

LOS_ERRNO_TSK_HOOK_NOT_MATCH

0x02000212

暂不使用该错误码

-

20

LOS_ERRNO_TSK_HOOK_IS_FULL

0x02000213

暂不使用该错误码

-

21

LOS_ERRNO_TSK_OPERATE_SYSTEM_TASK

0x02000214

不允许删除、挂起、延时系统级别的任务,例如idle任务、软件定时器任务,也不允许修改系统级别的任务优先级

检查任务ID,不要操作系统任务

22

LOS_ERRNO_TSK_SUSPEND_LOCKED

0x03000215

不允许将处于锁定状态的任务挂起

任务解锁后,再尝试挂起任务

23

LOS_ERRNO_TSK_FREE_STACK_FAILED

0x02000217

暂不使用该错误码

-

24

LOS_ERRNO_TSK_STKAREA_TOO_SMALL

0x02000218

暂不使用该错误码

-

25

LOS_ERRNO_TSK_ACTIVE_FAILED

0x03000219

暂不使用该错误码

-

26

LOS_ERRNO_TSK_CONFIG_TOO_MANY

0x0200021a

暂不使用该错误码

-

27

LOS_ERRNO_TSK_CP_SAVE_AREA_NOT_ALIGN

0x0200021b

暂不使用该错误码

-

28

LOS_ERRNO_TSK_MSG_Q_TOO_MANY

0x0200021d

暂不使用该错误码

-

29

LOS_ERRNO_TSK_CP_SAVE_AREA_NULL

0x0200021e

暂不使用该错误码

-

30

LOS_ERRNO_TSK_SELF_DELETE_ERR

0x0200021f

暂不使用该错误码

-

31

LOS_ERRNO_TSK_STKSZ_TOO_LARGE

0x02000220

创建任务时,设置了过大的任务栈

减小任务栈大小

32

LOS_ERRNO_TSK_SUSPEND_SWTMR_NOT_ALLOWED

0x02000221

暂不使用该错误码

-

34

LOS_ERRNO_TSK_CPU_AFFINITY_MASK_ERR

0x03000223

设置指定任务的运行cpu集合时,传入了错误的cpu集合

检查传入的cpu掩码

35

LOS_ERRNO_TSK_YIELD_IN_INT

0x02000224

不允许在中断中对任务进行Yield操作

不要在中断中进行Yield操作

36

LOS_ERRNO_TSK_MP_SYNC_RESOURCE

0x02000225

跨核任务删除同步功能,资源申请失败

通过设置更大的LOSCFG_BASE_IPC_SEM_LIMIT的值,增加系统支持的信号量个

37

LOS_ERRNO_TSK_MP_SYNC_FAILED

0x02000226

跨核任务删除同步功能,任务未及时删除

需要检查目标删除任务是否存在频繁的状态切换,导致系统无法在规定的时间内完成删除的动作

须知:

  • 错误码定义见错误码简介。8~15位的所属模块为任务模块,值为0x02。
  • 任务模块中的错误码序号 0x16、0x1c,未被定义,不可用。

开发流程

以创建任务为例,讲解开发流程。

  1. 通过make menuconfig配置任务模块。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_CORE_TSK_LIMIT

    系统支持的最大任务数

    [0, OS_SYS_MEM_SIZE)

    64

    LOSCFG_TASK_MIN_STACK_SIZE

    最小任务栈大小,一般使用默认值即可

    [0, OS_SYS_MEM_SIZE)

    0x800

    LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE

    默认任务栈大小

    [0, OS_SYS_MEM_SIZE)

    0x6000

    LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE

    IDLE任务栈大小,一般使用默认值即可

    [0, OS_SYS_MEM_SIZE)

    0x800

    LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO

    默认任务优先级,一般使用默认配置即可

    [0,31]

    10

    LOSCFG_BASE_CORE_TIMESLICE

    任务时间片调度开关

    YES/NO

    YES

    LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT

    同优先级任务最长执行时间(单位:Tick)

    [0, 65535]

    2

    LOSCFG_OBSOLETE_API

    使能后,任务参数使用旧方式UINTPTR auwArgs[4],否则使用新的任务参数VOID *pArgs。建议关闭此开关,使用新的任务参数

    YES/NO

    不同平台默认值不一样

    不同平台默认值不一样

    LOSCFG_LAZY_STACK

    使能惰性压栈功能

    YES/NO

    NO

    M核

    LOSCFG_BASE_CORE_TSK_MONITOR

    任务栈溢出检查和轨迹开关

    YES/NO

    YES

    LOSCFG_TASK_STATIC_ALLOCATION

    支持创建任务时,由用户传入任务栈

    YES/NO

    NO

  2. 锁任务调度LOS_TaskLock,防止高优先级任务调度。

  3. 创建任务LOS_TaskCreate,或静态创建任务LOS_TaskCreateStatic(需要打开LOSCFG_TASK_STATIC_ALLOCATION宏)。

  4. 解锁任务LOS_TaskUnlock,让任务按照优先级进行调度。

  5. 延时任务LOS_TaskDelay,任务延时等待。

  6. 挂起指定的任务LOS_TaskSuspend,任务挂起等待恢复操作。

  7. 恢复挂起的任务LOS_TaskResume。

平台差异性

无。

注意事项

  • 执行Idle任务时,会对之前已删除任务的任务控制块和任务栈进行回收。

  • 任务名是指针,并没有分配空间,在设置任务名时,禁止将局部变量的地址赋值给任务名指针。

  • 任务栈的大小按16字节大小或者sizeof(UINTPTR) * 2对齐。确定任务栈大小的原则是,够用就行,多了浪费,少了任务栈溢出。

  • 挂起当前任务时,如果任务已经被锁定,则无法挂起。

  • Idle任务及软件定时器任务不能被挂起或者删除。

  • 在中断处理函数中或者在锁任务的情况下,执行LOS_TaskDelay会失败。

  • 锁任务调度,并不关中断,因此任务仍可被中断打断。

  • 锁任务调度必须和解锁任务调度配合使用。

  • 设置任务优先级时可能会发生任务调度。

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

  • LOS_CurTaskPriSet和LOS_TaskPriSet接口不能在中断中使用,也不能用于修改软件定时器任务的优先级。

  • LOS_TaskPriGet接口传入的task ID对应的任务未创建或者超过最大任务数,统一返回0xffff。

  • 在删除任务时要保证任务申请的资源(如互斥锁、信号量等)已被释放。

  • 在多核模式下,锁任务调度只能锁住当前核的调度器,其他核仍然能正常调度。

  • 在多核模式下,由于跨核间任务的删除或挂起是异步执行的,因此操作的返回值并不代表最终操作的结果,仅代表上述请求已经发出。同时执行完成会存在延时。

  • 在多核模式下,如果开启任务跨核删除同步的功能(LOSCFG_KERNEL_SMP_TASK_SYNC选项),则跨核删除任务时,需要等待目标任务删除后才会返回结果,如果在设定的时间内未成功将任务删除,则会返回LOS_ERRNO_TSK_MP_SYNC_FAILED错误。开启该功能后,每个任务会增加1个信号量的开销。

编程实例

实例描述

本实例介绍基本的任务操作方法,包含2个不同优先级任务的创建、任务延时、任务锁与解锁调度、挂起和恢复等操作,阐述任务优先级调度的机制以及各接口的应用。

编程示例

前提条件:在menuconfig菜单中完成任务模块的配置。

UINT32 g_taskHiId;
UINT32 g_taskLoId;
#define TSK_PRIOR_HI 4
#define TSK_PRIOR_LO 5

UINT32 Example_TaskHi(VOID)
{
    UINT32 ret;

    printf("Enter TaskHi Handler.\r\n");

    /* 延时2个Tick,延时后该任务会挂起,执行剩余任务中最高优先级的任务(g_taskLoId任务) */
    ret = LOS_TaskDelay(2);
    if (ret != LOS_OK) {
        printf("Delay Task Failed.\r\n");
        return LOS_NOK;
    }

    /* 2个Tick时间到了后,该任务恢复,继续执行 */
    printf("TaskHi LOS_TaskDelay Done.\r\n");

    /* 挂起自身任务 */
    ret = LOS_TaskSuspend(g_taskHiId);
    if (ret != LOS_OK) {
        printf("Suspend TaskHi Failed.\r\n");
        return LOS_NOK;
    }
    printf("TaskHi LOS_TaskResume Success.\r\n");

    return ret;
}

/* 低优先级任务入口函数 */
UINT32 Example_TaskLo(VOID)
{
    UINT32 ret;

    printf("Enter TaskLo Handler.\r\n");

    /* 延时2个Tick,延时后该任务会挂起,执行剩余任务中最高优先级的任务(背景任务) */
    ret = LOS_TaskDelay(2);
    if (ret != LOS_OK) {
        printf("Delay TaskLo Failed.\r\n");
        return LOS_NOK;
    }

    printf("TaskHi LOS_TaskSuspend Success.\r\n");

    /* 恢复被挂起的任务g_taskHiId */
    ret = LOS_TaskResume(g_taskHiId);
    if (ret != LOS_OK) {
        printf("Resume TaskHi Failed.\r\n");
        return LOS_NOK;
    }

    printf("TaskHi LOS_TaskDelete Success.\r\n");

    return ret;
}

/* 任务测试入口函数,创建两个不同优先级的任务 */
UINT32 Example_TskCaseEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S initParam;

    /* 锁任务调度,防止新创建的任务比本任务高而发生调度 */
    LOS_TaskLock();

    printf("LOS_TaskLock() Success!\r\n");

    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskHi;
    initParam.usTaskPrio = TSK_PRIOR_HI;
    initParam.pcName = "TaskHi";
    initParam.uwStackSize = LOSCFG_TASK_MIN_STACK_SIZE;
    initParam.uwResved   = LOS_TASK_STATUS_DETACHED;
    /* 创建高优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
    ret = LOS_TaskCreate(&g_taskHiId, &initParam);
    if (ret != LOS_OK) {
        LOS_TaskUnlock();

        printf("Example_TaskHi create Failed!\r\n");
        return LOS_NOK;
    }

    printf("Example_TaskHi create Success!\r\n");

    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskLo;
    initParam.usTaskPrio = TSK_PRIOR_LO;
    initParam.pcName = "TaskLo";
    initParam.uwStackSize = LOSCFG_TASK_MIN_STACK_SIZE;
    initParam.uwResved   = LOS_TASK_STATUS_DETACHED;

    /* 创建低优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
    ret = LOS_TaskCreate(&g_taskLoId, &initParam);
    if (ret != LOS_OK) {
        LOS_TaskUnlock();

        printf("Example_TaskLo create Failed!\r\n");
        return LOS_NOK;
    }

    printf("Example_TaskLo create Success!\r\n");

    /* 解锁任务调度,此时会发生任务调度,执行就绪队列中最高优先级任务 */
    LOS_TaskUnlock();

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

LOS_TaskLock() Success!
Example_TaskHi create Success!
Example_TaskLo create Success!
Enter TaskHi Handler.
Enter TaskLo Handler.
TaskHi LOS_TaskDelay Done.
TaskHi LOS_TaskSuspend Success.
TaskHi LOS_TaskResume Success.
TaskHi LOS_TaskDelete Success.

完整实例代码

sample_task.c

编程实例 \(SMP\)

实例描述

本实例介绍基本的任务操作方法,包含任务创建、任务延时、任务锁与解锁调度、挂起和恢复等操作,阐述任务优先级调度的机制以及各接口的应用。

  1. 创建了2个任务:TaskHi和TaskLo。
  2. TaskHi为高优先级任务, 绑定在当前测试任务的CPU上。
  3. TaskLo为低优先级任务,不设置亲和性即不绑核。

须知: 由于TaskLo未设置亲和性,LOS_TaskLock对其没有锁任务的效果,因此"Example_TaskLo create Success!" 与 " Enter TaskLo Handler." 这两句打印并没有严格的先后顺序。

编程示例

前提条件:在menuconfig菜单中完成任务模块的配置和SMP模式使能。

UINT32 g_taskLoId;
UINT32 g_taskHiId;
#define TSK_PRIOR_HI 4
#define TSK_PRIOR_LO 5

UINT32 Example_TaskHi(VOID)
{
    UINT32 ret;

    printf("[cpu%d] Enter TaskHi Handler.\r\n", ArchCurrCpuid());

    /* 延时2个Tick,延时后该任务会挂起,执行剩余任务中最高优先级的任务(g_taskLoId任务) */
    ret = LOS_TaskDelay(2);
    if (ret != LOS_OK) {
        printf("Delay Task Failed.\r\n");
        return LOS_NOK;
    }

    /* 2个Tick后,该任务恢复,继续执行 */
    printf("TaskHi LOS_TaskDelay Done.\r\n");

    /* 挂起自身任务 */
    ret = LOS_TaskSuspend(g_taskHiId);
    if (ret != LOS_OK) {
        printf("Suspend TaskHi Failed.\r\n");
        return LOS_NOK;
    }
    printf("TaskHi LOS_TaskResume Success.\r\n");
    return ret;
}

/* 低优先级任务入口函数 */
UINT32 Example_TaskLo(VOID)
{
    UINT32 ret;

    printf("[cpu%d] Enter TaskLo Handler.\r\n", ArchCurrCpuid());

    /* 延时2个Tick,延时后该任务会挂起,执行剩余任务中就高优先级的任务(背景任务) */
    ret = LOS_TaskDelay(2);
    if (ret != LOS_OK) {
        printf("Delay TaskLo Failed.\r\n");
        return LOS_NOK;
    }

    printf("TaskHi LOS_TaskDelete Success.\r\n");
    return ret;
}

/* 任务测试入口函数,创建两个不同优先级的任务 */
UINT32 Example_TskCaseEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S initParam = {0};

    /* 锁任务调度 */
    LOS_TaskLock();

    printf("LOS_TaskLock() Success on cpu%d!\r\n", ArchCurrCpuid());

    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskHi;
    initParam.usTaskPrio = TSK_PRIOR_HI;
    initParam.pcName = "TaskHi";
    initParam.uwStackSize = LOSCFG_TASK_MIN_STACK_SIZE;
    initParam.uwResved   = LOS_TASK_STATUS_DETACHED;
#ifdef LOSCFG_KERNEL_SMP
    /* 绑定高优先级任务到CPU1运行 */
    initParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endif
    /* 创建高优先级任务,由于CPU1的调度器被锁,任务创建成功后不会马上执行 */
    ret = LOS_TaskCreate(&g_taskHiId, &initParam);
    if (ret != LOS_OK) {
        LOS_TaskUnlock();

        printf("Example_TaskHi create Failed!\r\n");
        return LOS_NOK;
    }

    printf("Example_TaskHi create Success!\r\n");

    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskLo;
    initParam.usTaskPrio = TSK_PRIOR_LO;
    initParam.pcName = "TaskLo";
    initParam.uwStackSize = LOSCFG_TASK_MIN_STACK_SIZE;
    initParam.uwResved   = LOS_TASK_STATUS_DETACHED;
#ifdef LOSCFG_KERNEL_SMP
    /* 低优先级任务不设置CPU亲和性 */
    initParam.usCpuAffiMask = 0;
#endif
    /* 创建低优先级任务,尽管锁任务调度,但是由于该任务没有绑定该处理器,任务创建成功后可以马上在其他CPU执行 */
    ret = LOS_TaskCreate(&g_taskLoId, &initParam);
    if (ret != LOS_OK) {
        LOS_TaskUnlock();

        printf("Example_TaskLo create Failed!\r\n");
        return LOS_NOK;
    }

    printf("Example_TaskLo create Success!\r\n");

    /* 解锁任务调度,此时会发生任务调度,执行就绪列表中最高优先级任务 */
    LOS_TaskUnlock();

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

LOS_TaskLock() success on cpu1!
Example_TaskHi create Success!
Example_TaskLo create Success!
[cpu2] Enter TaskLo Handler.
[cpu1] Enter TaskHi Handler.
TaskHi LOS_TaskDelete Success.
TaskHi LOS_TaskDelay Done.

完整实例代码

sample_task_smp.c

内存

概述

基本概念

内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。

在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。

Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。

  • 动态内存:在动态内存池中分配用户指定大小的内存块。

    • 优点:按需分配。
    • 缺点:内存池中可能出现碎片。
  • 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。

    • 优点:分配和释放效率高,静态内存池中无碎片。
    • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

动态内存运作机制

动态内存管理,即在内存资源充足的情况下,根据用户需求,从系统配置的一块比较大的连续内存(内存池,也是堆内存)中分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。

与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。

LiteOS动态内存支持bestfit(也称为dlink)和bestfit_little两种内存管理算法。

  1. bestfit

    bestfit内存管理结构如下图所示:

    图 1 bestfit动态内存管理结构图

    • 第一部分

      堆内存(也称内存池)的起始地址及堆区域总大小。

    • 第二部分

      本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。

      假设内存允许的最小节点为2min字节,则数组的第一个双向链表存储的是所有size为2min<size< 2min+1的free节点,第二个双向链表存储的是所有size为2min+1<size< 2min+2的free节点,依次类推第n个双向链表存储的是所有size为2min+n-1<size< 2min+n的free节点。每次申请内存的时候,会从这个数组检索最合适大小的free节点以分配内存。每次释放内存时,会将该内存作为free节点存储至这个数组以便下次再使用。

    • 第三部分

      占用内存池极大部分的空间,是用于存放各节点的实际区域。以下是LosMemDynNode节点结构体申明以及简单介绍:

      typedef struct {
          union {
              LOS_DL_LIST freeNodeInfo;         /* Free memory node */
              struct {
                  UINT32 magic;
                  UINT32 taskId   : 16;
              };
          };
          struct tagLosMemDynNode *preNode;
          UINT32 sizeAndFlag;
      } LosMemCtlNode;
      
      typedef struct tagLosMemDynNode {
          LosMemCtlNode selfNode;
      } LosMemDynNode;

      图 2 LosMemDynNode结构体介绍图

      图 3 对齐方式申请内存结果示意

      当申请到的节点包含的数据空间首地址不符合对齐要求时需要进行对齐,通过增加Gap域确保返回的指针符合对齐要求。

  2. bestfit_little。

    bestfit_little算法是在最佳适配算法的基础上加入slab机制形成的算法。最佳适配算法使得每次分配内存时,都会选择内存池中最小最适合的内存块进行分配,而slab机制可以用于分配固定大小的内存块,从而减小产生内存碎片的可能性。

    Huawei LiteOS内存管理中的slab机制支持配置slab class数目及每个class的最大空间。

    现以内存池中共有4个slab class,每个slab class的最大空间为512字节为例说明slab机制。这4个slab class是从内存池中按照最佳适配算法分配出来的。第一个slab class被分为32个16字节的slab块,第二个slab class被分为16个32字节的slab块,第三个slab class被分为8个64字节的slab块,第四个slab class被分为4个128字节的slab块。

    初始化内存模块时,首先初始化内存池,然后在初始化后的内存池中按照最佳适配算法申请4个slab class,再逐个按照slab内存管理机制初始化4个slab class。

    每次申请内存时,先在满足申请大小的最佳slab class中申请(比如用户申请20字节内存,就在slab块大小为32字节的slab class中申请),如果申请成功,就将slab内存块整块返回给用户,释放时整块回收。需要注意的是,如果满足条件的slab class中已无可以分配的内存块,则从内存池中按照最佳适配算法申请,而不会继续从有着更大slab块空间的slab class中申请。释放内存时,先检查释放的内存块是否属于slab class,如果是则还回对应的slab class中,否则还回内存池中。

静态内存运作机制

静态内存实质上是一个静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。

静态内存池由一个控制块和若干相同大小的内存块构成。控制块位于内存池头部,用于内存块管理。内存块的申请和释放以块大小为粒度。

图 4 静态内存示意图

动态内存

开发指导

使用场景

动态内存管理的主要工作是动态分配并管理用户申请到的内存区间。

动态内存管理主要用于用户需要使用大小不等的内存块的场景。当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。

功能

Huawei LiteOS系统中的动态内存管理模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

初始化和删除内存池

LOS_MemInit

初始化一块指定的动态内存池,大小为size

LOS_MemDeInit

删除指定内存池,仅打开LOSCFG_MEM_MUL_POOL时有效

申请、释放动态内存

LOS_MemAlloc

从指定动态内存池中申请size长度的内存

LOS_MemFree

释放已申请的内存

LOS_MemRealloc

按size大小重新分配内存块,并将原内存块内容拷贝到新内存块。如果新内存块申请成功,则释放原内存块

LOS_MemAllocAlign

从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存

获取内存池信息

LOS_MemPoolSizeGet

获取指定动态内存池的总大小

LOS_MemTotalUsedGet

获取指定动态内存池的总使用量大小

LOS_MemInfoGet

获取指定内存池的内存结构信息,包括空闲内存大小、已使用内存大小、空闲内存块数量、已使用的内存块数量、最大的空闲内存块大小

LOS_MemPoolList

打印系统中已初始化的所有内存池,包括内存池的起始地址、内存池大小、空闲内存总大小、已使用内存总大小、最大的空闲内存块大小、空闲内存块数量、已使用的内存块数量。仅打开LOSCFG_MEM_MUL_POOL时有效

获取内存块信息

LOS_MemFreeBlksGet

获取指定内存池的空闲内存块数量

LOS_MemUsedBlksGet

获取指定内存池已使用的内存块数量

LOS_MemTaskIdGet

获取申请了指定内存块的任务ID

LOS_MemLastUsedGet

获取内存池最后一个已使用内存块的结束地址

LOS_MemNodeSizeCheck

获取指定内存块的总大小和可用大小,仅打开LOSCFG_BASE_MEM_NODE_SIZE_CHECK时有效

LOS_MemFreeNodeShow

打印指定内存池的空闲内存块的大小及数量

检查指定内存池的完整性

LOS_MemIntegrityCheck

对指定内存池做完整性检查,仅打开LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK时有效

设置、获取内存检查级别,仅打开LOSCFG_BASE_MEM_NODE_SIZE_CHECK时有效

LOS_MemCheckLevelSet

设置内存检查级别

LOS_MemCheckLevelGet

获取内存检查级别

为指定模块申请、释放动态内存,仅打开LOSCFG_MEM_MUL_MODULE时有效

LOS_MemMalloc

从指定动态内存池分配size长度的内存给指定模块,并纳入模块统计

LOS_MemMfree

释放已经申请的内存块,并纳入模块统计

LOS_MemMallocAlign

从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存给指定模块,并纳入模块统计

LOS_MemMrealloc

按size大小重新分配内存块给指定模块,并将原内存块内容拷贝到新内存块,同时纳入模块统计。如果新内存块申请成功,则释放原内存块

获取指定模块的内存使用量

LOS_MemMusedGet

获取指定模块的内存使用量,仅打开LOSCFG_MEM_MUL_MODULE时有效

须知:

  • 上述接口中,通过宏开关控制的都是内存调测功能相关的接口。
  • 对于bestfit_little算法,只支持宏开关LOSCFG_MEM_MUL_POOL控制的多内存池相关接口和宏开关LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK控制的内存合法性检查接口,不支持其他内存调测功能。
  • 通过LOS_MemAllocAlign/LOS_MemMallocAlign申请的内存进行LOS_MemRealloc/LOS_MemMrealloc操作后,不能保障新的内存首地址保持对齐。
  • 对于bestfit_little算法,不支持对LOS_MemAllocAlign申请的内存进行LOS_MemRealloc操作,否则将返回失败。

开发流程

本节介绍使用动态内存的典型场景开发流程。

  1. 在los_config.h文件中配置项动态内存池起始地址与大小。

    配置项

    含义

    取值范围

    默认值

    依赖

    OS_SYS_MEM_ADDR

    系统动态内存起始地址

    [0, n)

    &m_aucSysMem1[0]

    OS_SYS_MEM_SIZE

    系统动态内存池的大小(DDR自适应配置),以byte为单位

    [0, n)

    从bss段末尾至系统DDR末尾

    • OS_SYS_MEM_ADDR:一般使用默认值即可。
    • OS_SYS_MEM_SIZE:一般使用默认值即可。
  2. 通过make menuconfig配置动态内存管理模块。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_MEM_BESTFIT

    选择bestfit内存管理算法

    YES/NO

    YES

    LOSCFG_KERNEL_MEM_BESTFIT_LITTLE

    选择bestfit_little内存管理算法

    YES/NO

    NO

    LOSCFG_KERNEL_MEM_SLAB_EXTENTION

    使能slab功能,可以降低系统持续运行过程中内存碎片化的程度

    YES/NO

    NO

    LOSCFG_KERNEL_MEM_SLAB_AUTO_EXPANSION_MODE

    slab自动扩展,当分配给slab的内存不足时,能够自动从系统内存池中申请新的空间进行扩展

    YES/NO

    NO

    LOSCFG_KERNEL_MEM_SLAB_EXTENTION

    LOSCFG_MEM_TASK_STAT

    使能任务内存统计

    YES/NO

    YES

    LOSCFG_KERNEL_MEM_BESTFIT或LOSCFG_KERNEL_MEM_BESTFIT_LITTLE

  3. 初始化LOS_MemInit。

    初始一个内存池后如图,生成一个 EndNode,并且剩余的内存全部被标记为FreeNode节点。注:EndNode作为内存池末尾的节点,size为0。

  4. 申请任意大小的动态内存LOS_MemAlloc。

    判断动态内存池中是否存在申请量大小的空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。

    调用三次LOS_MemAlloc函数可以创建三个节点,假设分别为UsedA,UsedB,UsedC,大小分别为sizeA,sizeB,sizeC。因为刚初始化内存池的时候只有一个大的FreeNode,所以这些内存块是从这个FreeNode中切割出来的。

    当内存池中存在多个FreeNode的时候进行malloc,将会适配最合适大小的FreeNode用来新建内存块,减少内存碎片。若新建的内存块不等于被使用的FreeNode的大小,则在新建内存块后,多余的内存又会被标记为一个新的FreeNode。

  5. 释放动态内存LOS_MemFree。

    回收内存块,供下一次使用。

    假设调用LOS_MemFree释放内存块UsedB,则会回收内存块UsedB,并且将其标记为FreeNode。在回收内存块时,相邻的FreeNode会自动合并。

平台差异性

无。

注意事项

  • 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外消耗内存,故实际用户可使用内存总量小于配置项OS_SYS_MEM_SIZE的大小。
  • 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign因为要进行地址对齐,可能会额外消耗部分内存,故存在一些遗失内存,当系统释放该对齐内存时,同时回收由于对齐导致的遗失内存。
  • 重新分配内存接口LOS_MemRealloc/LOS_MemMrealloc如果分配成功,系统会自己判定是否需要释放原来申请的内存,并返回重新分配的内存地址。如果重新分配失败,原来的内存保持不变,并返回NULL。禁止使用pPtr = LOS_MemRealloc(pool, pPtr, uwSize),即:不能使用原来的旧内存地址pPtr变量来接收返回值。
  • 对同一块内存多次调用LOS_MemFree/LOS_MemMfree时,第一次会返回成功,但对同一块内存多次重复释放会导致非法指针操作,结果不可预知。
  • 由于动态内存管理的内存节点控制块结构体LosMemDynNode中,成员sizeAndFlag的数据类型为UINT32,高两位为标志位,余下的30位表示内存结点大小,因此用户初始化内存池的大小不能超过1G,否则会出现不可预知的结果。

编程实例

实例描述

前提条件:在menuconfig菜单中完成动态内存的配置。

本实例执行以下步骤:

  1. 初始化一个动态内存池。
  2. 从动态内存池中申请一个内存块。
  3. 在内存块中存放一个数据。
  4. 打印出内存块中的数据。
  5. 释放该内存块。

编程实例

#define TEST_POOL_SIZE (2*1024*1024)
UINT8 g_testPool[TEST_POOL_SIZE];

VOID Example_DynMem(VOID)
{
    UINT32 *mem = NULL;
    UINT32 ret;

    ret = LOS_MemInit(g_testPool, TEST_POOL_SIZE);
    if (LOS_OK  == ret) {
        dprintf("内存池初始化成功!\n");
    } else {
        dprintf("内存池初始化失败!\n");
        return;
    }

    /*分配内存*/
    mem = (UINT32 *)LOS_MemAlloc(g_testPool, 4);
    if (NULL == mem) {
        dprintf("内存分配失败!\n");
        return;
    }
    dprintf("内存分配成功\n");

    /*赋值*/
    *mem = 828;
    dprintf("*mem = %d\n", *mem);

    /*释放内存*/
    ret = LOS_MemFree(g_testPool, mem);
    if (LOS_OK == ret) {
        dprintf("内存释放成功!\n");
    } else {
        dprintf("内存释放失败!\n");
    }

    return;
}

结果验证

输出结果如下:

内存池初始化成功!
内存分配成功
*mem = 828
内存释放成功!

完整实例代码

sample_mem.c

静态内存

开发指导

使用场景

当用户需要使用固定长度的内存时,可以通过静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。

功能

Huawei LiteOS的静态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

初始化静态内存池

LOS_MemboxInit

初始化一个静态内存池,根据入参设定其起始地址、总大小及每个内存块大小

清除静态内存块内容

LOS_MemboxClr

清零指定静态内存块的内容

申请、释放静态内存

LOS_MemboxAlloc

从指定的静态内存池中申请一块静态内存块

LOS_MemboxFree

释放指定的一块静态内存块

获取、打印静态内存池信息

LOS_MemboxStatisticsGet

获取指定静态内存池的信息,包括内存池中总内存块数量、已经分配出去的内存块数量、每个内存块的大小

LOS_ShowBox

打印指定静态内存池所有节点信息(打印等级是LOS_INFO_LEVEL),包括内存池起始地址、内存块大小、总内存块数量、每个空闲内存块的起始地址、所有内存块的起始地址

开发流程

本节介绍使用静态内存的典型场景开发流程。

  1. 通过make menuconfig配置静态内存管理模块。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_MEMBOX

    使能membox内存管理

    YES/NO

    YES

    LOSCFG_KERNEL_MEMBOX_STATIC

    选择静态内存方式实现membox

    YES/NO

    YES

    LOSCFG_KERNEL_MEMBOX

    LOSCFG_KERNEL_MEMBOX_DYNAMIC

    选择动态内存方式实现membox

    YES/NO

    NO

    LOSCFG_KERNEL_MEMBOX

  2. 规划一片内存区域作为静态内存池。

  3. 调用LOS_MemboxInit初始化静态内存池。

    初始化会将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处放置控制头。

  4. 调用LOS_MemboxAlloc接口分配静态内存。

    系统将会从空闲链表中获取第一个空闲块,并返回该内存块的起始地址。

  5. 调用LOS_MemboxClr接口。

    将入参地址对应的内存块清零。

  6. 调用LOS_MemboxFree接口。

    将该内存块加入空闲链表。

平台差异性

无。

注意事项

静态内存池区域,如果是通过动态内存分配方式获得的,在不需要静态内存池时,需要释放该段内存,避免发生内存泄露。

编程实例

实例描述

前提条件:在menuconfig菜单中完成静态内存的配置。

本实例执行以下步骤:

  1. 初始化一个静态内存池。
  2. 从静态内存池中申请一块静态内存。
  3. 在内存块存放一个数据。
  4. 打印出内存块中的数据。
  5. 清除内存块中的数据。
  6. 释放该内存块。

编程实例

VOID Example_StaticMem(VOID)
{
    UINT32 *mem = NULL;
    UINT32 blkSize = 10;
    UINT32 boxSize = 100;
    UINT32 boxMem[1000];
    UINT32 ret;

    ret = LOS_MemboxInit(&boxMem[0], boxSize, blkSize);
    if(ret != LOS_OK) {
        printf("内存池初始化失败!\n");
        return;
    } else {
        printf("内存池初始化成功!\n");
    }

    /*申请内存块*/
    mem = (UINT32 *)LOS_MemboxAlloc(boxMem);
    if (NULL == mem) {
        printf("内存分配失败!\n");
        return;
    }
    printf("内存分配成功\n");

    /*赋值*/
    *mem = 828;
    printf("*mem = %d\n", *mem);

     /*清除内存内容*/
     LOS_MemboxClr(boxMem, mem);
     printf("清除内存内容成功\n *mem = %d\n", *mem);

    /*释放内存*/
    ret = LOS_MemboxFree(boxMem, mem);
    if (LOS_OK == ret) {
        printf("内存释放成功!\n");
    } else {
        printf("内存释放失败!\n");
    }

    return;
}

结果验证

输出结果如下:

内存池初始化成功!
内存分配成功
*mem = 828
清除内存内容成功
*mem = 0
内存释放成功!

完整实例代码

sample_membox.c

中断

概述

基本概念

中断是指出现需要时,CPU暂停执行当前程序,转而执行新程序的过程。即在程序运行过程中,出现了一个必须由CPU立即处理的事务。此时,CPU暂时中止当前程序的执行转而处理这个事务,这个过程就叫做中断。

外设可以在没有CPU介入的情况下完成一定的工作,但某些情况下也需要CPU为其执行一定的工作。通过中断机制,在外设不需要CPU介入时,CPU可以执行其它任务,而当外设需要CPU时,将通过产生中断信号使CPU立即中断当前任务来响应中断请求。这样可以使CPU避免把大量时间耗费在等待、查询外设状态的操作上,大大提高系统实时性以及执行效率。

Huawei LiteOS的中断特性:

  • 中断共享,且可配置。
  • 中断嵌套,即高优先级的中断可抢占低优先级的中断,且可配置。
  • 使用独立中断栈,可配置。
  • 可配置支持的中断优先级个数。
  • 可配置支持的中断数。

中断相关的硬件介绍

与中断相关的硬件可以划分为三类:设备、中断控制器、CPU本身。

  • 设备

    发起中断的源,当设备需要请求CPU时,产生一个中断信号,该信号连接至中断控制器。

  • 中断控制器

    中断控制器是CPU众多外设中的一个,它一方面接收其它外设中断引脚的输入,另一方面,它会发出中断信号给CPU。可以通过对中断控制器编程来打开和关闭中断源、设置中断源的优先级和触发方式。常用的中断控制器有VIC(Vector Interrupt Controller)和GIC(General Interrupt Controller)。在ARM Cortex-M系列中使用的中断控制器是NVIC(Nested Vector Interrupt Controller)。在ARM Cortex-A7中使用的中断控制器是GIC。

  • CPU

    CPU会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。

中断相关概念

中断号

每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。

中断请求

“紧急事件”需向CPU提出申请(发一个电脉冲信号),要求中断,及要求CPU暂停当前执行的任务,转而处理该“紧急事件”,这一申请过程称为中断请求。

中断优先级

为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。

中断处理程序

当外设产生中断请求后,CPU暂停当前的任务,转而响应中断申请,即执行中断处理程序。产生中断的每个设备都有相应的中断处理程序。

中断嵌套

中断嵌套也称为中断抢占,指的是正在执行一个中断处理程序时,如果有另一个优先级更高的中断源提出中断请求,这时会暂时终止当前正在执行的优先级较低的中断源的中断处理程序,转而去处理更高优先级的中断请求,待处理完毕,再返回到之前被中断的处理程序中继续执行。

中断触发

中断源向中断控制器发送中断信号,中断控制器对中断进行仲裁,确定优先级,将中断信号送给CPU。中断源产生中断信号的时候,会将中断触发器置“1”,表明该中断源产生了中断,要求CPU去响应该中断。

中断触发类型

外部中断申请通过一个物理信号发送到NVIC/GIC,可以是电平触发或边沿触发。

中断向量

中断服务程序的入口地址。

中断向量表

存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。

中断共享

当外设较少时,可以实现一个外设对应一个中断号,但为了支持更多的硬件设备,可以让多个设备共享一个中断号,共享同一个中断号的中断处理程序形成一个链表。当外部设备产生中断申请时,系统会遍历执行中断号对应的中断处理程序链表直到找到对应设备的中断处理程序。在遍历执行过程中,各中断处理程序可以通过检测设备ID,判断是否是这个中断处理程序对应的设备产生的中断。

核间中断

对于多核系统,中断控制器允许一个CPU的硬件线程去中断其他CPU的硬件线程,这种方式被称为核间中断。核间中断的实现基础是多CPU内存共享,采用核间中断可以减少某个CPU负荷过大,有效提升系统效率。目前只有GIC中断控制器支持。

运作机制

  • Huawei LiteOS的中断机制支持中断共享:

    中断共享的实现依赖于链表,对应每一个中断号创建一个链表,链表节点中包含注册的中断处理函数和函数入参。当对同一中断号多次创建中断时,将中断处理函数和函数入参添加到中断号对应的链表中,因此当硬件产生中断时,通过中断号查找到其对应的链表,遍历执行链表直到找到对应设备的中断处理函数。

  • Huawei LiteOS的中断嵌套:

    • GIC与NVIC的中断嵌套由硬件实现。
    • RISC-V的中断嵌套实现机制为:中断嵌套下,中断A触发后会将当前的操作进行压栈,调用中断处理程序前,将MIE设置为1,允许新的中断被响应。在A执行中断处理程序的过程中,如果有更高优先级的中断B被触发,B 会将当前的操作即中断A相关的操作进行压栈,然后执行B的中断处理程序。待B的中断处理程序执行完后,会暂时的将mstatus寄存器中的MIE域置为0,关闭中断响应,将中断A相关的操作进行出栈,将MIE设置为1,允许处理器再次响应中断,中断B结束,继续执行中断A。

开发指导

使用场景

当有中断请求产生时,CPU暂停当前的任务,转而去响应外设请求。根据需要,用户通过中断申请,注册中断处理程序,可以指定CPU响应中断请求时所执行的具体操作。

功能

Huawei LiteOS 的中断模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

创建和删除中断

LOS_HwiCreate

中断创建,注册中断号、中断触发模式、中断优先级、中断处理程序。中断被触发时,handleIrq会调用该中断处理程序

LOS_HwiDelete

删除中断

打开和关闭所有中断

LOS_IntUnLock

打开当前处理器所有中断响应

LOS_IntLock

关闭当前处理器所有中断响应

LOS_IntRestore

恢复到使用LOS_IntLock关闭所有中断之前的状态

使能和屏蔽指定中断

LOS_HwiDisable

中断屏蔽(通过设置寄存器,禁止CPU响应该中断)

LOS_HwiEnable

中断使能(通过设置寄存器,允许CPU响应该中断)

设置中断优先级

LOS_HwiSetPriority

设置中断优先级

触发中断

LOS_HwiTrigger

触发中断(通过写中断控制器的相关寄存器模拟外部中断)

清除中断寄存器状态

LOS_HwiClear

清除中断号对应的中断寄存器的状态位,此接口依赖中断控制器版本,非必需

核间中断

LOS_HwiSendIpi

向指定核发送核间中断,此接口依赖中断控制器版本和cpu架构,该函数仅在SMP模式下支持

设置中断亲和性

LOS_HwiSetAffinity

设置中断的亲和性,即设置中断在固定核响应(该函数仅在SMP模式下支持)

HWI错误码

对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

OS_ERRNO_HWI_NUM_INVALID

0x02000900

创建或删除中断时,传入了无效中断号

检查中断号,给定有效中断号

2

OS_ERRNO_HWI_PROC_FUNC_NULL

0x02000901

创建中断时,传入的中断处理程序指针为空

传入非空中断处理程序指针

3

OS_ERRNO_HWI_CB_UNAVAILABLE

0x02000902

无可用中断资源

暂不使用该错误码

4

OS_ERRNO_HWI_NO_MEMORY

0x02000903

创建中断时,出现内存不足的情况

增大动态内存空间,有两种方式可以实现:

  • 设置更大的系统动态内存池,配置项为OS_SYS_MEM_SIZE
  • 释放一部分动态内存

5

OS_ERRNO_HWI_ALREADY_CREATED

0x02000904

创建中断时,发现要注册的中断号已经创建

对于非共享中断号的情况,检查传入的中断号是否已经被创建;对于共享中断号的情况,检查传入中断号的链表中是否已经有匹配函数参数的设备ID

6

OS_ERRNO_HWI_PRIO_INVALID

0x02000905

创建中断时,传入的中断优先级无效

传入有效中断优先级。优先级有效范围依赖于硬件,外部可配

7

OS_ERRNO_HWI_MODE_INVALID

0x02000906

中断模式无效

传入有效中断模式[0,1]

8

OS_ERRNO_HWI_FASTMODE_ALREADY_CREATED

0x02000907

创建硬中断时,发现要注册的中断号,已经创建为快速中断

暂不使用该错误码

9

OS_ERRNO_HWI_INTERR

0x02000908

接口在中断中调用

暂不使用该错误码

10

OS_ERRNO_HWI_SHARED_ERROR

0x02000909

创建中断时:发现hwiMode指定创建共享中断,但是未设置设备ID;或hwiMode指定创建非共享中断,但是该中断号之前已创建为共享中断;或配置LOSCFG_NO_SHARED_IRQ为yes,但是创建中断时,入参指定创建共享中断

删除中断时:设备号创建时指定为共享中断,删除时未设置设备ID,删除错误。

检查入参,创建时参数hwiMode与irqParam保持一致,hwiMode为0,表示不共享,此时irqParam应为NULL;当hwiMode为IRQF_SHARD时表示共享,irqParam需设置设备ID;LOSCFG_NO_SHARED_IRQ为YES时,即非共享中断模式下,只能创建非共享中断。删除中断时irqParam要与创建中断时的参数一样

11

OS_ERRNO_HWI_ARG_INVALID

0x0200090a

注册中断入参有误

暂不使用该错误码

12

OS_ERRNO_HWI_HWINUM_UNCREATE

0x0200090b

中断共享情况下,删除中断时,中断号对应的链表中,无法匹配到相应的设备ID

对于共享中断号的情况,检查传入中断号的链表中是否已经有匹配函数参数的设备ID

须知: 错误码定义见错误码简介。8~15位的所属模块为中断模块,值为0x09。

开发流程

  1. 通过make menuconfig配置中断模块,菜单路径为:Kernel ---> Hardware Interrupt。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_ARCH_INTERRUPT_PREEMPTION

    使能中断嵌套

    YES/NO

    NO

    ARMv8、RISC-V

    LOSCFG_IRQ_USE_STANDALONE_STACK

    使用独立中断栈

    YES/NO

    YES

    依赖核,某些平台可能没有此配置项

    LOSCFG_NO_SHARED_IRQ

    使能中断不共享

    YES/NO

    NO

    LOSCFG_PLATFORM_HWI_LIMIT

    最大中断使用数

    根据芯片手册适配

    根据芯片手册适配

    LOSCFG_HWI_PRIO_LIMIT

    可设置的中断优先级个数

    根据芯片手册适配

    根据芯片手册适配

  2. 调用中断创建接口LOS_HwiCreate创建中断。

  3. 如果是SMP模式,调用LOS_HwiSetAffinity设置中断的亲和性,否则直接进入步骤4。

  4. 调用LOS_HwiEnable接口使能指定中断。

  5. 调用LOS_HwiTrigger接口触发指定中断(该接口通过写中断控制器的相关寄存器模拟外部中断,一般的外设设备,不需要执行这一步)。

  6. 调用LOS_HwiDisable接口屏蔽指定中断,此接口根据实际情况使用,判断是否需要屏蔽中断。

  7. 调用LOS_HwiDelete接口删除指定中断,此接口根据实际情况使用,判断是否需要删除中断。

注意事项

  • 根据具体硬件,配置支持的最大中断数及可设置的中断优先级个数。
  • 中断共享机制,支持不同的设备使用相同的中断号注册同一中断处理程序,但中断处理程序的入参pDevId(设备号)必须唯一,代表不同的设备。即同一中断号,同一dev只能挂载一次;但同一中断号,同一中断处理程序,dev不同则可以重复挂载。
  • 中断处理程序耗时不能过长,否则会影响CPU对中断的及时响应。
  • 中断响应过程中不能执行引起调度的函数。
  • 中断恢复LOS_IntRestore()的入参必须是与之对应的LOS_IntLock()的返回值(即关中断之前的CPSR值)。
  • Cortex-M系列处理器中0-15中断为内部使用,Cortex-A7中0-31中断为内部使用,因此不建议用户去申请和创建。

编程实例

实例描述

本实例实现如下功能:

  1. 创建中断。
  2. 设置中断亲和性。
  3. 使能中断。
  4. 触发中断。
  5. 屏蔽中断。
  6. 删除中断。

编程示例

前提条件:menuconfig菜单中配置中断使用最大数、配置可设置的中断优先级个数。

代码实现如下:

#include "los_hwi.h"
#include "los_typedef.h"
#include "los_task.h"

STATIC VOID HwiUsrIrq(VOID)
{
    printf("\n in the func HwiUsrIrq \n"); 
}

/* cpu0 trigger, cpu0 response */
UINT32 It_Hwi_001(VOID)
{
    UINT32 ret;
    UINT32 irqNum = 26; /* ppi */
    UINT32 irqPri = 0x3;

    ret = LOS_HwiCreate(irqNum, irqPri, 0, (HWI_PROC_FUNC)HwiUsrIrq, 0);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }

#ifdef LOSCFG_KERNEL_SMP
    ret = LOS_HwiSetAffinity(irqNum, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    if (ret != LOS_OK) {
        return LOS_NOK;
    }
#endif
    ret = LOS_HwiEnable(irqNum);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }

    ret = LOS_HwiTrigger(irqNum);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }

    LOS_TaskDelay(1);

    ret = LOS_HwiDisable(irqNum);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }

    ret = LOS_HwiDelete(irqNum, NULL);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }

    return LOS_OK;
}

完整实例

sample_hwi.c

异常接管

概述

基本概念

异常接管是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如打印异常发生时当前函数的调用栈信息、CPU现场信息、任务的堆栈情况等。

异常接管作为一种调测手段,可以在系统发生异常时给用户提供有用的异常信息,譬如异常类型、发生异常时的系统状态等,方便用户定位分析问题。

Huawei LiteOS的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等),以及CPU现场等信息。

运作机制

每个函数都有自己的栈空间,称为栈帧。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长。

以ARM32 CPU架构为例,每个栈帧中都会保存PC、LR、SP和FP寄存器的历史值。

堆栈分析

  • LR寄存器(Link Register),链接寄存器,指向函数的返回地址。

  • R11:可以用作通用寄存器,在开启特定编译选项时可以用作帧指针寄存器FP,用来实现栈回溯功能。

    GNU编译器(gcc)默认将R11作为存储变量的通用寄存器,因而默认情况下无法使用FP的栈回溯功能。为支持调用栈解析功能,需要在编译参数中添加-fno-omit-frame-pointer选项,提示编译器将R11作为FP使用。

  • FP寄存器(Frame Point),帧指针寄存器,指向当前函数的父函数的栈帧起始地址。利用该寄存器可以得到父函数的栈帧,从栈帧中获取父函数的FP,就可以得到祖父函数的栈帧,以此类推,可以追溯程序调用栈,得到函数间的调用关系。

    当系统发生异常时,系统打印异常函数的栈帧中保存的寄存器内容,以及父函数、祖父函数的栈帧中的LR、FP寄存器内容,用户就可以据此追溯函数间的调用关系,定位异常原因。

堆栈分析原理如下图所示,实际堆栈信息根据不同CPU架构有所差异,此处仅做示意。

图 1 堆栈分析原理示意图

图中不同颜色的寄存器表示不同的函数。可以看到函数调用过程中,寄存器的保存。通过FP寄存器,栈回溯到异常函数的父函数,继续按照规律对栈进行解析,推出函数调用关系,方便用户定位问题。

使用指南

功能

异常接管对系统运行期间发生的芯片硬件异常进行处理,不同芯片的异常类型存在差异,具体异常类型可以查看芯片手册。

定位流程

异常接管一般的定位步骤如下:

  1. 打开编译后生成的镜像反汇编(asm)文件。
  2. 搜索PC指针(指向当前正在执行的指令)在asm中的位置,找到发生异常的函数。
  3. 根据LR值查找异常函数的父函数。
  4. 重复步骤3,得到函数间的调用关系,找到异常原因。

具体的定位方法会在实例中举例说明。

注意事项

要查看调用栈信息,必须添加编译选项宏-fno-omit-frame-pointer支持stack frame,否则编译时FP寄存器是关闭的。

问题定位实例

在某ARM32平台上通过错误释放内存,触发系统异常。系统异常被挂起后,能在串口中看到异常调用栈打印信息和关键寄存器信息,如下所示,其中excType表示异常类型,此处值为4表示数据终止异常,其它数值可以查看芯片手册。通过这些信息可以定位到异常所在函数和其调用栈关系,为分析异常原因提供第一手资料。

excType: 4
taskName = MNT_send
taskId = 6
task stackSize = 12288
excBuffAddr pc = 0x8034d3cc
excBuffAddr lr = 0x8034d3cc
excBuffAddr sp = 0x809ca358
excBuffAddr fp = 0x809ca36c
*******backtrace begin*******
traceback 0 -- lr = 0x803482fc
traceback 0 -- fp = 0x809ca38c
traceback 1 -- lr = 0x80393e34
traceback 1 -- fp = 0x809ca3a4
traceback 2 -- lr = 0x8039e0d0
traceback 2 -- fp = 0x809ca3b4
traceback 3 -- lr = 0x80386bec
traceback 3 -- fp = 0x809ca424
traceback 4 -- lr = 0x800a6210
traceback 4 -- fp = 0x805da164

定位步骤如下:

  1. 打开编译后生成的 asm 文件,默认是 vs_server.asm(默认生成在Huawei_LiteOS/out/<platform>目录下,其中的platform为具体的平台名)。

  2. 搜索PC指针 8034d3cc 在asm文件中的位置(去掉0x)。

    PC地址指向发生异常时程序正在执行的指令。在当前执行的二进制文件对应的asm文件中,查找PC值8034d3cc,找到当前CPU正在执行的指令行,得到如下图所示结果。

    从图中可以看到:

    1. 异常时CPU正在执行的指令是ldrh r2, [r4, #-4]。
    2. 异常发生在函数osSlabMemFree中。

    结合ldrh指令分析,此指令是从内存的(r4-4)地址中读值,将其load到寄存器r2中。再结合异常时打印的寄存器信息,查看此时r4的值。下图是异常时打印的寄存器信息,可以看到,r4此时值是0xffffffff。

    显然,r4的值超出了内存范围,故CPU执行到该指令时发生了数据终止异常。根据汇编知识,从asm文件可以看到,r4是从r1 mov过来,而r1是函数第二个入参,于是可以确认,在调用osSlabMemFree时传入了0xffffffff(或-1)这样一个错误入参。

    接下来,需要查找谁调用了osSlabMemFree函数。

  3. 根据 LR链接寄存器值 查找调用栈。

    从异常信息的backtrace begin开始,打印的是调用栈信息。在asm文件中查找backtrace 0对应的LR,如下图所示。

    可见,是LOS_MemFree调用了osSlabMemFree。依此方法,可得到异常时函数调用关系如下:MNT_buf_send(业务函数) -> free -> LOS_MemFree -> osSlabMemFree。

    最终,通过排查业务中MNT_buf_send实现,发现其中存在错误使用指针的问题,导致free了一个错误地址,引发上述异常。

错误处理

概述

基本概念

错误处理指程序运行错误时,调用错误处理模块的接口函数,上报错误信息,并调用注册的钩子函数进行特定处理,保存现场以便定位问题。

通过错误处理,可以控制和提示程序中的非法输入,防止程序崩溃。

运作机制

错误处理是一种机制,用于处理异常状况。当程序出现错误时,会显示相应的错误码。此外,如果注册了相应的错误处理函数,则会执行这个函数。

图 1 错误处理示意图

开发指导

错误码简介

调用API接口时可能会出现错误,此时接口会返回对应的错误码,以便快速定位错误原因。

错误码是一个32位的无符号整型数,31~24位表示错误等级,23~16位表示错误码标志(当前该标志值为0),15~8位代表错误码所属模块,7~0位表示错误码序号。

表 1 错误码中的错误等级

错误等级

数值

含义

NORMAL

0

提示

WARN

1

告警

ERR

2

严重

FATAL

3

致命

例如将任务模块中的错误码LOS_ERRNO_TSK_NO_MEMORY定义为FATAL级别的错误,模块ID为LOS_MOD_TSK,错误码序号为0,其定义如下:

#define LOS_ERRNO_TSK_NO_MEMORY  LOS_ERRNO_OS_FATAL(LOS_MOD_TSK, 0x00)
#define LOS_ERRNO_OS_FATAL(MID, ERRNO)  \
    (LOS_ERRTYPE_FATAL | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | ((UINT32)(ERRNO)))

说明: LOS_ERRTYPE_FATAL:错误等级为FATAL,值为0x03000000 LOS_ERRNO_OS_ID:错误码标志,值为0x000000 MID:所属模块,LOS_MOD_TSK的值为0x2 ERRNO:错误码序号 所以LOS_ERRNO_TSK_NO_MEMORY的值为0x03000200

错误码接管

有时只靠错误码不能快速准确的定位问题,为方便用户分析错误,错误处理模块支持注册错误处理的钩子函数,发生错误时,用户可以调用LOS_ErrHandle接口以执行错误处理函数。

Huawei LiteOS 的错误处理模块为用户提供下面几个接口,接口详细信息可以查看API参考。

接口名

描述

参数

备注

LOS_RegErrHandle

注册错误处理钩子函数

func:错误处理钩子函数

-

LOS_ErrHandle

调用钩子函数,处理错误

fileName:存放错误日志的文件名

系统内部调用时,入参为"os_unspecific_file"

lineNo:发生错误的代码行号

系统内部调用时,若值为0xa1b2c3f8,表示未传递行号

errnoNo:错误码

-

paraLen:入参para的长度

系统内部调用时,入参为0

para:错误标签

系统内部调用时,入参为NULL

须知: 系统内部会在某些难以定位的错误处,主动调用注册的钩子函数(目前只在互斥锁模块和信号量模块中主动调用了钩子函数)。

注意事项

系统中只有一个错误处理的钩子函数。当多次注册钩子函数时,最后一次注册的钩子函数会覆盖前一次注册的函数。

编程实例

实例描述

在下面的例子中,演示如下功能:

  1. 注册错误处理钩子函数。
  2. 执行错误处理函数。

编程示例

代码实现如下:

#include "los_err.h"
#include "los_typedef.h"
#include <stdio.h>

void Test_ErrHandle(CHAR *fileName, UINT32 lineNo, UINT32 errorNo, UINT32 paraLen, VOID  *para)
{
    printf("err handle ok\n");
}

static UINT32 TestCase(VOID)
{
    UINT32 errNo = 0;
    UINT32 ret;
    UINT32 errLine = 16;

    LOS_RegErrHandle(Test_ErrHandle);

    ret = LOS_ErrHandle("os_unspecific_file", errLine, errNo, 0, NULL);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

Huawei LiteOS # err handle ok

完整实例代码

sample_err.c

队列

概述

基本概念

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

任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。

消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。

Huawei LiteOS中使用队列实现任务异步通信,具有如下特性:

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

运作机制

队列控制块

typedef enum {
    OS_QUEUE_READ =0,
    OS_QUEUE_WRITE =1,
    OS_QUEUE_N_RW =2
} QueueReadWrite;

/**
  * Queue information block structure
  */
typedef struct 
{
    UINT8       *queueHandle;                    /* 队列指针 */
    UINT8       queueState;                      /* 队列状态 */
    UINT8       queueMemType;                    /* 创建队列时内存分配的方式 */
    UINT16      queueLen;                        /* 队列中消息节点个数,即队列长度 */
    UINT16      queueSize;                       /* 消息节点大小 */
    UINT32      queueID;                         /* 队列ID */
    UINT16      queueHead;                       /* 消息头节点位置(数组下标)*/
    UINT16      queueTail;                       /* 消息尾节点位置(数组下标)*/
    UINT16      readWriteableCnt[OS_QUEUE_N_RW]; /* 数组下标0的元素表示队列中可读消息数,                              
                                                    数组下标1的元素表示队列中可写消息数 */
    LOS_DL_LIST readWriteList[OS_QUEUE_N_RW];    /* 读取或写入消息的任务等待链表, 
                                                    下标0:读取链表,下标1:写入链表 */
    LOS_DL_LIST memList;                         /* CMSIS-RTOS中的MailBox模块使用的内存块链表 */
} LosQueueCB;

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

  • OS_QUEUE_UNUSED:队列没有被使用。
  • OS_QUEUE_INUSED:队列被使用中。

每个队列控制块中都含有创建队列时的内存分配方式:

  • OS_QUEUE_ALLOC_DYNAMIC:创建队列时所需的队列空间,由系统自行动态申请内存获取。
  • OS_QUEUE_ALLOC_STATIC:创建队列时所需的队列空间,由接口调用者自行申请后传入接口。

队列运作原理

创建队列时,创建队列成功会返回队列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 队列读写数据操作示意图

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

开发指导

使用场景

队列用于任务间通信,可以实现消息的异步处理。同时消息的发送方和接收方不需要彼此联系,两者间是解耦的。

功能

Huawei LiteOS中的队列模块提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

创建/删除消息队列

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、队列长度、消息节点大小、头节点、尾节点、可读节点数量、可写节点数量、等待读操作的任务、等待写操作的任务、等待mail操作的任务

队列错误码

对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_QUEUE_MAXNUM_ZERO

0x02000600

系统支持的最大队列数为0

系统支持的最大队列数应该大于0。如果不使用队列模块,则将队列模块静态裁剪开关LOSCFG_BASE_IPC_QUEUE设置为NO

2

LOS_ERRNO_QUEUE_NO_MEMORY

0x02000601

队列初始化时,从动态内存池申请内存失败

设置更大的系统动态内存池,配置项为OS_SYS_MEM_SIZE,或减少系统支持的最大队列数

3

LOS_ERRNO_QUEUE_CREATE_NO_MEMORY

0x02000602

创建队列时,从动态内存池申请内存失败

设置更大的系统动态内存池,配置项为OS_SYS_MEM_SIZE,或减少要创建队列的队列长度和消息节点大小

4

LOS_ERRNO_QUEUE_SIZE_TOO_BIG

0x02000603

创建队列时消息节点大小超过上限

更改入参消息节点大小,使之不超过上限

5

LOS_ERRNO_QUEUE_CB_UNAVAILABLE

0x02000604

创建队列时,系统中已经没有空闲队列

增加系统支持的最大队列数

6

LOS_ERRNO_QUEUE_NOT_FOUND

0x02000605

传递给删除队列接口的队列ID大于等于系统支持的最大队列数

确保队列ID是有效的

7

LOS_ERRNO_QUEUE_PEND_IN_LOCK

0x02000606

当任务被锁定时,禁止在队列中阻塞等待写消息或读消息

使用队列前解锁任务

8

LOS_ERRNO_QUEUE_TIMEOUT

0x02000607

等待处理队列超时

检查设置的超时时间是否合适

9

LOS_ERRNO_QUEUE_IN_TSKUSE

0x02000608

队列存在阻塞任务而不能被删除

使任务能够获得资源而不是在队列中被阻塞

10

LOS_ERRNO_QUEUE_WRITE_IN_INTERRUPT

0x02000609

在中断处理程序中不能以阻塞模式写队列

将写队列设为非阻塞模式,即将写队列的超时时间设置为0

11

LOS_ERRNO_QUEUE_NOT_CREATE

0x0200060a

队列未创建

创建该队列,或更换为一个已经创建的队列

12

LOS_ERRNO_QUEUE_IN_TSKWRITE

0x0200060b

队列读写不同步

同步队列的读写,即多个任务不能并发读写同一个队列

13

LOS_ERRNO_QUEUE_CREAT_PTR_NULL

0x0200060c

对于创建队列接口,保存队列ID的入参为空指针

确保传入的参数不为空指针

14

LOS_ERRNO_QUEUE_PARA_ISZERO

0x0200060d

对于创建队列接口,入参队列长度或消息节点大小为0

传入正确的队列长度和消息节点大小

15

LOS_ERRNO_QUEUE_INVALID

0x0200060e

传递给读队列或写队列或获取队列信息接口的队列ID大于等于系统支持的最大队列数

确保队列ID有效

16

LOS_ERRNO_QUEUE_READ_PTR_NULL

0x0200060f

传递给读队列接口的指针为空

确保传入的参数不为空指针

17

LOS_ERRNO_QUEUE_READSIZE_IS_INVALID

0x02000610

传递给读队列接口的缓冲区大小为0或者大于0xFFFB

传入的一个正确的缓冲区大小需要大于0且小于0xFFFC

18

LOS_ERRNO_QUEUE_WRITE_PTR_NULL

0x02000612

传递给写队列接口的缓冲区指针为空

确保传入的参数不为空指针

19

LOS_ERRNO_QUEUE_WRITESIZE_ISZERO

0x02000613

传递给写队列接口的缓冲区大小为0

传入正确的缓冲区大小

20

LOS_ERRNO_QUEUE_WRITE_SIZE_TOO_BIG

0x02000615

传递给写队列接口的缓冲区大小比队列的消息节点大小要大

减小缓冲区大小,或增大队列的消息节点大小

21

LOS_ERRNO_QUEUE_ISFULL

0x02000616

写队列时没有可用的空闲节点

写队列之前,确保在队列中存在可用的空闲节点,或者使用阻塞模式写队列,即设置大于0的写队列超时时间

22

LOS_ERRNO_QUEUE_PTR_NULL

0x02000617

传递给获取队列信息接口的指针为空

确保传入的参数不为空指针

23

LOS_ERRNO_QUEUE_READ_IN_INTERRUPT

0x02000618

在中断处理程序中不能以阻塞模式读队列

将读队列设为非阻塞模式,即将读队列的超时时间设置为0

24

LOS_ERRNO_QUEUE_MAIL_HANDLE_INVALID

0x02000619

CMSIS-RTOS 1.0中的mail队列,释放内存块时,发现传入的mail队列ID无效

确保传入的mail队列ID是正确的

25

LOS_ERRNO_QUEUE_MAIL_PTR_INVALID

0x0200061a

CMSIS-RTOS 1.0中的mail队列,释放内存块时,发现传入的mail内存池指针为空

传入非空的mail内存池指针

26

LOS_ERRNO_QUEUE_MAIL_FREE_ERROR

0x0200061b

CMSIS-RTOS 1.0中的mail队列,释放内存块失败

传入非空的mail队列内存块指针

27

LOS_ERRNO_QUEUE_ISEMPTY

0x0200061d

队列已空

读队列之前,确保队列中存在未读的消息,或者使用阻塞模式读队列,即设置大于0的读队列超时时间

28

LOS_ERRNO_QUEUE_READ_SIZE_TOO_SMALL

0x0200061f

传递给读队列接口的读缓冲区大小小于队列消息节点大小

增加缓冲区大小,或减小队列消息节点大小

须知:

  • 错误码定义见错误码简介。8~15位的所属模块为队列模块,值为0x06。
  • 队列模块中的错误码序号0x11、0x14未被定义,不可用。

开发流程

使用队列模块的典型流程如下:

  1. 通过make menuconfig配置队列模块。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_IPC_QUEUE

    队列模块裁剪开关

    YES/NO

    YES

    LOSCFG_QUEUE_STATIC_ALLOCATION

    支持以用户分配内存的方式创建队列

    YES/NO

    NO

    LOSCFG_BASE_IPC_QUEUE

    LOSCFG_BASE_IPC_QUEUE_LIMIT

    系统支持的最大队列数

    <65535

    1024

    LOSCFG_BASE_IPC_QUEUE

  2. 创建队列。创建成功后,可以得到队列ID。

  3. 写队列。

  4. 读队列。

  5. 获取队列信息。

  6. 删除队列。

注意事项

  • 系统支持的最大队列数是指:整个系统的队列资源总个数,而非用户能使用的个数。例如:系统软件定时器多占用一个队列资源,那么用户能使用的队列资源就会减少一个。
  • 创建队列时传入的队列名和flags暂时未使用,作为以后的预留参数。
  • 队列接口函数中的入参timeout是相对时间。
  • LOS_QueueReadCopy和LOS_QueueWriteCopy及LOS_QueueWriteHeadCopy是一组接口,LOS_QueueRead和LOS_QueueWrite及LOS_QueueWriteHead是一组接口,两组接口需要配套使用。
  • 鉴于LOS_QueueWrite和LOS_QueueWriteHead和LOS_QueueRead这组接口实际操作的是数据地址,用户必须保证调用LOS_QueueRead获取到的指针所指向的内存区域在读队列期间没有被异常修改或释放,否则可能导致不可预知的后果。
  • 鉴于LOS_QueueWrite和LOS_QueueWriteHead和LOS_QueueRead这组接口实际操作的是数据地址,也就意味着实际写和读的消息长度仅仅是一个指针数据,因此用户使用这组接口之前,需确保创建队列时的消息节点大小,为一个指针的长度,避免不必要的浪费和读取失败。
  • 当队列使用结束后,如果存在动态申请的内存,需要及时释放这些内存。

编程实例

实例描述

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

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

编程示例

前提条件:在menuconfig菜单中完成队列模块的配置。

#include "los_task.h"
#include "los_queue.h"

static UINT32 g_queue;
#define BUFFER_LEN 50

VOID send_Entry(VOID)
{
    UINT32 i = 0;
    UINT32 ret = 0;
    CHAR abuf[] = "test is message x";
    UINT32 len = sizeof(abuf);

    while (i < 5) {
        abuf[len -2] = '0' + i;
        i++;

        ret = LOS_QueueWriteCopy(g_queue, abuf, len, 0);
        if(ret != LOS_OK) {
            dprintf("send message failure, error: %x\n", ret);
        }

        LOS_TaskDelay(5);
    }
}

VOID recv_Entry(VOID)
{
    UINT32 ret = 0;
    CHAR readBuf[BUFFER_LEN] = {0};
    UINT32 readLen = BUFFER_LEN;

    while (1) {
        ret = LOS_QueueReadCopy(g_queue, readBuf, &readLen, 0);
        if(ret != LOS_OK) {
            dprintf("recv message failure, error: %x\n", ret);
            break;
        }

        dprintf("recv message: %s\n", readBuf);
        LOS_TaskDelay(5);
    }

    while (LOS_OK != LOS_QueueDelete(g_queue)) {
        LOS_TaskDelay(1);
    }

    dprintf("delete the queue success!\n");
}

UINT32 Example_CreateTask(VOID)
{
    UINT32 ret = 0; 
    UINT32 task1, task2;
    TSK_INIT_PARAM_S initParam;

    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)send_Entry;
    initParam.usTaskPrio = 9;
    initParam.uwStackSize = LOS_TASK_MIN_STACK_SIZE;
    initParam.pcName = "sendQueue";
#ifdef LOSCFG_KERNEL_SMP
    initParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endif
    initParam.uwResved = LOS_TASK_STATUS_DETACHED;

    LOS_TaskLock();
    ret = LOS_TaskCreate(&task1, &initParam);
    if(ret != LOS_OK) {
        dprintf("create task1 failed, error: %x\n", ret);
        return ret;
    }

    initParam.pcName = "recvQueue";
    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)recv_Entry;
    ret = LOS_TaskCreate(&task2, &initParam);
    if(ret != LOS_OK) {
        dprintf("create task2 failed, error: %x\n", ret);
        return ret;
    }

    ret = LOS_QueueCreate("queue", 5, &g_queue, 0, BUFFER_LEN);
    if(ret != LOS_OK) {
        dprintf("create queue failure, error: %x\n", ret);
    }

    dprintf("create the queue success!\n");
    LOS_TaskUnlock();
    return ret;
}

结果验证

create the queue success!
recv message: test is message 0
recv message: test is message 1
recv message: test is message 2
recv message: test is message 3
recv message: test is message 4
recv message failure, error: 200061d
delete the queue success!

完整实例代码

sample_queue.c

事件

概述

基本概念

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

多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。

  • 一对多同步模型:一个任务等待多个事件的触发。可以是任意一个事件发生时唤醒任务处理事件,也可以是几个事件都发生后才唤醒任务处理事件。
  • 多对多同步模型:多个任务等待多个事件的触发。

Huawei LiteOS提供的事件具有如下特点:

  • 任务通过创建事件控制块来触发事件或等待事件。
  • 事件间相互独立,内部实现为一个32位无符号整型,每一位标识一种事件类型。第25位不可用,因此最多可支持31种事件类型。
  • 事件仅用于任务间的同步,不提供数据传输功能。
  • 多次向事件控制块写入同一事件类型,在被清零前等效于只写入一次。
  • 多个任务可以对同一事件进行读写操作。
  • 支持事件读写超时机制。

事件控制块

/**
 * Event control structure
 */
typedef struct tagEvent {

    UINT32 uwEventID;            /* 事件ID,每一位标识一种事件类型 */
    LOS_DL_LIST    stEventList;  /* 读取事件的任务链表 */
} EVENT_CB_S, *PEVENT_CB_S;

uwEventID:用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生、1表示该事件类型已经发生),一共31种事件类型,第25位系统保留。

事件读取模式

在读事件时,可以选择读取模式。读取模式如下:

  • 所有事件(LOS_WAITMODE_AND):逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。
  • 任一事件(LOS_WAITMODE_OR):逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。
  • 清除事件(LOS_WAITMODE_CLR):这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。

运作机制

任务在调用LOS_EventRead接口读事件时,可以根据入参事件掩码类型eventMask读取事件的单个或者多个事件类型。事件读取成功后,如果设置LOS_WAITMODE_CLR会清除已读取到的事件类型,反之不会清除已读到的事件类型,需显式清除。可以通过入参选择读取模式,读取事件掩码类型中所有事件还是读取事件掩码类型中任意事件。

任务在调用LOS_EventWrite接口写事件时,对指定事件控制块写入指定的事件类型,可以一次同时写多个事件类型。写事件会触发任务调度。

任务在调用LOS_EventClear接口清除事件时,根据入参事件和待清除的事件类型,对事件对应位进行清0操作。

图 1 事件唤醒任务示意图

开发指导

使用场景

事件可应用于多种任务同步场景,在某些同步场景下可替代信号量。

功能

Huawei LiteOS 的事件模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

初始化事件

LOS_EventInit

初始化一个事件控制块

读/写事件

LOS_EventRead

读取指定事件类型,超时时间为相对时间:单位为Tick

LOS_EventWrite

写指定的事件类型

清除事件

LOS_EventClear

清除指定的事件类型

校验事件掩码

LOS_EventPoll

根据用户传入的事件ID、事件掩码及读取模式,返回用户传入的事件是否符合预期

销毁事件

LOS_EventDestroy

销毁指定的事件控制块

Event错误码

对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

序号

定义

实际值

描述

参考解决方案

1

LOS_ERRNO_EVENT_SETBIT_INVALID

0x02001c00

写事件时,将事件ID的第25个bit设置为1。这个比特位OS内部保留,不允许设置为1

事件ID的第25bit置为0

2

LOS_ERRNO_EVENT_READ_TIMEOUT

0x02001c01

读事件超时

增加等待时间或者重新读取

3

LOS_ERRNO_EVENT_EVENTMASK_INVALID

0x02001c02

入参的事件ID是无效的

传入有效的事件ID参数

4

LOS_ERRNO_EVENT_READ_IN_INTERRUPT

0x02001c03

在中断中读取事件

启动新的任务来获取事件

5

LOS_ERRNO_EVENT_FLAGS_INVALID

0x02001c04

读取事件的mode无效

传入有效的mode参数

6

LOS_ERRNO_EVENT_READ_IN_LOCK

0x02001c05

任务锁住,不能读取事件

解锁任务,再读取事件

7

LOS_ERRNO_EVENT_PTR_NULL

0x02001c06

传入的参数为空指针

传入非空入参

8

LOS_ERRNO_EVENT_READ_IN_SYSTEM_TASK

0x02001c07

在系统任务中读取事件,如idle和软件定时器

启动新的任务来获取事件

9

LOS_ERRNO_EVENT_SHOULD_NOT_DESTORY

0x02001c08

事件链表上仍有任务,无法被销毁

先检查事件链表是否为空

须知: 错误码定义见错误码简介。8~15位的所属模块为事件模块,值为0x1c。

开发流程

使用事件模块的典型流程如下:

  1. 通过make menuconfig配置事件。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_IPC_EVENT

    事件功能的裁剪开关

    YES/NO

    YES

    LOSCFG_BASE_IPC_EVENT_LIMIT

    最大支持的事件控制块数量

    1024

    LOSCFG_BASE_IPC_EVENT

  2. 调用事件初始化LOS_EventInit接口,初始化事件等待队列。

  3. 写事件LOS_EventWrite,写入指定的事件类型。

  4. 读事件LOS_EventRead,可以选择读取模式。

  5. 清除事件LOS_EventClear,清除指定的事件类型。

平台差异性

无。

注意事项

  • 在系统初始化之前不能调用读写事件接口。如果调用,系统运行会不正常。
  • 在中断中,可以对事件对象进行写操作,但不能进行读操作。
  • 在锁任务调度状态下,禁止任务阻塞于读事件。
  • LOS_EventClear 入参值是:要清除的指定事件类型的反码(~events)。
  • 为了区别LOS_EventRead接口返回的是事件还是错误码,事件掩码的第25位不能使用。

编程实例

实例描述

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

  1. 在任务Example_TaskEntry创建任务Example_Event,其中任务Example_Event优先级高于Example_TaskEntry。
  2. 在任务Example_Event中读事件0x00000001,阻塞,发生任务切换,执行任务Example_TaskEntry。
  3. 在任务Example_TaskEntry向任务Example_Event写事件0x00000001,发生任务切换,执行任务Example_Event。
  4. Example_Event得以执行,直到任务结束。
  5. Example_TaskEntry得以执行,直到任务结束。

编程示例

前提条件:在menuconfig菜单中完成事件模块的配置。

代码实现如下:

#include "los_event.h"
#include "los_task.h"
#include "securec.h"

/* 任务ID */
UINT32 g_testTaskId;

/* 事件控制结构体 */
EVENT_CB_S g_exampleEvent;

/* 等待的事件类型 */
#define EVENT_WAIT 0x00000001

/* 用例任务入口函数 */
VOID Example_Event(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, 100);
    if (event == EVENT_WAIT) {
        printf("Example_Event,read event :0x%x\n", event);
    } else {
        printf("Example_Event,read event timeout\n");
    }
}

UINT32 Example_TaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S task1;

    /* 事件初始化 */
    ret = LOS_EventInit(&g_exampleEvent);
    if (ret != LOS_OK) {
        printf("init event failed .\n");
        return -1;
    }

    /* 创建任务 */
    (VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Event;
    task1.pcName       = "EventTsk1";
    task1.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE;
    task1.usTaskPrio   = 5;
    ret = LOS_TaskCreate(&g_testTaskId, &task1);
    if (ret != LOS_OK) {
        printf("task create failed .\n");
        return LOS_NOK;
    }

    /* 写g_testTaskId 等待事件 */
    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_TaskDelete(g_testTaskId);
    if (ret != LOS_OK) {
        printf("task delete 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

完整实例代码

sample_event.c

信号量

概述

基本概念

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

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

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

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

  • 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。
  • 用作同步时,初始信号量计数值为0。任务1获取信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。

运作机制

信号量控制块

/**
 * Semaphore control structure.
 */
typedef struct {
    UINT8           semStat;          /* 是否使用标志位 */
    UINT8           semType;          /* 信号量类型 */
    UINT16          semCount;         /* 信号量计数 */
    UINT32          semId;            /* 信号量索引号 */
    LOS_DL_LIST     semList;          /* 挂接阻塞于该信号量的任务 */
} LosSemCB;

信号量运作原理

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

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

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

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

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

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

图 1 信号量运作示意图

开发指导

使用场景

在多任务系统中,信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务,中断与任务的同步中。信号量常用于协助一组相互竞争的任务访问共享资源。

功能

Huawei LiteOS 的信号量模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

创建/删除信号量

LOS_SemCreate

创建信号量,返回信号量ID

LOS_BinarySemCreate

创建二值信号量,其计数值最大为1

LOS_SemDelete

删除指定的信号量

申请/释放信号量

LOS_SemPend

申请指定的信号量,并设置超时时间

LOS_SemPost

释放指定的信号量

说明: 信号量有三种申请模式:无阻塞模式、永久阻塞模式、定时阻塞模式。

  • 无阻塞模式:即任务申请信号量时,入参timeout等于0。若当前信号量计数值不为0,则申请成功,否则立即返回申请失败。
  • 永久阻塞模式:即任务申请信号量时,入参timeout等于0xFFFFFFFF。若当前信号量计数值不为0,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该信号量,阻塞任务才会重新得以执行。
  • 定时阻塞模式:即任务申请信号量时,0<timeout<0xFFFFFFFF。若当前信号量计数值不为0,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该信号量,则该任务可成功获取信号量继续执行,若超时前未获取到信号量,接口将返回超时错误码。

信号量错误码

对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_SEM_NO_MEMORY

0x02000700

初始化信号量时,内存空间不足

调整OS_SYS_MEM_SIZE以确保有足够的内存供信号量使用,或减小系统支持的最大信号量数LOSCFG_BASE_IPC_SEM_LIMIT

2

LOS_ERRNO_SEM_INVALID

0x02000701

信号量ID不正确或信号量未创建

传入正确的信号量ID或创建信号量后再使用

3

LOS_ERRNO_SEM_PTR_NULL

0x02000702

传入空指针

传入合法指针

4

LOS_ERRNO_SEM_ALL_BUSY

0x02000703

创建信号量时,系统中已经没有未使用的信号量

及时删除无用的信号量或增加系统支持的最大信号量数LOSCFG_BASE_IPC_SEM_LIMIT

5

LOS_ERRNO_SEM_UNAVAILABLE

0x02000704

无阻塞模式下未获取到信号量

选择阻塞等待或根据该错误码适当处理

6

LOS_ERRNO_SEM_PEND_INTERR

0x02000705

中断期间非法调用LOS_SemPend申请信号量

中断期间禁止调用LOS_SemPend

7

LOS_ERRNO_SEM_PEND_IN_LOCK

0x02000706

任务被锁,无法获得信号量

在任务被锁时,不能调用LOS_SemPend申请信号量

8

LOS_ERRNO_SEM_TIMEOUT

0x02000707

获取信号量超时

将时间设置在合理范围内

9

LOS_ERRNO_SEM_OVERFLOW

0x02000708

信号量计数值已达到最大值,无法再继续释放该信号量

根据该错误码适当处理

10

LOS_ERRNO_SEM_PENDED

0x02000709

等待信号量的任务队列不为空

唤醒所有等待该信号量的任务后,再删除该信号量

11

LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK

0x0200070a

在系统任务中获取信号量,如idle和软件定时器

不要在系统任务中获取信号量

须知: 错误码定义见错误码简介。8~15位的所属模块为信号量模块,值为0x07。

开发流程

信号量的开发典型流程:

  1. 通过make menuconfig配置信号量模块。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_IPC_SEM

    信号量模块裁剪开关

    YES/NO

    YES

    LOSCFG_BASE_IPC_SEM_LIMIT

    系统支持的信号量最大数

    [0, 65535]

    1024

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

  3. 申请信号量LOS_SemPend。

  4. 释放信号量LOS_SemPost。

  5. 删除信号量LOS_SemDelete。

平台差异性

无。

注意事项

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

编程实例

实例描述

本实例实现如下功能:

  1. 测试任务Example_TaskEntry创建一个信号量,锁任务调度,创建两个任务Example_SemTask1、Example_SemTask2,Example_SemTask2优先级高于Example_SemTask1,两个任务中申请同一信号量,解锁任务调度后两任务阻塞,测试任务Example_TaskEntry释放信号量。
  2. Example_SemTask2得到信号量,被调度,然后任务休眠20Tick,Example_SemTask2延迟,Example_SemTask1被唤醒。
  3. Example_SemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被Example_SemTask2持有,Example_SemTask1挂起,10Tick后仍未得到信号量,Example_SemTask1被唤醒,试图以永久阻塞模式申请信号量,Example_SemTask1挂起。
  4. 20Tick后Example_SemTask2唤醒, 释放信号量后,Example_SemTask1得到信号量被调度运行,最后释放信号量。
  5. Example_SemTask1执行完,40Tick后任务Example_TaskEntry被唤醒,执行删除信号量,删除两个任务。

编程示例

前提条件:在menuconfig菜单中完成信号量的配置。

代码实现如下:

#include "los_sem.h"
#include "securec.h"

/* 任务ID */
static UINT32 g_testTaskId01;
static UINT32 g_testTaskId02;
/* 测试任务优先级 */
#define TASK_PRIO_TEST  5
/* 信号量结构体id */
static UINT32 g_semId;

VOID Example_SemTask1(VOID)
{
    UINT32 ret;

    printf("Example_SemTask1 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("Example_SemTask1 timeout and try get sem g_semId wait forever.\n");
        /*永久阻塞模式申请信号量*/
        ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);
        printf("Example_SemTask1 wait_forever and get sem g_semId .\n");
        if (ret == LOS_OK) {
            LOS_SemPost(g_semId);
            return;
        }
    }
}

VOID Example_SemTask2(VOID)
{
    UINT32 ret;
    printf("Example_SemTask2 try get sem g_semId wait forever.\n");
    /* 永久阻塞模式申请信号量 */
    ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);

    if (ret == LOS_OK) {
        printf("Example_SemTask2 get sem g_semId and then delay 20ticks .\n");
    }

    /* 任务休眠20 ticks */
    LOS_TaskDelay(20);

    printf("Example_SemTask2 post sem g_semId .\n");
    /* 释放信号量 */
    LOS_SemPost(g_semId);
    return;
}

UINT32 ExampleTaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S task1;
    TSK_INIT_PARAM_S task2;

   /* 创建信号量 */
    LOS_SemCreate(0,&g_semId);

    /* 锁任务调度 */
    LOS_TaskLock();

    /* 创建任务1 */
    (VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;
    task1.pcName       = "TestTsk1";
    task1.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE;
    task1.usTaskPrio   = TASK_PRIO_TEST;
    ret = LOS_TaskCreate(&g_testTaskId01, &task1);
    if (ret != LOS_OK) {
        printf("task1 create failed .\n");
        return LOS_NOK;
    }

    /* 创建任务2 */
    (VOID)memset_s(&task2, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;
    task2.pcName       = "TestTsk2";
    task2.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE;
    task2.usTaskPrio   = (TASK_PRIO_TEST - 1);
    ret = LOS_TaskCreate(&g_testTaskId02, &task2);
    if (ret != LOS_OK) {
        printf("task2 create failed .\n");
        return LOS_NOK;
    }

    /* 解锁任务调度 */
    LOS_TaskUnlock();

    ret = LOS_SemPost(g_semId);

    /* 任务休眠40 ticks */
    LOS_TaskDelay(40);

    /* 删除信号量 */
    LOS_SemDelete(g_semId);

    /* 删除任务1 */
    ret = LOS_TaskDelete(g_testTaskId01);
    if (ret != LOS_OK) {
        printf("task1 delete failed .\n");
        return LOS_NOK;
    }
    /* 删除任务2 */
    ret = LOS_TaskDelete(g_testTaskId02);
    if (ret != LOS_OK) {
        printf("task2 delete failed .\n");
        return LOS_NOK;
    }

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

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

完整实例代码

sample_sem.c

互斥锁

概述

基本概念

互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对临界资源的独占式处理。另外,互斥锁可以解决信号量存在的优先级翻转问题。

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

Huawei LiteOS提供的互斥锁具有如下特点:

  • 通过优先级继承算法,解决优先级翻转问题。
  • 多任务阻塞等待同一个锁的场景,支持基于任务优先级等待和FIFO两种模式。

运作机制

多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能被独占使用。互斥锁怎样来避免这种冲突呢?

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

图 1 互斥锁运作示意图

开发指导

使用场景

多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥锁可以提供任务间的互斥机制,防止两个任务在同一时刻访问相同的临界资源,从而实现独占式访问。

功能

Huawei LiteOS 的互斥锁模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

创建/删除互斥锁

LOS_MuxCreate

创建互斥锁

LOS_MuxDelete

删除指定互斥锁

申请/释放互斥锁

LOS_MuxPend

申请指定互斥锁

LOS_MuxPost

释放指定互斥锁

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

  • 无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。
  • 永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。
  • 定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。 释放互斥锁:
  • 如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。
  • 如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

互斥锁错误码

对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_MUX_NO_MEMORY

0x02001d00

初始化互斥锁模块时,内存不足

设置更大的系统动态内存池,配置项为OS_SYS_MEM_SIZE,或减少系统支持的最大互斥锁个数

2

LOS_ERRNO_MUX_INVALID

0x02001d01

互斥锁不可用

传入有效的互斥锁ID

3

LOS_ERRNO_MUX_PTR_NULL

0x02001d02

创建互斥锁时,入参为空指针

传入有效指针

4

LOS_ERRNO_MUX_ALL_BUSY

0x02001d03

创建互斥锁时,系统中已经没有可用的互斥锁

增加系统支持的最大互斥锁个数

5

LOS_ERRNO_MUX_UNAVAILABLE

0x02001d04

申请互斥锁失败,因为锁已经被其他线程持有

等待其他线程解锁或者设置等待时间

6

LOS_ERRNO_MUX_PEND_INTERR

0x02001d05

在中断中使用互斥锁

禁止在中断中申请/释放互斥锁

7

LOS_ERRNO_MUX_PEND_IN_LOCK

0x02001d06

锁任务调度时,不允许以阻塞模式申请互斥锁

以非阻塞模式申请互斥锁,或使能任务调度后再阻塞申请互斥锁

8

LOS_ERRNO_MUX_TIMEOUT

0x02001d07

申请互斥锁超时

增加等待时间,或采用一直等待模式

9

LOS_ERRNO_MUX_OVERFLOW

0x02001d08

暂不使用该错误码

-

10

LOS_ERRNO_MUX_PENDED

0x02001d09

删除正在使用的互斥锁锁

等待解锁后再删除该互斥锁

11

LOS_ERRNO_MUX_GET_COUNT_ERR

0x02001d0a

暂不使用该错误码

-

12

LOS_ERRNO_MUX_REG_ERROR

0x02001d0b

暂不使用该错误码

-

13

LOS_ERRNO_MUX_PEND_IN_SYSTEM_TASK

0x02001d0c

系统任务中获取互斥锁,如idle和软件定时器

不在系统任务中申请互斥锁

须知: 错误码定义见错误码简介。8~15位的所属模块为互斥锁模块,值为0x1d。

开发流程

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

  1. 通过make menuconfig配置互斥锁模块。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_IPC_MUX

    互斥锁模块裁剪开关

    YES/NO

    YES

    LOSCFG_MUTEX_WAITMODE_PRIO

    互斥锁基于任务优先级的等待模式

    YES/NO

    YES

    LOSCFG_BASE_IPC_MUX

    LOSCFG_MUTEX_WAITMODE_FIFO

    互斥锁基于FIFO的等待模式

    YES/NO

    NO

    LOSCFG_BASE_IPC_MUX

    LOSCFG_BASE_IPC_MUX_LIMIT

    系统支持的最大互斥锁个数

    <65535

    1024

    LOSCFG_BASE_IPC_MUX

  2. 创建互斥锁LOS_MuxCreate。

  3. 申请互斥锁LOS_MuxPend。

  4. 释放互斥锁LOS_MuxPost。

  5. 删除互斥锁LOS_MuxDelete。

平台差异性

无。

注意事项

  • 互斥锁不能在中断服务程序中使用。
  • Huawei LiteOS作为实时操作系统需要保证任务调度的实时性,尽量避免任务的长时间阻塞,因此在获得互斥锁之后,应该尽快释放互斥锁。
  • 持有互斥锁的过程中,不得再调用LOS_TaskPriSet等接口更改持有互斥锁任务的优先级。
  • 互斥锁不支持多个相同优先级任务翻转的场景。

编程实例

实例描述

本实例实现如下流程。

  1. 任务Example_TaskEntry创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1、Example_MutexTask2。Example_MutexTask2优先级高于Example_MutexTask1,解锁任务调度,然后Example_TaskEntry任务休眠300Tick。
  2. Example_MutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,Example_MutexTask2挂起,Example_MutexTask1被唤醒。
  3. Example_MutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。10Tick超时时间到达后,Example_MutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。
  4. 100Tick休眠时间到达后,Example_MutexTask2被唤醒, 释放互斥锁,唤醒Example_MutexTask1。Example_MutexTask1成功获取到互斥锁后,释放锁。
  5. 300Tick休眠时间到达后,任务Example_TaskEntry被调度运行,删除互斥锁,删除两个任务。

编程示例

前提条件:通过make menuconfig完成互斥锁的配置。

代码实现如下:

/* 互斥锁句柄id */
UINT32 g_testMux;
/* 任务ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;

VOID Example_MutexTask1(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);
        return;
    } else 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);
                return;
            }
    }
    return;
}

VOID Example_MutexTask2(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 Example_TaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S task1;
    TSK_INIT_PARAM_S task2;

    /* 创建互斥锁 */
    LOS_MuxCreate(&g_testMux);

    /* 锁任务调度 */
    LOS_TaskLock();

    /* 创建任务1 */
    memset(&task1, 0, sizeof(TSK_INIT_PARAM_S));
    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1;
    task1.pcName       = "MutexTsk1";
    task1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task1.usTaskPrio   = 5;
    ret = LOS_TaskCreate(&g_testTaskId01, &task1);
    if (ret != LOS_OK) {
        printf("task1 create failed.\n");
        return LOS_NOK;
    }

    /* 创建任务2 */
    memset(&task2, 0, sizeof(TSK_INIT_PARAM_S));
    task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2;
    task2.pcName       = "MutexTsk2";
    task2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task2.usTaskPrio   = 4;
    ret = LOS_TaskCreate(&g_testTaskId02, &task2);
    if (ret != LOS_OK) {
        printf("task2 create failed.\n");
        return LOS_NOK;
    }

    /* 解锁任务调度 */
    LOS_TaskUnlock();
    /* 休眠300Ticks */
    LOS_TaskDelay(300);

    /* 删除互斥锁 */
    LOS_MuxDelete(g_testMux);

    /* 删除任务1 */
    ret = LOS_TaskDelete(g_testTaskId01);
    if (ret != LOS_OK) {
        printf("task1 delete failed .\n");
        return LOS_NOK;
    }
    /* 删除任务2 */
    ret = LOS_TaskDelete(g_testTaskId02);
    if (ret != LOS_OK) {
        printf("task2 delete failed .\n");
        return LOS_NOK;
    }

    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.

完整实例代码

sample_mutex.c

软件定时器

概述

基本概念

软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器。当经过设定的Tick数后,会触发用户自定义的回调函数。

硬件定时器受硬件的限制,数量上不足以满足用户的实际需求。因此为了满足用户需求,提供更多的定时器,Huawei LiteOS提供了软件定时器功能,支持如下特性:

  • 创建软件定时器。
  • 启动软件定时器。
  • 停止软件定时器。
  • 删除软件定时器。
  • 获取软件定时器剩余Tick数。
  • 可配置支持的软件定时器个数。

运作机制

软件定时器是系统资源,在模块初始化的时候已经分配了一块连续内存。

软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先触发的准则。

软件定时器以Tick为基本计时单位,当创建并启动一个软件定时器时,Huawei LiteOS会根据当前系统Tick时间及设置的定时时长确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。

当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,检查是否有定时器超时,若有则将超时的定时器记录下来。Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用已经记录下来的定时器的回调函数。

定时器状态

  • OS_SWTMR_STATUS_UNUSED(定时器未使用)

    系统在定时器模块初始化时,会将系统中所有定时器资源初始化成该状态。

  • OS_SWTMR_STATUS_TICKING(定时器处于计数状态)

    在定时器创建后调用LOS_SwtmrStart接口启动,定时器将变成该状态,是定时器运行时的状态。

  • OS_SWTMR_STATUS_CREATED(定时器创建后未启动,或已停止)

    定时器创建后,不处于计数状态时,定时器将变成该状态。

定时器模式

软件定时器提供了三类模式:

  • 单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动删除。
  • 周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动停止定时器,否则将永远持续执行下去。
  • 单次触发定时器,但这类定时器超时触发后不会自动删除,需要调用定时器删除接口删除定时器。

开发指导

使用场景

  • 创建一个单次触发的定时器,超时后执行用户自定义的回调函数。
  • 创建一个周期性触发的定时器,超时后执行用户自定义的回调函数。

功能

Huawei LiteOS的软件定时器模块为用户提供下面几种功能,接口详细信息可以查看API参考。

表 1

功能分类

接口名

描述

创建/删除定时器

LOS_SwtmrCreate

创建定时器,设置定时器的定时时长、定时器模式、回调函数,并返回定时器ID

LOS_SwtmrDelete

删除定时器

启动/停止定时器

LOS_SwtmrStart

启动定时器

LOS_SwtmrStop

停止定时器

获得软件定时器剩余Tick数

LOS_SwtmrTimeGet

获得软件定时器剩余Tick数

软件定时器错误码

对存在失败可能性的操作返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_SWTMR_PTR_NULL

0x02000300

软件定时器回调函数为空

定义软件定时器回调函数

2

LOS_ERRNO_SWTMR_INTERVAL_NOT_SUITED

0x02000301

软件定时器的定时时长为0

重新定义定时器的定时时长

3

LOS_ERRNO_SWTMR_MODE_INVALID

0x02000302

不正确的软件定时器模式

确认软件定时器模式,范围为[0,2]

4

LOS_ERRNO_SWTMR_RET_PTR_NULL

0x02000303

入参的软件定时器ID指针为NULL

定义ID变量,传入有效指针

5

LOS_ERRNO_SWTMR_MAXSIZE

0x02000304

软件定时器个数超过最大值

重新设置软件定时器最大个数,或者等待一个软件定时器释放资源

6

LOS_ERRNO_SWTMR_ID_INVALID

0x02000305

入参的软件定时器ID不正确

确保入参合法

7

LOS_ERRNO_SWTMR_NOT_CREATED

0x02000306

软件定时器未创建

创建软件定时器

8

LOS_ERRNO_SWTMR_NO_MEMORY

0x02000307

初始化软件定时器模块时,内存不足

调整OS_SYS_MEM_SIZE,以确保有足够的内存供软件定时器使用

9

LOS_ERRNO_SWTMR_MAXSIZE_INVALID

0x02000308

暂不使用该错误码

-

10

LOS_ERRNO_SWTMR_HWI_ACTIVE

0x02000309

在中断中使用定时器

修改源代码确保不在中断中使用

11

LOS_ERRNO_SWTMR_HANDLER_POOL_NO_MEM

0x0200030a

暂不使用该错误码

-

12

LOS_ERRNO_SWTMR_QUEUE_CREATE_FAILED

0x0200030b

在软件定时器初始化时,创建定时器队列失败

调整OS_SYS_MEM_SIZE,以确保有足够的内存供软件定时器创建队列

13

LOS_ERRNO_SWTMR_TASK_CREATE_FAILED

0x0200030c

在软件定时器初始化时,创建定时器任务失败

调整OS_SYS_MEM_SIZE,以确保有足够的内存供软件定时器创建任务

14

LOS_ERRNO_SWTMR_NOT_STARTED

0x0200030d

未启动软件定时器

启动软件定时器

15

LOS_ERRNO_SWTMR_STATUS_INVALID

0x0200030e

不正确的软件定时器状态

检查确认软件定时器状态

16

LOS_ERRNO_SWTMR_SORTLIST_NULL

0x0200030f

暂不使用该错误码

-

17

LOS_ERRNO_SWTMR_TICK_PTR_NULL

0x02000310

用以获取软件定时器剩余Tick数的入参指针为NULL

定义有效变量以传入有效指针

18

LOS_ERRNO_SWTMR_SORTLINK_CREATE_FAILED

0x02000311

在软件定时器初始化时,创建定时器链表失败

调整OS_SYS_MEM_SIZE,以确保有足够的内存供软件定时器创建链表

须知: 错误码定义见错误码简介。8~15位的所属模块为软件定时器模块,值为0x03。

开发流程

软件定时器的典型开发流程:

  1. 通过make menuconfig配置软件定时器。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_CORE_SWTMR

    软件定时器裁剪开关

    YES/NO

    YES

    LOSCFG_BASE_IPC_QUEUE

    LOSCFG_BASE_CORE_SWTMR_LIMIT

    最大支持的软件定时器数

    <65535

    1024

    LOSCFG_BASE_CORE_SWTMR

    LOSCFG_BASE_CORE_SWTMR_IN_ISR

    在中断中直接执行回调函数

    YES/NO

    NO

    LOSCFG_BASE_CORE_SWTMR

    LOSCFG_BASE_CORE_TSK_SWTMR_STACK_SIZE

    软件定时器任务栈大小

    [LOSCFG_TASK_MIN_STACK_SIZE, OS_SYS_MEM_SIZE)

    24576

    LOSCFG_BASE_CORE_SWTMR

  2. 创建定时器LOS_SwtmrCreate,设置定时器的定时时长、定时器模式、超时后的回调函数。

  3. 启动定时器LOS_SwtmrStart。

  4. 获得软件定时器剩余Tick数LOS_SwtmrTimeGet。

  5. 停止定时器LOS_SwtmrStop。

  6. 删除定时器LOS_SwtmrDelete。

注意事项

  • 软件定时器的回调函数中不应执行过多操作,不建议使用可能引起任务挂起或者阻塞的接口或操作,如果使用会导致软件定时器响应不及时,造成的影响无法确定。
  • 软件定时器使用了系统的一个队列和一个任务资源。软件定时器任务的优先级设定为0,且不允许修改 。
  • 系统可配置的软件定时器个数是指:整个系统可使用的软件定时器总个数,并非用户可使用的软件定时器个数。例如:系统多占用一个软件定时器,那么用户能使用的软件定时器资源就会减少一个。
  • 创建单次不自删除属性的定时器,用户需要自行调用定时器删除接口删除定时器,回收定时器资源,避免资源泄露。
  • 软件定时器的定时精度与系统Tick时钟的周期有关。

编程实例

实例描述

在下面的例子中,演示如下功能:

  1. 软件定时器创建、启动、停止、删除操作。
  2. 单次软件定时器,周期软件定时器使用方法。

编程示例

前提条件:在menuconfig菜单中完成软件定时器的配置。

代码实现如下:

UINT32 g_timerCount1 = 0;
UINT32 g_timerCount2 = 0;

VOID Timer1_CallBack(UINT32 arg)
{
    UINT64 lastTick;

    g_timerCount1++;
    lastTick=(UINT32)LOS_TickCountGet();
    dprintf("g_timerCount1=%d\n", g_timerCount1);
    dprintf("tick_last1=%d\n", lastTick);
}

VOID Timer2_CallBack(UINT32 arg)
{
    UINT64 lastTick;

    lastTick=(UINT32)LOS_TickCountGet();
    g_timerCount2++;
    dprintf("g_timerCount2=%d\n", g_timerCount2);
    dprintf("tick_last2=%d\n", lastTick);
}

VOID Timer_example(VOID)
{
    UINT16 id1;     // Timer1 id
    UINT16 id2;     // Timer2 id
    UINT32 tick;

    LOS_SwtmrCreate(1000, LOS_SWTMR_MODE_ONCE, Timer1_CallBack, &id1, 1);
    LOS_SwtmrCreate(100, LOS_SWTMR_MODE_PERIOD, Timer2_CallBack, &id2, 1);
    dprintf("create Timer1 success\n");

    LOS_SwtmrStart(id1);
    dprintf("start Timer1 sucess\n");
    LOS_TaskDelay(200);
    LOS_SwtmrTimeGet(id1, &tick);
    dprintf("tick =%d\n", tick);
    LOS_SwtmrStop(id1);
    dprintf("stop Timer1 sucess\n");

    LOS_SwtmrStart(id1);
    LOS_TaskDelay(1000);
    LOS_SwtmrDelete(id1);
    dprintf("delete Timer1 sucess\n");

    LOS_SwtmrStart(id2);
    dprintf("start Timer2\n");
    LOS_TaskDelay(1000);
    LOS_SwtmrStop(id2);
    LOS_SwtmrDelete(id2); 
}

结果验证

得到的结果为:

Create Timer1 success
start Timer1 sucess
tick =800
Stop Timer1 sucess
g_timerCount1=1201
tick_last1=1201
delete Timer1 success
Start Timer2
g_timerCount2=1
tick_last2=1301
g_timerCount2=2
tick_last2=1401
g_timerCount2=3
tick_last2=1501
g_timerCount2=4
tick_last2=1601
g_timerCount2=5
tick_last2=1701
g_timerCount2=6
tick_last2=1801
g_timerCount2=7
tick_last2=1901
g_timerCount2=8
tick_last2=2001
g_timerCount2=9
tick_last2=2101
g_timerCount2=10
tick_last2=2201

完整实例代码

sample_Timer.c

自旋锁

概述

在多核环境中,由于使用相同的内存空间,存在对同一资源进行访问的情况,所以需要互斥访问机制来保证同一时刻只有一个核进行操作。自旋锁就是这样的一种机制。

自旋锁是指当一个线程在获取锁时,如果锁已经被其它线程获取,那么该线程将循环等待,并不断判断是否能够成功获取锁,直到获取到锁才会退出循环。因此建议保护耗时较短的操作,防止对系统整体性能有明显的影响。

自旋锁与互斥锁比较类似,它们都是为了解决对共享资源的互斥使用问题。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个持有者。但是两者在调度机制上略有不同,对于互斥锁,如果锁已经被占用,锁申请者会被阻塞;但是自旋锁不会引起调用者阻塞,会一直循环检测自旋锁是否已经被释放。

开发指南

使用场景

自旋锁可以提供任务之间的互斥访问机制,用来防止两个任务在同一时刻访问相同的共享资源。

功能

Huawei LiteOS 的自旋锁模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

初始化自旋锁

LOS_SpinInit

动态初始化自旋锁

SPIN_LOCK_INIT

静态初始化自旋锁

申请/释放自旋锁

LOS_SpinLock

申请指定的自旋锁,如果无法获取锁,会一直循环等待

LOS_SpinTrylock

尝试申请指定的自旋锁,如果无法获取锁,直接返回失败,而不会一直循环等待

LOS_SpinUnlock

释放指定的自旋锁

申请/释放自旋锁(同时进行关中断保护)

LOS_SpinLockSave

关中断后,再申请指定的自旋锁

LOS_SpinUnlockRestore

先释放指定的自旋锁,再恢复中断状态

获取自旋锁持有状态

LOS_SpinHeld

检查自旋锁是否已经被持有

开发流程

自旋锁的开发典型流程:

  1. 自旋锁依赖于SMP,可以通过make menuconfig配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_SMP

    SMP控制开关

    YES/NO

    YES

    硬件支持多核

    LOSCFG_KERNEL_SMP_CORE_NUM

    多核core数量

    与架构相关

    2

  2. 创建自旋锁:使用LOS_SpinInit初始化自旋锁,或者使用SPIN_LOCK_INIT初始化静态内存的自旋锁。

  3. 申请自旋锁:使用接口LOS_SpinLock/LOS_SpinTrylock/LOS_SpinLockSave申请指定的自旋锁,申请成功就继续往后执行锁保护的代码;申请失败在自旋锁申请中忙等,直到申请到自旋锁为止。

  4. 释放自旋锁:使用LOS_SpinUnlock/LOS_SpinUnlockRestore接口释放自旋锁。锁保护代码执行完毕后,释放对应的自旋锁,以便其他核申请自旋锁。

注意事项

  • 同一个任务不能对同一把自旋锁进行多次加锁,否则会导致死锁。
  • 自旋锁中会执行本核的锁任务操作,因此需要等到最外层完成解锁后本核才会进行任务调度。
  • LOS_SpinLock与LOS_SpinUnlock允许单独使用,即可以不进行关中断,但是用户需要保证使用的接口只会在任务或中断中使用。如果接口同时会在任务和中断中被调用,请使用LOS_SpinLockSave与LOS_SpinUnlockRestore,因为在未关中断的情况下使用LOS_SpinLock可能会导致死锁。
  • 耗时的操作谨慎选用自旋锁,可使用互斥锁进行保护。
  • 未开启SMP的单核场景下,自旋锁功能无效,只有LOS_SpinLockSave与LOS_SpinUnlockRestore接口有关闭恢复中断功能。
  • 建议LOS_SpinLock和LOS_SpinUnlock,LOS_SpinLockSave和LOS_SpinUnlockRestore配对使用,避免出错。

编程实例

实例描述

本实例实现如下流程。

  1. 任务Example_TaskEntry初始化自旋锁,创建两个任务Example_SpinTask1、Example_SpinTask2,分别运行于两个核。
  2. Example_SpinTask1、Example_SpinTask2中均执行申请自旋锁的操作,同时为了模拟实际操作,在持有自旋锁后进行延迟操作,最后释放自旋锁。
  3. 300Tick后任务Example_TaskEntry被调度运行,删除任务Example_SpinTask1和Example_SpinTask2。

须知: 多核的运行时序不是固定的,因此也存在任务执行顺序不同的情况。

编程示例

前提条件:在menuconfig中,将LOSCFG_KERNEL_SMP配置项打开,并设置多核core数量。

代码实现如下:

#include "los_spinlock.h"
#include "los_task.h"

/* 自旋锁句柄id */
SPIN_LOCK_S g_testSpinlock;
/* 任务ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;

VOID Example_SpinTask1(VOID)
{
    UINT32 i;
    UINTPTR intSave;

    /* 申请自旋锁 */
    dprintf("task1 try to get spinlock\n");
    LOS_SpinLockSave(&g_testSpinlock, &intSave);
    dprintf("task1 got spinlock\n");
    for(i = 0; i < 5000; i++) {
        asm volatile("nop");
    }

    /* 释放自旋锁 */
    dprintf("task1 release spinlock\n");
    LOS_SpinUnlockRestore(&g_testSpinlock, intSave);

    return;
}

VOID Example_SpinTask2(VOID)
{
    UINT32 i;
    UINTPTR intSave;

    /* 申请自旋锁 */
    dprintf("task2 try to get spinlock\n");
    LOS_SpinLockSave(&g_testSpinlock, &intSave);
    dprintf("task2 got spinlock\n");
    for(i = 0; i < 5000; i++) {
        asm volatile("nop");
    }

    /* 释放自旋锁 */
    dprintf("task2 release spinlock\n");
    LOS_SpinUnlockRestore(&g_testSpinlock, intSave);

    return;
}

UINT32 Example_TaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S stTask1;
    TSK_INIT_PARAM_S stTask2;

    /* 初始化自旋锁 */
    LOS_SpinInit(&g_testSpinlock);

    /* 创建任务1 */
    memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S));
    stTask1.pfnTaskEntry  = (TSK_ENTRY_FUNC)Example_SpinTask1;
    stTask1.pcName        = "SpinTsk1";
    stTask1.uwStackSize   = LOSCFG_TASK_MIN_STACK_SIZE;
    stTask1.usTaskPrio    = 5;
#ifdef LOSCFG_KERNEL_SMP
    /* 绑定任务到CPU0运行 */
    stTask1.usCpuAffiMask = CPUID_TO_AFFI_MASK(0);
#endif
    ret = LOS_TaskCreate(&g_testTaskId01, &stTask1);
    if(ret != LOS_OK) {
        dprintf("task1 create failed .\n");
        return LOS_NOK;
    }

    /* 创建任务2 */
    memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S));
    stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SpinTask2;
    stTask2.pcName       = "SpinTsk2";
    stTask2.uwStackSize  = LOSCFG_TASK_MIN_STACK_SIZE;
    stTask2.usTaskPrio   = 5;
#ifdef LOSCFG_KERNEL_SMP
    /* 绑定任务到CPU1运行 */
    stTask1.usCpuAffiMask = CPUID_TO_AFFI_MASK(1);
#endif
    ret = LOS_TaskCreate(&g_testTaskId02, &stTask2);
    if(ret != LOS_OK) {
        dprintf("task2 create failed .\n");
        return LOS_NOK;
    }

    /* 任务休眠300Ticks */
    LOS_TaskDelay(300);

    /* 删除任务1 */
    ret = LOS_TaskDelete(g_testTaskId01);
    if(ret != LOS_OK) {
        dprintf("task1 delete failed .\n");
        return LOS_NOK;
    }
    /* 删除任务2 */
    ret = LOS_TaskDelete(g_testTaskId02);
    if(ret != LOS_OK) {
        dprintf("task2 delete failed .\n");
        return LOS_NOK;
    }

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

task2 try to get spinlock
task2 got spinlock
task1 try to get spinlock
task2 release spinlock
task1 got spinlock
task1 release spinlock

完整实例代码

sample_spinlock.c

C++支持

概述

基本概念

C++作为目前使用最广泛的编程语言之一,支持类、封装、重载等特性,是在C语言基础上开发的一种面向对象的编程语言。

运作机制

STL(Standard Template Library)标准模板库,是一些“容器”的集合,也是算法和其他一些组件的集合。其目的是标准化组件,使用标准化组件后可以不用重新开发,直接使用现成的组件。

开发指导

功能

功能分类

接口名

描述

使用C++特性的前置条件

LOS_CppSystemInit

初始化C++构造函数

说明: 该函数有3个入参:

  • 第一个参数:init_array段的起始地址。
  • 第二个参数:init_array段的结束地址。
  • 第三个参数:标记调用C++特性时的场景。

开发流程

  1. 通过make menuconfig使能C++支持。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_CPPSUPPORT

    C++特性的裁剪开关

    YES/NO

    YES

    LOSCFG_KERNEL_EXTKERNEL

  2. 使用C++特性之前,需要调用函数LOS_CppSystemInit,初始化C++构造函数。

  3. C函数与C++函数混合调用。

    在C++中调用C程序的函数,代码需加入C++包含的宏:

    #ifdef __cplusplus
    #if __cplusplus
    extern "C" {
    #endif /* __cplusplus */
    #endif /* __cplusplus */
    /* code */
    ...
    #ifdef __cplusplus
    #if __cplusplus
    }
    #endif /* __cplusplus */
    #endif /* __cplusplus */

注意事项

Huawei LiteOS的C++功能需要编译器适配才能支持,编译器编译链接C++代码时需要使用Huawei LiteOS提供的C库。

编程实例

实例描述

本实例实现了在Huawei LiteOS上运行C++的代码。

  1. 编写C++代码。
  2. 在运行C++代码之前,先在app_init函数里以NO_SCATTER参数调用LOS_CppSystemInit初始化C++构造函数。
  3. 在LOS_CppSystemInit之后调用编写好的C++代码。

编程示例

前提条件:通过make menuconfig使能C++支持。

C++代码实现如下:

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif /* __cplusplus */
#endif /* __cplusplus */

using namespace std;

class TestClass {
public:
    TestClass(int arg);
    ~TestClass(void);
    void PrintTest(void);
    void StringTest(void);
    void MapTest(void);
private:
    int intTest;
    string stringTest;
    map<string, int> mapTest;
};
TestClass::TestClass(int arg)
{
    cout << "TestClass is constructed here, arg = " << arg << endl;
    intTest = arg;
}
TestClass::~TestClass(void)
{
    cout << "TestClass is destructed" << endl;
}
void TestClass::PrintTest(void)
{
    cout << __FUNCTION__ << " enter" << endl;
    cout << " intTest = " << this->intTest << endl;
}
void TestClass::StringTest(void)
{
    cout << __FUNCTION__ << " enter" << endl;
    string a("Lite");
    string b("OS");
    string c("LiteOS");
    if (a != b) {
        cout << " " << a << " != " << b << endl;
    }
    a += b;
    if (a == c) {
        cout << " " << a << " == " << c << endl;
    }
}
void TestClass::MapTest(void)
{
    cout << __FUNCTION__ << " enter" << endl;
    mapTest.insert(pair<string, int>("Huawei", 1));
    mapTest.insert(pair<string, int>("LiteOS", 2));
    mapTest.insert(pair<string, int>("Open", 3));
    mapTest.insert(pair<string, int>("Source", 4));
    cout << " show map key&value" << endl;
    for (auto &it : mapTest) {
        cout << " " << it.first << " " << it.second << endl;
    }
    mapTest["LiteOS"] = 8; /* 8: new value */
    cout << " change value of \"LiteOS\" key" << endl;
    for (auto &it : mapTest) {
        cout << " " << it.first << " " << it.second << endl;
    }
}

void CppTestEntry(void)
{
    cout << "LiteOS cpp sample start" << endl;
    TestClass test(123);
    test.PrintTest();
    test.StringTest();
    test.MapTest();
    cout << "LiteOS cpp sample stop" << endl;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif /* __cplusplus */
#endif /* __cplusplus */

app_init中初始化C++构造函数后,调用C++代码,实现如下:

void app_init(void)
{
    ......
    /* 初始化C++构造函数 */
    LOS_CppSystemInit((UINT32)&__init_array_start__, (UINT32)&__init_array_end__, NO_SCATTER);
    /* 调用C++代码 */
    CppTestEntry();
    ......
}

结果验证

运行CppTestEntry()函数,结果如下:

LiteOS cpp sample start
TestClass is constructed here, arg = 123
PrintTest enter
 intTest = 123
StringTest enter
 Lite != OS
 LiteOS == LiteOS
MapTest enter
 show map key&value
 Huawei 1
 LiteOS 2
 Open 3
 Source 4
 change value of "LiteOS" key
 Huawei 1
 LiteOS 8
 Open 3
 Source 4
LiteOS cpp sample stop
TestClass is destructed

完整实例代码

TestClass.cpp

其他

时间管理

概述

基本概念

时间管理以系统时钟为基础,给应用程序提供所有和时间有关的服务。

系统时钟是由定时器/计数器产生的输出脉冲触发中断产生的,一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”。系统时钟也称为时标或者Tick。

用户以秒、毫秒为单位计时,而操作系统以Tick为单位计时,当用户需要对系统进行操作时,例如任务挂起、延时等,此时需要时间管理模块对Tick和秒/毫秒进行转换。

Huawei LiteOS的时间管理模块提供时间转换、统计、延迟功能。

相关概念

  • Cycle

    系统最小的计时单位。Cycle的时长由系统主时钟频率决定,系统主时钟频率就是每秒钟的Cycle数。

  • Tick

    Tick是操作系统的基本时间单位,由用户配置的每秒Tick数决定。

开发指导

使用场景

用户需要了解当前系统运行的时间以及Tick与秒、毫秒之间的转换关系等。

功能

Huawei LiteOS的时间管理提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

时间转换

LOS_MS2Tick

毫秒转换成Tick

LOS_Tick2MS

Tick转化为毫秒

时间统计

LOS_CyclePerTickGet

每个Tick多少Cycle数

LOS_TickCountGet

获取自系统启动以来的Tick数

LOS_GetCpuCycle

获取自系统启动以来的Cycle数

LOS_CurrNanosec

获取自系统启动以来的纳秒数

延时管理

LOS_Udelay

以us为单位的忙等,但可以被优先级更高的任务抢占

LOS_Mdelay

以ms为单位的忙等,但可以被优先级更高的任务抢占

时间管理错误码

时间转换存在出错的可能性,需要返回对应的错误码,以便快速定位错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_TICK_CFG_INVALID

0x02000400

无效的系统Tick配置

在板级配置适配时配置有效的系统主时钟频率OS_SYS_CLOCK,通过make menuconfig配置有效的LOSCFG_BASE_CORE_TICK_PER_SECOND

2

LOS_ERRNO_TICK_NO_HWTIMER

0x02000401

暂不使用该错误码

-

3

LOS_ERRNO_TICK_PER_SEC_TOO_SMALL

0x02000402

暂不使用该错误码

-

开发流程

时间管理的典型开发流程:

  1. 根据实际需求,在板级配置适配时确认是否使能LOSCFG_BASE_CORE_TICK_HW_TIME宏选择外部定时器,并配置系统主时钟频率OS_SYS_CLOCK(单位Hz)。OS_SYS_CLOCK的默认值基于硬件平台配置。

  2. 通过make menuconfig配置LOSCFG_BASE_CORE_TICK_PER_SECOND。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_CORE_TICK_PER_SECOND

    每秒Tick数

    (0, 1000]

    100

  3. 调用时钟转换/统计接口。

注意事项

  • 时间管理不是单独的功能模块,依赖于OS_SYS_CLOCK和LOSCFG_BASE_CORE_TICK_PER_SECOND两个配置选项。
  • 系统的Tick数在关中断的情况下不进行计数,故系统Tick数不能作为准确时间使用。

编程实例

实例描述

在下面的例子中,介绍了时间管理的基本方法,包括:

  1. 时间转换:将毫秒数转换为Tick数,或将Tick数转换为毫秒数。
  2. 时间统计:每Tick的Cycle数、自系统启动以来的Tick数和延迟后的Tick数。

编程示例

前提条件:

  • 使用每秒的Tick数LOSCFG_BASE_CORE_TICK_PER_SECOND的默认值100。
  • 配好OS_SYS_CLOCK系统主时钟频率。

时间转换:

VOID Example_TransformTime(VOID)
{
    UINT32 ms;
    UINT32 tick;

    tick = LOS_MS2Tick(10000);    // 10000ms转换为tick
    dprintf("tick = %d \n",tick);
    ms = LOS_Tick2MS(100);        // 100tick转换为ms
    dprintf("ms = %d \n",ms);
}

时间统计和时间延迟:

VOID Example_GetTime(VOID)
{
    UINT32 cyclePerTick;
    UINT64 tickCount;

    cyclePerTick  = LOS_CyclePerTickGet();
    if(0 != cyclePerTick) {
        dprintf("LOS_CyclePerTickGet = %d \n", cyclePerTick);
    }

    tickCount = LOS_TickCountGet();
    if(0 != tickCount) {
        dprintf("LOS_TickCountGet = %d \n", (UINT32)tickCount);
    }

    LOS_TaskDelay(200);
    tickCount = LOS_TickCountGet();
    if(0 != tickCount) {
        dprintf("LOS_TickCountGet after delay = %d \n", (UINT32)tickCount);
    }
}

结果验证

编译运行得到的结果为:

时间转换:

tick = 1000
ms = 1000

时间统计和时间延迟:

LOS_CyclePerTickGet = 495000 
LOS_TickCountGet = 1 
LOS_TickCountGet after delay = 201

完整实例代码

sample_time.c

双向链表

概述

基本概念

双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向前一个节点的指针。其头指针head是唯一确定的。

从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。

开发指导

功能

Huawei LiteOS的双向链表模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

初始化链表

LOS_ListInit

将指定节点初始化为双向链表节点

LOS_DL_LIST_HEAD

定义一个节点并初始化为双向链表节点

增加节点

LOS_ListAdd

将指定节点插入到双向链表头端

LOS_ListHeadInsert

将指定节点插入到双向链表头端,同LOS_ListAdd

LOS_ListTailInsert

将指定节点插入到双向链表尾端

删除节点

LOS_ListDelete

将指定节点从链表中删除

LOS_ListDelInit

将指定节点从链表中删除,并使用该节点初始化链表

判断双向链表是否为空

LOS_ListEmpty

判断链表是否为空

获取节点

LOS_DL_LIST_LAST

获取指定节点的前驱结点

LOS_DL_LIST_FIRST

获取指定节点的后继结点

获取结构体信息

LOS_DL_LIST_ENTRY

获取包含链表的结构体地址,接口的第一个入参表示的是链表中的某个节点,第二个入参是要获取的结构体名称,第三个入参是链表在该结构体中的名称

LOS_OFF_SET_OF

获取指定结构体内的成员相对于结构体起始地址的偏移量

遍历双向链表

LOS_DL_LIST_FOR_EACH

遍历双向链表

LOS_DL_LIST_FOR_EACH_SAFE

遍历双向链表,并存储当前节点的后继节点用于安全校验

遍历包含双向链表的结构体

LOS_DL_LIST_FOR_EACH_ENTRY

遍历指定双向链表,获取包含该链表节点的结构体地址

LOS_DL_LIST_FOR_EACH_ENTRY_SAFE

遍历指定双向链表,获取包含该链表节点的结构体地址,并存储包含当前节点的后继节点的结构体地址

LOS_DL_LIST_FOR_EACH_ENTRY_HOOK

遍历指定双向链表,获取包含该链表节点的结构体地址,并在每次循环中调用钩子函数

开发流程

双向链表的典型开发流程:

  1. 调用LOS_ListInit/LOS_DL_LIST_HEAD初始双向链表。
  2. 调用LOS_ListAdd/LOS_ListHeadInsert向链表头部插入节点。
  3. 调用LOS_ListTailInsert向链表尾部插入节点。
  4. 调用LOS_ListDelete删除指定节点。
  5. 调用LOS_ListEmpty判断链表是否为空。
  6. 调用LOS_ListDelInit删除指定节点并以此节点初始化链表。

注意事项

  • 需要注意节点指针前后方向的操作。
  • 链表操作接口,为底层接口,不对入参进行判空,需要使用者确保传参合法。
  • 如果链表节点的内存是动态申请的,删除节点时,要注意释放内存。

编程实例

实例描述

本实例实现如下功能:

  1. 初始化双向链表。
  2. 增加节点。
  3. 删除节点。
  4. 测试操作是否成功。

编程示例

代码实现如下:

#include "stdio.h"
#include "los_list.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif /* __cpluscplus */
#endif /* __cpluscplus */

static UINT32 DLlist_sample(VOID)
{
    LOS_DL_LIST DLlist = {NULL,NULL};
    LOS_DL_LIST DLlistNode01 = {NULL,NULL};
    LOS_DL_LIST DLlistNode02 = {NULL,NULL};
    LOS_DL_LIST DLlistNode03 = {NULL,NULL};

    dprintf("Initial head\n");
    LOS_ListInit(&DLlist);

    LOS_ListAdd(&DLlist, &DLlistNode01);
    if (DLlistNode01.pstNext == &DLlist && DLlistNode01.pstPrev == &DLlist) {
        dprintf("Add DLlistNode01 success \n");
    }

    LOS_ListTailInsert(&DLlist, &DLlistNode02);
    if (DLlistNode02.pstNext == &DLlist && DLlistNode02.pstPrev == &DLlistNode01) {
        dprintf("Tail insert DLlistNode02 success \n");
    }

    LOS_ListHeadInsert(&DLlistNode02, &DLlistNode03);
    if (DLlistNode03.pstNext == &DLlist && DLlistNode03.pstPrev == &DLlistNode02) {
        dprintf("Head insert DLlistNode03 success \n");
    }

    LOS_ListDelInit(&DLlistNode03);
    LOS_ListDelete(&DLlistNode01);
    LOS_ListDelete(&DLlistNode02);

    if (LOS_ListEmpty(&DLlist)) {
        dprintf("Delete success \n");
    }

    return LOS_OK;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif /* __cpluscplus */
#endif /* __cpluscplus */

结果验证

编译运行得到的结果为:

Initial head 
Add DLlistNode01 success 
Tail insert DLlistNode02 success 
Head insert DLlistNode03 success 
Delete success 

原子操作

概述

基本概念

在支持多任务的操作系统中,修改一块内存区域的数据需要“读取-修改-写入”三个步骤。然而同一内存区域的数据可能同时被多个任务访问,如果在修改数据的过程中被其他任务打断,就会造成该操作的执行结果无法预知。

使用开关中断的方法固然可以保证多任务执行结果符合预期,但这种方法显然会影响系统性能。

ARMv6架构引入了LDREX和STREX指令,以支持对共享存储器更缜密的非阻塞同步。由此实现的原子操作能确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,即操作的原子性。

运作机制

Huawei LiteOS通过对ARMv6架构中的LDREX和STREX进行封装,向用户提供了一套原子操作接口。

  • LDREX Rx, [Ry]

    读取内存中的值,并标记对该段内存为独占访问:

    • 读取寄存器Ry指向的4字节内存数据,保存到Rx寄存器中。
    • 对Ry指向的内存区域添加独占访问标记。
  • STREX Rf, Rx, [Ry]

    检查内存是否有独占访问标记,如果有则更新内存值并清空标记,否则不更新内存:

    • 有独占访问标记

      1. 将寄存器Rx中的值更新到寄存器Ry指向的内存。
      2. 标志寄存器Rf置为0。
    • 没有独占访问标记

      1. 不更新内存。
      2. 标志寄存器Rf置为1。
  • 判断标志寄存器

    • 标志寄存器为0时,退出循环,原子操作结束。
    • 标志寄存器为1时,继续循环,重新进行原子操作。

开发指导

使用场景

有多个任务对同一个内存数据进行加减或交换操作时,使用原子操作保证结果的可预知性。

功能

Huawei LiteOS的原子数据包含两种类型Atomic(有符号32位数)与 Atomic64(有符号64位数)。原子操作模块为用户提供下面几种功能,接口详细信息可以查看API参考。

表 1 Atomic原子操作功能列表

功能分类

接口名

描述

LOS_AtomicRead

读取内存数据

LOS_AtomicSet

写入内存数据

LOS_AtomicAdd

对内存数据做加法

LOS_AtomicSub

对内存数据做减法

LOS_AtomicInc

对内存数据加1

LOS_AtomicIncRet

对内存数据加1并返回运算结果

LOS_AtomicDec

对内存数据减1

LOS_AtomicDecRet

对内存数据减1并返回运算结果

交换

LOS_AtomicXchg32bits

交换内存数据,原内存中的值以返回值的方式返回

LOS_AtomicCmpXchg32bits

比较并交换内存数据,返回比较结果

表 2 Atomic64原子操作功能列表

功能分类

接口名

描述

LOS_Atomic64Read

读取64位内存数据

LOS_Atomic64Set

写入64位内存数据

LOS_Atomic64Add

对64位内存数据做加法

LOS_Atomic64Sub

对64位内存数据做减法

LOS_Atomic64Inc

对64位内存数据加1

LOS_Atomic64IncRet

对64位内存数据加1并返回运算结果

LOS_Atomic64Dec

对64位内存数据减1

LOS_Atomic64DecRet

对64位内存数据减1并返回运算结果

交换

LOS_AtomicXchg64bits

交换64位内存数据,原内存中的值以返回值的方式返回

LOS_AtomicCmpXchg64bits

比较并交换64位内存数据,返回比较结果

须知: 原子操作中,操作数及其结果不能超过函数所支持位数的最大值。

平台差异性

无。

注意事项

目前原子操作接口只支持整型数据。

编程实例

实例描述

调用原子操作相关接口,观察结果:

  1. 创建两个任务

    • 任务一用LOS_AtomicInc对全局变量加100次。
    • 任务二用LOS_AtomicDec对全局变量减100次。
  2. 子任务结束后在主任务中打印全局变量的值。

编程示例

#include "los_hwi.h"
#include "los_atomic.h"
#include "los_task.h"

UINT32 g_testTaskId01;
UINT32 g_testTaskId02;
Atomic g_sum;
Atomic g_count;

UINT32 Example_Atomic01(VOID)
{
    int i = 0;
    for(i = 0; i < 100; ++i) {
        LOS_AtomicInc(&g_sum);
    }

    LOS_AtomicInc(&g_count);
    return LOS_OK;
}

UINT32 Example_Atomic02(VOID)
{
    int i = 0;
    for(i = 0; i < 100; ++i) {
        LOS_AtomicDec(&g_sum);
    }

    LOS_AtomicInc(&g_count);
    return LOS_OK;
}

UINT32 Example_TaskEntry(VOID)
{
    TSK_INIT_PARAM_S stTask1={0};
    stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic01;
    stTask1.pcName       = "TestAtomicTsk1";
    stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    stTask1.usTaskPrio   = 4;
    stTask1.uwResved     = LOS_TASK_STATUS_DETACHED;

    TSK_INIT_PARAM_S stTask2={0};
    stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic02;
    stTask2.pcName       = "TestAtomicTsk2";
    stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    stTask2.usTaskPrio   = 4;
    stTask2.uwResved     = LOS_TASK_STATUS_DETACHED;

    LOS_TaskLock();
    LOS_TaskCreate(&g_testTaskId01, &stTask1);
    LOS_TaskCreate(&g_testTaskId02, &stTask2);
    LOS_TaskUnlock();

    while(LOS_AtomicRead(&g_count) != 2);
    dprintf("g_sum = %d\n", g_sum);

    return LOS_OK;
}

结果验证

g_sum = 0

完整实例代码

sample_atomic.c

位操作

概述

基本概念

位操作是指对二进制数的bit位进行操作。程序可以设置某一变量为状态字,状态字中的每一bit位(标志位)可以具有自定义的含义。

开发指南

使用场景

系统提供标志位的置1和清0操作,可以改变标志位的内容,同时还提供获取状态字中标志位为1的最高位和最低位的功能。用户也可以对系统的寄存器进行位操作。

功能

Huawei LiteOS的位操作模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

置1/清0标志位

LOS_BitmapSet

对状态字的某一标志位进行置1操作

LOS_BitmapClr

对状态字的某一标志位进行清0操作

获取标志位为1的bit位

LOS_HighBitGet

获取状态字中为1的最高位

LOS_LowBitGet

获取状态字中为1的最低位

注意事项

无。

编程实例

实例描述

对数据实现位操作,本实例实现如下功能:

  1. 某一标志位置1。
  2. 获取标志位为1的最高bit位。
  3. 某一标志位清0。
  4. 获取标志位为1的最低bit位。

编程示例

#include "los_bitmap.h"
#include "los_printf.h"

static UINT32 Bit_Sample(VOID)
{
  UINT32 flag = 0x10101010;
  UINT16 pos;

  dprintf("\nBitmap Sample!\n");
  dprintf("The flag is 0x%8x\n", flag);

  pos = 8;
  LOS_BitmapSet(&flag, pos);
  dprintf("LOS_BitmapSet:\t pos : %d, the flag is 0x%0+8x\n", pos, flag);

  pos = LOS_HighBitGet(flag);
  dprintf("LOS_HighBitGet:\t The highest one bit is %d, the flag is 0x%0+8x\n", pos, flag);

  LOS_BitmapClr(&flag, pos);
  dprintf("LOS_BitmapClr:\t pos : %d, the flag is 0x%0+8x\n", pos, flag);

  pos = LOS_LowBitGet(flag);
  dprintf("LOS_LowBitGet:\t The lowest one bit is %d, the flag is 0x%0+8x\n\n", pos, flag);

  return LOS_OK;
}

结果验证

编译运行得到结果:

Bitmap Sample!
The flag is 0x10101010
LOS_BitmapSet: pos : 8,  the flag is 0x10101110
LOS_HighBitGet:The highest one bit is 28, the flag is 0x10101110
LOS_BitmapClr: pos : 28, the flag is 0x00101110
LOS_LowBitGet: The lowest one bit is 4, the flag is 0x00101110

附录

Huawei LiteOS Kernel启动流程