HiSpark编程指南

- 调测资料
  - SDK Sample使用指南
    - 前言
    - 概述
    - Sample框架
    - 外设 Sample
    - SLE UART Sample
    - SLE UART DMA Sample
    - SLE Mouse Sample
    - SLE amic vdt with dongle
    - SLE microphone
    - SLE measure dis
    - BLE Lowpower Sample
    - BLE UART Sample
    - BLE Keyboard Sample
    - BLE SLE TAG Sample
    - BLE Mouse Sample
    - USB Keyboard Sample
    - USB Mouse Sample
    - USB AMIC VDT Sample
    - RCU
    - 多连接
    - air mouse
    - SLE MOUSE with Dongle
    - SLE OTA dongle
    - game mouse
    - BLE UUID
    - 缩略语
  - SDK二次开发网络安全注意事项
    - 前言
    - 产品安全解决方案
    - 结论
  - SDK开发环境搭建用户指南
    - 前言
    - 开发环境搭建
    - 编译SDK
    - 新建APP
  - 内存维测指导书
    - 前言
    - 简介
    - 内存维测功能
  - 射频AT测试指南
    - 前言
    - 射频测试AT指令
    - AT指令组包示例
    - 注意事项
  - 维测指导书
    - 前言
    - 概述
    - 日志说明
    - 日志分析
    - 问题定位
    - 典型案例
    - 常见问题FAQ
- 软件资料
  - AT命令使用指南
    - 前言
    - 指令说明
    - 通用AT指令
    - BT业务AT指令
    - 新增AT指令方法
  - DFU升级APK开发指南
    - 前言
    - 功能特性
    - 系统框架
    - 工程文件部署
    - 界面实现
    - 升级界面Fragment实现
    - 关于界面Fragment实现
    - 应用清单AndroidManifest
  - DFX软件开发指南
    - 前言
    - DIAG维测功能
    - 死机诊断
    - Dump解析
  - EFUSE使用指南
    - 前言
    - 概述
    - 使用软件接口读写EFUSE
    - 使用BurnTool烧写EFUSE
  - NFC TAG软件开发指导书
    - 前言
    - 概述
    - 开发指导
  - NV存储用户指南
    - 前言
    - NV简介
    - NV编译预置
    - NV API指南
    - 定制化NV结构使用介绍
    - 通过NV自定义传输数据最大长度
  - Release notes
    - 1.0.18版本描述文件
    - 1.0.17版本描述文件
    - 1.0.16版本描述文件
  - SDK开发指南
    - 前言
    - 概述
    - 系统接口
  - SDK接口变更说明书
    - 前言
    - BLE SDK接口变更说明
  - SP31 SLP使用指南
    - 开源版本
  - USB HID复合设备开发指南
    - 前言
    - 概述
    - 实现方式
  - 代码框架说明
    - 前言
    - SDK模块框架
    - SDK目录结构
    - SDK目录说明
  - 低功耗FAQ
    - 前言
    - 系统无法睡眠
    - IO无法唤醒系统
    - 唤醒后外设异常
    - 低功耗导致OTA升级异常
    - 功耗比预期高
    - 锁中断导致BT业务阻塞
    - 概率性狗超时
    - 供电模式&软件配置
    - 提前唤醒时间问题
  - 低功耗sample使用指南
    - 前言
    - 编译&测试
    - 系统状态管理
    - 用户指南
  - 低功耗开发指南
    - 前言
    - 低功耗概述
    - 低功耗方案
    - 开发指引
    - 功耗优化
    - 常见问题
  - 功率配置说明书
    - 前言
    - 射频功率配置说明
  - 升级方案使用指南
    - 前言
    - 概述
    - 升级包制作
    - 升级包传输
    - 升级包保存
    - 升级包本地升级
    - FAQ
  - 快速入门指南
    - 前言
    - 文档目录
    - 环境搭建
    - 基础功能开发
    - 调优
    - 特性开发
    - 测试报告
  - 星闪应用层SLE-Link协议
    - 前言
    - 概述
    - SLE Frame格式定义
    - 服务接口定义
    - 交互流程示例
  - 星闪软件开发指导书
    - 前言
    - 概述
    - Device Discovery接口
    - Connection Manager接口
    - SSAP Server接口
    - SSAP client接口
    - Low Latency接口
    - Transmission Manager接口
    - SLE Passkey接口
    - SLE Channel Map 接口
  - 红外学习使用指南
    - 前言
    - 红外学习方式
    - 通过比较器进行红外学习
    - 使用指南
  - 蓝牙软件开发指导书
    - 前言
    - 概述
    - GAP接口
    - GATT server接口
    - GATT client接口
  - 设备驱动开发指南
    - 前言
    - Pinctrl
    - GPIO
    - UART
    - SPI
    - I2C
    - ADC
    - DMA
    - PWM
    - WDT
    - Timer
    - Systick
    - TCXO
    - Flash
    - SFC
    - QDEC
    - KEYSCAN
    - PDM
    - USB_HID
    - USB_DFU
    - I2S
    - TRNG
    - 附：MIDDLEWARE
  - 音频编解码开发指导书
    - 前言
    - 概述
    - Audio Encoder
    - Audio Decoder
    - 调试与功能
- 工具资料
  - BurnTool工具使用指导书
    - 前言
    - BurnTool工具简介
    - 操作指南
    - FAQ
  - HiSpark Studio使用指南
    - 前言
    - 工具简介
    - 开发环境搭建
    - 工程管理
    - 工程配置
    - 编译运行
    - 软件包烧录
    - 栈分析和镜像分析
    - 工程调试
    - 串口控制台工具
    - Kconfig配置
    - GUI工程创建与使用
    - 高阶分析功能使用
    - 常见错误

### 该页内容

- 前言
- 概述
  - 背景介绍
  - 使用约束
- 系统接口
  - 概述
  - 任务
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 内存管理
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 中断机制
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 队列
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 事件
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 互斥锁
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 信号量
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 时间管理
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例
  - 软件定时器
    - 概述
    - 开发流程
    - 注意事项
    - 编程实例

# 前言

概述

本文档主要介绍BS2X的SDK开发相关内容，包括SDK架构、接口实现机制与使用说明（包括工作原理、按场景描述接口使用方法和注意事项）。

注：本文档以BS21为例，不再单独说明。

产品版本

与本文档相对应的产品版本如下。

产品名称 | 产品版本
--- | ---
BS2X | V100

读者对象

本文档主要适用于以下工程师：

- 技术支持工程师

- 软件开发工程师

符号约定

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

符号 | 说明
--- | ---
| 表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。
| 表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。
| 表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。
| 用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。 “须知”不涉及人身伤害。
| 对正文中重点信息的补充说明。 “说明”不是安全警示信息，不涉及人身、设备及环境伤害信息。

修改记录

文档版本 | 发布日期 | 修改说明
--- | --- | ---
03 | 2025-01-24 | 更新“中断机制”的“注意事项”小节内容。
02 | 2024-07-04 | 更新“使用约束”小节内容。
01 | 2024-05-15 | 第一次正式版本发布。
00B03 | 2024-02-29 | 更新“使用约束”小节内容。
00B02 | 2023-10-27 | 第二次临时版本发布。
00B01 | 2023-09-27 | 第一次临时版本发布。

# 概述

## 背景介绍

BS2X系列的平台软件对应用层实现了底层屏蔽，并对应用软件直接提供API（Application Programming Interface）接口完成相应功能。典型的系统应用架构如图1所示。

图 1 系统应用框架图

该框架可以分为以下几个层次：

- APP层：即应用层。

- API层：提供基于SDK开发的通用接口。

- Platform平台层：提供SOC系统板级支持包，包括如下功能。

  - 芯片和外围器件驱动。

  - 操作系统。

  - 系统管理。

- Service服务层：提供包含BT等应用协议栈。用于上层应用软件进行数据收发等操作。

- 第三方库：提供给Service服务层或提供给应用层使用的第三方软件库。

## 使用约束

- 系统在启动过程中已完成UART、Flash、WDT、NV等驱动的初始化。用户在开发过程中，请勿重复初始化这些模块，否则会引起系统错误。

- 系统启动运行后，会占用一些系统资源：中断、内存、任务、消息队列、事件、信号量、定时器、互斥锁等。用户在应用层开发中释放资源时，仅可释放用户申请的资源，请勿释放系统资源。

- Liteos系统资源配置位于“sdk/kernel/liteos/liteos_v208.6.0_b017/Huawei_LiteOS/tools/build/config/bs2x.config”(bs2x.config默认和芯片对应；特殊版本的，和build/config/target_config/bs20(bs21/bs21a/bs22/bs26)/config.py中的liteos_kconfig选项对应。）以bs21为例，默认使用sdk/kernel/liteos/liteos_v208.6.0_b017/Huawei_LiteOS/tools/build/config/bs21.config；

  config.py中的bs21-rcu配置了liteos_kconfig': 'bs21_rcu’，则使用“sdk/kernel/liteos/liteos_v208.6.0_b017/Huawei_LiteOS/tools/build/config/bs21_rcu.config”文件，用户调整资源使用时，需要修改此文档。

# 系统接口

## 概述

系统接口是包括对任务、事件等系统资源进行所需操作的接口。

SDK支持用户客制化系统资源，以BS21为例，资源配置需编辑“sdk\kernel\liteos\liteos_v208.5.0\Huawei_LiteOS\tools\build\config\bs21.config”文件，按需合理地配置资源项将有效降低系统资源浪费，提高运行效率，又可避免资源不足。常用配置项如表1所示。

表 1 bs21.config中的常用资源配置项

配置项 | 描述
--- | ---
LOSCFG_BASE_CORE_TSK_LIMIT | 系统任务数上限。创建任务时，ID超过此数值将创建失败。
LOSCFG_BASE_IPC_SEM_LIMIT | 系统信号量个数上限。资源不足时，引起创建信号量失败。
LOSCFG_BASE_IPC_MUX_LIMIT | 系统互斥锁个数上限。资源不足时，引起创建互斥锁失败。
LOSCFG_BASE_IPC_QUEUE_LIMIT | 消息队列个数上限。资源不足时，创建消息队列将失败。
LOSCFG_BASE_CORE_SWTMR_LIMIT | 软件定时器个数上限。资源不足时，创建软件定时器将失败。
LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE | IDLE任务的栈大小。
LOSCFG_BASE_CORE_TSK_SWTMR_STACK_SIZE | 软件定时器任务的栈大小。

## 任务

### 概述

任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源，并独立于其它任务运行。任务模块可以给用户提供多个任务，实现了任务之间的切换和通信，帮助用户管理业务程序流程。任务模块具有如下特性：

- 支持多任务，一个任务表示一个线程。

- 任务是抢占式调度机制，同时支持时间片轮转调度方式。

- 高优先级的任务可打断低优先级任务，低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。

- 有32个优先级[0，31]，最高优先级为0，最低优先级为31。由于系统自身任务需要及时调度，建议用户使用任务优先级范围是[10,30]。应用级任务建议使用低于系统级任务的优先级。

重要概念

- 任务状态

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

  任务状态通常分为以下4种：

  - 就绪态（Ready）：该任务在就绪列表中，只等待CPU。

  - 运行态（Running）：该任务正在执行。

  - 阻塞态（Blocked）：该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读事件等。

  - 退出态（Dead）：该任务运行结束，等待系统回收资源。

  图 1 任务状态示意图

  任务状态迁移说明：

  - 就绪态→运行态：

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

  - 运行态→阻塞态：

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

  - 阻塞态→就绪态（阻塞态→运行态）：

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

  - 就绪态→阻塞态：

    任务也有可能在就绪态时被阻塞（挂起），此时任务状态会有就绪态转变为阻塞态，该任务从就绪列表中删除，不会参与任务调度，直到该任务被恢复。

  - 运行态→就绪态：

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

  - 运行态→退出态

    运行中的任务运行结束，任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出以及Invalid状态。例如，未设置分离属性（LOS_TASK_STATUS_DETACHED）的任务，运行结束后对外呈现的是Invalid状态，即退出态。

  - 阻塞态→退出态

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

- 任务ID

  任务ID，在任务创建时通过参数返回给用户，作为任务的一个非常重要的标识。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。

- 任务优先级

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

- 任务入口函数

  每个新任务得到调度后将执行的函数。该函数由用户实现，在任务创建时，通过任务创建结构体指定。

- 任务控制块TCB

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

- 任务栈

  每一个任务都拥有一个独立的栈空间，我们称为任务栈。栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。任务在任务切换时会将切出任务的上下文信息保存在自身的任务栈空间里面，以便任务恢复时还原现场，从而在任务恢复后在切出点继续开始执行。

- 任务上下文

  任务在运行过程中使用到的一些资源，如寄存器等，我们称为任务上下文。当这个任务挂起时，其他任务继续执行，在任务恢复后，如果没有把任务上下文保存下来，有可能任务切换会修改寄存器中的值，从而导致未知错误。因此，在任务挂起的时候会将本任务的任务上下文信息，保存在自己的任务栈里面，以便任务恢复后，从栈空间中恢复挂起时的上下文信息，从而继续执行被挂起时被打断的代码。

- 任务切换

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

运行机制

系统任务管理模块提供如下功能：

- 任务创建。

- 任务延时。

- 任务挂起和任务恢复。

- 锁任务调度和解锁任务调度。

- 根据ID查询任务控制块信息。

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

### 开发流程

使用场景

任务创建后，内核可以执行锁任务调度，解锁任务调度，挂起，恢复，延时等操作，同时也可以设置任务优先级，获取任务优先级。任务结束的时候，如果任务的状态是自删除状态（LOS_TASK_STATUS_DETACHED），则进行当前任务自删除操作。

用户的代码需实现app_main接口。系统初始化阶段会调用app_main接口，用户的初始化操作可在app_main中完成。如果用户需要多个任务，可在app_main中创建新任务。建议用户使用任务优先级范围是10～30。应用级任务建议使用低于系统级任务的优先级。

功能说明

系统中的任务管理模块为用户提供的功能如表1所示。

表 1 系统任务管理模块接口说明

接口名称 | 说明
--- | ---
osal_kthread_create | 创建任务。
osal_kthread_destroy | 删除指定的任务
osal_kthread_suspend | 挂起指定任务。
osal_kthread_resume | 恢复挂起指定任务。
osal_kthread_set_priority | 设置任务优先级。
osal_get_current_tid | 获取当前任务ID。
osal_kthread_lock | 禁止系统任务调度。
osal_kthread_unlock | 允许系统任务调度。
osal_msleep | 任务睡眠，单位ms。

开发流程

以bs21创建任务为例，创建任务的开发流程：

1. 在bs21.config中配置任务数。

   配置LOSCFG_BASE_CORE_TSK_LIMIT系统支持最大任务数需要根据用户需求配置。

2. 调用锁任务接口：osal_kthread_lock，锁住任务，防止高优先级任务调度。

3. 调用创建任务接口：osal_kthread_create。

4. 调用解锁任务接口：osal_kthread_unlock，让任务按照优先级进行调度。

5. 调用挂起指定的任务接口：osal_kthread_suspend，任务挂起等待恢复操作。

6. 调用恢复挂起的任务接口：osal_kthread_resume。

错误码

osal接口支持维测打印开关，关闭OSALLOG_DISABLE宏定义后，osal接口运行过程中出现异常结果会打印异常信息。

异常错误码请参考“sdk\kernel\liteos\liteos_v208.5.0\Huawei_LiteOS\kernel\include\los_task.h”中错误说明。

任务错误码如表2所示。

表 2 任务错误码说明

序号 | 定义 | 实际数值 | 说明 | 参考解决方案
--- | --- | --- | --- | ---
1 | LOS_ERRNO_TSK_NO_MEMORY | 0x03000200 | 内存空间不足。 | 增大动态内存空间，有两种方式可以实现： 设置更大的系统动态内存池 释放一部分动态内存。 如果错误发生在LiteOS启动过程中的任务初始化，还可以通过减少系统支持的最大任务数来解决；如果错误发生在任务创建过程中，也可以减小任务栈大小来解决。
2 | LOS_ERRNO_TSK_PTR_NULL | 0x02000201 | 传递给任务创建接口的任务参数为空指针，或者传递给任务信息获取的接口的参数为空指针。 | 确保传入的参数不为空指针。
3 | LOS_ERRNO_TSK_PRIOR_ERROR | 0x02000203 | 创建任务或者设置任务优先级时，传入的优先级参数不正确。 | 检查任务优先级，必须在0～31的范围内。
4 | LOS_ERRNO_TSK_ENTRY_NULL | 0x02000204 | 创建任务时传入的任务入口函数为空指针。 | 定义任务入口函数。
5 | LOS_ERRNO_TSK_NAME_EMPTY | 0x02000205 | 创建任务时传入的任务名为空指针。 | 设置任务名。
6 | LOS_ERRNO_TSK_STKSZ_TOO_SMALL | 0x02000206 | 创建任务时传入的任务栈太小。 | 增大任务的任务栈大小使之不小于系统设置最小任务栈大小。
7 | LOS_ERRNO_TSK_ID_INVALID | 0x02000207 | 超出OS支持范围内的无效的任务ID。 | 检查任务ID。
8 | LOS_ERRNO_TSK_ALREADY_SUSPENDED | 0x02000208 | 挂起任务时，发现任务已经被挂起。 | 等待这个任务被恢复后，再去尝试挂起这个任务。
9 | LOS_ERRNO_TSK_NOT_SUSPENDED | 0x02000209 | 恢复任务时，发现任务未被挂起。 | 挂起这个任务后，再去尝试恢复这个任务。
10 | LOS_ERRNO_TSK_NOT_CREATED | 0x0200020a | 未创建任务。 | 创建这个任务，这个错误可能会发生在以下操作中： 删除任务。 恢复/挂起任务。 设置指定任务的优先级。 获取指定任务的信息。 设置指定任务的运行CPU集合。
11 | LOS_ERRNO_TSK_DELETE_LOCKED | 0x0300020b | 删除任务时，任务处于锁定状态。 | 解锁任务之后再删除任务。
12 | LOS_ERRNO_TSK_DELAY_IN_INT | 0x0300020d | 中断期间，进行任务延时。 | 等待退出中断后再进行延时操作。
13 | LOS_ERRNO_TSK_DELAY_IN_LOCK | 0x0200020e | 在任务锁定状态下，延时该任务。 | 解锁任务之后再延时任务。
14 | LOS_ERRNO_TSK_SUSPEND_LOCKED | 0x03000215 | 不允许将处于锁定状态的任务挂起。 | 任务解锁后，再尝试挂起任务。

### 注意事项

- 创建新任务时，会对之前已删除任务的任务控制块和任务栈进行回收。

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

- 若指定的任务栈大小为0，则使用配置默认的任务栈大小。

- 任务栈的大小按16字节大小对齐。确定任务栈大小的原则为够用即可：多则浪费，少则任务栈溢出。

- 当前任务和已锁定的任务，不能被挂起。

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

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

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

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

- 系统可配置的任务资源个数是指整个系统的任务资源总个数，而非用户能使用的任务资源个数。例如：系统软件定时器多占用一个任务资源数，则系统可配置的任务资源就会减少一个。

- 不建议使用osal_kthread_set_priority接口来修改软件定时器任务的优先级，否则可能会导致系统出现问题。

- osal_kthread_set_priority接口不能在中断中使用。

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

- 尽量少创建task，可采用内存池方案避免内存碎片化。

### 编程实例

下面的示例介绍任务的基本操作方法：

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define TASK_PRI            25      /* 任务优先级范围，从高到低:  0～31 */
    #define TASK_STACK_SIZE     0x1000
    static void example_task_entry(void* arg)
    {
        unused(arg);
        osal_printk("Example task is running!\n");
    }
    void example_task_init(void)
    {
        uint32_t ret;
        osal_task *example_task_info;
        /* 创建任务期间锁住任务调度 */
        osal_kthread_lock();
        /* 创建线程 */
        example_task_info = osal_kthread_create((osal_kthread_handler)example_task_entry, NULL, "example_task", TASK_STACK_SIZE);
        /* 设置线程优先级 */
        ret = osal_kthread_set_priority(example_task_info->task, TASK_PRI);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 任务创建完成解锁任务调度 */
        osal_kthread_unlock();
        /* 任务开始调度 */
    }

结果验证：

    Example task is running!

## 内存管理

### 概述

内存管理模块管理系统的内存资源，通过对内存的申请/释放操作来管理用户和OS对内存的使用，使内存的利用率和效率最优，最大限度地解决系统的内存碎片问题。其中，OS的内存管理为动态内存管理，提供内存初始化、分配、释放等功能。

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

- 优点：按需分配。

- 缺点：内存池中可能出现碎片。

### 开发流程

使用场景

内存管理的主要工作是动态的划分并管理用户分配好的内存区间。动态内存管理主要是在用户需要使用大小不等的内存块的场景中使用。当用户需要分配内存时，可以通过操作系统的动态内存申请函数索取指定大小内存块，一旦使用完毕，通过动态内存释放函数归还所占用内存，使之可以重复使用。

> 说明： 针对默认不使用OS内存池，单独划分出内存区间进行使用的场景，需要用户修改boot和kernel链接脚本，详细步骤参考“编程实例”。

功能说明

动态内存管理模块提供的接口如表1所示。

表 1 动态内存管理接口说明

接口名称 | 说明
--- | ---
osal_kmalloc | 从系统动态内存池中申请一块内存。
osal_kfree | 释放系统内存池已申请的内存块。
osal_kmalloc_align | 从系统内存池申请一块地址对齐的内存。
osal_pool_mem_init | 初始化一个内存池。
osal_pool_mem_alloc | 从指定的内存池申请一块内存。
osal_pool_mem_alloc_align | 从指定的内存池申请一块地址对齐的内存。
osal_pool_mem_free | 释放指定内存池已申请的内存块。

错误码

内存申请成功会返回申请的内存地址，如果申请失败则会返回NULL。

### 注意事项

- 系统中osal_kmalloc_xxx和osal_pool_mem_alloc_xxx函数如果分配成功，返回分配的内存指针。如果分配失败，则返回NULL。

- 系统中多次调用osal的free接口时，第一次会返回成功，但对同一块内存进行多次重复释放会导致非法指针操作，结果不可预知。

- 对于存储到用户指定内存区间的全局变量，系统启动时不会进行初始化或清零，需要用户自行进行管理。

### 编程实例

实例一：演示APP层内存申请以及释放操作。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define EXAMPLE_MEM_SIZE 100
    void example_mem(void)
    {
        /* 申请内存 */
        void* mem = osal_kmalloc(EXAMPLE_MEM_SIZE,  NULL);
        if (mem == NULL) {
            osal_printk("Malloc failed!\n");
        }
        osal_printk("Using memory as expected!\n");
        /* 释放内存 */
        osal_kfree(mem);
    }

结果验证

    Using memory as expected!

## 中断机制

### 概述

中断是指CPU暂停执行当前程序，转而执行新程序的过程。中断相关的硬件可以划分为3类：

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

- 中断控制器：接收中断输入并上报给CPU。可以设置中断源的优先级、触发方式、打开和关闭等操作。

- CPU：判断和执行中断任务。

中断相关的名词解释：

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

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

- 中断优先级：为使系统能够及时响应并处理所有中断，系统根据中断事件的重要性和紧迫程度，将中断源分为若干个级别，称作中断优先级。系统中所有的中断源优先级相同，不支持中断嵌套或抢占。

- 中断处理程序：当外设产生中断请求后，CPU暂停当前的任务，转而响应中断申请，即执行中断处理程序。

- 中断触发：中断源发出并送给CPU控制信号，将接口卡上的中断触发器置“1”，表明该中断源产生了中断，要求CPU去响应该中断,CPU暂停当前任务，执行相应的中断处理程序。

- 中断触发类型：外部中断申请通过一个物理信号发送到CPU，可以是电平触发或边沿触发。

- 中断向量：中断服务程序的入口地址。

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

### 开发流程

使用场景

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

功能说明

系统支持的中断机制接口如表1所示。

表 1 中断机制接口说明

接口名称 | 说明
--- | ---
osal_irq_lock | 关闭全部中断。 关中断后不能执行引起调度的函数，如osal_sleep或其他阻塞接口。 关中断仅保护可预期的短时间的操作，否则影响中断响应，可能引起性能问题。 返回值为当前中断状态即CPSR值。
osal_irq_restore | 恢复关中断前的状态。 入参必须是与之对应的关中断时保存的关中断之前的CPSR的值。
osal_in_interrupt | 检查是否在中断上下文中。
osal_irq_enable | 使能指定中断。
osal_irq_disable | 去使能指定中断。
osal_irq_request | 注册中断。
osal_irq_free | 清除注册中断。
osal_irq_set_priority | 设置中断优先级。
osal_irq_clear | 清除中断标志。

错误码

OSAL接口支持维测打印开关，关闭OSALLOG_DISABLE宏定义后，OSAL封装的内核接口运行过程中会打印异常信息值，便于快速定位问题。

中断机制错误码如表2所示。

表 2 中断机制错误码说明

序号 | 定义 | 实际数值 | 说明 | 参考解决方案
--- | --- | --- | --- | ---
1 | LOS_ERRNO_HWI_NUM_INVALID | 0x02000900 | 创建或删除中断时，传入了无效中断号。 | 检查中断号，给定有效中断号。
2 | LOS_ERRNO_HWI_PROC_FUNC_NULL | 0x02000901 | 创建中断时，传入的中断处理程序指针为空；如果调用其他接口返回此错误码则表示该接口功能不支持。 | 传入非空中断处理程序指针。
3 | LOS_ERRNO_HWI_NO_MEMORY | 0x02000903 | 创建中断时，出现内存不足的情况。 | 增大动态内存空间，有两种方式可以实现： 设置更大的系统动态内存池。 释放一部分动态内存。
4 | LOS_ERRNO_HWI_ALREADY_CREATED | 0x02000904 | 创建中断时，发现要注册的中断号已经创建。 | 对于非共享中断号的情况，检查传入的中断号是否已经被创建；对于共享中断号的情况，检查传入中断号的链表中是否已经有匹配函数参数的设备ID。
5 | LOS_ERRNO_HWI_PRIO_INVALID | 0x02000905 | 设置的中断优先级无效。 | 传入有效中断优先级。优先级有效范围依赖于硬件，外部可配。
6 | LOS_ERRNO_HWI_INTERR | 0x02000908 | 在中断中调用osal_irq_request接口。 | 查看osal_irq_request接口的使用是否正确。

### 注意事项

- 根据具体硬件，配置支持的最大中断数及中断初始化操作的寄存器地址。

- 中断处理程序耗时不能过长，影响CPU对中断的及时响应。

- 中断响应过程中不能执行引起任务调度的函数。

- 中断恢复osal_irq_restore()的入参必须是与之对应的osal_irq_lock()保存的关中断之前的CPSR的值。

- 中断的处理函数中不能使用mutex、malloc、sleep、delay函数，代码须尽量短小、运行快速，对于较复杂的操作需通过抛事件给中断下半部处理。

- BS2X内核的配置文件bs21.config默认打开了中断嵌套（LOSCFG_ARCH_INTERRUPT_PREEMPTION），BS2X的riscv内核开启中断嵌套时，对于电平触发方式，需要在中断处理程序结束时调用osal_irq_clear接口，主动清除中断寄存器状态，该接口只有在中断嵌套场景生效；非嵌套场景用户无需清除中断控制器状态。

- 在使能中断嵌套后，严禁在中断中调整中断优先级，异常的提高优先级，可能会导致中断重入。

### 编程实例

本实例实现如下功能：

- 关闭全部中断。

- 中断使能。

- 中断去使能。

- 恢复关闭中断前的状态。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define UART_HANDLE_PRIO    1    /* 中断优先级范围，从高到低:  0 - 7 */
    void uart_irqhandle(int32_t irq,void *dev)
    {
        unused(irq);
        unused(dev);
        osal_printk("\n int the func uart_irqhandle \n");
    }
    void example_irq(void)
    {
        uint32_t irq_idx = 10;
        uint32_t uvIntSave;
        /* 开关所有中断 */
        uvIntSave = osal_irq_lock();
        osal_irq_restore(uvIntSave);
        /* 注册中断 */
        osal_irq_request(irq_idx, (osal_irq_handler)uart_irqhandle, NULL, "uart irq", NULL);
        /* 设置中断优先级 */
        osal_irq_set_priority(irq_idx, UART_HANDLE_PRIO);
        /* 使能中断 */
        osal_irq_enable(irq_idx);
        /* 去使能中断 */
        osal_irq_disable(irq_idx);
    }

## 队列

### 概述

队列又称消息队列，是一种常用于任务间通信的数据结构，实现了接收来自任务或中断的不固定长度的消息，接收方根据消息ID读取消息。

任务能够从队列里面读取消息：

- 当队列中的消息是空时，挂起读取任务。

- 当队列中有新消息时，挂起的读取任务被唤醒并处理新消息。

- 用户在处理业务时，消息队列提供了异步处理机制，允许将一个消息放入队列，但并不立即处理它，同时队列还能起到缓冲消息作用。

系统中使用队列数据结构实现任务异步通信工作，具有如下特性：

- 消息以先进先出方式排队，支持异步读写工作方式。

- 读队列和写队列都支持超时机制。

- 发送消息类型由通信双方约定，可以允许不同长度（不超过队列节点最大值）消息。

- 一个任务能够从任意一个消息队列接收和发送消息。

- 多个任务能够从同一个消息队列接收和发送消息。

- 当队列使用结束后，如果是动态申请的内存，需要通过释放内存函数回收。

### 开发流程

使用场景

多任务间通信，可通过消息队列完成。

功能说明

消息队列提供的接口如表1所示。

表 1 队列接口说明

接口名称 | 说明
--- | ---
osal_msg_queue_create | 创建消息队列。
osal_msg_queue_delete | 删除消息队列。
osal_msg_queue_write_copy | 发送消息到队列尾部。
osal_msg_queue_write_head_copy | 发送消息到队列头。
osal_msg_queue_read_copy | 阻塞接收消息，单位：ms。
osal_msg_queue_is_full | 检查消息队列是否已满。
osal_msg_queue_get_msg_num | 获取当前已经使用的消息队列个数。

开发流程

使用队列模块的典型流程：

1. 创建消息队列osal_msg_queue_create。创建成功后，可以得到消息队列的ID值。

2. 发送消息osal_msg_queue_write_copy。

3. 消息等待接收osal_msg_queue_read_copy。

4. 队列状态管理osal_msg_queue_is_full、osal_msg_queue_get_msg_num。

5. 删除队列osal_msg_queue_delete。

错误码

OSAL接口支持维测打印开关，关闭OSALLOG_DISABLE宏定义后，OSAL封装的内核接口运行会打印异常信息值，以便快速定位错误原因。

队列操作失败错误码如表2所示。

表 2 队列错误码说明

序号 | 定义 | 实际数值 | 说明 | 参考解决方案
--- | --- | --- | --- | ---
1 | LOS_ERRNO_QUEUE_NO_MEMORY | 0x02000601 | 队列初始化时，从动态内存池申请内存失败。 | 设置更大的系统动态内存池，配置项为OS_SYS_MEM_SIZE，或减少系统支持的最大队列数。
2 | LOS_ERRNO_QUEUE_CREATE_NO_MEMORY | 0x02000602 | 创建队列时，从动态内存池申请内存失败。 | 设置更大的系统动态内存池，配置项为OS_SYS_MEM_SIZE，或减少要创建队列的队列长度和消息节点大小。
3 | LOS_ERRNO_QUEUE_SIZE_TOO_BIG | 0x02000603 | 创建队列时消息节点大小超过上限。 | 更改入参消息节点大小，使之不超过上限。
4 | LOS_ERRNO_QUEUE_CB_UNAVAILABLE | 0x02000604 | 创建队列时，系统中已经没有空闲队列。 | 增加系统支持的最大队列数。
5 | LOS_ERRNO_QUEUE_NOT_FOUND | 0x02000605 | 传递给删除队列接口的队列ID大于等于系统支持的最大队列数。 | 确保队列ID是有效的。
6 | LOS_ERRNO_QUEUE_PEND_IN_LOCK | 0x02000606 | 当任务被锁定时，禁止在队列中阻塞等待写消息或读消息。 | 使用队列前解锁任务。
7 | LOS_ERRNO_QUEUE_TIMEOUT | 0x02000607 | 等待处理队列超时。 | 检查设置的超时时间是否合适。
8 | LOS_ERRNO_QUEUE_IN_TSKUSE | 0x02000608 | 队列存在阻塞任务而不能被删除。 | 使任务能够获得资源而不是在队列中被阻塞。
9 | LOS_ERRNO_QUEUE_WRITE_IN_INTERRUPT | 0x02000609 | 在中断处理程序中不能以阻塞模式写队列。 | 将写队列设为非阻塞模式，即将写队列的超时时间设置为0。
10 | LOS_ERRNO_QUEUE_NOT_CREATE | 0x0200060a | 队列未创建。 | 创建该队列，或更换为一个已经创建的队列。
11 | LOS_ERRNO_QUEUE_IN_TSKWRITE | 0x0200060b | 队列读写不同步。 | 同步队列的读写，即多个任务不能并发读写同一个队列。
12 | LOS_ERRNO_QUEUE_CREAT_PTR_NULL | 0x0200060c | 对于创建队列接口，保存队列ID的入参为空指针。 | 确保传入的参数不为空指针。
13 | LOS_ERRNO_QUEUE_PARA_ISZERO | 0x0200060d | 对于创建队列接口，入参队列长度或消息节点大小为0。 | 传入正确的队列长度和消息节点大小。
14 | LOS_ERRNO_QUEUE_INVALID | 0x0200060e | 传递给读队列或写队列或获取队列信息接口的队列ID大于等于系统支持的最大队列数。 | 确保队列ID有效。
15 | LOS_ERRNO_QUEUE_READ_PTR_NULL | 0x0200060f | 传递给读队列接口的指针为空。 | 确保传入的参数不为空指针。
16 | LOS_ERRNO_QUEUE_READSIZE_IS_INVALID | 0x02000610 | 传递给读队列接口的缓冲区大小为0或大于0xFFFB。 | 传入的一个正确的缓冲区大小需要大于0且小于0xFFFC。
17 | LOS_ERRNO_QUEUE_WRITE_PTR_NULL | 0x02000612 | 传递给写队列接口的缓冲区指针为空。 | 确保传入的参数不为空指针。
18 | LOS_ERRNO_QUEUE_WRITESIZE_ISZERO | 0x02000613 | 传递给写队列接口的缓冲区大小为0。 | 传入正确的缓冲区大小。
19 | LOS_ERRNO_QUEUE_WRITE_SIZE_TOO_BIG | 0x02000615 | 传递给写队列接口的缓冲区大小比队列的消息节点大小要大。 | 减小缓冲区大小，或增大队列的消息节点大小。
20 | LOS_ERRNO_QUEUE_ISFULL | 0x02000616 | 写队列时没有可用的空闲节点。 | 写队列之前，确保在队列中存在可用的空闲节点，或者使用阻塞模式写队列，即设置大于0的写队列超时时间。
21 | LOS_ERRNO_QUEUE_PTR_NULL | 0x02000617 | 传递给获取队列信息接口的指针为空。 | 确保传入的参数不为空指针。
22 | LOS_ERRNO_QUEUE_READ_IN_INTERRUPT | 0x02000618 | 在中断处理程序中不能以阻塞模式读队列。 | 将读队列设为非阻塞模式，即将读队列的超时时间设置为0。
23 | LOS_ERRNO_QUEUE_ISEMPTY | 0x0200061d | 队列已空。 | 读队列之前，确保队列中存在未读的消息，或者使用阻塞模式读队列，即设置大于0的读队列超时时间。
24 | LOS_ERRNO_QUEUE_READ_SIZE_TOO_SMALL | 0x0200061f | 传递给读队列接口的读缓冲区大小小于队列消息节点大小。 | 增加缓冲区大小，或减小队列消息节点大小。

### 注意事项

- 等待消息：在中断、关中断、锁任务上下文禁止调用等待消息接口，进而产生不可控的异常调度。

- 发送消息：在关中断上下文禁止调用发送消息接口，进而产生不可控的异常调度。

- 发送消息（超时时间非0）：在中断、锁任务上下文禁止调用超时时间非0发送消息接口，进而产生不可控的异常调度。

- 系统可配置的队列资源个数是指整个系统的队列资源总个数，而非用户能使用的个数。例如：系统软件定时器多占用一个队列资源，那么系统可配置的队列资源就会减少一个。

- 队列接口函数中的入参timeout是指相对时间。

- 当队列使用结束后，如果存在动态申请的内存，需要及时释放这些内存。

### 编程实例

创建一个队列，两个任务：

- 任务1调用发送接口发送消息。

- 任务2通过接收接口接收消息。

步骤如下：

1. 通过osal_kthread_create创建任务1和任务2。

2. 通过osal_msg_queue_create创建一个消息队列。

3. 在任务1 调用osal_msg_queue_write_copy发送消息。

4. 在任务2 调用osal_msg_queue_read_copy接收消息。

5. 通过osal_msg_queue_delete删除队列。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define TASK_PRI1       25
    #define TASK_PRI2       26
    #define MSP_QUEUE_SIZE  15
    #define MSG_MAX_LEN     50
    static unsigned long g_msg_queue;
    uint8_t abuf[] = "test is message x";
    /*任务1发送数据*/
    void example_send_task(void *arg)
    {
        uint32_t i = 0,ret = 0;
        uint32_t uwlen = sizeof(abuf);
        unused(arg);
        while (i < 5) {
            abuf[uwlen - 2] = '0' + i;
            i++;
            /*将abuf里的数据写入队列*/
            ret = osal_msg_queue_write_copy(g_msg_queue, abuf, sizeof(abuf), OSAL_WAIT_FOREVER);
            if(ret != OSAL_SUCCESS) {
                osal_printk("send message failure,error:%x\n",ret);
            }
            osal_msleep(5);
        }
    }
    /*任务2接收数据*/
    void example_recv_task(void *arg)
    {
        unused(arg);
        uint8_t msg[50] = {0};
        uint32_t ret = 0;
        /*设置buff缓冲区大小以及存放读取到的消息大小*/
        uint32_t msg_rev_size = 50;
        while (1) {
            /*读取队列里的数据存入msg里*/
            ret = osal_msg_queue_read_copy(g_msg_queue, msg, &msg_rev_size, OSAL_WAIT_FOREVER);
            if(ret != OSAL_SUCCESS) {
                osal_printk("recv message failure,error:%x\n",ret);
                break;
            }
            osal_printk("recv message:%s\n", (char *)msg);
            osal_msleep(5);
        }
        /*删除队列。需根据具体情况删除，大多数情况下无需删除队列，且在有任务占用等情况下删除队列会导致失败。以下代码仅供API展示*/
        osal_msg_queue_delete(g_msg_queue);
    }
    int example_msg_queue(void)
    {
        uint32_t ret = 0;
        osal_task *example_task1_info, *example_task2_info;
        /* 创建队列 */
        ret = osal_msg_queue_create("name", MSP_QUEUE_SIZE, &g_msg_queue, NULL, MSG_MAX_LEN);
        if(ret != OSAL_SUCCESS) {
            osal_printk("create queue failure!,error:%x\n",ret);
        }
        osal_printk("create the queue success! queue_id = %d\n", g_msg_queue);
        /* 创建任务期间锁住任务调度 */
        osal_kthread_lock();
        /* 创建任务1 */
        example_task1_info = osal_kthread_create((osal_kthread_handler)example_send_task, NULL, "example_task1", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task1_info->task, TASK_PRI1);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 创建任务2 */
        example_task2_info = osal_kthread_create((osal_kthread_handler)example_recv_task, NULL, "example_task2", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task2_info->task, TASK_PRI2);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 任务创建完成解锁任务调度 */
        osal_kthread_unlock();
        return ret;
    }

结果验证：

    create the queue success! queue_id = 2
    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

## 事件

### 概述

事件是一种任务间通信的机制，可用于实现任务间的同步。

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

- 一对多同步模型：一个任务等待多个事件的触发。

- 多对多同步模型：多个任务等待多个事件的触发。

任务可以通过创建事件控制块来实现对事件的触发和等待操作。

事件接口具有如下特点：

- 事件不与任务相关联，事件相互独立，内部实现为一个32位的无符号整型变量，用于标识该任务发生的事件类型，其中每一位表示一种事件类型。

  - 0：该事件类型未发生。

  - 1：该事件类型已经发生。

- 事件仅用于任务间的同步，不提供数据传输功能。

- 多次向任务发送同一事件类型等效于只发送一次。

- 多个任务可以对同一事件进行读写操作。

- 支持事件读写超时机制。

在读事件时，可以选择读取模式。读取模式如下：

- 所有事件（OSAL_WAITMODE_AND）：读取掩码中所有事件类型，只有读取的所有事件类型都发生，才能读取成功。

- 任一事件（OSAL_WAITMODE_OR）： 读取掩码中任一事件类型，读取的事件中任意一种事件类型发生，即可读取成功。

- 清除事件（OSAL_WAITMODE_CLR）：这是一种附加读取模式，可以与 OSAL_WAITMODE_AND和OSAL_WAITMODE_OR结合使用（OSAL_WAITMODE_AND| OSAL_WAITMODE_CLR或 OSAL_WAITMODE_OR| OSAL_WAITMODE_CLR），设置该模式读取成功后，对应事件类型位会自动清除。

运行机制：

读事件时，可以根据入参事件掩码类型mask读取事件的单个或多个事件类型。事件读取成功后，如果设置OSAL_WAITMODE_CLR会清除已读取到的事件类型，反之不会清除已读到的事件类型，需显式清除。可以通过入参选择读取模式，读取事件掩码类型中所有事件还是读取事件掩码类型中任意事件。

- 写事件时，对指定事件写入指定的事件类型，可以一次同时写多个事件类型。写事件会触发任务调度。

- 清除事件时，根据入参事件和待清除的事件类型，对事件对应位进行清0操作。

### 开发流程

使用场景

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

功能说明

系统中的事件模块为用户提供的接口如表1所示。

表 1 事件接口说明

接口名称 | 说明
--- | ---
osal_event_init | 初始化一个事件控制块。
osal_event_read | 阻塞读取指定事件类型，等待超时时间为相对时间，单位：ms。
osal_event_write | 写指定的事件类型。
osal_event_clear | 清除指定的事件类型。
osal_event_destroy | 销毁指定的事件控制块。

开发流程

使用事件模块的典型流程：

1. 调用事件初始化osal_event_init接口，初始化事件等待队列。

2. 写事件osal_event_write，配置事件掩码类型。

3. 读事件osal_event_read，选择读取模式。

4. 清除事件osal_event_clear，清除指定的事件类型。

5. 事件调用osal_event_destroy完成事件资源回收。

错误码

OSAL接口支持维测打印开关，关闭OSALLOG_DISABLE宏定义后，OSAL封装的内核接口运行过程中出现异常会打印异常信息值，事件存在失败的可能性操作包括：事件初始化、事件销毁、事件读写、事件清除，便于快速定位问题。

事件错误码如表2所示。

表 2 事件错误码说明

序号 | 定义 | 实际值 | 说明 | 参考解决方案
--- | --- | --- | --- | ---
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_SHOULD_NOT_DESTORY | 0x02001c08 | 事件链表上仍有任务，无法被销毁。 | 检查事件链表是否为空。

### 注意事项

- 在系统初始化之前不能调用读写事件接口。如果调用，则系统运行会不正常。

- 在中断中，可以对事件对象进行写操作，但不能进行读操作。

- 在锁任务调度状态下，禁止任务阻塞与读事件。

- hi_event_clear入参值是要清除的指定事件类型的反码（~event_bits）。

- 事件掩码支持bit[0]～bit[23]，bit[24]～bit[31]不支持。

### 编程实例

本示例中，任务example_task_entry_event 创建一个任务example_event，example_event读事件阻塞，example_task_entry_event 向该任务写事件。

1. 在任务example_task_entry_event 创建任务example_event，其中任务example_event优先级高于example_task_entry_event 。

2. 在任务example_event中读事件0x00000001，阻塞，发生任务切换，执行任务 example_task_entry_event。

3. 在任务example_task_entry_event 向任务Example_Event写事件0x00000001，发生任务切换，执行任务example_event。

4. example_event得以执行，直到任务结束。

5. example_task_entry_event 得以执行，直到任务结束。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define TEST_EVENT          (1 << 0)
    #define TASK_PRI_EVENT      27
    static osal_event g_event_id;
    void example_event(void* param)
    {
        uint32_t ret;
        unused(param);
        /* 超时 等待方式读事件, 超时时间为永远等待 */
        osal_printk("example_event wait event 0x%x \n", TEST_EVENT);
        ret = osal_event_read(&g_event_id, TEST_EVENT, OSAL_WAIT_FOREVER, OSAL_WAITMODE_AND);
        if (ret == OSAL_SUCCESS) {
            osal_printk("example_event read event :0x%x\n", TEST_EVENT);
        } else {
            osal_printk("example_event read event fail!\n");
        }
    }
    uint32_t example_task_entry_event(void)
    {
        uint32_t ret = 0;
        osal_task *example_task1_info;
        /* 初始化事件 */
        osal_event_init(&g_event_id);
        /* 创建任务期间锁住任务调度 */
        osal_kthread_lock();
        /* 创建任务1 */
        example_task1_info = osal_kthread_create((osal_kthread_handler)example_event, NULL, "example_task1", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task1_info->task, TASK_PRI_EVENT);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 任务创建完成解锁任务调度 */
        osal_kthread_unlock();
        /* 写用例任务等待的事件 */
        osal_printk("example_task_entry_event write event.\n");
        ret = osal_event_write(&g_event_id, TEST_EVENT);
        if(ret != OSAL_SUCCESS){
            osal_printk("event write failed .\n");
            return OSAL_FAILURE;
        }
        osal_printk("example_task_entry_event event write success .\n");
        /* 清标志位 */
        ret = osal_event_clear(&g_event_id, TEST_EVENT);
        if (ret != OSAL_SUCCESS) {
            osal_printk("event clear failed .\n");
            return OSAL_FAILURE;
        }
        osal_printk("example_task_entry_event event clear success.\n");
        /* 删除任务 */
        osal_kthread_destroy(example_task1_info->task, 0);
        return OSAL_SUCCESS;
    }

结果验证：

    example_event wait event 0x1
    example_task_entry_event write event .
    example_event read event :0x1
    example_task_entry_event event write success .
    example_task_entry_event event clear success.

## 互斥锁

### 概述

互斥锁又称互斥型信号量，是一种特殊的二值性信号量，用于实现对共享资源的独占式处理。任意时刻互斥锁的状态只有两种：

- 闭锁：当有任务持有时，互斥锁处于闭锁状态，这个任务获得该互斥锁的所有权。

- 开锁：当该任务释放它时，该互斥锁被开锁，任务失去该互斥锁的所有权。

当一个任务持有互斥锁时，其他任务将不能再对该互斥锁进行开锁或持有。多任务环境下往往存在多个任务竞争同一共享资源的应用场景，互斥锁可被用于对共享资源的保护从而实现独占式访问。另外，互斥锁可以解决信号量存在的优先级翻转问题。

互斥锁接口具有如下特点：

通过优先级继承算法，解决优先级翻转问题。

### 开发流程

使用场景

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

功能说明

系统中的互斥锁模块为用户提供的功能如表1所示。

表 1 互斥锁接口说明

接口名称 | 说明
--- | ---
osal_mutex_init | 初始化互斥锁。
osal_mutex_destroy | 删除指定的互斥锁。
osal_mutex_lock_timeout | 阻塞获取互斥锁，单位：ms。
osal_mutex_unlock | 释放指定的互斥锁。

开发流程

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

1. 创建互斥锁osal_mutex_init。

2. 申请互斥锁osal_mutex_lock_timeout。

   申请模式有3种：

   - 无阻塞模式：任务需要申请互斥锁，若该互斥锁当前没有任务持有，或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务，则申请成功，超时时间设置为0。

   - 永久阻塞模式：任务需要申请互斥锁，若该互斥锁当前没有被占用，则申请 成功。否则，该任务进入阻塞态，系统切换到就绪任务中优先级高者继续 执行。任务进入阻塞态后，直到有其他任务释放该互斥锁，阻塞任务才会重 新得以执行，超时时间设置为OSAL_MUTEX_WAIT_FOREVER。

   - 定时阻塞模式：任务需要申请互斥锁，若该互斥锁当前没有被占用，则申请 成功。否则该任务进入阻塞态，系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后，指定时间超时前有其他任务释放该互斥锁，或者用户指定时间超时后，阻塞任务才会重新得以执行，超时时间设置为一个合理的超时值。

3. 释放互斥锁osal_mutex_unlock。

   - 如果有任务阻塞于指定互斥锁，则唤醒被阻塞任务中优先级高的，该任务进入就绪态，并进行任务调度。

   - 如果没有任务阻塞于指定互斥锁，则互斥锁释放成功。

4. 删除互斥锁osal_mutex_destroy。

错误码

OSAL接口支持维测打印开关，关闭OSALLOG_DISABLE宏定义后，OSAL封装的内核接口运行过程中会打印异常信息值，便于快速定位问题。

互斥锁错误码说明如表2所示。

表 2 互斥锁错误码说明

序号 | 定义 | 实际数值 | 说明 | 参考解决方案
--- | --- | --- | --- | ---
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_PENDED | 0x02001d09 | 删除正在使用的互斥锁。 | 等待解锁后再删除该互斥锁。

### 注意事项

- 如果由于达到互斥锁数量上限而导致互斥锁创建失败，可以通过配置“bs25.config”文件里面的LOSCFG_BASE_IPC_MUX_LIMIT来增大互斥锁数量上限。

- 两个任务不能对同一把互斥锁加锁。如果某任务对已被持有的互斥锁加锁，则该任务会被挂起，直到持有该锁的任务对互斥锁解锁，才能执行对这把互斥锁的加锁操作。

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

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

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

### 编程实例

本实例实现如下流程：

1. 任务example_task_entry_mux创建一个互斥锁，锁任务调度，创建两个任务example_mutex_task1、example_mutex_task2，example_mutex_task2优先级高于example_mutex_task1，解锁任务调度。

2. example_mutex_task2被调度，永久申请互斥锁，然后任务休眠100ms， example_mutex_task2挂起，example_mutex_task1被唤醒。

3. example_mutex_task1申请互斥锁，等待时间为10ms，因互斥锁仍被example_mutex_task2持有，example_mutex_task1挂起，10ms后未拿到互斥锁，example_mutex_task1被唤醒，试图以永久等待申请互斥锁，example_mutex_task1挂起。

4. 100ms后example_mutex_task2唤醒， 释放互斥锁后，example_mutex_task1被调度 运行，后释放互斥锁。

5. example_mutex_task1执行完，300ms后任务example_task_entry_mux被调度运行，删除互斥锁。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define TASK_PRI_TASK1      21
    #define TASK_PRI_TASK2      20
    static osal_mutex g_mux_id;
    void example_mutex_task1(void* param)
    {
        unused(param);
        uint32_t ret;
        osal_printk("task1 try to get mutex,wait 10 ms.\n");
        ret = osal_mutex_lock_timeout(&g_mux_id, 10);
        if (ret == OSAL_SUCCESS) {
            osal_printk("task1 get mutex g_mux_id.\n");
            osal_mutex_unlock(&g_mux_id);
        }else {
            osal_printk("task1 timeout and try to get  mutex, wait forever.\n");
            ret= osal_mutex_lock_timeout(&g_mux_id, OSAL_WAIT_FOREVER);
            if (ret == OSAL_SUCCESS) {
                osal_printk("task1 wait forever,get mutex g_mux_id.\n");
                osal_mutex_unlock(&g_mux_id);
            }
        }
    }
    void example_mutex_task2(void* param)
    {
        unused(param);
        osal_printk("task2 try to get mutex, wait forever.\n");
        osal_mutex_lock_timeout(&g_mux_id, OSAL_WAIT_FOREVER);
        osal_printk("task2 get mutex g_mux_id and suspend 100 ms.\n");
        osal_msleep(100);
        osal_printk("task2 resumed and post the g_mux_id\n");
        osal_mutex_unlock(&g_mux_id);
    }
    uint32_t example_task_entry_mutex(void)
    {
        uint32_t ret = 0;
        osal_task *example_task1_info, *example_task2_info;
        /* 创建互斥量 */
        osal_mutex_init(&g_mux_id);
        /* 创建任务期间锁住任务调度 */
        osal_kthread_lock();
        /* 创建任务1 */
        example_task1_info = osal_kthread_create((osal_kthread_handler)example_mutex_task1, NULL, "example_task1", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task1_info->task, TASK_PRI_TASK1);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 创建任务2 */
        example_task2_info = osal_kthread_create((osal_kthread_handler)example_mutex_task2, NULL, "example_task2", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task2_info->task, TASK_PRI_TASK2);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 任务创建完成解锁任务调度 */
        osal_kthread_unlock();
        /* 延时3s，回收资源 */
        osal_msleep(3000);
        osal_mutex_destroy(&g_mux_id);
        osal_kthread_destroy(example_task1_info->task, 0);
        osal_kthread_destroy(example_task2_info->task, 0);
        return OSAL_SUCCESS;
    }

结果验证：

    task2 try to get mutex, wait forever.
    task2 get mutx g_mux_id and suspend 100 ms.
    task1 try to get mutex, wait 10 ms.
    task1 timeout and try to get mutex, wait forever.
    task2 resumed and post the g_mux_id.
    task1 wait forever,get mutex g_mux_id.

## 信号量

### 概述

信号量（Semaphore）是一种实现任务间通信的机制，实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。

在多任务系统中，各任务之间需要同步或互斥实现临界资源的保护，信号量功能可以为用户提供这方面的支持。通常一个信号量的计数值用于对应有效的资源数，表示剩下的可被占用的互斥资源数。其值的含义分2种情况：

- 0：没有积累下来的Post操作，且有可能有在此信号量上阻塞的任务。

- 正值：有一个或多个Post下来的释放操作。

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

- 用作同步时，信号量在创建后被置为空，任务1取信号量而阻塞，任务2在某种条件发生后，释放信号量，于是任务1得以进入READY或RUNNING态，从而达到了两个任务间的同步。

- 用作互斥时，信号量创建后记数是满的，在需要使用临界资源时，先取信号量使其变空，这样其他任务需要使用临界资源时就会因为无法取到信号量而阻塞，从而保证了临界资源的安全。

信号量运作原理：

- 信号量初始化：为配置的N个信号量申请内存（N值可以由用户自行配置，受内存限制），并把所有的信号量初始化成未使用，并加入到未使用链表中供系统使用。

- 信号量创建：从未使用的信号量链表中获取一个信号量资源，并设定初值。

- 信号量申请：如果其计数器值＞0，则直接减1返回成功。否则任务阻塞，等待其它任务释放该信号量，等待的超时时间可设定。当任务被一个信号量阻塞时，将该任务挂到信号量等待任务队列的队尾。信号量释放，如果没有任务等待该信号量，则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。

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

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

### 开发流程

使用场景

信号量是一种非常灵活的同步方式，可以运用在多种场合中，实现锁、同步、资源计数等功能，也能方便用于任务与任务、中断与任务的同步中。

功能说明

系统中的信号量模块为用户提供的功能如表1所示。

表 1 信号量接口说明

接口描述 | 说明
--- | ---
osal_sem_init | 创建信号量。
osal_sem_binary_sem_init | 创建二进制信号量。
osal_sem_destroy | 销毁指定的信号量。
osal_sem_down_timeout | 阻塞获取指定的信号量，单位：ms。
osal_sem_up | 释放指定的信号量。

开发流程

信号量的开发典型流程：

1. 创建信号量osal_sem_init或者osal_sem_binary_sem_init。

2. 申请信号量osal_sem_down_timeout。

   信号量有3种申请模式：

   - 无阻塞模式：任务需要申请信号量，若当前信号量的任务数没有到信号量设定的上限，则申请成功。否则，立即返回申请失败。超时时间设置为0。

   - 永久阻塞模式：任务需要申请信号量，若当前信号量的任务数没有到信号量设定的上限，则申请成功。否则，该任务进入阻塞态，系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后，直到有其他任务释放该信号量，阻塞任务才会重新得以执行。超时时间设置为OSAL_SEM_WAIT_FOREVER。

   - 定时阻塞模式：任务需要申请信号量，若当前信号量的任务数没有到信号量设定的上限，则申请成功。否则，该任务进入阻塞态，系统切换到就绪任务中优先级高者继续执行。任务进入阻塞态后，指定时间超时前有其他任务 释放该信号量，或者用户指定时间超时后，阻塞任务才会重新得以执行。超时时间设置为合理的值。

3. 释放信号量osal_sem_up。

   - 如果有任务阻塞于指定信号量，则唤醒该信号量阻塞队列上的第一个任务。 该任务进入就绪态，并进行调度。

   - 如果没有任务阻塞于指定信号量，释放信号量成功。

4. 删除信号量osal_sem_destroy。

错误码

OSAL接口支持维测打印开关，关闭OSALLOG_DISABLE宏定义后，OSAL封装的内核接口运行过程中会打印异常信息值，会打印信号量操作失败的情况的错误码，以便快速定位错误原因。

信号量错误码说明如表2所示。

表 2 信号量错误码说明

序号 | 定义 | 实际数值 | 描述 | 参考解决方案
--- | --- | --- | --- | ---
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 | 中断期间非法调用osal_sem_down_timeout申请信号量。 | 中断期间禁止调用osal_sem_down_timeout。
7 | LOS_ERRNO_SEM_PEND_IN_LOCK | 0x02000706 | 任务被锁，无法获得信号量。 | 在任务被锁时，不能调用osal_sem_down_timeout申请信号量。
8 | LOS_ERRNO_SEM_TIMEOUT | 0x02000707 | 获取信号量超时。 | 将时间设置在合理范围内。
9 | LOS_ERRNO_SEM_OVERFLOW | 0x02000708 | 信号量计数值已达到最大值，无法再继续释放该信号量。 | 根据该错误码适当处理。
10 | LOS_ERRNO_SEM_PENDED | 0x02000709 | 等待信号量的任务队列不为空。 | 唤醒所有等待该信号量的任务后，再删除该信号量。

### 注意事项

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

### 编程实例

本实例实现两个任务同步获取信号量的功能，步骤如下：

1. 测试任务example_task_entry_sem创建一个信号量，锁任务调度，创建两个任务example_sem_task1、example_sem_task2，example_sem_task2优先级高于example_sem_task1，两个任务中申请同一信号量，解锁任务调度后两任务阻塞， 测试任务example_task_entry_sem释放信号量。

2. example_sem_task2得到信号量，被调度，然后任务休眠200ms， example_sem_task2延迟，example_sem_task1被唤醒。

3. example_sem_task1定时阻塞模式申请信号量，等待时间为100ms，因信号量仍被example_semtask2持有，example_sem_task1挂起，100ms后仍未得到信号量，example_sem_task1被唤醒，试图以永久阻塞模式申请信号量，example_sem_task1挂起。

4. 200ms后example_sem_task2唤醒，释放信号量后，example_sem_task1得到信号量被调度运行，后释放信号量。

5. example_sem_task1执行完，400ms后任务example_task_entry_sem被唤醒，执行删除信号量，删除两个任务。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    #define TASK_PRIO_TEST 21
    static osal_semaphore g_sem_id;
    void example_sem_task1(void* param)
    {
        unused(param);
        uint32_t ret;
        osal_printk("example_sem_task1 try get sem g_sem_id ,timeout 100 ms.\n");
        /* 定时阻塞模式申请信号量，定时时间为100ms */
        ret = osal_sem_down_timeout(&g_sem_id, 100);
        /* 申请到信号量 */
        if(ret == OSAL_SUCCESS) {
            osal_sem_up(&g_sem_id);
        } else {
            /* 定时时间到，未申请到信号量 */
            osal_printk("example_sem_task1 timeout and try get sem g_sem_id wait forever.\n");
            /* 永久阻塞模式申请信号量 */
            ret = osal_sem_down_timeout(&g_sem_id, OSAL_WAIT_FOREVER);
            osal_printk("example_sem_task1 wait_forever and get sem g_sem_id .\n");
            if (ret == OSAL_SUCCESS) {
                osal_sem_up(&g_sem_id);
            }
        }
    }
    void example_sem_task2(void* param)
    {
        unused(param);
        uint32_t ret;
        osal_printk("example_sem_task2 try get sem g_sem_id wait forever.\n");
        /* 永久阻塞模式申请信号量 */
        ret = osal_sem_down_timeout(&g_sem_id, OSAL_WAIT_FOREVER);
        if (ret == OSAL_SUCCESS) {
            osal_printk("example_sem_task2 get sem g_sem_id and then delay 200ms .\n");
        }
        /* 任务休眠200ms */
        osal_msleep(200);
        osal_printk("example_sem_task2 post sem g_sem_id .\n");
        /* 释放信号量 */
        osal_sem_up(&g_sem_id);
    }
    uint32_t example_task_entry_sem(void)
    {
        uint32_t ret;
        osal_task *example_task1_info, *example_task2_info;
        /* 创建信号量 */
        osal_sem_init(&g_sem_id, 0);
        /* 创建任务期间锁住任务调度 */
        osal_kthread_lock();
        /* 创建任务1 */
        example_task1_info = osal_kthread_create((osal_kthread_handler)example_sem_task1, NULL, "example_task1", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task1_info->task, TASK_PRI_TASK1);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 创建任务2 */
        example_task2_info = osal_kthread_create((osal_kthread_handler)example_sem_task2, NULL, "example_task2", TASK_STACK_SIZE);
        ret = osal_kthread_set_priority(example_task2_info->task, TASK_PRI_TASK2);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task create failed!\n");
        }
        /* 任务创建完成解锁任务调度 */
        osal_kthread_unlock();
        osal_sem_up(&g_sem_id);
        /*任务休眠400ms*/
        osal_msleep(400);
        /*删除信号量*/
        osal_sem_destroy(&g_sem_id);
        /*删除任务1*/
        osal_kthread_destroy(example_task1_info->task, 0);
        /*删除任务2*/
        osal_kthread_destroy(example_task2_info->task, 0);
        return OSAL_SUCCESS;
    }

结果验证：

编译运行得到的结果为：

    example_sem_task2 try get sem g_sem_id wait forever.
    example_sem_task1 try get sem g_sem_id ,timeout 100 ms.
    example_sem_task2 get sem g_sem_id and then delay 200ms.
    example_sem_task1 timeout and tty get sem g_sem_id wait forever.
    example_sem_task2 post sem g_sem_id.
    example_sem_task1 wait_forever and get sem g_sem_id.

## 时间管理

### 概述

时间管理以系统时钟为基础，提供给应用程序所有和时间有关的服务，系统中的时间管理模块提供时间转换、统计、延迟功能以满足用户对时间相关需求的实现。

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

- Tick：操作系统的基本时间单位，对应的时长由系统主频及每秒Tick数决定，默认每秒1000个ticks，即每个tick时长为1ms。

时间管理接口主要提供的功能如表1所示。

表 1 时间管理接口说明

接口名称 | 说明
--- | ---
osal_udelay | CPU空等时间（单位：μs）。
osal_mdelay | CPU空等时间（单位：ms）。
osal_get_jiffies | 获取当前的Tick数。
osal_jiffies_to_msecs | Tick转换为毫秒。

### 开发流程

使用场景

用户需要了解当前系统运行的时间以及Tick与毫秒之间的转换关系，以及需要liteos提供μs级或者ms级的死延时。

开发流程

时间管理的常用延时函数：

1. 获取当前时间戳。

2. 调用延时接口osal_mdelay。

3. 获取延时后的时间戳。

错误码

无

### 注意事项

- 系统的Tick数在关中断的情况下不进行计数，故系统Tick数不能作为准确时间计算，延时精度要求高的场景，可使用来源于32M时钟的TCXO计数接口，使用请参见《BS25 设备驱动开发指导书》中“TCXO”章节。

- osal_get_jiffies接口获取的tick每次重新启动后都会重置，在内核启动后开始计时，使用时请注意。

- 使用osal_xdelay时，底层实现为死循环延时，在开任务调度器的情况下，高优先级依旧可以打断延时线程，低于延时任务优先级的则在延时期间得不到调度。

### 编程实例

统计osal_mdelay前后的count变化代码示例如下：

    #include "common_def.h"
    #include "soc_osal.h"
    void example_delay_init(void)
    {
        uint64_t timer1 = osal_get_jiffies();
        osal_mdelay(100);
        uint64_t timer2 = osal_get_jiffies();
        osal_printk("Example_task time delay count = %lld!\n", (uint64_t)(timer2 - timer1));
    }

## 软件定时器

### 概述

硬件定时器受硬件的限制，数量上不足以满足用户的实际需求，因此为了满足用户需求，提供更多的定时器，系统提供软件定时器功能。软件定时器是基于系统Tick时钟中断且由软件来模拟的定时器，当经过设定的Tick时钟计数值后会触发用户定义的回调函数。定时精度与系统Tick时钟的周期有关。软件定时器扩展了定时器的数量，允许创建更多的定时业务。软件定时器功能支持：

- 软件定时器创建。

- 软件定时器启动。

- 软件定时器停止。

- 软件定时器删除。

运作机制：

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

- 当Tick中断到来时，在Tick中断处理函数中扫描软件定时器的计时任务，查看是否有定时器超时，如果有，则将超时的定时器记录到内核数据结构。

- Tick中断处理函数结束后，软件定时器任务（优先级为最高）被唤醒，在该任务中调用超时定时器的回调函数。

软件定时器提供2类定时器机制：

- 单次触发定时器：在启动后只会触发一次定时器事件。

- 周期触发定时器：会周期性地触发定时器事件，直到用户手动地停止定时器，否则将永远持续执行。

### 开发流程

使用场景

- 创建一个单次触发的定时器，超时后执行回调函数。

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

功能说明

系统中的软件定时器模块为用户提供的功能如表1所示。

表 1 软件定时器接口说明

接口名称 | 说明
--- | ---
osal_timer_init | 创建定时器。
osal_timer_destroy | 删除定时器。
osal_timer_start | 启动定时器。定时器超时时间（单位：ms）
osal_timer_stop | 停止定时器。
osal_timer_mod | 修改定时器，执行后定时器会重启。

开发流程

ms级软件定时器的典型开发流程：

1. 创建定时器osal_timer_init。返回函数运行结果，成功或失败。

2. 启动定时器osal_timer_start。

3. 停止定时器osal_timer_stop。

4. 删除定时器osal_timer_destroy。

错误码

对软件定时器存在失败可能性的操作包括：创建、删除、暂停、重启定时器等，均需要返回对应的错误码，以便快速定位错误原因。

软件定时器错误码如表2所示。

表 2 软件定时器错误码说明

序号 | 定义 | 实际值 | 描述 | 参考解决方案
--- | --- | --- | --- | ---
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_HWI_ACTIVE | 0x02000309 | 在中断中使用定时器。 | 修改源代码确保不在中断中使用。
10 | LOS_ERRNO_SWTMR_QUEUE_CREATE_FAILED | 0x0200030b | 在软件定时器初始化时，创建定时器队列失败。 | 调整OS_SYS_MEM_SIZE，以确保有足够的内存供软件定时器创建队列。
11 | LOS_ERRNO_SWTMR_TASK_CREATE_FAILED | 0x0200030c | 在软件定时器初始化时，创建定时器任务失败。 | 调整OS_SYS_MEM_SIZE，以确保有足够的内存供软件定时器创建任务。
12 | LOS_ERRNO_SWTMR_NOT_STARTED | 0x0200030d | 未启动软件定时器。 | 启动软件定时器。
13 | LOS_ERRNO_SWTMR_STATUS_INVALID | 0x0200030e | 不正确的软件定时器状态。 | 检查确认软件定时器状态。
14 | LOS_ERRNO_SWTMR_TICK_PTR_NULL | 0x02000310 | 用以获取软件定时器剩余Tick数的入参指针为NULL。 | 定义有效变量以传入有效指针。
15 | LOS_ERRNO_SWTMR_SORTLINK_CREATE_FAILED | 0x02000311 | 在软件定时器初始化时，创建定时器链表失败。 | 调整OS_SYS_MEM_SIZE，以确保有足够的内存供软件定时器创建链表。
16 | LOS_ERRNO_SWTMR_INVALID_SYNCDEL | 0x02000312 | 在中断或软件定时器回调中尝试同步删除定时器，删除失败。 | 调整代码逻辑，避免在中断或软件定时器回调中同步删除软件定时器。

### 注意事项

- 软件定时器的回调函数中请勿做过多操作，请勿使用可能引起任务挂起或阻塞的接口或操作。

- 软件定时器使用了系统的一个队列和一个任务资源，软件定时器任务的优先级设定为0，且不允许修改 。

- 系统可配置的软件定时器资源个数是指整个系统可使用的软件定时器资源总个数，而并非是用户可使用的软件定时器资源个数。例如：系统软件定时器多占用一个软件定时器资源数，那么用户能使用的软件定时器资源就会减少一个。

- 定时器创建之后，不会被系统自动删除，用户需要调用定时器删除接口删除定时器，回收定时器资源，避免资源泄露。

### 编程实例

在下面的例子中，演示如下功能：

- 软件定时器创建、启动、删除、暂停操作。

- 单次软件定时器、周期软件定时器使用方法。

代码示例：

    #include "common_def.h"
    #include "soc_osal.h"
    static uint32_t g_timercount1 = 0;
    static uint32_t g_timercount2 = 0;
    osal_timer timer_id1, timer_id2;
    void test_timer1_callback(unsigned long arg)  // 回调函数1
    {
        unused(arg);
        g_timercount1++;
        osal_printk("g_timercount1=%d\n",g_timercount1);
        /* 开启下一次软件timer定时 */
        osal_timer_start(&timer_id1);
    }
    void test_timer2_callback(unsigned long arg)  // 回调函数2
    {
        unused(arg);
        g_timercount2 ++;
        osal_printk("g_timercount2=%d\n",g_timercount2);
        /* 开启下一次软件timer定时 */
        osal_timer_start(&timer_id2);
    }
    void example_task_entry_timer(void)
    {
        uint32_t ret;
        /* 创建单次软件定时器，时间为1000ms，启动到1000ms数时执行回调函数1 */
        timer_id1.timer = NULL;
        timer_id1.data = NULL;
        timer_id1.handler = test_timer1_callback; /* 回调函数 */
        timer_id1.interval = 1000; /* 1000ms */
        ret = osal_timer_init(&timer_id1);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task timer1 create failed!\n");
        }
        osal_printk("create Timer1 success\n");
        /* 启动一次软件timer定时 */
        osal_timer_start(&timer_id1);
        osal_printk("start Timer1 success\n");
        osal_msleep(1200);//延时1200ms数
        osal_timer_stop(&timer_id1);
        osal_timer_destroy(&timer_id1);//删除软件定时器
        osal_printk("delete Timer1 success\n");
        /*创建单次软件定时器，每100ms数执行回调函数2 */
        timer_id2.timer = NULL;
        timer_id2.data = NULL;
        timer_id2.handler = test_timer2_callback; /* 回调函数 */
        timer_id2.interval = 100; /* 100ms */
        ret = osal_timer_init(&timer_id2);
        if (ret != OSAL_SUCCESS) {
            osal_printk("Example_task timer2 create failed!\n");
        }
        osal_printk("create Timer2 success\n");
        osal_timer_start(&timer_id2);//启动周期性软件定时器
        osal_printk("start Timer2\n");
        osal_msleep(1000);
        osal_timer_stop(&timer_id2);
        osal_timer_destroy(&timer_id2);
        osal_printk("delete Timer2 success\n");
    }

结果验证：

    create Timer1 success
    start Timer1 success
    g_timercount1=1
    delete Timer1 success
    start Timer2
    g_timercount2=1
    g_timercount2=2
    g_timercount2=3
    g_timercount2=4
    g_timercount2=5
    g_timercount2=6
    g_timercount2=7
    g_timercount2=8
    g_timercount2=9
    g_timercount2=10
    stop Timer2 success
    delete Timer2 success