前言

概述

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

读者对象

本文档主要适用于LiteOS的开发者。

本文档主要适用于以下对象:

  • 物联网端侧软件开发工程师

  • 物联网架构设计师

符号约定

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

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修订记录

文档版本

发布日期

修改说明

01

2026-03-17

第一次正式版本发布

概述

背景介绍

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

图 1 LiteOS的基本框架图

LiteOS的基础内核包括不可裁剪的极小内核和可裁剪的其他模块。极小内核只包含任务管理和调度相关的基本功能。可裁剪的模块包括信号量、互斥锁、队列管理、事件管理、软件定时器、内存管理、中断管理、异常管理和系统时钟Tick。

LiteOS支持在单核环境上运行。

LiteOS的优势

  • 高实时性,高稳定性。

  • 低功耗。支持动态加载、分散加载。

  • 支持功能静态裁剪。

  • 快速启动。

内核各模块简介

  • 任务管理

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

  • 内存管理

    • 提供静态内存和动态内存两种算法,支持内存的申请与释放。目前支持的内存管理算法包括固定大小的BOX算法、动态申请的bestfit算法和bestfit_little算法。

    • 提供内存统计、内存越界检测功能。

  • 硬件相关

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

    • 中断管理:提供中断的创建、删除、使能、禁止及请求位清除功能。

    • 异常管理:系统运行过程中发生异常后,跳转至异常处理模块,打印当前异常的函数调用栈信息或保存当前系统状态。

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

  • IPC通信

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

    • 消息队列:支持消息队列的创建、删除、发送和接收功能。

    • 事件:支持读事件和写事件功能。

    • 信号量:支持信号量的创建、删除、申请和释放功能。

    • 互斥锁:支持互斥锁的创建、删除、申请和释放功能。

  • 软件定时器

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

  • 低功耗

    Tickless:Tickless机制是通过计算下一次有意义的时钟中断的时间,来减少不必要的时钟中断,从而降低系统功耗。打开Tickless功能后,系统会在CPU空闲时启动Tickless机制。

  • 扩展内核/维测

    • CPU占用率:可以获取系统或者指定任务的CPU占用率。

    • Trace事件监测:实时获取事件发生的上下文,并写入缓冲区。支持自定义缓冲区,监测指定模块的事件,开启/停止Trace,清除/输出trace缓冲区数据等。

    • Perf:一款性能分析工具,可以通过Perf查找性能瓶颈、定位热点代码。LiteOS支持配置采样数据缓冲区、配置采样事件、启动/停止Perf、输出采样数据等功能。

    • Shell:LiteOS Shell使用串口或Telnet工具接收用户输入的命令,通过命令的方式调用、执行相应的应用程序。LiteOS Shell支持常用的基本调试功能,包含系统、文件、网络和动态加载等相关命令,同时支持用户添加自定义命令。

  • C++支持

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

支持的核

支持的核

芯片

LinxCore131、LinxCore132、LinxCore170

Hi3863、Hi3853

使用约束

  • LiteOS提供一套自有OS接口,同时也支持POSIX和CMSIS接口。请勿混用这些接口。混用接口可能导致不可预知的错误,例如:用POSIX接口申请信号量,但用LiteOS接口释放信号量。

  • 开发驱动程序只能用LiteOS的接口,上层APP建议用POSIX接口。

基础内核

任务

概述

基本概念

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

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

  • 支持多任务。

  • 一个任务表示一个线程。

  • 抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。

  • 相同优先级任务支持时间片轮转调度方式。

  • 共有32个优先级[0, 31],最高优先级为0,最低优先级为31。

  • 任务存在两种属性(detached、joinable)。默认情况下任务创建的属性为detached。

任务相关概念

  • 任务状态

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

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

    • 就绪(Ready):该任务在就绪队列中,只等待CPU。

    • 运行(Running):该任务正在执行。

    • 阻塞(Blocked):该任务不在就绪队列中,包含任务被挂起(suspend状态)、任务被延时(delay状态)、任务正在等待信号量、读写队列或者等待事件等。

    • 退出态(Dead):该任务运行结束,等待系统回收资源,包含任务处于僵尸状态(zombie状态)、任务已经被回收(unused状态)。

  • 任务状态迁移

    图 1 任务状态示意图

    任务状态迁移说明:

    • 就绪态→运行态

      任务创建后进入就绪态,发生任务切换时,就绪队列中最高优先级的任务被执行,从而进入运行态,同时该任务从就绪队列中移出。

    • 运行态→阻塞态

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

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

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

    • 就绪态→阻塞态

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

    • 运行态→就绪态

      有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪队列中最高优先级任务会被取出并切换到运行态,那么原先运行的任务由运行态变为就绪态,并插入到就绪队列中。

    • 运行态→退出态

      运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出状态以及僵尸状态。例如,joinable属性的任务运行结束但是没有被回收资源,对外呈现的就是僵尸状态,即退出态。

    • 阻塞态→退出态

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

  • 任务ID

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

  • 任务优先级

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

  • 任务入口函数

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

  • 任务栈

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

  • 任务上下文

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

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

  • 任务控制块TCB

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

  • 任务切换

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

  • 任务属性

    任务存在两种属性(detached、joinable),任务的不同属性可以决定任务的终止方式。joinable属性即为非分离状态,这种状态的任务退出后,不会自动释放资源,需要被其他任务通过调用LOS_TaskJoin接口等待获取其返回值,才能被释放资源。detached属性即为分离状态,这种状态的任务退出后将自动回收资源,并且这个任务不能被等待,等待是没有意义的。

调度器相关概念

LiteOS包含两种调度模式,即全局队列调度器模式和多队列调度器模式,在不同场景下各有优势,当前系统默认使用全局调度器模式。

  • 全局队列调度器模式:系统仅使用一个优先级队列保存准备运行的任务,所有核使用同一优先级队列调度,所有任务抢占同一个优先级队列,因此全局队列调度器模式拥有更高的实时性。

  • 多队列调度器模式:仅多核使用,系统按照核数为每个核创建一个优先级队列,每个核仅使用自己的优先级队列进行调度,系统通过负载均衡算法将任务在不同的优先级队列上进行迁移,相比全局队列调度模式可以有效减少频繁的核间迁移。

运作机制

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

开发指导

使用场景

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

功能

LiteOS的任务管理模块提供下面几种功能,接口详细信息请参见API参考。

功能分类

接口名

描述

创建和删除任务

LOS_TaskCreateOnly

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

LOS_TaskCreate

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

LOS_TaskCreateOnlyStatic

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

LOS_TaskCreateStatic

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

LOS_TaskDelete

删除指定的任务。

LOS_TaskJoin

以阻塞的方式等待指定的任务并回收任务资源。

控制任务状态

LOS_TaskResume

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

LOS_TaskSuspend

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

LOS_TaskDelay

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

LOS_TaskYield

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

LOS_TaskDetach

将任务属性设置为detached。

控制任务调度

LOS_TaskLock

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

LOS_TaskUnlock

解锁任务调度。

LOS_TaskUnlockNoSched

解锁任务调度,并继续执行本任务。

控制任务优先级

LOS_CurTaskPriSet

设置当前任务的优先级。

LOS_TaskPriSet

设置指定任务的优先级。

LOS_TaskPriGet

获取指定任务的优先级。

回收任务栈资源

LOS_TaskResRecycle

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

获取任务信息

LOS_CurTaskIDGet

获取当前任务的ID。

LOS_TaskInfoGet

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

LOS_TaskIsScheduled

查询当前核是否已经开始任务调度。

任务信息维测

LOS_TaskSwitchHookReg

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

任务空闲处理回调

LOS_IdleHandlerHookReg

注册空闲任务钩子函数,当系统空闲时调用。

申请任务的安全栈

LOS_TaskAllocSecureContext

申请任务的安全栈。只有开启LOSCFG_TRUSTZONE宏开关后,该函数才可使用。

须知: 各个任务的任务栈大小可以在创建任务时进行针对性的设置,若设置为0,则使用默认任务栈大小(LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE)作为任务栈大小。

配置项

  • 共享栈特性

    • 共享栈特性说明:在原生系统中,每个任务都拥有自己的独立栈区,引入共享栈后,将会有部分任务共用同一片任务栈,称为共享栈,任务发生调度时,老任务的栈数据从共享栈拷出到堆,新任务的栈数据从堆拷回到共享栈。

    • 共享栈使用说明:

      LOSCFG_SHARED_STACK:共享栈总开关,开启后才能使用共享栈特性。

      LOSCFG_SHARED_STACK_SIZE:配置共享栈的大小(Byte),宏默认值为16384,使用时可以根据实际情况调整,调整范围为:3072到65536,且需要能够整除16,建议配置为“参与共享的任务中任务栈的最大值”。

      目前共享栈特性使用白名单策略,在名单内的任务为共享任务。配置方式如下(以hiwing为例):

      在文件“Huawei_LiteOS/self_src/targets/hiwing/board.c”的g_taskBlackList数组中进行配置,一共3个字段,分别表示“任务名称”、“是否参与共享”、“是否在栈数据拷贝时进行压缩”。

      示例如下:

      /*
      在此处设置共享栈白名单,名单内的任务参与共享
      只有参与共享的任务才能参与压缩
      */
      const TaskBlackList g_sharedTask[LOSCFG_BASE_CORE_TSK_LIMIT + 1] = {
          //              taskName               加入共享?                            加入压缩?
          {"app_Task",                            TRUE,                               FALSE},
          {"SerialShellTask",                     TRUE,                               FALSE},
          {"SerialEntryTask",                     TRUE,                               FALSE},
          {"TskInSharedBlackLits",                TRUE,                               FALSE},
      };
      

    须知:

    • 如果任务创建时,指定任务使用创建时传入的栈,则该任务无法参与共享。

    • 共享栈开启后,还可以开启LOSCFG_SHARED_TASK_STAT宏增加任务信息显示功能,但会增加内存开销约0.6KB。

    功能分类

    接口名

    描述

    显示任务的调度性能信息

    SharedTaskStatPrint

    打印任务性能信息:

    第一组为总体性能数据,包括任务调度最大耗时、平均耗时以及调度耗时超过8000 cycleTime的次数。

    第二组为单任务性能数据,包括任务名称、ID、调度次数、参与压缩拷贝次数(未参与或未开启压缩则为0)、压缩成功次数、压缩成功率、拷出到堆的最大耗时、拷入共享栈的最大耗时,以及拷贝耗时超过8000 cycleTime的次数。

  • 加权公平调度特性

    • 加权公平调度(后文简称WCFS)特性说明:

      无WCFS的系统下,系统中存在一组优先级队列PriQueue,任务根据自己的优先级插入到不同的队列中,任务调度时,拿取优先级最高的队列的队首。

      引入WCFS之后,任务将根据自己的优先级以及运行时间,加权计算得到一个虚拟运行时间,根据虚拟运行时间进行优先级排序,达到相对公平的状态。任务调度时,先从CFS队列中获取一个优先级最高的CFS任务CFSTask,然后获取优先级最高的RT任务(非CFS任务)RTTask,比较CFSTask与RTTask的优先级,选择其中优先级更高的执行,如果CFSTask与RTTask的优先级相同,则执行RTTask。RTTask任务和CFS任务都执行完时,CPU才会去执行Idle任务。

    • 使用说明:

      打开宏LOSCFG_WCFS_SCHEDULER即可使用CFS特性。目前加权公平调度特性使用黑名单策略,在名单内的任务不参与加权公平调度。配置方式如下(以hiwing为例):

      在文件“LiteOS/Huawei_LiteOS/self_src/targets/hiwing/board.c”中的g_rtTaskName数组中进行配置,将不参与加权公平调度的线程名加入数组即可。

      示例如下:

      CHAR *g_rtTaskName[CFS_TASK_LIST_NUM] = {
          "app_Task",
          "SerialShellTask",
          "SerialEntryTask",
      };
      
    • 其他:

      目前LiteOS还提供UINT32 LOS_RemoveTaskFromCFS(UINT32 taskid);接口,该接口传入线程id,对应id的线程将被加入WCFS黑名单,成功则返回OK。

      说明: 注意:如果该线程已经不参与WCFS,或者该线程为IdleTask,也返回OK。

TASK状态

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

定义

实际数值

描述

LOS_TASK_STATUS_JOINABLE

0x0080

任务不会自动释放资源,需要被其他任务等待获取其返回值,才能被释放资源

LOS_TASK_STATUS_DETACHED

0x0100

任务是自删除的

用户在调用创建任务接口时,可以将创建任务的TSK_INIT_PARAM_S参数的uwResved域设置为LOS_TASK_STATUS_DETACHED(detached属性)或者LOS_TASK_STATUS_JOINABLE(joinable属性)。

须知: 任务joinable属性受LOSCFG_TASK_JOINABLE开关影响。

  • 打开菜单(如何打开请参见“打开menuconfig菜单”),LOSCFG_TASK_JOINABLE配置位于Kernel ---> Basic Config ---> Task --->Enable Join/Detach mechanism。

  • LOSCFG_TASK_JOINABLE打开,只有将任务状态设置为LOS_TASK_STATUS_JOINABLE才能设置为joinable属性的任务,joinable属性的任务不会自删除,需要用户通过调用LOS_TaskJoin函数去回收joinable属性任务的资源,或者通过调用LOS_TaskDetach接口设置任务为detached属性,detached属性的任务运行完成之后会自动回收资源。

  • LOSCFG_TASK_JOINABLE关闭,任务只有detached属性,不管TSK_INIT_PARAM_S参数的uwResved域是否设置为LOS_TASK_STATUS_JOINABLE。

  • LOS_TaskDelete接口无法回收joinable属性的任务资源,只能通过LOS_TaskJoin接口。

TASK优先级

LiteOS任务的优先级当前支持范围为(LOS_TASK_PRIORITY_HIGHEST ~ LOS_TASK_PRIORITY_LOWEST),数值越小,优先级越高。

定义

实际数值

描述

LOS_TASK_PRIORITY_HIGHEST

0

任务最高优先级。

LOS_TASK_PRIORITY_LOWEST

31

任务最低优先级。

TASK错误码

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

定义

实际数值

描述

参考解决方案

LOS_ERRNO_TSK_NO_MEMORY

0x03000200

内存空间不足。

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

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

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

LOS_ERRNO_TSK_PTR_NULL

0x02000201

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

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

LOS_ERRNO_TSK_STKSZ_NOT_ALIGN

0x02000202

创建一个由用户自行传入任务栈的任务时,任务栈起始地址或任务栈大小没有按照LOSCFG_STACK_POINT_ALIGN_SIZE对齐。

任务栈起始地址和任务栈大小按照LOSCFG_STACK_POINT_ALIGN_SIZE对齐。

LOS_ERRNO_TSK_PRIOR_ERROR

0x02000203

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

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

LOS_ERRNO_TSK_ENTRY_NULL

0x02000204

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

定义任务入口函数。

LOS_ERRNO_TSK_NAME_EMPTY

0x02000205

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

设置任务名。

LOS_ERRNO_TSK_STKSZ_TOO_SMALL

0x02000206

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

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

LOS_ERRNO_TSK_ID_INVALID

0x02000207

超出OS支持范围内的无效的任务ID。

检查任务ID。

LOS_ERRNO_TSK_ALREADY_SUSPENDED

0x02000208

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

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

LOS_ERRNO_TSK_NOT_SUSPENDED

0x02000209

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

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

LOS_ERRNO_TSK_NOT_CREATED

0x0200020A

未创建任务。

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

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

LOS_ERRNO_TSK_DELETE_LOCKED

0x0300020B

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

解锁任务之后再删除任务。

LOS_ERRNO_TSK_DELAY_IN_INT

0x0300020D

中断期间,进行任务延时。

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

LOS_ERRNO_TSK_DELAY_IN_LOCK

0x0200020E

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

解锁任务之后再延时任务。

LOS_ERRNO_TSK_YIELD_IN_LOCK

0x0200020F

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

任务解锁后再进行Yield操作。

LOS_ERRNO_TSK_YIELD_NOT_ENOUGH_TASK

0x02000210

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

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

LOS_ERRNO_TSK_TCB_UNAVAILABLE

0x02000211

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

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

LOS_ERRNO_TSK_OPERATE_SYSTEM_TASK

0x02000214

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

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

LOS_ERRNO_TSK_SUSPEND_LOCKED

0x03000215

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

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

LOS_ERRNO_TSK_STKSZ_TOO_LARGE

0x02000220

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

减小任务栈大小。

LOS_ERRNO_TSK_CPU_AFFINITY_MASK_ERR

0x03000223

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

检查传入的CPU掩码。

LOS_ERRNO_TSK_YIELD_IN_INT

0x02000224

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

不要在中断中进行Yield操作。

LOS_ERRNO_TSK_MP_SYNC_RESOURCE

0x02000225

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

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

LOS_ERRNO_TSK_MP_SYNC_FAILED

0x02000226

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

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

LOS_ERRNO_TSK_ALLOC_SECURE_INT

0x02000227

不允许在中断中申请任务安全栈。

不要在中断中申请任务安全栈。

LOS_ERRNO_TSK_SECURE_ALREADY_ALLOC

0x02000228

任务的安全栈已经申请过。

不要重复为同一个任务申请安全栈。

LOS_ERRNO_TSK_ALLOC_SECURE_FAILED

0x02000229

申请安全栈失败。

安全世界的内存不足,建议释放部分安全世界的内存。

LOS_ERRNO_TSK_FREE_SECURE_FAILED

0x0200022A

释放安全栈失败。

当前未使用。

LOS_ERRNO_TSK_NOT_ALLOW_IN_INT

0x0200022B

此接口不允许在中断上下文中。

不要在中断中调用对应接口。

LOS_ERRNO_TSK_SCHED_LOCKED

0x0200022C

此接口可能存在阻塞,不允许关闭调度。

检查调度锁逻辑。

LOS_ERRNO_TSK_ALREADY_JOIN

0x0200022D

joinable的任务不能被多个任务同时等待。

检查是否已经存在其他任务正在等待。

LOS_ERRNO_TSK_IS_DETACHED

0x0200022E

当前任务属性为detached。

检查是否join一个detached属性的任务或者重复设置任务为detached属性。

LOS_ERRNO_TSK_NOT_JOIN_SELF

0x0200022F

任务不能join自己。

检查taskid是否传入正确。

LOS_ERRNO_TSK_IS_ZOMBIE

0x02000230

不允许操作一个处于僵尸状态的任务。

检查是否对一个处于僵尸状态的任务进行删除、设置优先级/属性/CPU亲和性、suspend、resume,不要去操作一个僵尸状态的任务。

须知:

  • 错误码定义见“错误码简介”错误码简介。8~15位代表的所属模块为任务模块,值为0x02。

  • 任务模块中的错误码序号 0x16、0x1C,未被定义,不可用。

开发流程

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

  1. 打开菜单,进入Kernel ---> Basic Config ---> Task菜单,完成任务模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_CORE_TSK_LIMIT

    系统支持的最大任务数。

    (0,256]

    不同平台默认值不一样

    LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE

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

    (0,OS_SYS_MEM_SIZE)

    不同平台默认值不一样

    LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE

    默认任务栈大小。

    (0,OS_SYS_MEM_SIZE)

    不同平台默认值不一样

    LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE

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

    (0,OS_SYS_MEM_SIZE)

    不同平台默认值不一样

    LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO

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

    [0,31]

    10

    LOSCFG_BASE_CORE_TIMESLICE

    任务时间片调度开关。

    YES/NO

    YES

    LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT

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

    (0,65535]

    不同平台默认值不一样

    LOSCFG_OBSOLETE_API

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

    YES/NO

    不同平台默认值不一样

    LOSCFG_BASE_CORE_TSK_MONITOR

    任务栈溢出检查和监测开关。

    YES/NO

    YES

    LOSCFG_TASK_STACK_STATIC_ALLOCATION

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

    YES/NO

    NO

    LOSCFG_TASK_JOINABLE

    开启内核join/detach机制。

    YES/NO

    NO

    LOSCFG_TASK_STACK_DYNAMIC_ALLOCATION

    支持创建任务时,给任务栈动态分配内存。

    YES/NO

    YES

  2. 打开菜单,进入Kernel ---> Basic Config ---> Enable Scheduler菜单,完成任务调度相关配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_SCHED_SQ

    全局队列调度器模式。

    YES/NO

    YES

    LOSCFG_SCHED_MQ

    多队列调度器模式。

    YES/NO

    NO

    LOSCFG_KERNEL_SMP

    LOSCFG_SCHED_LOAD_BALANCE_SIMPLE

    Simple均衡算法。

    YES/NO

    NO

    LOSCFG_SCHED_MQ

    LOSCFG_SCHED_LOAD_BALANCE_CPUP

    基于CPUP均衡算法。

    YES/NO

    NO

    LOSCFG_SCHED_MQ

    LOSCFG_KERNEL_CPUP

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

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

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

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

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

  8. 恢复挂起的任务LOS_TaskResume。

平台差异性

无。

DFX

OsDbgTskInfoGet函数:调用OsDbgTskInfoGet函数,传参为taskId,将展示该任务的任务名、入口函数、taskId、优先级、当前状态、栈大小、使用峰值、sp指针位置、栈顶、CPUP状态(启用CPUP特性时打印)等。传参为0xFFFFFFFF,将展示所有任务的信息。

开启加权公平调度特性时,参与公平调度的任务优先级后,会带有[CFS]标志。

开启共享栈特性时,参与共享栈的任务的栈大小、栈顶、峰值相同。

注意事项

  • 任务创建:

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

    • 创建任务时,会按照16Byte大小或者sizeof(UINTPTR) * 2对齐的方式为任务栈分配内存。确定任务栈大小的原则是:够用就行,多了浪费,少了任务栈溢出。

  • 锁任务:

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

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

  • 任务优先级:

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

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

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

  • 禁止操作:

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

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

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

  • 任务资源:

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

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

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

编程实例

实例描述

本实例介绍基本的任务操作方法,包含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_BASE_CORE_TSK_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_BASE_CORE_TSK_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.

内存

概述

基本概念

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

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

LiteOS的内存管理分为静态内存管理和动态内存管理。

  • 动态内存管理:在动态内存池中分配用户指定大小的内存块,支持bestfit(也称为dlink)和bestfit_little两种内存管理算法。

    • 优点:按需分配。

    • 缺点:内存池中可能出现碎片。

  • 静态内存管理:在静态内存池中分配用户初始化时预设(固定)大小的内存块。

    • 优点:分配和释放效率高,静态内存池中无碎片。

    • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

动态内存运作机制——bestfit

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

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

bestfit内存管理结构如图1所示。

图 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域确保返回的指针符合对齐要求。

动态内存运作机制——bestfit_little

bestfit_little算法也是一种最佳适配算法,相比于bestfit算法,少掉了图1中的第二部分,因此体积更小,但是申请释放性能不如bestfit。一般建议在内存空间充裕的情况下,选择bestfit算法。

slab运作机制

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

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

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

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

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

图 4 内存数据结构概览

静态内存运作机制

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

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

图 5 静态内存示意图

动态内存

开发指导

使用场景

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

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

功能

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

功能分类

接口名

描述

初始化和删除内存池

LOS_MemInit

初始化一块指定的动态内存池,大小为size(开启LOSCFG_MEM_MUL_POOL_ALLOC时该接口需谨慎使用,初始化后需要主动申请内存避免内存池被错误释放)。

LOS_MemDeInit

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

LOS_MemPoolInit

初始化一块指定的动态内存池,大小为size,属性为attr(表示是否开启slab功能)。

申请、释放动态内存

LOS_MemAlloc

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

LOS_MemFree

释放已申请的内存。

LOS_MemRealloc

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

LOS_MemAllocAlign

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

多内存池申请、释放内存

LOS_MulPoolRegister

内存池地址获取和释放钩子注册,从钩子中获取内存池的地址和大小,仅LOSCFG_MEM_MUL_POOL_ALLOC时生效。(不支持重复注册)

LOS_MulPoolAlloc

从非系统池子中申请size长度的内存,内存不够时自动创建新内存池,仅LOSCFG_MEM_MUL_POOL_ALLOC时生效。

LOS_MulPoolRealloc

从非系统池子中重申请size长度的内存,内存不够时自动创建新内存池,仅LOSCFG_MEM_MUL_POOL_ALLOC时生效。

LOS_MulPoolFree

从非系统池子中释放已申请的内存,仅LOSCFG_MEM_MUL_POOL_ALLOC时生效。

LOS_MulPoolShrink

释放没有使用的内存池,仅LOSCFG_MEM_MUL_POOL_ALLOC时生效。

获取内存池信息

LOS_MemPoolSizeGet

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

LOS_MemTotalUsedGet

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

LOS_MemInfoGet

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

LOS_MemPoolList

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

获取内存块信息

LOS_MemFreeBlksGet

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

LOS_MemTotalPeakUsedGet

返回内存池的最大使用峰值(依赖LOSCFG_MEM_TASK_STAT宏)

LOS_MemTotalUsedCostGet

返回内存池中使用的内存实际占用大小(包括内存管理部分)

LOS_MemUsedBlksGet

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

LOS_MemTaskIdGet

获取申请了指定内存块的任务ID,仅LOSCFG_MEM_DEBUG或 LOSCFG_MEM_TASK_STAT时生效。

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大小重新分配内存块给指定模块,并将原内存块内容拷贝到新内存块,同时纳入模块统计。如果新内存块申请成功,则释放原内存块。

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

LOS_MemMusedGet

获取指定模块的内存使用量,单位:Byte。

配置slab size

LOS_SlabSizeCfg

用来配置slab class size,必须在LOS_MemInit之前调用。

获取任务使用内存

LOS_MemTaskHeapInfoGet

打印指定任务使用的内存信息。

dfx功能

LOS_SaveCallerRa

保存当前任务的调用者。

LOS_IsSetCallerRa

判断当前任务是否已经保存调用者。

LOS_ResetCallerRa

重置当前任务的调用者。

LOS_MemDfxOpsReg

注册一个DFX操作集,使系统能够记录内存操作信息。

LOS_MemAllocDfxHook

在内存分配操作后被调用,记录分配的相关信息。

LOS_MemFreeDfxHook

在内存释放操作后被调用,记录释放的相关信息。

LOS_MemPoolOpsReg

注册一个DFX操作集,使系统能够记录内存池操作信息。

LOS_MemPoolMemAllocHook

在内存池分配操作后被调用,记录分配的相关信息。

LOS_MemPoolMemFreeHook

在内存池释放操作后被调用,记录释放的相关信息。

须知:

  • 动态内存提供了内存调测功能,具体使用方法见“内存调测方法”。

  • 对于bestfit_little算法,只支持“多内存池机制”和“内存合法性检查”,不支持其他内存调测功能。

  • 上述接口中,通过宏开关控制的都是内存调测功能相关的接口。

  • 通过LOS_MemAllocAlign/LOS_MemMallocAlign申请的内存进行LOS_MemRealloc/LOS_MemMrealloc操作后,不能保障新的内存首地址保持对齐。

  • 对于bestfit_little算法,不支持对LOS_MemAllocAlign申请的内存进行LOS_MemRealloc操作,否则将返回失败。

  • 对于多内存池申请、释放功能仅支持在bestfit算法下使用。

说明: LiteOS提供了多模块内存统计功能,该功能基于普通内存接口的封装接口,增加模块ID作为入参。不同业务模块进行内存操作时,调用对应封装接口,可统计各模块的内存使用情况,并通过模块ID获取指定模块的内存使用情况。 应用场景:系统业务模块化清晰,用户需统计各模块的内存占用情况。 依赖:目前只有bestfit内存管理算法支持该功能,需要使能LOSCFG_KERNEL_MEM_BESTFIT。 使用方法:

  1. 该功能配置项为LOSCFG_MEM_MUL_MODULE,配置路径是Kernel ---> Memory Management ---> Dynamic Memory Management ---> Enable Memory module statistics;

  2. 每个业务模块配置唯一module ID,模块代码中在内存操作时调用对应接口,并传入相应模块ID;

  3. 通过LOS_MemMusedGet接口获取指定模块的内存使用情况,可用于模块内存占用优化分析。 注意事项:

  4. 模块ID由宏MEM_MODULE_MAX限定,当系统模块个数超过该值时,需通过配置LOSCFG_MEM_MODULE_MAX来修改MEM_MODULE_MAX的大小,配置路径:Kernel ---> Memory Management --->Enable Memory module statistics ---> Max Memory Module Number。

  5. 模块中所有内存操作都需调用LOS_MemM开头的接口,否则可能导致统计不准确。

开发流程

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

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

    配置项

    含义

    取值范围

    默认值

    依赖

    OS_SYS_MEM_ADDR

    系统动态内存起始地址

    [0,n)

    __heap_start

    OS_SYS_MEM_SIZE

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

    [0,n)

    从bss段末尾至系统DDR末尾

    • OS_SYS_MEM_ADDR:一般使用默认值即可。

    • OS_SYS_MEM_SIZE:一般使用默认值即可。

  2. 打开菜单,进入Kernel ---> Memory Management菜单,完成动态内存管理模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    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

    LOSCFG_MEM_MUL_MODULE

    多模块内存统计

    YES/NO

    NO

    LOSCFG_KERNEL_MEM_BESTFIT

    LOSCFG_MEM_MODULE_MAX

    多模块内存统计最大模块数量

    8-1024

    32

    LOSCFG_MEM_MUL_MODULE

    LOSCFG_MEM_MUL_POOL

    多内存池链接统计功能

    YES/NO

    NO

    LOSCFG_KERNEL_MEM_BESTFIT或LOSCFG_KERNEL_MEM_BESTFIT_LITTLE

    LOSCFG_MEM_MUL_POOL_ALLOC

    多内存池弹性扩展功能

    YES/NO

    NO

    LOSCFG_MEM_MUL_POOL和LOSCFG_KERNEL_MEM_BESTFIT且关闭LOSCFG_EXC_INTERACTION

  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会自动合并。

平台差异性

无。

注意事项

  • 内存分配:

    • 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign由于需要进行地址对齐,可能会额外消耗部分内存,因此存在一些遗失内存。当系统释放该对齐内存时,会同时回收因对齐导致的遗失内存。

    • 重新分配内存接口LOS_MemRealloc/LOS_MemMrealloc如果分配成功,系统会自己判定是否需要释放原来申请的内存,并返回重新分配的内存地址。如果重新分配失败,原来的内存保持不变,并返回NULL。禁止使用pPtr = LOS_MemRealloc(pool, pPtr, uwSize),即:不能使用原来的旧内存地址pPtr变量来接收返回值。

  • 内存释放:

    对同一块内存多次调用LOS_MemFree/LOS_MemMfree时,第一次会返回成功,但对同一块内存多次重复释放会导致非法指针操作,结果不可预知。

  • 其他限制:

    • 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外消耗内存,故实际用户可使用内存总量小于配置项OS_SYS_MEM_SIZE的大小。

    • 由于动态内存管理的内存节点控制块结构体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
内存释放成功!
多模块内存使用实例

实例描述

前提条件:在menuconfig菜单中完成多模块内存统计的配置。

本实例执行以下步骤:

  1. 从动态内存池中为模块0申请一个内存块。

  2. 获取模块0的内存使用量。

  3. 从动态内存池中为模块1申请一个内存块。

  4. 获取模块1的内存使用量。

  5. 释放模块0和模块1的内存。

编程示例

void test(void)
{
    void *ptr = NULL;
    void *ptrTmp = NULL;
    UINT32 size = 0x10;
    UINT32 module = 0;
    UINT32 memUsed = 0;

    ptr = LOS_MemMalloc(OS_SYS_MEM_ADDR, size, module);
    if (ptr == NULL) {
        PRINTK("module %d malloc failed\n", module);
    } else {
        PRINTK("module %d malloc successed\n", module);
    }

    memUsed = LOS_MemMusedGet(module);
    PRINTK("module %d mem used size %d\n", module, memUsed);

    module = 1;
    ptrTmp = LOS_MemMalloc(OS_SYS_MEM_ADDR, size, module);
    if (ptrTmp == NULL) {
        PRINTK("module %d malloc failed\n", module);
    } else {
        PRINTK("module %d malloc successed\n", module);
    }

    memUsed = LOS_MemMusedGet(module);
    PRINTK("module %d mem used size %d\n", module, memUsed);

    module = 0;
    (VOID)LOS_MemMfree(OS_SYS_MEM_ADDR, ptr, module);
    module = 1;
    (VOID)LOS_MemMfree(OS_SYS_MEM_ADDR, ptrTmp, module);
}

结果验证

编译运行得到的结果为:

module 0 malloc successed
module 0 mem used size 32
module 1 malloc successed
module 1 mem used size 32

静态内存

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

初始化静态内存池

LOS_MemboxInit

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

清除静态内存块内容

LOS_MemboxClr

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

申请、释放静态内存

LOS_MemboxAlloc

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

LOS_MemboxFree

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

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

LOS_MemboxStatisticsGet

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

LOS_ShowBox

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

开发流程

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

  1. 打开菜单,进入Kernel ---> Memory Management菜单,完成静态内存管理模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    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
内存释放成功!

中断

概述

基本概念

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

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

LiteOS的中断特性:

  • 中断嵌套,即高优先级的中断可抢占低优先级的中断,且可配置。

  • 使用独立中断栈,可配置。

  • 可配置支持的中断优先级个数。

  • 可配置支持的中断数。

  • 中断底半部,且可配置。

中断相关的硬件介绍

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

  • 设备

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

  • 中断控制器

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

  • CPU

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

中断相关概念

  • 中断号:

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

  • 中断请求:

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

  • 中断优先级:

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

  • 中断处理程序:

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

  • 中断嵌套:

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

  • 中断触发:

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

  • 中断触发类型:

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

  • 中断向量:

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

  • 中断向量表:

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

  • 中断共享:

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

  • 核间中断:

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

  • 中断底半部:

    区别于中断处理程序,中断底半部运行在任务上下文,用户可在中断处理程序中,将实时性要求不高,非紧急的业务添加到中断底半部执行。

运作机制

  • LiteOS的中断嵌套:

    GIC与NVIC的中断嵌套由硬件实现。

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

  • LiteOS的中断底半部:

    中断底半部依赖链表、任务与事件机制。

    在中断处理程序中,用户添加底半部处理函数到队列中,待中断退出后,在中断底半部任务中逐个执行用户添加的底半部处理函数。

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

创建和删除中断

LOS_HwiCreate

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

LOS_HwiDelete

删除中断,请注意不要在注册的中断 handler中调用此接口,共享模式下,可能会访问非法内存。

打开和关闭所有中断

LOS_IntUnLock

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

LOS_IntLock

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

LOS_IntRestore

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

使能和屏蔽指定中断

LOS_HwiDisable

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

LOS_HwiEnable

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

设置中断优先级

LOS_HwiSetPriority

设置中断优先级。

触发中断

LOS_HwiTrigger

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

清除中断寄存器状态

LOS_HwiClear

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

设置中断前处理钩子函数

LOS_HwiPreHookReg

设置中断前处理钩子函数。

设置中断后处理钩子函数

LOS_HwiPostHookReg

设置中断后处理钩子函数。

获取中断响应次数

LOS_HwiRespCntGet

获取中断的响应次数。

添加中断底半部函数

LOS_HwiBhworkAdd

添加中断底半部函数。

HWI错误码

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

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_HWI_NUM_INVALID

(OS_ERRNO_HWI_NUM_INVALID)

0x02000900

创建或删除中断时,传入了无效中断号。老版本的错误码OS_ERRNO_HWI_NUM_INVALID后续不再支持。

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

2

LOS_ERRNO_HWI_PROC_FUNC_NULL

(OS_ERRNO_HWI_PROC_FUNC_NULL)

0x02000901

创建中断时,传入的中断处理程序指针为空;如果调用其他接口返回此错误码则表示该接口功能不支持。老版本的错误码OS_ERRNO_HWI_PROC_FUNC_NULL后续不再支持。

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

3

LOS_ERRNO_HWI_NO_MEMORY

(OS_ERRNO_HWI_NO_MEMORY)

0x02000903

创建中断时,出现内存不足的情况。老版本的错误码OS_ERRNO_HWI_NO_MEMORY后续不再支持。

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

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

4

LOS_ERRNO_HWI_ALREADY_CREATED

(OS_ERRNO_HWI_ALREADY_CREATED)

0x02000904

创建中断时,发现要注册的中断号已经创建。老版本的错误码OS_ERRNO_HWI_ALREADY_CREATED后续不再支持。

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

5

LOS_ERRNO_HWI_PRIO_INVALID

(OS_ERRNO_HWI_PRIO_INVALID)

0x02000905

创建中断时,传入的中断优先级无效。老版本的错误码OS_ERRNO_HWI_PRIO_INVALID后续不再支持。

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

6

LOS_ERRNO_HWI_INTERR

(OS_ERRNO_HWI_INTERR)

0x02000908

在中断中调用request_irq接口或bus_request_intr接口。老版本的错误码OS_ERRNO_HWI_INTERR后续不再支持。注意开源版本中没有这两个接口。

查看request_irq、bus_request_intr接口的使用是否正确。

7

LOS_ERRNO_HWI_SHARED_ERROR

(OS_ERRNO_HWI_SHARED_ERROR)

0x02000909

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

删除中断时:设备号创建时指定为共享中断,删除时未设置设备ID,删除错误。老版本的错误码OS_ERRNO_HWI_SHARED_ERROR后续不再支持。

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

9

LOS_ERRNO_HWI_NO_CPUP_MEMORY

0x0200090C

使能CPUP开关LOSCFG_CPUP_CB_NUM_CONFIGURABLE后,创建中断时,如果CPUP的中断相关控制块数目不够,会返回此错误。

根据实际的中断个数,而非中断最大标号,确定配置宏LOSCFG_CPUP_IRQ_CB_NUM,最大不超过LOSCFG_PLATFORM_HWI_LIMIT。

10

LOS_ERRNO_HWI_NOT_INTERRUPT_CONTEXT

0x0200090D

调用LOS_HwiBhworkAdd时不在中断上下文。

在中断上下文中调用LOS_HwiBhworkAdd。

11

LOS_ERRNO_HWI_PTR_NULL

0x0200090E

函数入参指针为空。

入参传入有效的非空指针。

12

LOS_ERRNO_HWI_ARG_NOT_ENABLED

0x0200090F

没有开启LOSCFG_HWI_WITH_ARG时,LOS_HwiCreate最后一个参数传入了非空指针。

LOS_HwiCreate最后一参数传入NULL或者开启LOSCFG_HWI_WITH_ARG。

13

LOS_ERRNO_HWI_AFFI_INVALID

0x02000910

传入LOS_HwiSetAffinity时传入了不非法的cpuMask。

入参传入有效的cpuMask。

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

开发流程

  1. 打开菜单,进入Kernel ---> Interrupt Management菜单,完成中断模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_ARCH_INTERRUPT_PREEMPTION

    使能中断嵌套

    YES/NO

    NO

    GICv3、RISC-V

    LOSCFG_IRQ_USE_STANDALONE_STACK

    使用独立中断栈

    YES/NO

    YES

    依赖CPU架构,目前Cortex-A、Cortex-R、ARM64、LingLong支持可选,RISC-V默认使用独立中断栈

    LOSCFG_PLATFORM_HWI_LIMIT

    最大中断使用数

    根据芯片手册适配

    根据芯片手册适配

    LOSCFG_HWI_PRIO_LIMIT

    可设置的中断优先级个数

    根据芯片手册适配

    根据芯片手册适配

    LOSCFG_IRQ_STACK_SIZE

    中断栈大小

    根据板子自适配

    0x2000

    目前只有RISC-V/ARM64可配置

    LOSCFG_NMI_STACK_SIZE

    不可屏蔽中断栈大小

    根据板子自适配

    0x800

    目前只有RISC-V可配置

    LOSCFG_HWI_PRE_POST_PROCESS

    中断前后处理钩子函数

    YES/NO

    NO

    LOSCFG_HWI_BOTTOM_HALF

    使能中断底半部

    YES/NO

    NO

    LOSCFG_BASE_IPC_EVENT

    LOSCFG_HWI_BOTTOM_HALF_WORK_LIMIT

    中断底半部队列最多缓存的个数

    根据板子自适配

    16

    LOSCFG_HWI_BOTTOM_HALF

    LOSCFG_HWI_WITH_ARG

    使用带参数的中断创建

    YES/NO

    YES

    LOSCFG_KERNEL_MEM_ALLOC

    LOSCFG_ARCH_INTERRUPT_TAKEOVER

    开启中断接管

    YES/NO

    YES

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

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

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

  5. 在中断处理函数中,调用LOS_HwiBhworkAdd接口添加中断底半部处理函数(如果无底半部处理函数,则可以不执行这一步)。

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

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

注意事项

  • 中断处理:

    • 中断处理程序耗时不能过长,否则会影响CPU对中断的及时响应。

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

  • 中断共享:

    中断共享机制,支持不同的设备使用相同的中断号注册同一中断处理程序,但中断处理程序的入参pDevId(设备号)必须唯一,代表不同的设备。即同一中断号,同一dev只能挂载一次;但同一中断号,同一中断处理程序,dev不同则可以重复挂载。

  • 中断触发:

    RISC-V himideerV200不支持LOS_HwiTrigger接口,因为RISC-V himideerV200中断控制器pending寄存器不可写;用户需要模拟中断触发进行测试,请使用软中断或外设中断验证中断触发流程。

  • 中断寄存器状态清除:

    RISC-V himideerV200开启中断嵌套时,对于电平触发方式,需要在中断处理程序结束时调用LOS_HwiClear接口,主动清除中断寄存器状态。即对于RISC-V himideerV200,LOS_HwiClear接口只有在中断嵌套场景生效;非嵌套场景用户无需清除中断控制器状态,LOS_HwiClear接口无清除中断状态效果。

  • 中断底半部:

    中断底半部用户处理函数中禁止调用延时接口,避免调用阻塞类接口。中断底半部任务运行时机具有不确定性,只有当中断底半部任务优先级最高,才会运行。

  • 兼容接口:

    LiteOS有兼容Linux的中断接口,request_irq、disable_irq、enable_irq、free_irq,详见“中断”。开发者可根据自身需要,选择适合的接口。

  • 其他限制:

    • 根据具体硬件,配置支持的最大中断数及可设置的中断优先级个数。

    • 中断恢复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 UINT32 g_BhworkCnt = 0;

STATIC VOID HwiBhfunc(VOID *data)
{
    UINT32 *count = (UINT32 *data);
    (*count)++;
}

STATIC VOID HwiUsrIrq(VOID)
{
    UINT32 ret;
    ret = LOS_HwiBhworkAdd(HwiBhfunc, &g_BhworkCnt);
    if (ret != LOS_OK) {
        printf("\n hwi bhwork add failed, %x \n", ret);
    }
    printf("\n in the func HwiUsrIrq \n"); 
}

/* cpu0 trigger, cpu0 response */
UINT32 It_Hwi_001(VOID)
{
    UINT32 ret;
    HWI_HANDLE_T irqNum = 26;
    HWI_PRIOR_T  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;
}

异常接管

概述

基本概念

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

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

LiteOS的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等)以及CPU现场等信息。针对某些RISC-V架构的芯片,对内存size要求较高的场景,LiteOS提供了极小特性宏LOSCFG_EXC_SIMPLE_INFO(menuconfig菜单项为:Kernel ---> Exception Management ---> Enable Exception Simple Info),用于裁剪多余的异常提示字符串信息,但是仍然保留发生异常时的CPU执行环境的所有信息。

运作机制

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

以RISC-V 32位架构为例,每个栈帧中通常保存了返回地址(RA)、栈指针(SP)和帧指针(FP)的历史值,用于回溯函数调用关系。

堆栈分析:

  • RA寄存器(x1):返回地址寄存器,指向当前函数返回后要执行的下一条指令地址。

  • SP寄存器(x2):栈指针寄存器,指向当前栈帧的栈顶。

  • FP寄存器(x8):可用作帧指针寄存器(frame pointer),在编译时开启特定选项后,它指向当前函数的父函数栈帧的起始地址。RISC-V中x8也被称为s0或fp。

    默认情况下,GCC编译器可能会将x8作为通用寄存器使用(例如用于保存局部变量),此时无法直接通过FP回溯栈帧。为了支持调用栈解析,需要在编译参数中添加“-fno-omit-frame-pointer”,强制编译器将x8用作帧指针。这样,每个函数入口会保存上一帧的FP和RA,形成链式结构。

    当系统发生异常时,CPU会打印异常现场的寄存器值(包括ra、sp、fp 等)。通过当前栈帧中保存的fp可以找到父函数的栈帧,进而从父栈帧中取出其保存的ra(即父函数的返回地址)以及它的fp,如此迭代,即可追溯出完整的函数调用栈,帮助开发者快速定位异常原因。

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

    1. 假设函数调用链:main函数调用func1,func1又调用func2。此时正在执行func2,其栈帧如下(地址从高到低,每格4字节):

      图 1 堆栈分析原理示意图

    2. 关键点说明:

      sp(栈指针):始终指向当前栈帧的最低地址(即最后分配的空间)。在func2执行时,sp指向func2局部变量下方的空闲区域(或已占用的底部)。

      fp(帧指针):指向当前栈帧中保存上一帧fp的位置。例如,func2的fp指向的地址存放着func1的fp值,而 func1的fp又指向main的fp位置,从而形成链表。

      返回地址ra:每个函数在调用子函数前,会将当前的ra保存到自己的栈帧中(通常位于保存的fp上方4字节处,如图1中func2的ra在fp+4位置)。这样,当函数返回时,可以从栈帧中恢复ra。

    3. 异常回溯过程:

      当发生异常时,CPU会保存当前寄存器值(包括sp、fp、ra 等)。假设当前在func2中发生异常,我们可以通过以下步骤回溯调用栈:

      1. 从当前fp指向的地址读取内容,得到上一帧的fp(即func1的fp值)。

      2. 从当前fp + 4地址读取内容,得到当前函数func2的返回地址(即func1中调用func2的下一条指令地址)。

      3. 将上一步得到的fp作为新的当前fp,重复步骤3.a3.b,即可依次获得func1的返回地址(即main中调用func1的下一条指令)和main的fp,直到fp为0或无效值。

      这种链式结构让调试器和异常处理程序能够精确还原函数调用序列,快速定位问题发生的上下文。

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

暂停核

LOS_Panic

暂停当前核运行并打印信息。

栈回溯

LOS_BackTrace

打印当前任务的栈回溯信息。

LOS_TaskBackTrace

打印指定任务的栈回溯信息。

定位流程

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

  1. 打开编译后生成的镜像反汇编(asm)文件。

  2. 搜索PC指针(指向当前正在执行的指令)在asm中的位置,找到发生异常的函数。

  3. 根据LR值查找异常函数的父函数。

  4. 重复3,得到函数间的调用关系,找到异常原因。

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

配置项

打开菜单,进入Kernel ---> Exception Management菜单,完成异常接管模块的配置。

配置项

含义

取值范围

默认值

依赖

LOSCFG_EXC_SIMPLE_INFO

使能后将减少异常输出的信息

YES/NO

NO

LOSCFG_EXC_STACK_SIZE

异常栈大小

根据板子自适配

0x800

目前只有RISC-V可配置

LOSCFG_SVC_STACK_SIZE

系统调用(SVC)栈大小

根据板子自适配

0x2000

LOSCFG_ARCH_ARM_CORTEX_A 或 LOSCFG_ARCH_ARM_CORTEX_R 或 LOSCFG_ARCH_ARM_926

注意事项

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

用户可通过调用ArchSetExcHook和ArchSetNMIHook接口来设定自定义的异常处理函数。然而,在异常处理函数内部必须使用LOS_TaskLock锁定任务调度机制,以避免因触发调度而导致二次异常或其他潜在问题的发生。

编程实例

通过错误释放内存,触发系统异常。系统异常被挂起后,能在串口中看到异常调用栈打印信息和关键寄存器信息,如下所示,其中Type表示异常类型,此处值为0x2表示非法指令 ,其它数值可以查看芯片手册。通过这些信息可以定位到异常所在函数和其调用栈关系。

定位步骤如下:

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

  2. 搜索mepc指针2616ecfa在.asm文件中的位置(去掉0x)。

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

    从图中可以看到:

    1. 异常时CPU正在执行的指令unimp。

    2. 异常发生在“It_los_task_045.c”文件的“__asm volatile (".word 0x00000000")”语句。

    接下来,需要查找谁调用了这个语句。

  3. 根据ra返回寄存器值查找调用栈。

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

    可见,是FuncC调用了“__asm volatile (".word 0x00000000")”语句发生异常。依此方法,可得到异常时函数调用关系如下:FuncA(业务函数) ---> FuncB---> FuncC ---> __asm volatile (".word 0x00000000")。

    最终,可以通过该方法排查出整个异常调用栈的信息。

错误处理

概述

基本概念

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

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

运作机制

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

图 1 错误处理示意图

开发指导

错误码简介

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

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

例如将任务模块中的错误码LOS_ERRNO_TSK_NO_MEMORY定义为致命级别的错误,模块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:错误等级为致命,值为0x03000000。

  • LOS_ERRNO_OS_ID:错误码标志,值为0x000000。

  • MID:所属模块,LOS_MOD_TSK的值为0x2。

  • ERRNO:错误码序号。 所以LOS_ERRNO_TSK_NO_MEMORY的值为0x03000200。

错误码接管

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

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

队列

概述

基本概念

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

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

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

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:创建队列时所需的队列空间,由接口调用者自行申请后传入接口。

队列运作原理

表 1 队列运作原理描述表

功能

运作原理描述

创建队列

创建队列成功会返回队列ID。

在队列控制块中维护着一个消息头节点位置Head和一个消息尾节点位置Tail,用于表示当前队列中消息的存储情况。

  • Head表示队列中被占用的消息节点的起始位置。
  • Tail表示被占用的消息节点的结束位置,也是空闲消息节点的起始位置。

(注:队列刚创建时,Head和Tail均指向队列起始位置。)

写队列

根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)队列进行写操作。

写队列支持以下2种写入方式。

  • 向队列尾节点写入:尾节点写入时,根据Tail找到起始空闲消息节点作为数据写入对象,如果Tail已经指向队列尾部则采用回卷方式。
  • 向队列头节点写入:头节点写入时,将Head的前一个节点作为数据写入对象,如果Head指向队列起始位置则采用回卷方式。

读队列

根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)队列进行读操作会引起任务挂起。

  • 如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。
  • 如果Head已经指向队列尾部则采用回卷方式。

删除队列

根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态。如果是通过系统动态申请内存方式创建的队列,还会释放队列所占内存。

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

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

开发指导

使用场景

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

功能

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操作的任务。

获取全量队列信息

LOS_QueueInfoAllGet

功能与LOS_QueueInfoGet相同,相比与LOS_QueueInfoGet接口,支持任务数大于64的场景。

队列错误码

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

序号

定义

实际数值

描述

参考解决方案

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_MAIL_HANDLE_INVALID

0x02000619

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

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

24

LOS_ERRNO_QUEUE_MAIL_PTR_INVALID

0x0200061A

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

传入非空的mail内存池指针。

25

LOS_ERRNO_QUEUE_MAIL_FREE_ERROR

0x0200061B

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

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

26

LOS_ERRNO_QUEUE_ISEMPTY

0x0200061D

队列已空。

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

27

LOS_ERRNO_QUEUE_READ_SIZE_TOO_SMALL

0x0200061F

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

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

28

LOS_ERRNO_QUEUE_INFO_SIZE_TOO_SMALL

0x02000620

读取队列信息时传入的缓冲区大小小于要存储的信息大小。

增加缓冲区大小,或减少要获取的任务数量。

须知:

  • 错误码定义见“错误码简介”。8~15位代表的所属模块为队列模块,值为0x06。

  • 队列模块中的错误码序号0x11、0x14未被定义,不可用。

开发流程

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

  1. 打开菜单,进入Kernel ---> Enable Queue菜单,完成队列模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    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

    LOSCFG_QUEUE_DYNAMIC_ALLOCATION

    支持创建队列时,动态分配内存

    YES/NO

    YES

    LOSCFG_KERNEL_MEM_ALLOC

    LOSCFG_QUEUE_PEAK_STAT

    支持队列峰值统计

    YES/NO

    NO

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

  3. 写队列。

  4. 读队列。

  5. 获取队列信息。

  6. 删除队列。

注意事项

  • 队列创建:

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

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

  • 队列读写:

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

  • 其他:

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

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

编程实例

实例描述

创建一个队列,两个任务。任务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!

事件

概述

基本概念

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

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

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

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

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接口清除事件时,根据入参事件和待清除的事件类型,对事件对应位进行清零操作。

图 1 事件唤醒任务示意图

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

初始化事件

LOS_EventInit

初始化一个事件控制块。

读/写事件

LOS_EventRead

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

LOS_EventWrite

写指定的事件类型。

LOS_EventCondRead

读取满足指定条件的事件,超时时间为相对时间:单位为Tick,与LOS_EventCondWrite成对使用。

LOS_EventCondWrite

写指定的事件,与LOS_EventCondRead成对使用。

清除事件

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_SHOULD_NOT_DESTORY

0x02001C08

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

先检查事件链表是否为空。

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

开发流程

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

  1. 打开菜单,进入Kernel ---> Enable Event菜单,完成事件模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_IPC_EVENT

    事件功能的裁剪开关

    YES/NO

    YES

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

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

  4. 读事件LOS_EventRead或LOS_EventCondRead,读取指定的事件类型或条件事件。

  5. 清除事件LOS_EventClear,清除指定的事件类型,该接口只适用于事件类型的事件。

  6. 销毁事件LOS_EventDestroy。

平台差异性

无。

注意事项

  • 在系统初始化之前不能调用读写事件接口。如果调用,系统会运行异常。

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

  • 在锁定任务调度状态下,禁止任务阻塞于读事件。

  • 为了区别LOS_EventRead接口返回的是事件还是错误码,事件掩码的第25位不能使用。

  • LOS_EventClear入参值是要清除的指定事件类型的反码(~events)。

  • LOS_EventWrite和LOS_EventRead成对使用,LOS_EventCondWrite和LOS_EventCondRead成对使用,这两组接口的区别在于,前面的用于读取或写入指定事件类型的事件;而后面的一组接口,用于读取或写入指定事件条件的事件,事件条件由用户实现,写事件任务不用关心事件条件,而是广播式告知读事件任务,即唤醒读任务,读任务判断读取的事件条件是否成功,这样可能会带来一定的性能开销,但是可简化写事件任务。

编程实例

基于事件类型的读写

实例描述

示例中,任务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

基于事件条件的读写

实例描述

示例中,任务ExampleTaskEntry中创建两个读事件任务readTask1和readTask2和一个写事件任务writeTask,由writeTask依次将readTask1和readTask2等待的条件置为真,并写事件,readTask1和readTask2读取的事件符合预期,结束阻塞。可以通过示例日志中打印的先后顺序理解事件操作时伴随的任务切换。

  1. 在任务ExampleTaskEntry中创建两个读事件任务readTask1和readTask2;

  2. 任务readTask1等待的条件EventCondition1当前为假,当前未读取到目的事件,且未超时,任务阻塞;

  3. 任务readTask2等待的条件EventCondition2当前为假,当前未读取到目的事件,且未超时,任务阻塞;

  4. 在任务ExampleTaskEntry中创建写事件任务writeTask;

  5. writeTask将readTask1等待的条件置为真,写事件;

  6. readTask1和readTask2都得到唤醒,但因只有readTask1的条件为真,所以readTask1读事件成功,向下执行,readTask2读事件失败,继续阻塞;

  7. writeTask将readTask2等待的条件置为真,写事件;

  8. readTask2得到唤醒,条件为真,所以readTask2读事件成功,向下执行。

编程示例

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

代码实现如下:

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

/* 任务ID */
static UINT32 g_readTskId1;
static UINT32 g_readTskId2;
static UINT32 g_writeTskId;

/* g_readTskId1和g_readTskId2等待的条件 */
static int g_testData1 = 0;
static int g_testData2 = 0;

/* 事件控制结构体 */
static EVENT_CB_S g_event;

/* 写事件任务入口函数 */
static VOID WriteTaskFunc(VOID)
{
    UINT32 ret;

    g_testData1 = 1;
    printf("\nWriteTaskFunc: write for ReadTaskFunc1\n\n");
    ret = LOS_EventCondWrite(&g_event);
    if (ret != LOS_OK) {
        printf("WriteTaskFunc failed: ret = %u\n", ret);
        return;
    }
    LOS_TaskDelay(2);    /* 2: delay 2 ticks */

    printf("\nWriteTaskFunc: write for ReadTaskFunc2\n\n");
    g_testData2 = 1;
    ret = LOS_EventCondWrite(&g_event);
    if (ret != LOS_OK) {
        printf("WriteTaskFunc failed: ret = %u\n", ret);
        return;
    }
    LOS_TaskDelay(2);    /* 2: delay 2 ticks */

    return;
}

/* 读事件任务1预期想读取的事件条件 */
static BOOL EventCondition1(VOID)
{
    BOOL ret;

    ret = (g_testData1 == 1) ? TRUE : FALSE;
    printf("EventCondition1: ret = %s\n", (ret ? "TRUE" : "FALSE"));
    return ret;
}

/* 读事件任务1入口函数 */
static VOID ReadTaskFunc1(VOID)
{
    UINT32 ret;

    ret = LOS_EventCondRead(&g_event, EventCondition1(), LOS_WAIT_FOREVER);
    if (ret != LOS_OK) {
        printf("ReadTaskFunc1 read failed!\n");
        return;
    }

    printf("ReadTaskFunc1 read succeeded!\n");
    return;
}

/* 读事件任务2预期想读取的事件条件 */
static BOOL EventCondition2(VOID)
{
    BOOL ret;

    ret = (g_testData2 == 1) ? TRUE : FALSE;
    printf("EventCondition2: ret = %s\n", (ret ? "TRUE" : "FALSE"));
    return ret;
}

/* 读事件任务2入口函数 */
static VOID ReadTaskFunc2(VOID)
{
    UINT32 ret;

    ret = LOS_EventCondRead(&g_event, EventCondition2(), LOS_WAIT_FOREVER);
    if (ret != LOS_OK) {
        printf("ReadTaskFunc2 read failed!\n");
        return;
    }

    printf("ReadTaskFunc2 read succeeded!\n");
    return;
}

static VOID InitTaskParam(TSK_INIT_PARAM_S *task, char *taskName, TSK_ENTRY_FUNC entry, UINT16 prio, UINT16 affi)
{
    task->pcName        = taskName;
    task->pfnTaskEntry  = entry;
    task->usTaskPrio    = prio;
    task->uwStackSize   = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task->uwResved      = LOS_TASK_STATUS_DETACHED;

#ifdef LOSCFG_KERNEL_SMP
    task->usCpuAffiMask = affi;
#endif
}

static UINT32 ExampleTaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S readTask1 = {0};
    TSK_INIT_PARAM_S readTask2 = {0};
    TSK_INIT_PARAM_S writeTask = {0};

    ret = LOS_EventInit(&g_event);
    if (ret != LOS_OK) {
        return ret;
    }

    InitTaskParam(&readTask1, "EventReadTsk1", (TSK_ENTRY_FUNC)ReadTaskFunc1, 5, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    ret = LOS_TaskCreate(&g_readTskId1, &readTask1);
    if (ret != LOS_OK) {
        return ret;
    }

    InitTaskParam(&readTask2, "EventReadTsk2", (TSK_ENTRY_FUNC)ReadTaskFunc2, 5, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    ret = LOS_TaskCreate(&g_readTskId2, &readTask2);
    if (ret != LOS_OK) {
        return ret;
    }

    InitTaskParam(&writeTask, "EventWriteTsk45", (TSK_ENTRY_FUNC)WriteTaskFunc, 5, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    ret = LOS_TaskCreate(&g_writeTskId, &writeTask);
    if (ret != LOS_OK) {
        return ret;
    }

    (VOID)LOS_TaskDelay(6);   /* 6: delay 6 ticks */

    (VOID)LOS_EventDestroy(&g_event);
    return LOS_OK;
}

结果验证

编译运行得到的结果为:

EventCondition1: ret = FALSE
EventCondition2: ret = FALSE

WriteTaskFunc: write for ReadTaskFunc1

EventCondition1: ret = TRUE
ReadTaskFunc1 read succeeded!
EventCondition2: ret = FALSE

WriteTaskFunc: write for ReadTaskFunc2

EventCondition2: ret = TRUE
EventCondition2: ret = TRUE
ReadTaskFunc2 read succeeded!

信号量

概述

基本概念

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

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

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

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

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

  • 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量。二值信号量的计数值最大为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 信号量运作示意图

开发指导

使用场景

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

功能

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

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

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

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

开发流程

信号量的开发典型流程:

  1. 打开菜单,进入Kernel ---> Enable Sem菜单,完成信号量的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    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 .

读写信号量

概述

基本概念

读写信号量(rwsem)与普通信号量的功能相似,常用于共享资源的互斥访问和任务同步。读写信号量将持有者分为读者和写者,如果一个持有者需要对该信号量保护的共享资源进行写操作,那么将这类持有者定义为写者;如果持有者只是访问共享资源,而不修改,那么将这类持有者定义为读者。

读写信号量支持一个读写信号量同时拥有不受上限的读者数,即读者不排他可并发;但是一个读写信号量同时只能有一个写者,写者是排他性的、独占性的,写写互斥、读写互斥。

运作机制

读写信号量控制块:

typedef struct {
    UINT8       rwsemState;      /* 是否使用标志位 */
    INT16       rwsemCount;      /* 读写信号量拥有者的数量 */
    UINT32      rwsemId;         /* 读写信号量索引号 */
    LOS_DL_LIST waitList;        /* 等待队列,用于记录阻塞于该读写信号量的任务 */
} OsRwsemCB;

读写信号量运作原理

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

  • 创建读写信号量:从未使用链表中获取一个读写信号量。

  • 申请读信号量:若该信号量还未被持有,或者正在被其他读者持有,返回成功;否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个读写信号量阻塞时,该任务会被挂到读写信号量等待队列的队尾。

  • 申请写信号量:若该信号量还未被持有,返回成功;否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个读写信号量阻塞时,该任务会被挂到读写信号量等待队列的队尾。

  • 释放读信号量:若没有任务等待该读写信号量,则直接返回;当信号量处于读状态,等待队列中的队首任务只能是写者,唤醒这一个写者,然后返回。

  • 释放写信号量:若没有任务等待该读写信号量,则直接返回;如果等待队列的队首任务是写者,则唤醒这一个写者,然后返回;如果等待队列的队首任务是读者,则唤醒这个读者及后面连续的多个读者,直到等待队列为空,或者遇到写者停止。

  • 降级写信号量为读信号量:信号量变更为读信号量,若没有任务等待该读写信号量或者等待队列的队首任务是写者,则直接返回;否则唤醒等待队列的队首读者及后面连续的多个读者,直到等待队列为空,或者遇到写者停止。

  • 删除读写信号量:将正在使用的读写信号量设置为未使用的状态,并挂回到未使用链表。

    说明: 读写信号量允许多个任务在同一时刻访问共享资源,但同一时刻只能有一个任务可修改共享资源,这样对于读任务多于写任务的场景,可有效提高系统的整体性能。

开发指导

使用场景

读写信号量是基于普通信号量优化的一种多任务同步互斥机制,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务同步中,适用于读任务多于写任务的场景。

功能

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

功能分类

接口名

描述

创建/删除读写信号量

LOS_RwsemCreate

创建读写信号量,返回读写信号量ID。

LOS_RwsemDelete

删除指定的读写信号量。

申请/释放读写信号量

LOS_RwsemPendRead

申请指定的读写信号量为读信号量,支持超时等待获取。

LOS_RwsemPostRead

释放指定的读信号量。

LOS_RwsemPendWrite

申请指定的读写信号量为写信号量,支持超时等待获取。

LOS_RwsemPostWrite

释放指定的写信号量。

LOS_RwsemDowngradeWrite

将指定的写信号量降级为读信号量。

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

  • 无阻塞模式:即任务申请读写信号量时,入参timeout等于0。若当前读写信号量可获取,则申请成功,否则立即返回申请失败。

  • 永久阻塞模式:即任务申请读写信号量时,入参timeout等于0xFFFFFFFF。若当前读写信号量可获取,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到其他任务释放该读写信号量,使得该读写信号量可重新被获取,阻塞任务才会重新得以执行。

  • 定时阻塞模式:即任务申请读写信号量时,0<timeout<0xFFFFFFFF。若当前读写信号量可获取,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,如果超时前其他任务释放该信号量,使得该读写信号量可重新被获取,则该任务可成功获取读写信号量继续执行,若超时前未获取到读写信号量,接口将返回超时错误码。

读写信号量错误码

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

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_RWSEM_INVALID

0x02002200

读写信号量ID不正确或读写信号量未创建。

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

2

LOS_ERRNO_RWSEM_PTR_NULL

0x02002201

传入空指针。

传入合法指针。

3

LOS_ERRNO_RWSEM_ALL_BUSY

0x02002202

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

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

4

LOS_ERRNO_RWSEM_PEND_INTERR

0x02002203

中断期间非法调用API,例如以阻塞的方式申请读写信号量。

合理调用API,不要在中断期间以阻塞方式获取读写信号量。

5

LOS_ERRNO_RWSEM_PEND_IN_LOCK

0x02002204

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

在任务被锁时,不要调用API获取读写信号量。

6

LOS_ERRNO_RWSEM_TIMEOUT

0x02002205

获取读写信号量超时。

将时间设置在合理范围内。

7

LOS_ERRNO_RWSEM_PENDED

0x02002206

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

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

8

LOS_ERRNO_RWSEM_INVALID_STATUS

0x02002207

释放或降级读写信号量时,信号量的状态不正确。

请正确释放和降级读写信号量。

9

LOS_ERRNO_RWSEM_UNAVAILABLE

0x02002208

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

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

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

开发流程

读写信号量的典型开发流程如下:

  1. 打开菜单,进入Kernel ---> Enable Rwsem菜单,完成读写信号量的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_BASE_IPC_RWSEM

    读写信号量模块裁剪开关

    YES/NO

    YES

    LOSCFG_BASE_IPC_RWSEM_LIMIT

    系统支持的读写信号量最大数

    [0,65535]

    1024

    LOSCFG_BASE_IPC_RWSEM

  2. 创建读写信号量LOS_RwsemCreate。

  3. 申请读信号量LOS_RwsemPendRead。

  4. 释放读信号量LOS_RwsemPostRead。

  5. 删除读写信号量LOS_RwsemDelete。

平台差异性

无。

注意事项

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

编程实例

实例描述

本实例实现如下功能:

  1. 测试任务ExampleTaskEntry创建一个读写信号量,并申请写信号量,锁任务调度,创建三个任务ExampleReadTask1、ExampleReadTask2和ExampleWriteTask2;ExampleReadTask1和ExampleReadTask2的优先级高于ExampleWriteTask2任务,并申请读信号量;ExampleWriteTask2申请写信号量。

  2. ExampleTaskEntry解锁任务调度后,ExampleReadTask1、ExampleReadTask2获取读信号量失败,永久阻塞等待,ExampleWriteTask2等待1tick获取写信号量失败,改为永久阻塞等待获取写信号量。

  3. ExampleTaskEntry降级写信号量为读信号量,ExampleReadTask1和ExampleReadTask2获取读信号量成功,ExampleWriteTask2继续阻塞。

  4. 在ExampleReadTask1、ExampleReadTask2和ExampleTaskEntry释放读信号量之后,ExampleWriteTask2获取写信号量成功。

  5. ExampleWriteTask2释放写信号量。

编程示例

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

代码实现如下:

#include "los_rwsem.h"
#include "securec.h"

static UINT32 g_rwsemId; /* 读写信号量结构体id */

VOID ExampleReadTask1(VOID)
{
    UINT32 ret;

    /* 定时时间到,未申请到读信号量 */
    printf("[ExampleReadTask1] try get rwsem as read, wait forever.\n");
    /* 永久阻塞模式申请读信号量 */
    ret = LOS_RwsemPendRead(g_rwsemId, LOS_WAIT_FOREVER);
    if (ret == LOS_OK) {
        printf("[ExampleReadTask1] get rwsem success.\n");
        LOS_TaskDelay(3);
        printf("[ExampleReadTask1] post rwsem.\n");
        LOS_RwsemPostRead(g_rwsemId);
        return;
    }
}

VOID ExampleReadTask2(VOID)
{
    UINT32 ret;

    /* 定时时间到,未申请到读信号量 */
    printf("[ExampleReadTask2] try get rwsem as read, wait forever.\n");
    /* 永久阻塞模式申请读信号量 */
    ret = LOS_RwsemPendRead(g_rwsemId, LOS_WAIT_FOREVER);
    if (ret == LOS_OK) {
        printf("[ExampleReadTask2] get rwsem success.\n");
        LOS_TaskDelay(3);
        printf("[ExampleReadTask2] post rwsem.\n");
        LOS_RwsemPostRead(g_rwsemId);
        return;
    }
}

VOID ExampleWriteTask2(VOID)
{
    UINT32 ret;

    printf("[ExampleWriteTask2] try get rwsem as write, timeout 1 tick.\n");
    /* 定时阻塞模式申请写信号量,定时时间为1tick */
    ret = LOS_RwsemPendWrite(g_rwsemId, 1);

    /* 申请到写信号量 */
    if (ret == LOS_OK) {
         LOS_RwsemPostWrite(g_rwsemId);
         return;
    }
    /* 定时时间到,未申请到写信号量 */
    if (ret == LOS_ERRNO_RWSEM_TIMEOUT) {
        printf("[ExampleWriteTask2] timeout and try get rwsem as write, wait forever.\n");
        /* 永久阻塞模式申请写信号量 */
        ret = LOS_RwsemPendWrite(g_rwsemId, LOS_WAIT_FOREVER);
        if (ret == LOS_OK) {
            printf("[ExampleWriteTask2] get rwsem success.\n");
            LOS_TaskDelay(3);
            printf("[ExampleWriteTask2] post rwsem.\n");
            LOS_RwsemPostWrite(g_rwsemId);
            return;
        }
    }
    return;
}

static VOID InitTaskParam(TSK_INIT_PARAM_S *task, char *taskName, TSK_ENTRY_FUNC entry, UINT16 prio, UINT16 affi)
{
    task->pcName        = taskName;
    task->pfnTaskEntry  = entry;
    task->usTaskPrio    = prio;
    task->uwStackSize   = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task->uwResved      = LOS_TASK_STATUS_DETACHED;

#ifdef LOSCFG_KERNEL_SMP
    task->usCpuAffiMask = affi;
#endif
}

UINT32 ExampleTaskEntry(VOID)
{
    UINT32 ret;
    UINT32 readTsk1Id, readTsk2Id, writeTsk2Id;
    TSK_INIT_PARAM_S readTask1 = {0};
    TSK_INIT_PARAM_S readTask2 = {0};
    TSK_INIT_PARAM_S writeTask2 = {0};

   /* 创建读写信号量 */
    ret = LOS_RwsemCreate(&g_rwsemId);
    if (ret != LOS_OK) {
        printf("[ExampleTaskEntry] create rwsem failed.\n");
        return LOS_NOK;
    }

    ret = LOS_RwsemPendWrite(g_rwsemId, 0);
    if (ret != LOS_OK) {
        printf("[ExampleTaskEntry] get rwsem failed.\n");
        return LOS_NOK;
    }
    printf("[ExampleTaskEntry] get rwsem success.\n");

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

    /* 创建读任务1 */
    InitTaskParam(&readTask1, "resemReadTsk1", (TSK_ENTRY_FUNC)ExampleReadTask1, 5, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    ret = LOS_TaskCreate(&readTsk1Id, &readTask1);
    if (ret != LOS_OK) {
        printf("[ExampleTaskEntry] readTask1 create failed.\n");
        return LOS_NOK;
    }

    /* 创建读任务2 */
    InitTaskParam(&readTask2, "resemReadTsk2", (TSK_ENTRY_FUNC)ExampleReadTask2, 5, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    ret = LOS_TaskCreate(&readTsk2Id, &readTask2);
    if (ret != LOS_OK) {
        printf("[ExampleTaskEntry] readTask2 create failed.\n");
        return LOS_NOK;
    }

    /* 创建写任务2 */
    InitTaskParam(&writeTask2, "resemWriteTsk2", (TSK_ENTRY_FUNC)ExampleWriteTask2, 9, CPUID_TO_AFFI_MASK(ArchCurrCpuid()));
    ret = LOS_TaskCreate(&writeTsk2Id, &writeTask2);
    if (ret != LOS_OK) {
        printf("[ExampleTaskEntry] writeTask2 create failed.\n");
        return LOS_NOK;
    }

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

    /* 让出CPU 5tick,等待readTask1、readTask2、writeTask2先执行并阻塞 */
    LOS_TaskDelay(5);

    /* 写信号量降级为读信号量 */
    printf("[ExampleTaskEntry] downgrade rwsem.\n");
    LOS_RwsemDowngradeWrite(g_rwsemId);

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

    LOS_RwsemPostRead(g_rwsemId);

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

    /* 删除读写信号量 */
    LOS_RwsemDelete(g_rwsemId);

    return LOS_OK;
}

结果验证

编译运行所得结果如下:

[ExampleTaskEntry] get rwsem success.
[ExampleReadTask1] try get rwsem as read, wait forever.
[ExampleReadTask2] try get rwsem as read, wait forever.
[ExampleWriteTask2] try get rwsem as write, timeout 1 tick.
[ExampleWriteTask2] timeout and try get rwsem as write, wait forever.
[ExampleTaskEntry] downgrade rwsem.
[ExampleReadTask1] get rwsem success.
[ExampleReadTask2] get rwsem success.
[ExampleReadTask1] post rwsem.
[ExampleReadTask2] post rwsem.
[ExampleWriteTask2] get rwsem success.
[ExampleWriteTask2] post rwsem.

互斥锁

概述

基本概念

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

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

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

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

  • 多任务阻塞等待同一个锁的场景,支持基于任务优先级等待和FIFO两种模式。

运作机制

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

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

图 1 互斥锁运作示意图

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

创建/删除互斥锁

LOS_MuxCreate

创建互斥锁。

LOS_MuxDelete

删除指定互斥锁。

申请/释放互斥锁

LOS_MuxPend

申请指定互斥锁。

LOS_MuxPost

释放指定互斥锁。

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

  • 无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。

  • 永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。

  • 定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。 释放互斥锁:

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

  • 如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

互斥锁错误码

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

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_MUX_NO_MEMORY

0x02001D 00

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

设置更大的系统动态内存池,配置项为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

删除正在使用的互斥锁。

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

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

开发流程

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

  1. 打开菜单,进入Kernel ---> Enable Mutex菜单,完成互斥锁的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    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

    LOSCFG_BASE_IPC_MUX_CREATOR

    记录锁的创建者信息

    YES/NO

    NO

    LOSCFG_BASE_IPC_MUX

  2. 创建互斥锁LOS_MuxCreate。

  3. 申请互斥锁LOS_MuxPend。

  4. 释放互斥锁LOS_MuxPost。

  5. 删除互斥锁LOS_MuxDelete。

平台差异性

无。

注意事项

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

  • 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被调度运行,删除互斥锁,删除两个任务。

编程示例

前提条件:打开菜单完成互斥锁的配置。

代码实现如下:

/* 互斥锁句柄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.

软件定时器

概述

基本概念

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

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

  • 创建软件定时器。

  • 启动软件定时器。

  • 停止软件定时器。

  • 删除软件定时器。

  • 获取软件定时器剩余Tick数。

  • 可配置支持的软件定时器个数。

运作机制

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

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

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

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

  • 定时器状态:

    • OS_SWTMR_STATUS_UNUSED(定时器未使用)

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

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

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

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

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

    • OS_SWTMR_STATUS_DELETING (定时器被删除了)

      定时器类型是单次触发类型,在定时器超时后,定时器将变成该状态。

  • 定时器模式:

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

    • 单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动删除。

    • 周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动停止定时器,否则将永远持续执行下去。

    • 单次触发定时器,但这类定时器超时触发后不会自动删除,需要调用定时器删除接口删除定时器。

开发指导

使用场景

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

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

功能

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

功能分类

接口名

描述

创建/删除定时器

LOS_SwtmrCreate

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

LOS_SwtmrDelete

异步删除定时器,接口在入参检测后立即返回,不保障接口返回时软件定时器资源已经被释放,软件定时器资源将在目标定时器所有回调执行完毕后释放。

LOS_SwtmrSyncDelete

同步删除定时器,接口在入参检测后,会等待目标定时器的所有回调执行完毕,而后删除定时器,释放软件定时器资源。

启动/停止定时器

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],暂不支持软件定时器模式设为3。

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

在中断或软件定时器回调中尝试同步删除定时器,删除失败。

调整代码逻辑,避免在中断或软件定时器回调中同步删除软件定时器。

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

开发流程

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

  1. 打开菜单,进入Kernel ---> Enable Software Timer菜单,完成软件定时器的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    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

    (菜单路径为:Kernel ---> Basic Config ---> Task)

    软件定时器任务栈大小

    [LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE,OS_SYS_MEM_SIZE)

    24576

    LOSCFG_BASE_CORE_SWTMR

    LOSCFG_SWTMR_SYNC_DELETE

    支持软件定时器同步删除

    YES/NO

    NO

    LOSCFG_BASE_CORE_SYS_RES_CHECK

    LOSCFG_BASE_CORE_SWTMR_ALIGN

    支持软件定时器对齐

    YES/NO

    NO

    LOSCFG_BASE_CORE_USE_MULTI_LIST

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

  3. 启动定时器LOS_SwtmrStart。

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

  5. 停止定时器LOS_SwtmrStop。

  6. 删除定时器LOS_SwtmrDelete。

注意事项

  • 软件定时器的回调函数中不应执行过多操作,不建议使用可能引起任务挂起或者阻塞的接口或操作,如果使用会导致软件定时器响应不及时,可能会影响正常业务功能。

  • 如果没有使能LOSCFG_BASE_CORE_SWTMR_IN_ISR宏,软件定时器将使用系统的一个任务资源。软件定时器任务的优先级设定为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

自旋锁

概述

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

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

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

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

注意事项

初始化自旋锁

LOS_SpinInit

动态初始化自旋锁。

由调用者保证入参合法。

SPIN_LOCK_INIT

静态初始化自旋锁。

不涉及。

申请/释放自旋锁

LOS_SpinLock

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

由调用者保证入参合法。

LOS_SpinTrylock

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

由调用者保证入参合法。

LOS_SpinUnlock

释放指定的自旋锁。

由调用者保证入参合法。

LOS_SpinUnlockNoSched

释放指定的自旋锁,并继续执行本任务(建议参考注释使用在特殊场景,错误使用会导致调度时序错乱)。

由调用者保证入参合法。

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

LOS_SpinLockSave

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

由调用者保证入参合法。

LOS_SpinUnlockRestore

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

由调用者保证入参合法。

获取自旋锁持有状态

LOS_SpinHeld

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

由调用者保证入参合法。

开发流程

自旋锁的开发典型流程:

  1. 自旋锁依赖于SMP,打开菜单,配置项的菜单路径为:Kernel ---> Enable Kernel SMP。

    配置项

    含义

    取值范围

    默认值

    依赖

    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_BASE_CORE_TSK_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_BASE_CORE_TSK_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

分级初始化

概述

基本概念

分级初始化将Kernel的启动过程分成多个阶段,阶段之间存在依赖关系,后初始化的阶段可能会依赖前初始化阶段的接口。对于Kernel启动过程新增的调用接口,不再需要修改启动代码添加函数调用,可通过分级初始化注册宏进行接口注册,Kernel在启动时,按照顺序自动执行所有注册的初始化接口。

运作机制

  • 每一个需要在Kernel初始化时执行的函数,通过使用分级启动宏都会生成一个函数指针变量,指向该函数。

  • 编译生成镜像时,链接脚本将上述的函数指针变量冠段。

  • Kernel初始化,从冠段地址取出所有函数指针,依次执行所有初始化函数。

开发指导

使用场景

分级初始化,可以实现Kernel初始化模块与其他模块的充分解耦。Kernel初始化模块不再感知具体模块的初始化接口,初始化接口也不用关心如何被调用。

功能

LiteOS中的分级初始化模块提供下面几种功能,接口详细信息可以查看API参考。

接口名

描述

LOS_SYS_INIT

注册一个初始化函数,在Kernel阶段会运行该函数。

层级划分:分级初始化时,下表中越靠前的层级,优先级越高,越先得到执行。

启动层级宏

描述

SYS_INIT_LEVEL_EARLY

Kernel启动早期阶段,该阶段仅供用户使用,并且注册到该阶段的初始化函数不可依赖Kernel资源。

SYS_INIT_LEVEL_AHEAD

Kernel启动预备阶段,该阶段仅供系统模块使用,为内核初始化做准备。

SYS_INIT_LEVEL_ARCH

该阶段仅供系统模块使用,架构相关初始化。

SYS_INIT_LEVEL_KERNEL

该阶段仅供系统模块使用,内核基础子模块初始化。

SYS_INIT_LEVEL_KERNEL_ADDITION

该阶段仅供系统模块使用,扩展内核模块初始化。

SYS_INIT_LEVEL_COMPONENT

该阶段仅供系统模块使用,组件相关初始化。

SYS_INIT_LEVEL_APP

应用程序初始化。

SYS_INIT_LEVEL_RESERVE

Kernel启动后期阶段,该阶段供用户使用,注册到该阶段的初始化函数可依赖Kernel资源。

优先级划分:同一层级的初始化函数之间,可能存在依赖关系。sync优先级只能用于设置同一层级内的函数优先级。sync值越小,优先级越高,越先执行。

优先级宏

描述

SYS_INIT_SYNC_0

sync 0优先级,在同一层级内最先执行。

SYS_INIT_SYNC_1

sync 1优先级。

SYS_INIT_SYNC_2

sync 2优先级。

SYS_INIT_SYNC_3

sync 3优先级。

SYS_INIT_SYNC_4

sync 4优先级。

开发流程

  1. 确定初始化接口的依赖关系,进而确定层级及优先级。

  2. 调用LOS_SYS_INIT注册初始化接口。

注意事项

  • 分级初始化适用于类型为typedef unsigned int (*SysInitcallFunc)(void)的初始化函数。

  • 由于不会在代码中直接调用初始化函数,一些编译器可能会因此优化掉初始化函数,导致注册失败,建议使用-u链接选项。

  • 同一个层级、sync优先级内,可以注册多个初始化接口,但这些接口之间不能存在依赖关系。

编程实例

实例描述

在下面的实例中,描述了分级初始化注册接口的使用方法:

  1. 确定初始化接口的依赖关系、层级及优先级。

  2. 注册初始化接口。

编程示例

#include "stdio.h"
#include "los_init.h"
#include "los_list.h"
#include "los_hwi.h"

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

UINT32 g_count;
LOS_DL_LIST g_DLlist;

UINT32 InitLevelTest1(VOID)
{
    dprintf("InitLevelTest1 init\n");
    g_count = 10;
    LOS_ListInit(&g_DLlist);

    return LOS_OK;
}
/* InitLevelTest1 has no dependence, set SYS_INIT_LEVEL_EARLY level */
LOS_SYS_INIT(InitLevelTest1, SYS_INIT_LEVEL_EARLY, SYS_INIT_SYNC_0);

STATIC VOID HwiUsrIrq(VOID)
{
    g_count++;
}

UINT32 InitLevelTest2(VOID)
{
    UINT32 ret;
    HWI_HANDLE_T irqNum = 26;
    HWI_PRIOR_T  irqPri = 0x3;

    dprintf("InitLevelTest2 init\n");
    ret = LOS_HwiCreate(irqNum, irqPri, 0, (HWI_PROC_FUNC)HwiUsrIrq, 0);
    if (ret != LOS_OK) {
        return LOS_NOK;
    }
    return ret ;
}
/* InitLevelTest2 rely on hwi module, set SYS_INIT_LEVEL_RESERVE level */
LOS_SYS_INIT(InitLevelTest2, SYS_INIT_LEVEL_RESERVE, SYS_INIT_SYNC_0);

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

结果验证

编译运行得到的结果为:

InitLevelTest1 init
InitLevelTest2 init

其他

时间管理

概述

基本概念

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

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

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

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

相关概念

  • Cycle

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

  • Tick

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

开发指导

使用场景

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

功能

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

功能分类

接口名

描述

时间转换

LOS_MS2Tick

采用向上取整的计算方法将毫秒转换成Tick。

LOS_Tick2MS

Tick转化为毫秒。

时间统计

LOS_CyclePerTickGet

每个Tick多少Cycle数。

LOS_TickCountGet

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

LOS_GetCpuCycle

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

LOS_CurrNanosec

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

延时管理

LOS_Udelay

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

LOS_Mdelay

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

时间管理错误码

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

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_TICK_CFG_INVALID

0x02000400

无效的系统Tick配置。

在板级配置适配时配置有效的系统主时钟频率OS_SYS_CLOCK,打开菜单配置有效的LOSCFG_BASE_CORE_TICK_PER_SECOND。

开发流程

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

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

  2. 打开菜单配置LOSCFG_BASE_CORE_TICK_PER_SECOND,配置项的菜单路径为:Kernel --->Basic Config --->Task。

    配置项

    含义

    取值范围

    默认值

    依赖

    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

双向链表

概述

基本概念

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

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

开发指导

功能

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指令,以支持对共享存储器更缜密的非阻塞同步。由此实现的原子操作能确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,即操作的原子性。

运作机制

LiteOS通过对ARMv6架构中的LDREX和STREX进行封装,向用户提供了一套原子操作接口。

  • LDREX Rx, [Ry]

    读取内存中的值,并标记对该段内存为独占访问:

    • 读取寄存器Ry指向的4Byte内存数据,保存到Rx寄存器中。

    • 对Ry指向的内存区域添加独占访问标记。

  • STREX Rf, Rx, [Ry]

    检查内存是否有独占访问标记,如果有则更新内存值并清空标记,否则不更新内存:

    • 有独占访问标记

      1. 将寄存器Rx中的值更新到寄存器Ry指向的内存。

      2. 标志寄存器Rf置为0。

    • 没有独占访问标记

      1. 不更新内存。

      2. 标志寄存器Rf置为1。

  • 判断标志寄存器

    • 标志寄存器为0时,退出循环,原子操作结束。

    • 标志寄存器为1时,继续循环,重新进行原子操作。

开发指导

使用场景

有多个任务对同一个内存数据进行加减或交换操作时,使用原子操作保证结果的可预知性。

功能

LiteOS的原子数据包含两种类型Atomic(有符号32位数)与 Atomic64(有符号64位数)。原子操作模块为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

注意事项

LOS_AtomicRead

读取内存数据。

由调用者保证入参合法。

LOS_AtomicSet

写入内存数据。

由调用者保证入参合法。

LOS_AtomicAdd

对内存数据做加法。

由调用者保证入参合法。

LOS_AtomicSub

对内存数据做减法。

由调用者保证入参合法。

LOS_AtomicInc

对内存数据加1。

由调用者保证入参合法。

LOS_AtomicIncRet

对内存数据加1并返回运算结果。

由调用者保证入参合法。

LOS_AtomicDec

对内存数据减1。

由调用者保证入参合法。

LOS_AtomicDecRet

对内存数据减1并返回运算结果。

由调用者保证入参合法。

交换

LOS_AtomicXchg32bits

交换内存数据,原内存中的值以返回值的方式返回。

由调用者保证入参合法。

LOS_AtomicCmpXchg32bits

比较并交换内存数据,返回比较结果。

由调用者保证入参合法。

功能分类

接口名

描述

注意事项

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. 创建两个任务。

    1. 任务一用LOS_AtomicInc对全局变量加100次。

    2. 任务二用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

位操作

概述

基本概念

位操作是指对二进制数的bit位进行操作。程序可以设置某一变量为状态字,状态字中的每一bit位(标志位)可以具有自定义的含义。

开发指导

使用场景

系统提供标志位的置1和清0操作,可以改变标志位的内容,同时还提供获取状态字中标志位为1的最高位和最低位的功能。用户也可以对系统的寄存器进行位操作。

功能

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

环形缓冲(ringbuf)

概述

基本概念

环形缓冲(ringbuf)作为数据的环形缓冲机制,可以实现数据流以先入先出的方式进行缓存。

ringbuf控制块

typedef struct {
    UINT32 startIdx;                       /* ringbuf读下标 */
    UINT32 endIdx;                         /* ringbuf写下标 */
    UINT32 size;                           /* ringbuf总大小,单位为字节 */
    UINT32 remain;                         /* ringbuf剩余可用大小,单位为字节 */
    SPIN_LOCK_S lock;                      /* ringbuf自旋锁 */
    RingbufStatus status                   /* ringbuf状态 */
    RingbufFlag flag;                      /* Ringbuf flag */
    CHAR *fifo;                            /* ringbuf缓冲区起始地址 */
} Ringbuf;

每个ringbuf有两种状态:

  • RBUF_UNINIT:ringbuf未初始化。

  • RBUF_INITED:ringbuf已初始化。

每个ringbuf有两种模式:

  • RBUF_NORMAL:ringbuf正常模式。

  • RBUF_OVERWRITE:ringbuf可覆盖写模式。

运作机制

图 1 环形缓冲运作示意图

ringbuf控制块中维护着缓冲区的起始地址,和读/写指针的相对偏移。startIdx是读指针相对于ringbuf缓冲区起始地址的偏移,endIdx是写指针的相对偏移。startIdx与endIdx之间为缓冲的数据。ringbuf刚创建时,读写指针偏移均为0。

  • 写入数据到ringbuf时,先判断ringbuf标志。标志为RBUF_NORMAL时,处于正常模式;标志为RBUF_OVERWRITE时,处于覆盖写模式。

    • 正常模式:写入数据到ringbuf时,先判断ringbuf是否还有剩余可用内存。当还有可用内存时才会继续写入,endIdx增长,并减少ringbuf的剩余可用内存;否则丢弃写入数据,可根据LOS_RingbufWrite()的返回值判断写入的数据量。如果ringbuf已经存满数据,因为不会覆盖旧数据,所以无法继续写入。

    • 可覆盖写模式:写入数据到ringbuf时,先判断ringbuf要写入的数据。当要写入数据大于size时,清空ringbuf,仅写入size长度数据;当要写入数据小于size且大于剩余内存remain时,清理一部分最早缓存的数据,再写入数据;当要写入数据小于剩余内存remain时,直接写入;最后均返回实际写入长度。

  • 读取数据时,会按照先入先出的方式,读取最早缓存的数据,startIdx增长,并增加ringbuf的剩余可用内存。读取数据后,因为读指针向后移动,所以无法再次读取相同的数据。

    endIdx增长到等于startIdx时,表示ringbuf已经写满,所以无法继续写入,需要读取出数据才能继续写数据到ringbuf。

开发指导

使用场景

ringbuf用于数据缓冲,遵循先入先出的原则。

功能

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

功能分类

接口名

描述

初始化/重置环形缓冲

LOS_RingbufInit

初始化环形缓冲区,缓冲区内存、大小和标志通过入参传入。

LOS_RingbufReset

重置缓冲区的状态,读写偏移重置为0,且清除缓冲区的数据。

读/写环形缓冲

LOS_RingbufRead

从环形缓冲区中读取指定大小的数据到指定地址空间,并返回读取到的数据量。

LOS_RingbufPeek

从环形缓冲区中预读指定大小的数据到指定地址空间,并返回读取到的数据量。

LOS_RingbufWrite

写入指定大小的数据到环形缓冲区,并返回成功写入到缓冲区的数据量。

获取缓冲数据量

LOS_RingbufUsedSize

获取环形缓冲区中已缓存的数据量。

开发流程

使用ringbuf模块开发的典型流程如下:

  1. 打开菜单,进入Kernel ---> Enable Ringbuf菜单,完成ringbuf模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_RINGBUF

    ringbuf裁剪开关

    YES/NO

    NO

  2. 为ringbuf申请一块内存;

  3. 通过LOS_RingbufInit初始化ringbuf。

  4. 写ringbuf。

  5. 在合适时机读ringbuf。

注意事项

  • ringbuf写接口行为由LOS_RingbufInit接口在初始化时决定。

  • 通过LOS_RingbufRead接口读取缓冲区的数据后,该数据无法被再次读取。

  • LOS_RingbufPeek接口仅预读缓冲区数据,未消费该数据,该数据仍然可以被读取。

  • 正常模式下,缓冲区写满时,无法继续写入,请检查LOS_RingbufWrite的返回值以确认实际写入的数据量。

  • 使用完ringbuf后,需要用户自己释放申请的内存。

编程实例

实例描述

创建一个ringbuf,两个任务。任务1定时写入数据到ringbuf,任务2在ringbuf数据达到一定量时,取出数据。

  1. 锁任务调度,通过LOS_TaskCreate创建任务1和任务2。

  2. 申请一块内存用作环形缓冲区。

  3. 通过LOS_RingbufInit初始化一个缓冲区。

  4. 解锁任务调度。

  5. 任务1往环形缓冲区中写入数据。

  6. 任务2判断数据达到水线后,读取数据。

编程示例

static Ringbuf g_Ringbuf = {0};
#define READ_TRIGGER_THRESHOLD 32
#define RINGBUF_SIZE 128

VOID WriteTask(VOID)
{
    UINT32 i = 0;
    UINT32 ret = 0;
    CHAR data[READ_TRIGGER_THRESHOLD * 2 + 1] = "LiteOS test data, send in WriteTask, receive & check in ReadTask";
    for (;;) {
        ret = LOS_RingbufWrite(&g_Ringbuf, data + i, READ_TRIGGER_THRESHOLD / 2);
        if (ret != READ_TRIGGER_THRESHOLD / 2) {
            PRINT_ERR("real write count is %u\n", ret);
        }
        i += ret;
        if (i >= strlen(data)) {
            break;
        } else {
            LOS_Msleep(1000); /* 1000: sleep 1000ms */
        }
    }
}

VOID ReadTask(VOID)
{
    UINT32 i = 0;
    UINT32 ret = 0;
    UINT32 count = 0;
    CHAR data[READ_TRIGGER_THRESHOLD * 2 + 1] = {0};
    for (;;) {
        count = LOS_RingbufUsedSize(&g_Ringbuf);
        if (count >= READ_TRIGGER_THRESHOLD) {
            ret = LOS_RingbufRead(&g_Ringbuf, data + i, count);
            i += ret;
            PRINTK("read data count %u \n", i);
            if (i == READ_TRIGGER_THRESHOLD * 2) {
                PRINTK("data receive done: %s\n", data);
                break;
            }
        } else {
            PRINTK("info: data count %u < %u\n", count, READ_TRIGGER_THRESHOLD);
        }
        LOS_Msleep(1000); /* 1000: sleep 1000ms */
    }
}

UINT32 SampleTaskCreat(VOID)
{
    UINT32 ret = 0;
    UINT32 task1, task2;
    CHAR *buff = NULL;
    TSK_INIT_PARAM_S initParam;

    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)WriteTask;
    initParam.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;
    initParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    initParam.pcName = "WriteTask";
#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) {
        PRINT_ERR("create WriteTask failed, error: %x\n", ret);
        return ret;
    }

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

    buff = (CHAR *)LOS_MemAlloc(m_aucSysMem0, RINGBUF_SIZE);
    if (buff == NULL) {
        PRINT_ERR("malloc falied");
        return LOS_NOK;
    }
    ret = LOS_RingbufInit(&g_Ringbuf, buff, RINGBUF_SIZE, RBUF_NORMAL);
    if (ret != LOS_OK) {
        PRINT_ERR("ringbuf init falied");
        return LOS_NOK;
    }

    PRINTK("create the ringbuf success!\n");
    LOS_TaskUnlock();
    return ret;
}

结果验证

编译运行得到的结果为:

create the ringbuf success!
info: data count 16 < 32
read data count 32
info: data count 16 < 32
read data count 64
data receive done: LiteOS test data, send in WriteTask, receive & check in ReadTask

通用内存访问

概述

基本概念

当寄存器或内存位于内存空间时,称为IO内存。LiteOS提供了一组通用接口来操作I/O内存。

开发指导

使用场景

系统提供读写(1、2、4、8)字节寄存器操作,方便编写驱动代码。

功能

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

功能分类

接口名

描述

读/写64位数据

WRITE64(addr,value)

将64位数据(value)写入64位寄存器(addr)。

READ64(addr,value)

读取64位寄存器(addr)。

WRITE64_MB(addr,value)

将64位数据(value)写入64位寄存器(addr),写入前使用数据同步屏障同步。

READ64_MB(addr,value)

读取64位寄存器(addr),读取前使用数据同步屏障同步。

读/写32位数据

WRITE32(addr,value)

将32位数据(value)写入32位寄存器(addr)。

READ32(addr,value)

读取32位寄存器(addr)。

WRITE32_MB(addr,value)

将32位数据(value)写入32位寄存器(addr),写入前使用数据同步屏障同步。

READ32_MB(addr,value)

读取32位寄存器(addr),读取前使用数据同步屏障同步。

读/写16位数据

WRITE16(addr,value)

将16位数据(value)写入16位寄存器(addr)。

READ16(addr,value)

读取16位寄存器(addr)。

WRITE16_MB(addr,value)

将16位数据(value)写入16位寄存器(addr),写入前使用数据同步屏障同步。

READ16_MB(addr,value)

读取16位寄存器(addr),读取前使用数据同步屏障同步。

读/写8位数据

WRITE8(addr,value)

将8位数据(value)写入8位寄存器(addr)。

READ8(addr,value)

读取8位寄存器(addr)。

WRITE8_MB(addr,value)

将8位数据(value)写入8位寄存器(addr),写入前使用数据同步屏障同步。

READ8_MB(addr,value)

读取8位寄存器(addr),读取前使用数据同步屏障同步。

须知: 如果32位ARM架构写64位寄存器通过str指令访问,会导致高32位寄存器地址和低32位寄存器地址的数据不能同时生效,因此通过strd指令实现。

注意事项

无。

扩展内核

动态加载

概述

基本概念

动态加载是一种程序加载技术。

静态链接是在链接阶段将程序各模块文件链接成一个完整的可执行文件,运行时作为整体一次性加载进内存。动态加载允许用户将程序各模块编译成独立的.o文件和.so文件,而不将它们链接成一个可执行文件,在需要使用某模块时再动态地将其加载进内存。

LiteOS支持动态加载OBJ目标文件(.o文件)和SO共享目标文件(.so文件),需要目标文件、系统二进制镜像文件配合使用。

图 1 动态加载示意图

动态加载相关概念

符号表在表现形式上是记录了符号名及其所在内存地址的数组。符号表在动态加载模块初始化时被载入到动态加载模块的符号管理结构中。在加载用户模块进行符号重定位时,动态加载模块通过查找符号管理结构得到相应符号所在地址。

运作机制

对于so文件,可能以ZIP(压缩)或者NOZIP(非压缩)的形式存放在存储介质中,相应的有两种加载策略。

  • NOZIP策略:普通的so文件可以通过多次lseek与read获取相应的文件信息。首先根据so文件的 SegmentHeader信息,计算出对应的可Load段的大小,然后分配出相应的空间,将相应的可Load段加载到内存并完成动态加载工作。因为每次仅仅读取文件的一小部分,不存在内存浪费问题。

  • ZIP策略:读取ZIP压缩文件时,必须一次性将文件全部读取到内存中(mem1),但是第一次读取时并不知道对应的so文件中可Load段的大小,所以需要二次分配内存(mem2)。因为mem2中已经具备了本次动态加载所需的所有信息,如果让mem1和mem2并存就会造成内存浪费并导致内存峰值过高。解决办法是分两次读取ZIP文件,第一次读取的目的仅仅是计算最终所需的内存大小(计算完毕,立刻释放相应内存),第二次读取的目的才是根据相应信息完成动态加载工作。可见,ZIP加载策略是以牺牲指令为代价来避免内存峰值过高的一种策略。

开发指导

使用场景

静态链接将程序各模块文件链接成一个整体,运行时一次性加载进内存,具有代码装载速度快等优点。但当程序规模较大,模块变更升级较为频繁时,会存在内存和磁盘空间浪费、模块更新困难等问题。

动态加载技术可以较好地解决上述静态链接中存在的问题,在程序需要执行外部模块中的代码时,动态地将外部模块加载进内存,不需要该模块时再卸载,可以实现公共代码的共享以及模块的平滑升级等功能。

功能

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

功能分类

接口名

描述

销毁动态加载

LOS_LdDestroy

销毁动态加载模块。

动态加载模块文件

LOS_SoLoad

动态加载一个so模块(依赖文件系统)。

LOS_MemLoad

动态加载一个so或obj模块(不依赖文件系统,从指定内存空间加载)。

LOS_ObjLoad

动态加载一个obj模块(依赖文件系统)。

动态加载模块文件到静态空间

LOS_MemLoadStatic

动态加载一个so或obj模块到指定的静态空间(不依赖文件系统,从指定内存空间加载)。

LOS_SoLoadStatic

动态加载一个so模块(依赖文件系统)到指定的静态空间。

获取运行视图下模块需要的空间大小

LOS_ModuleSizeGet

计算加载并运行一个so或obj模块所需要的静态空间大小。

LOS_DynSizeGetFromFS

计算加载并运行一个so模块所需要的静态空间大小(依赖文件系统)。

查找符号地址

LOS_FindSymByName

在模块或系统符号表中查找符号地址。

卸载模块

LOS_ModuleUnload

卸载一个模块。

设置动态加载的搜索路径/参数/内存池地址

LOS_PathAdd

添加模块的搜索路径。

LOS_DynParamReg

设置so模块的动态加载参数,使用该接口需要打开LOSCFG_DYNLOAD_DYN_FROM_FS宏开关,即只支持从文件系统中加载so模块时才能设置其动态加载参数。

LOS_DynMemPoolSet

设置动态加载使用的内存池地址。

说明: LOS_DynMemPoolSet接口入参必须是经过LOS_MemInit初始化的内存池地址,即通过LiteOS内存管理算法管理,并且保证该内存池与系统内存池不重合。该接口需在加载.so或者.o文件前使用。

须知: 不同目标平台的内存保护/内存管理单元设计可能存在差异,在使用本章节提供的接口前,请务必阅读目标平台的相关文档以理解平台硬件能力和使用限制。

DYNLOAD错误码

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

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_LD_INVALID_ARG

0x02002600

传入的参数中含有NULL,0等非法值。

传入正确的参数

2

LOS_ERRNO_LD_INVALID_ELF

0x02002601

传入的ELF不正确或者是不支持的类型。

传入正确的ELF文件

开发流程

动态加载主要有以下几个步骤:

  1. 准备编译环境

  2. 编译待加载的模块文件

  3. 编写动态加载的业务代码

  4. 编译系统镜像

  5. 准备系统环境

准备编译环境

  1. 添加.o和.so模块编译选项。

    • .o模块的编译选项中需要添加“-nostdlib -fno-PIC”选项。

    • .so模块的编译选项中需要添加“-nostdlib -fPIC -shared”选项。

    说明: 以下列出的编译选项,可以根据实际需要自行选择。 -z max-page-size=value 设置.o和.so模块可加载的program segment的对齐参数为value值,添加该选项,可以有效减少各相邻可加载的segment的虚拟地址之间由于对齐需要而产生的空白区域。如果不添加该选项,默认对齐参数为0x10000。

    须知:

    • IPC的动态加载需要用户保证所提供的模块文件中所有LD_SHT_PROGBITS、LD_SHT_NOBITS类型的section起始地址都是4字节对齐,否则拒绝加载该模块。

    • 生成.o和.so模块时禁止链接编译器中的标准库(即不允许使用-ldl、-lpthread、-lc等),且务必使用编译选项“-nostdlib”。

    .o和.so模块编译选项添加示例如下:

    RM = -rm -rf
     
    CC = arm-himix100-linux-gcc
     
     
    SRCS = $(wildcard *.c)
    OBJS = $(patsubst %.c,%.o,$(SRCS))
    SOS = $(patsubst %.c,%.so,$(SRCS))
     
    all: $(SOS)
     
    $(OBJS): %.o : %.c
       @$(CC) -nostdlib -c $< -fno-PIC -o $@ 
     
    $(SOS): %.so : %.c
        @$(CC) -nostdlib $< -fPIC -shared -o $@ 
     
    clean:
        @$(RM) $(SOS) $(OBJS)
     
    .PHONY: all clean
    
  2. 修改系统的Makefile。

    编译系统镜像的Makefile必须include根目录下的“config.mk”文件,并使用“config.mk”中的“LITEOS_CFLAGS”或“LITEOS_CXXFLAGS”编译选项,示例如下:

    LITEOSTOPDIR ?= ../..
     
    SAMPLE_OUT = .
     
    include $(LITEOSTOPDIR)/config.mk 
    RM = -rm -rf
     
    LITEOS_LIBDEPS := --start-group $(LITEOS_LIBDEP) --end-group
     
    SRCS = $(wildcard sample.c)
     
    OBJS = $(patsubst %.c,$(SAMPLE_OUT)/%.o,$(SRCS))
     
    all: $(OBJS)
     
    clean:
        @$(RM) *.o  sample *.bin *.map *.asm
     
    $(OBJS): $(SAMPLE_OUT)/%.o : %.c
        $(CC) $(LITEOS_CFLAGS) -c $< -o $@
     
        $(LD) $(LITEOS_LDFLAGS) -uinit_jffspar_param --gc-sections -Map=$(SAMPLE_OUT)/sample.map -o $
        (SAMPLE_OUT)/sample ./$@ $(LITEOS_LIBDEPS) $(LITEOS_TABLES_LDFLAGS) $(LITEOS_DYNLDFLAGS)
        $(OBJCOPY) -O binary $(SAMPLE_OUT)/sample $(SAMPLE_OUT)/sample.bin
        $(OBJDUMP) -d $(SAMPLE_OUT)/sample >$(SAMPLE_OUT)/sample.asm
    

编译待加载的模块文件

请严格按如下步骤进行编译。以makefile为例介绍。

  1. 编译.o和.so模块,并将运行所需的所有.o和.so文件拷贝到同一目录下。

    说明: 以下列出的编译选项,可以根据实际需要自行选择。 -z max-page-size=value 设置.o和.so模块可加载的program segment的对齐参数为value值,添加该选项,可以有效减少各相邻可加载的segment的虚拟地址之间由于对齐需要而产生的空白区域。如果不添加该选项,默认对齐参数为0x10000。

  2. 进入“RTOS_Lite/build/scripts/dynload_tools”目录执行“sym.sh”脚本,示例如下:

    LITEOSTOPDIR ?= ../..
    

    说明:

    • sym.sh脚本的入参“/home/wmin/customer/out”,是.o和.so文件所在的目录的绝对路径。“out/dynload_sym”为指定“los_dynload_gsymbol.c”生成目录的绝对路径,建议指定路径为工程的编译生成目录。

    • 注意如果所需加载的.o或.so被更新了,需要重新执行该脚本。该脚本会提取.o和.so文件中的所有系统符号(非用户定义的符号),以便在编译系统镜像时由编译器计算出对应符号的地址。

    • 必须要在“RTOS_Lite/build/scripts/dynload_tools”目录下执行该脚本。

    • 执行完“sym.sh”脚本后,确保“out/dynload_sym”目录下生成了“los_dynload_gsymbol.c”,并且保证该文件在编译阶段参与编译,以及生成的目标文件参与链接。否则,在加载.o和.so过程中会出现符号不能定位的问题。

  3. (可选)进入“RTOS_Lite/build/scripts/dynload_tools”执行litesym生成小型化可加载的文件:

    例如需要加载的文件为a.o, 当使能LOSCFG_DYNLOAD_LITE_SYMTAB时,需要将原始文件处理成自定义格式才能正确加载,可以用如下的命令生成。

    $ python3 litesym /home/wmin/customer/out/a.o -o 
    /home/wmin/customer/out/a_litesym.o
    

    说明:

    • 使能LOSCFG_DYNLOAD_LITE_SYMTAB时,加载的文件必须是经过symtab脚本处理过的,否则加载会失败。

    • 如果不需要使用LOSCFG_DYNLOAD_LITE_SYMTAB,不需要执行此脚本

编写动态加载的业务代码

  1. 打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Dynamic Load Feature菜单,完成动态加载模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_DYNLOAD

    动态加载模块的裁剪开关

    YES/NO

    YES

    LOSCFG_KERNEL_EXTKERNEL

    LOSCFG_KERNEL_DYNLOAD_DYN

    使能so文件加载

    YES/NO

    YES

    LOSCFG_KERNEL_DYNLOAD

    LOSCFG_DYNLOAD_DYN_FROM_FS

    使能so文件从文件系统加载

    YES/NO

    YES

    LOSCFG_KERNEL_DYNLOAD_DYN

    LOSCFG_DYNLOAD_DYN_FROM_MEM

    使能so文件从指定内存中加载

    YES/NO

    YES

    LOSCFG_KERNEL_DYNLOAD_DYN

    LOSCFG_KERNEL_DYNLOAD_REL

    使能obj文件加载

    YES/NO

    YES

    LOSCFG_KERNEL_DYNLOAD

    LOSCFG_DYNLOAD_DYN_FROM_MEM_STATIC

    使能so文件从内存中加载到指定内存的功能

    YES/NO

    NO

    LOSCFG_KERNEL_DYNLOAD_DYN

    LOSCFG_DYNLOAD_DYN_FROM_FS_STATIC

    使能so文件从文件系统中加载到指定内存的功能

    YES/NO

    NO

    LOSCFG_KERNEL_DYNLOAD_DYN

    LOSCFG_DYNLOAD_REL_FROM_FS

    使能obj文件从文件系统加载

    YES/NO

    YES

    LOSCFG_KERNEL_DYNLOAD_REL

    LOSCFG_DYNLOAD_REL_FROM_MEM

    使能obj文件从指定内存中加载

    YES/NO

    YES

    LOSCFG_KERNEL_DYNLOAD_REL

    LOSCFG_DYNLOAD_REL_FROM_MEM_STATIC

    使能obj文件从指定内存中加载到指定静态空间中的功能

    YES/NO

    NO

    LOSCFG_KERNEL_DYNLOAD_REL

    LOSCFG_DYNLOAD_LITE_SYMTAB

    使能自定义符号表,用于简化符号表建立过程,从而可以减少OS动态加载器大小

    YES/NO

    NO

    LOSCFG_DYNLOAD_REL_FROM_MEM && !LOSCFG_DYNLOAD_REL_FROM_FS &&

    !LOSCFG_DYNLOAD_REL_FROM_MEM_STATIC

    LOSCFG_KERNEL_DYNLOAD_MODULE_NUM

    配置用户能够同时加载的模块数量

    1-65535

    10

    LOSCFG_KERNEL_DYNLOAD

    LOSCFG_DYNLOAD_CPP_LIBRARY

    支持cpp动态库的动态加载

    YES/NO

    NO

    LOSCFG_KERNEL_EXTKERNEL

    须知: 如果同时使能LOSCFG_KERNEL_NX(数据段不可执行,依赖Cortex-A芯片,其在menuconfig中对应的菜单项为:Kernel ---> Enable Data Sec NX Feature)和动态加载模块,则只支持动态加载.so文件,其他形式的加载可能会有异常,同时需要配置动态加载使用的堆大小(位于系统动态内存池的尾部)

  2. 设置动态加载使用的内存池地址(可选)。

    调用LOS_DynMemPoolSet接口可以设置动态加载使用的内存池。如果不设置,默认使用系统内存池。

  3. 设置so文件的动态加载策略。

    在不同的应用场景下,so文件可能以ZIP或者NOZIP的形式存放于存储介质中。如果需要从文件系统中加载so文件,由于压缩文件与非压缩文件读写操作的差异性,在初始化动态加载模块之前需要指明具体的加载策略。

    DYNLOAD_PARAM_S dynloadParam = {ZIP}; // 设置ZIP或NOZIP策略
    LOS_DynParamReg(&dynloadParam);       // 设置具体的加载策略
    

    说明: 以ZIP格式存储的so文件必须采用ZIP加载策略,而普通的so文件使用上述两种策略都可以加载成功,建议使用NOZIP策略。如果没有设置,默认采用NOZIP加载策略。

  4. 使用相对路径(可选)。

    如果在动态加载模块时想使用相对路径,可以通过LOS_PathAdd接口添加.so和.o文件所在的绝对路径:

    ret = LOS_PathAdd("/yaffs/bin/dynload");
    if (ret != LOS_OK) {
        printf("add relative path failed");
        return 1;
    }
    

    添加路径后,调用LOS_SoLoad、LOS_ObjLoad、LOS_MemLoad接口时传入文件名即可,动态加载会在添加的路径下查找指定文件。

    说明:

    • 只有在调用LOS_PathAdd接口添加路径后,才能在调用动态加载模块接口时使用相对路径。

    • 可以多次调用LOS_PathAdd接口添加多个相对路径。

    • 如果添加的多个路径下有相同文件名的模块,则在加载模块时按照添加的先后依次在所有路径中查找,且只加载第一个查找到的文件。

  5. 加载用户模块。

    动态加载模块支持加载.o和.so模块。

    • 使用LOS_ObjLoad接口动态加载obj文件(注意只能从文件系统中加载obj模块):

      if ((handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o")) == NULL) {
          printf("load module ERROR!!!!!!\n");
          return 1;
      }
      
    • 使用LOS_SoLoad接口动态加载so文件:

      if ((handle = LOS_SoLoad("/yaffs/bin/dynload/foo.so")) == NULL) {
          printf("load module ERROR!!!!!!\n");
          return 1;
      }
      

    说明: 对于so文件的动态加载:

    • 如果模块A需要模块B,即模块A依赖模块B,如果明确指明了A.so依赖B.so(编译A.so时将B.so作为编译参数),那么加载A模块时会自动将B模块也加载进来。如果没有明确指明A模块与B模块的依赖关系,那么在加载A模块之前,必须保证B模块已经被成功加载。

    • 对于不支持文件系统的平台,支持直接从指定内存空间动态加载so,可以调用LOS_MemLoad接口实现。

  6. 获取用户模块中的符号地址。

    • 在特定用户模块中查找符号、

      需要在某个特定用户模块中查找符号地址时,调用LOS_FindSymByName接口,并将LOS_FindSymByName的第一个参数置为需要查找的用户模块的句柄。

      if ((magic = LOS_FindSymByName(handle, "os_symbol_table")) == NULL) {
          printf("symbol not found\n");
          return 1;
      }
      
    • 在全局符号表中查找符号

      需要在全局符号表(即OS模块,包括本模块和所有其他用户模块)中查找某个符号地址时,调用LOS_FindSymByName接口,并将LOS_FindSymByName的第一个参数置NULL

      if ((funTestCase0 = LOS_FindSymByName(NULL, "printf")) == NULL) {
          printf("symbol not found\n");
          return 1;
      }
      
  7. 使用获取到的符号地址。

    LOS_FindSymByName返回符号地址(VOID *指针),对该符号地址转换类型后,可以使用该符号。下面针对数据类型符号和函数类型符号举例说明。

    • 整数类型符号

      现有待加载的test.c,有一全局变量UINT32 g_test = 0,可以通过如下代码获取g_test的地址。

      const char *g_pscOsOSSymtblFilePath = "/yaffs/bin/dynload/test.so";
      UINT32 * g_testPtr = NULL;
      INT8 *ptr = (INT8 *)NULL;
      if ((pOSSymtblHandler = LOS_SoLoad(g_pscOsOSSymtblFilePath)) == NULL) {
          return LOS_NOK;
      }
      if ((ptr = LOS_FindSymByName(pOSSymtblHandler, "g_test")) == NULL) {
          printf("g_uwTest not found\n");
          return LOS_NOK;
      }
      g_testPtr = (UINT32 *)ptr; /* 强制类型转换成真实的指针类型 */
      
    • 函数类型符号

      foo.c中定义了一个无参的函数test_0和一个有两个参数的函数test_2,编译生成foo.o。

      foo.c:
      int test_0(void)
      {  
          return 0;
      }
      int test_2(int i, int j)
      {
          return 0;
      }
      

      以下代码演示在demo.c中获取foo.o模块中的函数并调用。

      demo.c:
      typedef int (* TST_CASE_FUNC)();                /* 无形参函数指针类型声明 */
      typedef int (* TST_CASE_FUNC1)(UINT32);         /* 单形参函数指针类型声明 */
      typedef int (* TST_CASE_FUNC2)(UINT32, UINT32); /* 双形参函数指针类型声明 */
       
      TST_CASE_FUNC funTestCase0 = NULL;              /* 函数指针定义 */
      TST_CASE_FUNC2 funTestCase2 = NULL;
      int ret;
       
      handle = LOS_ObjLoad("/yaffs/bin/dynload/foo.o");
      funTestCase0 = LOS_FindSymByName(handle, "test_0");
      if (funTestCase0 == NULL) {
          printf("can not find the function name\n");
          return 1;
      }
      ret = funTestCase0();
       
      funTestCase2 = LOS_FindSymByName(NULL, "test_2");
      if (funTestCase2 == NULL){
          printf("can not find the function name\n");
          return 1;
      }
      ret = funTestCase2(42, 57);
      
  8. 卸载模块。

    调用LOS_ModuleUnload接口卸载某个模块,将需要卸载的模块句柄作为参数传入该接口。对于已被加载过的obj或so文件的句柄,卸载时统一使用LOS_ModuleUnload接口。

    ret = LOS_ModuleUnload(handle);
    if (ret != LOS_OK) {
        printf("unload module failed");
        return 1;
    }
    
  9. 销毁动态加载模块。

    不再需要动态加载功能时,调用LOS_LdDestroy接口,销毁动态加载模块。

    须知:

    • 销毁动态加载模块时会自动卸载所有已被加载的模块,销毁后模块不能再使用。

    • 销毁动态加载模块前需确认模块不再使用。

编译系统镜像

在RTOS_Lite源码根目录下执行make,编译系统镜像。

编译完成后,在根目录out/平台名的目录下,可以看到生成的系统镜像“vs_server.bin”文件。

说明: 如果待加载的.o和.so中包含了未定义的外部符号(既没有定义在这些.o和.so文件中,也不是一个合法的系统全局符号),在编译系统镜像文件时会提示相应错误,需排查错误信息,确保系统镜像编译正确。

准备系统环境

.so文件(或.o文件)需要和系统镜像文件配合使用。

  • 如果选择从文件系统加载模块,则模块文件必须放置在文件系统中,例如YAFFS、FAT等文件系统。

  • 如果选择从指定内存空间加载,则忽略下文的2,只需要将模块文件烧写到指定内存空间即可。

建议操作顺序:

  1. 烧写系统镜像文件到开发板的flash中。

  2. 将.so文件(或.o文件)存储到开发板的flash中,这里分为两种情况:

    • 如果模块文件保存在可热拔插的SD卡设备上,直接将SD卡插到开发板上即可。如果需要更新.so文件(或.o文件),可将SD卡插到电脑上更新。

    • 如果模块文件保存在不可热插拔的存储设备上,可通过如下两种方式更新文件:

      1. 将模块文件编译进文件系统镜像,然后烧写文件系统镜像到开发板的flash中。

      2. 启动LiteOS系统后,通过tftp命令下载.so文件(或.o文件),示例命令如下:

        tftp -g -l /yaffs/bin/dynload/foo.so -r foo.so 10.67.211.235
        
  3. 启动系统进行验证

注意事项

  • 编译选项:

    • .o模块的编译选项中需要添加“-nostdlib -fno-PIC”选项。

    • .so模块的编译选项中需要添加“-nostdlib -fPIC -shared”选项。

  • 安全性:

    • 对于加载的模块文件,需要提供者保证它的来源可靠并且安全。强烈建议禁止应用系统从SD卡/U盘等外部高风险介质加载文件,如果确实需要,则由提供者/应用层保证文件的可靠性,如果由此产生损失或者问题,华为不承担任何责任。

    • 如果被加载的文件有问题,可能导致包括但不限于设备损坏、数据泄露/被篡改等一系列的问题,对此华为不承担任何责任。

  • 其他限制:

    • 在编译系统镜像之前必须确保所需加载的.o与.so已经就绪,这样才能保证后续在编译系统镜像文件时,这些.o和.so所调用到的外部符号信息已经集成到最终的系统镜像中。

    • 在加载.o和.so模块时,一旦发现该模块引用的外部符号被重复定义在其它没有依赖关系的模块中时,会拒绝对本次引用做重定位,导致加载该模块失败。为了避免使用异常,用户应该事先确保所有待加载的模块内部没有重复定义的符号(变量或函数接口)。

编程实例

实例描述

示例中,sample_foo.c中定义了一个无参的函数test_0和一个有两个参数的函数test_2,编译生成foo.o。在sample_Dynamic_loading.c中演示获取foo.o模块中的函数并调用。 sample_Dynamic_loading.c代码实现如下:

编程示例

typedefint (* TST_CASE_FUNC)(VOID);                 /* 无形参函数指针类型声明 */
typedefint (* TST_CASE_FUNC2)(UINT32, UINT32);     /* 双形参函数指针类型声明 */
TST_CASE_FUNC funTestCase0 = NULL;                /* 函数指针定义 */
TST_CASE_FUNC2 funTestCase2 = NULL;
int ret;
void *handle = NULL;
handle = LOS_ObjLoad("/jffs0/bin/dynload/dynload_test.o");
funTestCase0 = LOS_FindSymByName(handle, "test_0");
if (funTestCase0 == NULL) {
    printf("can not find the function name\n");
    return;
}
ret = funTestCase0();                            /* 调用该函数指针 */
funTestCase2 = LOS_FindSymByName(NULL, "test_2");
if (funTestCase2 == NULL){
    printf("can not find the function name\n");
    return;
}
ret = funTestCase2(42, 57);                      /* 调用该函数指针 */
ret = LOS_ModuleUnload(handle);
if (ret != LOS_OK) {
    printf("unload module failed");
    return;
}
LOS_LdDestroy();

编译运行得到的结果为:

Huawei LiteOS#
test_0
test_2: 42 57

低功耗框架

概述

低功耗框架是低功耗模块的统一管理框架,提供对Tickless、系统休眠、休眠唤醒等多种低功耗能力的支持。

低功耗框架默认使能Tickless。此外,由于低功耗特性与具体业务和硬件有较强的相关性,为了更好地适配硬件、扩展功能,低功耗框架开放了相关功能接口方便开发者适配。同时,在默认的低功耗框架中也可以自定义具体策略。

接口名

描述

LOS_PowerMgrInit

初始化低功耗框架,入参为框架参数指针,可以自定义低功耗策略及具体休眠动作。注意入参不能为空,否则将注册失败。

LOS_LowpowerHookReg

注册低功耗框架休眠入口函数,系统idle时调用该休眠函数(可不注册,LOS_PowerMgrInit初始化时会注册默认入口,使用系统自带的低功耗框架)。

低功耗框架错误码

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_LPM_INIT_NULL

0x02002100

传递给低功耗框架初始化接口的参数为空。

确保传入的参数不为空。

须知: 错误码定义见“错误码简介”。8~15位代表的所属模块为低功耗模块,值为0x21。

Tickless

概述

基本概念

Tickless机制是一种新的定时机制,它使用动态时钟中断替代周期性时钟定时。

任务调度通常通过时钟中断触发。在周期性定时机制下,每一次时钟中断都会检测当前调度条件是否满足,但能触发任务调度的时钟中断往往只占很小一部分。软件定时器任务必须通过时钟中断触发才能实现,每一次时钟中断也会检测软件定时器是否到期,若到期则调度该定时器的回调函数,而大部分时钟中断发生时并没有软件定时器到期。Tickless机制是通过预期省略不必要的时钟中断,确定下一次“有意义”的时钟中断的产生时刻,而不让“无意义”的时钟中断产生。通过省去CPU空闲时期无意义的时钟中断,可以有效降低系统的功耗。

运作机制

LiteOS的Tickless机制,采用的是“idle模式”,即在CPU空闲时期(idle任务中)开启Tickless机制。通过计算下一次任务调度的时间和下一次软件定时器到期的时间,将两者中的较小值设定为下一次时钟中断到来的时间,从而减少无意义的时钟中断。

开发指导

使用场景

系统CPU长时间处于空闲状态,且系统对功耗要求较高。

开发流程

Tickless的典型开发流程:

  1. 打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Low Power Management Framework ---> Low Power Management Configure 菜单,使能Tickless。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_TICKLESS

    Tickless的裁剪开关

    YES/NO

    YES

    LOSCFG_KERNEL_LOWPOWER

  2. 当系统长时间不运行业务时,进入idle任务后根据可休眠时间自动进入tickless模式。

平台差异性

无。

注意事项

由于Tickless机制与系统时间都通过Tick中断定时器计时,因此在时间精度要求极高的场景下不建议使用。

各平台由于Systick寄存器位数差异,为避免寄存器溢出,在配置tickless休眠时间时,需要根据寄存器可接受最大数值、系统时钟频率、LOSCFG_BASE_CORE_TICK_PER_SECOND计算tickless可正常运行的最大tick数。例如cortex-m4平台由于SysTick->LOAD寄存器为24位,系统时钟频率为120M,配置LOSCFG_BASE_CORE_TICK_PER_SECOND为1000,则配置tickless休眠时间应不超过对应阈值:(2^24 - 1) / (120000000 / 1000) = 139 ticks。

系统休眠

概述

基本概念

系统休眠是一种兼顾系统性能和能耗的低功耗机制,用户可通过自行配置休眠策略,在待机场景下调整系统时钟频率、调整外设工作模式或下电等,从而节省功耗,提升续航能力。

系统休眠的低功耗管控主要支持:

  • 睡眠模式。

  • 睡眠投票机制。

  • 系统调频。

低功耗流程注册结构体

目前,系统休眠支持浅睡、深睡两种模式。系统休眠默认包含浅睡逻辑,若用户有自定义休眠逻辑或深睡的需求,可以注册低功耗流程结构体struct PowerMgrParameter。

typedef struct {
    PowerMgrRunOps runOps;                  /* power manager run operations */
} PowerMgrParameter; 

typedef struct {
#ifdef LOSCFG_KERNEL_DYN_FREQ
    UINT32 (*changeFreq)(LosFreqMode freq); /**< Tune system frequency */
#endif
#if defined (LOSCFG_KERNEL_LIGHTSLEEP) || defined (LOSCFG_KERNEL_DEEPSLEEP)
    LosSleepMode (*getSleepMode)(VOID);     /**< GetSleepMode, provided for special needs before entering sleep */
#endif
#ifdef LOSCFG_KERNEL_LIGHTSLEEP
    VOID (*enterLightSleep)(VOID);    /**< Enter light sleep */
#endif
#ifdef LOSCFG_KERNEL_DEEPSLEEP
#ifdef LOSCFG_LOWPOWER_SLEEP_USERCONFIG
    VOID (*userPreConfig)(VOID);      /**< UserPreconfig, provided for special needs before entering sleep */
    VOID (*userPostConfig)(VOID);     /**< UserPostconfig, provided for special needs after wakeup */
#endif
    VOID (*systemWakeup)(VOID);       /**< System wakeup */
    VOID (*suspendConfig)(VOID);      /**< Supend device before entering deep sleep */
    VOID (*resumeConfig)(VOID);       /**< Resume device after wakeup from deep sleep */
#ifdef LOSCFG_KERNEL_SMP
    VOID (*otherCoreResume)(VOID);    /**< Other core Resume for multi-core scenes */
#endif
    VOID (*enterDeepSleep)(VOID);     /**< Enter deep sleep */
    VOID (*setWakeUpTimer)(UINT32 timeout);   /**< Set wakeup timer */
    UINT32 (*withdrawWakeUpTimer)(VOID);      /**< Withdraw wakeup timer */
#ifdef LOSCFG_KERNEL_RAM_SAVE
    VOID (*contextSave)(VOID);        /**< Context save */
    VOID (*contextRestore)(VOID);     /**< Context restore */
#endif
#endif
#ifdef LOSCFG_LOWPOWER_SLEEP_WFI
    VOID (*enterWFI)(VOID);     /**< Enter WFI mode */
#endif
} PowerMgrRunOps;

开发指导

用户可根据具体业务场景制定休眠策略,定义禁止/允许进入的睡眠模式、选择不同的睡眠模式。由于深睡等模式可能涉及到具体硬件和时钟,所以用户需自行实现相应函数以适配硬件。同时系统休眠模块在流程初始和结束阶段均有预留接口,对于有特殊需求的场景,可以进行睡眠流程的扩展。

系统休眠模块为用户提供如下功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

系统休眠开关

LOS_EnableLowPower

开启系统休眠,仅在debug模式生效。

LOS_DisableLowPower

关闭系统休眠,仅在debug模式生效。

LOS_GetLowPowerStatus

获取系统休眠开关状态,仅在debug模式生效。

系统休眠时间

LOS_SleepTicksGet

获取系统休眠时间。

系统投票

LOS_PowerMgrSleepLock

禁止进入指定的睡眠模式。

LOS_PowerMgrSleepUnLock

允许进入指定的睡眠模式。

系统调频

LOS_PowerMgrChangeFreq

系统调频。

默认的系统休眠流程如图1所示。

图 1 系统休眠流程图

在进入系统休眠时,首先进行睡眠前期的配置,根据系统可睡眠时间及用户定义的休眠策略,获取睡眠模式。

  • 若为深睡模式,则会执行深睡前期的配置,例如屏蔽中断、保存设备状态等。随后将挂起设备并设置唤醒源,系统进入深睡模式。

  • 若为浅睡或其他模式,则执行对应流程。通常情况下,Tickless将配合系统休眠浅睡共同使用,以屏蔽Tick中断。用户也可使用自定义的Tick屏蔽和恢复机制。默认浅睡仅进入Tickless+WFI模式。

开发流程

系统休眠的典型开发流程:

  1. 打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Low Power Management Framework ---> Low Power Management Configure菜单。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_LIGHTSLEEP

    系统休眠的浅睡模式裁剪开关

    YES/NO

    NO

    LOSCFG_KERNEL_LOWPOWER

    LOSCFG_KERNEL_DEEPSLEEP

    系统休眠的深睡模式裁剪开关

    YES/NO

    NO

    LOSCFG_KERNEL_LOWPOWER,且需要硬件平台支持

    LOSCFG_KERNEL_WAKEUPTIMER_CALIBRATION

    系统休眠深睡模式外部定时器校准tick开关

    YES/NO

    YES

    LOSCFG_KERNEL_DEEPSLEEP && LOSCFG_KERNEL_LOWPOWER,且需要硬件平台支持

    LOSCFG_KERNEL_RAM_SAVE

    系统休眠时深睡模式下的内存保存开关

    YES/NO

    NO

    LOSCFG_KERNEL_DEEPSLEEP,且需要硬件平台支持

    LOSCFG_LOWPOWER_SLEEP_USERCONFIG

    系统休眠时前/后处理开关

    YES/NO

    NO

    LOSCFG_KERNEL_DEEPSLEEP

    LOSCFG_KERNEL_DYN_FREQ

    系统休眠的调频裁剪开关

    YES/NO

    NO

    LOSCFG_KERNEL_LOWPOWER,且需要硬件平台支持

    LOSCFG_LOWPOWER_SLEEP_VETO

    系统休眠的投票裁剪开关

    YES/NO

    NO

    LOSCFG_KERNEL_LOWPOWER

    LOSCFG_LOWPOWER_SLEEP_BY_TICK

    系统休眠模式依照就绪时间自动识别开关

    YES/NO

    YES

    LOSCFG_KERNEL_LOWPOWER

    LOSCFG_LOWPOWER_SLEEP_WFI

    系统休眠WFI模式自定义配置开关

    YES/NO

    NO

    LOSCFG_KERNEL_LOWPOWER

    LOSCFG_MAX_SLEEP_TIME

    系统休眠最大睡眠阈值配置

    INT

    10000

    LOSCFG_KERNEL_LOWPOWER

    LOSCFG_MIN_LIGHT_SLEEP_TIME

    系统休眠最小浅睡阈值配置

    INT

    50

    LOSCFG_KERNEL_LIGHTSLEEP

    LOSCFG_MIN_DEEP_SLEEP_TIME

    系统休眠最小深睡阈值配置

    INT

    2000

    LOSCFG_KERNEL_DEEPSLEEP

    LOSCFG_DELTA_TICKS

    系统休眠深睡tick补偿配置

    INT

    1

    LOSCFG_KERNEL_DEEPSLEEP

    LOSCFG_EXT_TIMER_FREQ

    系统休眠深睡外部时钟频率配置

    INT

    1

    LOSCFG_KERNEL_DEEPSLEEP

  2. 在Menuconfig中配置系统休眠唤醒策略,包括最小浅睡/深睡阈值、最大睡眠阈值;如果开启深睡模式,根据需要选择是否开启自定义WFI、休眠前后处理、深睡tick补偿以及外部时钟频率等。

  3. 适配对应睡眠模式的休眠接口,详见“低功耗流程注册结构体”。

  4. 当系统业务繁忙时,调用LOS_PowerMgrSleepLock接口禁止进入指定的睡眠模式(深睡、浅睡);当系统业务空闲时,调用LOS_PowerMgrSleepUnLock接口允许进入指定的睡眠模式。注意这两接口应该是配套使用的。

  5. 系统待机时,根据业务投票及系统实际可休眠时长,自动进入相应休眠模式进行休眠。

注意事项

  • 开启系统深睡模式时,建议在”低功耗流程注册结构体”的子结构体成员struct PowerMgrRunOps的enterDeepSleep钩子函数中将cache写回内存。

  • 低功耗提供的调频机制,如果在系统启动之后和业务首次调频之前进入空闲状态,系统会默认把频率调到最高。

编程实例

实例描述

本实例在cortex-M架构的CPU下实现如下功能:

  1. 注册低功耗流程框架,适配深睡、RTC使能等功能接口;

  2. 创建用户任务,并在任务中延时5000Ticks,使系统进入深睡模式。

编程示例

前提条件:在menuconfig菜单中完成系统休眠深睡的配置,同时配置用户休眠阈值:LOSCFG_MIN_SLEEP_TIME、LOSCFG_MAX_SLEEP_TIME以及LOSCFG_MIN_DEEP_SLEEP_TIME。

部分代码实现如下:

  1. 注册低功耗框架

    #ifdef LOSCFG_KERNEL_LOWPOWER
    #include "los_lowpower.h"
    #endif
    const PowerMgrParameter gPowerMgrParameter = {       // 用户自定义系统休眠配置,成员函数用户自行按需实现
        .runOps = {
    #ifdefLOSCFG_KERNEL_LIGHTSLEEP
            .enterLightSleep = EnterLightSleep,          // 浅睡动作,此处设置为进入WFI模式
            .getSleepMode = GetSleepMode,                // 获取用户休眠模式
    #endif
    #ifdefLOSCFG_KERNEL_DEEPSLEEP
            .suspendConfig = SuspendDevice,              // 关闭外设动作
            .resumeConfig = ResumeDevice,                // 恢复外设
            .enterDeepSleep = EnterDeepSleep,            // 深睡动作,此处设置为STM32的standby模式
            .setWakeUpTimer = SetWakeUpTimer,            // 设置唤醒源,此处设置为根据入参(休眠时间)使能外部RTC
            .withdrawWakeUpTimer = WithdrawWakeUpTimer,  // 取消唤醒源,此处设置为停止RTC计时
    #endif
        }
    };
    INT32 main(VOID)
    {
        UINT32 ret;
    #ifdef LOSCFG_KERNEL_LOWPOWER
        // 注册低功耗流程框架,若仅需默认的Tickless及浅睡模式,可调用LOS_PowerMgrInit(NULL)注册系统默认框架。
        LOS_PowerMgrInit(gPowerMgrParameter);
    #endif
        HardwareInit();
        printf("[main]: Hello, welcome to liteos !\n");
        PRINT_RELEASE("********Hello LiteOS ********\n"
                      "\nversion : %s\nopen-version : %s %u.%u.%u\n"
                      "build data : %s %s\n\n"
                      "**********************************\n",
            HW_LITEOS_VER, HW_LITEOS_SYSNAME, MAJ_V, MIN_V, REL_V, __DATE__,
            __TIME__);
        OsSetMainTask();
        OsCurrTaskSet(OsGetMainTask());
        ret = OsMain();
        if (ret != LOS_OK) {
            return LOS_NOK;
        }
        RTC_Start();
        g_kernelInit = 1;
        OsStart();
        return LOS_OK;
    }
    
  2. 创建任务,并在任务入口函数中执行延时操作:

    STATIC VOID task_demo1(VOID)
    {
        while (1) {
            osDelay(5000); // 任务延时5000Ticks
        }
    }
    

结果验证

由于设置的深睡阈值为2000,任务延时5000Ticks,所以系统会进入深睡模式:

如上图所示,系统每隔5s将会从standby模式唤醒。

休眠唤醒

概述

基本概念

休眠唤醒(RunStop)是一套保存系统现场镜像以及从现场镜像中快速恢复运行的机制。

用户在系统运行的某一时刻调用休眠唤醒接口,可以将系统此时的状态快照(CPU现场及内存)保存到Flash上。系统重启后,可以通过快照恢复系统,使系统从快照保存的状态处继续运行。

运作机制

业务模块初始化并保持稳定运行后,调用休眠唤醒接口保存系统快照,这份快照包含了业务模块所需的所有CPU现场及内存状态。系统运行一段时间后,业务模块空闲时,主核主动下电进入低功耗模式。当有业务需要处理时,MCU发送特定数据包唤醒主核上电,硬件状态恢复后,系统从Flash快照处恢复运行,这样系统重新上电后无需重新初始化业务模块,从而实现业务模块的快速唤醒。

开发指导

使用场景

如果希望系统运行一段时间后进入低功耗模式,当有业务到来时,系统能够快速恢复到低功耗模式前的状态,从而快速恢复业务处理能力。对于这种应用场景,可以通过休眠唤醒实现。

在某项目中,休眠唤醒被用于实现芯片的休眠唤醒:系统业务运行稳定后,保存当前系统状态的快照。当系统进入空闲状态时,主核主动下电进入低功耗模式,当业务消息到来时MMU对主核上电,系统从快照处快速启动,恢复执行。以WIFI芯片为例,RunStop可以实现主核在低功耗模式下WIFI状态不断连,以及WIFI状态的快速唤醒。

功能

休眠唤醒模块为用户提供如下功能,接口详细信息可以查看API参考。

接口名

描述

LOS_MakeImage

将系统状态快照保存到指定介质中

开发流程

图 1 休眠唤醒流程图

  1. 使能低功耗框架和休眠唤醒。

    打开菜单,相关配置项的菜单路径为:Kernel ---> Enable Extend Kernel ---> Enable Low Power Management Framework ---> Low Power Management Configure。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_LOWPOWER

    低功耗框架裁剪开关

    YES/NO

    YES

    LOSCFG_KERNEL_EXTKERNEL

    LOSCFG_KERNEL_RUNSTOP

    休眠唤醒开关

    YES/NO

    NO

    休眠唤醒依赖Flash、Uboot和bestfit内存管理算法。Nand Flash的宏开关为LOSCFG_DRIVERS_MTD_NAND,spi_nor flash的宏开关为LOSCFG_DRIVERS_MTD_SPI_NOR

  2. 初始化低功耗框架。

    入口函数main中,在OsStart前调用 LOS_PowerMgrInit 初始化低功耗框架为系统默认框架,示例代码如下:

    #ifdef LOSCFG_KERNEL_LOWPOWER
    #include "los_lowpower.h"
    const PowerMgrRunOps runOps = {
        .getSleepMode = NULL,
    };
    
    UINT32 OsPreSystemConfig(VOID)
    {
        PowerMgrParameter param;
        param.runOps = runOps;
        return LOS_PowerMgrInit(&param);
    }
    
    #endif
    LITE_OS_SEC_TEXT_INIT int main(void)
    {
        ......
    #ifdef LOSCFG_KERNEL_LOWPOWER
        ret = OsPreSystemConfig();
        if (ret != LOS_OK) {
            return LOS_NOK;
        }
    #endif
        OsStart();
        ......
    }
    
  3. 编写快照业务代码。

    业务代码入口函数为app_init,该函数位于源码根目录的“self_src/targets/board/$(LITEOS_PLATFORM)/os_adapt/os_adapt.c“文件中。

    在快照业务代码后调用LOS_MakeImage将系统当前的状态快照保存到指定介质中,并通过MAKE_WOW_IMAGE宏控制非快照业务代码的编译,以实现快照镜像和全部镜像的编译,示例代码如下。

    void wakeup_callback(void)
    {
    #ifdef LOSCFG_KERNEL_CPUP
        LOS_CpupReset();
    #endif
        (VOID)LOS_HwiEnable(83);
    }
    #define NOR_ERASE_ALIGN_SIZE    (64 * 1024)
    #define NOR_READ_ALIGN_SIZE     1
    #define NOR_WRITE_ALIGN_SIZE    1
    int flash_read(void *memaddr, size_t start, size_t size)
    {
        return spiflash_read(memaddr, start, size);
    }
    int flash_write(void *memaddr, size_t start, size_t size)
    {
        spiflash_erase(start, size);
        return spiflash_write(memaddr, start, size);
    }
    void app_init(void)
    {
    #ifdef LOSCFG_KERNEL_RUNSTOP
        RUNSTOP_PARAM_S stRunstopParam;       // 定义一个LOS_MakeImage的参数变量
    #endif
    #ifdef LOSCFG_DRIVERS_MTD_SPI_NOR
        (void)spinor_init();
        mtd = get_mtd("spinor");
    #endif
    #if defined(LOSCFG_KERNEL_RUNSTOP) && defined(LOSCFG_DRIVERS_MTD_SPI_NOR)
        memset(&stRunstopParam, 0, sizeof(RUNSTOP_PARAM_S));
        /* LOS_MakeImage参数配置 */
        stRunstopParam.pfFlashReadFunc          = flash_read;             // 指定介质读函数
        stRunstopParam.pfFlashWriteFunc         = flash_write;            // 指定介质写函数
        stRunstopParam.pfIdleWakeupCallback     = idle_wakeup_callback;   // 指定从快照恢复后在idle任务中需要执行的回调函数
        stRunstopParam.pfImageDoneCallback      = NULL;                   // 指定快照镜像生成后的回调函数,此处为NULL
        stRunstopParam.pfWakeupCallback         = wakeup_callback;        // 指定从快照恢复后需要执行的回调函数       
        stRunstopParam.uwFlashEraseAlignSize    = NOR_ERASE_ALIGN_SIZE;   // 介质的擦除对齐参数
        stRunstopParam.uwFlashWriteAlignSize    = NOR_WRITE_ALIGN_SIZE;   // 介质的写对齐参数
        stRunstopParam.uwFlashReadAlignSize     = NOR_READ_ALIGN_SIZE;    // 介质的读对齐参数
        stRunstopParam.uwImageFlashAddr         = 0x100000;               // 烧写系统全镜像的介质起始地址
        stRunstopParam.uwWowFlashAddr           = 0x800000;               // 保存快照的介质起始地址
        LOS_MakeImage(&stRunstopParam);
    #endif
    #ifndef MAKE_WOW_IMAGE
    #ifdef LOSCFG_KERNEL_RUNSTOP
        PRINTK("Image length 0x%x\n", OsWowImageSizeGet());
    #endif
    #endif /* MAKE_WOW_IMAGE */
    }
    
  4. 配置WOW_SRC变量。

    在源码根目录“build/make/wow_scatter.mk”文件中配置WOW_SRC变量,将变量定义为调用休眠唤醒函数的业务源文件路径,如下所示,其中LITEOSTOPDIR指代“RTOS_Lite/self_src”源码目录。

    WOW_SRC := $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM)/os_adapt/os_adapt.c
    
  5. 编译系统镜像。

    1. 在根目录下执行make命令,编译全部业务代码。

      RTOS_Lite$ make
      
    2. 编译完成后,查看镜像段的排布,以验证快照镜像是否生成成功。进入系统镜像生成目录(例如Hi3556V200平台的镜像生成目录为“out/hi3556v200”,其他类推),可以看到生成的系统ELF文件vs_server,执行“readelf -S vs_server”命令查看section头信息,结果如图2所示。

      图 2 系统ELF文件vs_server readelf结果

      图2中显示了与休眠唤醒有关的段信息(包括段的名称、起始地址及偏移大小)。其中.wow_rodata为休眠唤醒镜像的只读数据段,.wow_text为代码段,.wow_data为数据段,.wow_bss为bss段。

    3. 查看休眠唤醒链接脚本“RTOS_Lite/self_src/tools/scripts/ld/wow.ld”的.text段,可以看到新增了wow.O(*.text*),它实现了将休眠唤醒的快照部分代码相关符号整合到同一个段中,如图3所示。

      图 3 链接脚本.text段

  6. 将全部镜像烧写到Flash。

    进入串口工具界面,可以参考如下命令,将全部镜像烧写到Flash的0x100000地址位。

    tftp 0x81000000 vs_server.bin;sf probe 0;sf erase 0x100000 0x300000;sf write 0x81000000 0x100000 0x300000;
    

    其中,“vs_server.bin”为系统镜像文件名,先将其烧写到内存中一段高地址位0x81000000。然后烧写到Flash,起始地址为0x100000,烧写长度为0x300000,即烧写的镜像文件大小不能超过3M,请根据实际的镜像大小调整长度值。

    说明: 用户可以根据实际环境自主选择镜像烧写工具及烧写方法。

  7. 加载全部镜像。

    可以参考如下命令,从Flash的0x100000地址处读取全部镜像,加载到内存地址0x80200000处。

    sf probe 0;sf read 0x80200000 0x100000 0x300000;
    

    执行如下命令,从内存地址0x80200000处启动系统。

    go 0x80200000;
    
  8. 根据系统反馈的快照镜像大小,从快照处恢复运行。

    图 4 系统启动时的打印信息

    根据系统启动时打印的信息,可以获取休眠唤醒快照镜像大小,如图4红框中所示,本示例中快照大小为0x170000。快照被写到介质的0x800000地址处(从3中可以获得保存快照的介质的起始地址为0x800000)。

    重启系统,可以参考如下命令在uboot中执行,从保存快照的介质的0x800000地址处读取0x170000大小的快照镜像,加载到内存地址0x80200000处,然后执行“go 0x80200000”从快照启动系统。

    sf probe 0;sf read 0x80200000 0x800000 0x170000;
    go 0x80200000;
    

    启动效果如图5所示。

    图 5 uboot执行命令后的打印信息

    系统成功从快照启动。

注意事项

  • 用户在编写休眠唤醒业务时,需要保证“#ifndef MAKE_WOW_IMAGE”前涵盖了快照的所有代码和数据,否则从快照恢复后的系统中不会包含所有休眠唤醒的业务。

  • 休眠唤醒依赖于存储介质,当前LiteOS依赖Flash,因此该功能依赖Flash,如果关闭Flash支持,休眠唤醒功能也会关闭。如果需要在其他存储介质上使能休眠唤醒,请参考当前Flash的配置进行适配。

CPU占用率

概述

基本概念

LiteOS的CPUP(Central Processing Unit Percent,CPU占用率)分为系统CPU占用率、任务CPU占用率和中断CPU占用率。

系统CPU占用率是指周期时间内系统的CPU占用率,用于表示系统一段时间内的闲忙程度,也表示CPU的负载情况。

任务CPU占用率指单个任务的CPU占用率,用于表示单个任务在一段时间内的闲忙程度。

中断CPU占用率指周期时间内的中断占用率,用于表示系统一段时间内的中断处理时间。

运作机制

LiteOS的CPUP采用任务级记录的方式,在任务切换时,记录任务的切入时间,在下一次任务切换时,记录任务的切出时间,以此计算出任务本次的运行时间,累加到任务的总占用时间上。

CPU占用率的计算方法:

系统CPU占用率=系统中除idle任务外其他任务运行总时间/系统运行总时间。

任务CPU占用率=任务运行总时间/系统运行总时间。

中断CPU占用率=中断运行总时间/系统运行总时间

开发指导

使用场景

通过系统CPU占用率,判断当前系统负载是否超出设计规格。

通过系统中各个任务的CPU占用率,判断各个任务的CPU占用率是否符合设计的预期。

功能

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

功能分类

接口名

描述

获取系统CPU占用率

LOS_HistorySysCpuUsage

获取系统CPU占用率,不包含idle任务。

获取任务CPU占用率

LOS_HistoryTaskCpuUsage

获取指定任务的CPU占用率。

LOS_AllCpuUsage

使能LOSCFG_CPUP_INCLUDE_IRQ且设置入参flag为0时,获取所有中断的CPU占用率。设置入参flag为非0,或者关闭LOSCFG_CPUP_INCLUDE_IRQ后,获取所有任务的CPU占用率,这里的任务也包含了idle任务。

重置CPU占用率

LOS_CpupReset

重置CPU占用率数据,包含系统和任务的CPU占用率。

暂停CPU占用率统计

LOS_CpupStop

暂停CPU占用率统计,包含中断和任务的CPU占用率不再更新。

开启CPU占用率统计

LOS_CpupStart

开启CPU占用率统计,包含中断和任务的CPU占用率将先清0,然后以此为起点,重新统计。

说明:

  • 通过上述接口获取到的CPU占用率是千分比,所以CPU占用率的有效表示范围为0~1000。系统CPU占用率为1000,表示系统满负荷运转。任务CPU占用率为1000,表示在一段时间内系统一直在运行该任务。

  • 获取CPU占用率有三种模式,通过入参mode设置:

  • CPUP_LAST_MULIT_RECORD(值为0,原为CPUP_LAST_TEN_SECONDS):表示获取最近多次采样记录内的CPU占用率,默认为10s。

  • CPUP_LAST_ONE_RECORD(值为1,原为CPUP_LAST_ONE_SECONDS): 表示获取最近1次采样记录的CPU占用率,默认为1s。

  • CPUP_ALL_TIME(值为0xffff),或除0和1之外的其他值:表示获取自系统启动以来的CPU占用率。

CPUP错误码

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_CPUP_NO_MEMORY

0x02001E00

初始化CPU占用率模块时,内存不足。

调整OS_SYS_MEM_SIZE,以确保有足够的内存供CPU占用率模块使用。

2

LOS_ERRNO_CPUP_TASK_PTR_NULL

0x02001E01

入参存在空指针。

传入正确的指针。

3

LOS_ERRNO_CPUP_NO_INIT

0x02001E02

CPU占用率模块没有初始化。

初始化CPU占用率模块后,再使用该模块。

4

LOS_ERRNO_CPUP_MAXNUM_INVALID

0x02001E03

传入LOS_AllCpuUsage接口的入参(最大线程数或中断数)是一个无效值。

传入正确的最大线程数或中断数。

5

LOS_ERRNO_CPUP_THREAD_NO_CREATED

0x02001E04

获取指定任务的CPU占用率时,发现该任务未创建。

系统只能统计已经被创建的任务的CPU占用率,检查传入的任务ID对应的任务是否已经创建。

6

LOS_ERRNO_CPUP_TSK_ID_INVALID

0x02001E05

调用获取指定任务的CPU占用率的接口时,传入了无效的任务ID。

检查传入的任务ID的正确性。

须知: 错误码定义见“错误码简介”。8~15位代表的所属模块为CPUP模块,值为0x1E。

开发流程

CPU占用率的典型开发流程:

  1. 打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Cpup菜单,完成CPU占用率模块的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_CPUP

    CPUP模块的裁剪开关。

    YES/NO

    YES

    LOSCFG_KERNEL_EXTKERNEL

    LOSCFG_CPUP_START_STOP

    CPUP动态启停开关。

    YES/NO

    NO

    LOSCFG_KERNEL_CPUP && !LOSCFG_SCHED_LOAD_BALANCE_CPUP

    LOSCFG_CPUP_INCLUDE_IRQ

    使能该配置项后,可以在LOS_HistoryTaskCpuUsage和LOS_AllCpuUsage接口中获取中断的CPU占用率。关闭该配置项后,只能获取任务的CPU占用率。

    YES/NO

    YES

    LOSCFG_KERNEL_CPUP && LOSCFG_ARCH_INTERRUPT_TAKEOVER

    LOSCFG_CPUP_CB_NUM_CONFIGURABLE

    使能该配置项,CPUP的中断相关控制块的资源数量可单独配置,不再依赖LOSCFG_PLATFORM_HWI_LIMIT,可优化CPUP的size。

    YES/NO

    NO

    LOSCFG_CPUP_INCLUDE_IRQ

    LOSCFG_CPUP_IRQ_CB_NUM

    CPUP的中断相关控制块的资源数量,根据实际的中断个数来配置,最大不超过LOSCFG_PLATFORM_HWI_LIMIT。

    [1,LOSCFG_PLATFORM_HWI_LIMIT]

    LOSCFG_PLATFORM_HWI_LIMIT

    LOSCFG_CPUP_CB_NUM_CONFIGURABLE

    LOSCFG_CPUP_SAMPLE_PERIOD

    CPUP的采样周期,时间单位是tick,表示CPUP软件定时器的周期运行时间间隔。

    [10,1000],实际还会限制时间[100ms,10s]

    LOSCFG_BASE_CORE_TICK_PER_SECOND

    LOSCFG_KERNEL_CPUP && !LOSCFG_SCHED_LOAD_BALANCE_CPUP

    LOSCFG_CPUP_HISTORY_RECORD_NUM

    CPUP的最大采样记录数目,实际为环形缓冲buffer,表示用户最多可查看最近几次采样记录的CPU占用率信息。

    [1,10]

    10

    LOSCFG_KERNEL_CPUP && !LOSCFG_SCHED_LOAD_BALANCE_CPUP

  2. 获取系统CPU使用率LOS_HistorySysCpuUsage。

  3. 获取指定任务或中断的CPU使用率LOS_HistoryTaskCpuUsage。

  4. 获取所有任务或所有中断的CPU使用率LOS_AllCpuUsage。

平台差异性

无。

注意事项

  • CPU占用率对性能有一定影响,而一般只有在产品开发时需要了解各个任务的占用率,因此建议在发布产品时,关闭CPU占用率。

  • 关闭配置项LOSCFG_CPUP_INCLUDE_IRQ后,系统中的中断耗时会被统计到中断发生的任务中,即被中断打断的任务中。

编程实例

实例描述

本实例实现如下功能:

  1. 创建一个测试CPUP的任务。

  2. 获取系统最近1s内所有任务或中断的CPUP。

  3. 获取系统(除idle任务外)最近10s内的总CPU占用率。

  4. 获取CPUP测试任务的CPUP。

编程示例

前提条件:打开菜单配置好CPU占用率模块。

代码实现如下:

#include <unistd.h >
#include "los_task.h"
#include "los_cpup.h"

#define MAXTASKNUM  32

UINT32 cpupUse;
UINT32 g_cpuTestTaskId;

VOID Example_CPUP(VOID)
{
    printf("entry cpup test example\n");
    while(1) {
        usleep(100);
    }
}

UINT32 Example_CPUP_Test(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S cpupTestTask;
    CPUP_INFO_S cpupInfo[MAXTASKNUM];

    /* 创建测试CPUP的任务 */
    memset(&cpupTestTask, 0, sizeof(TSK_INIT_PARAM_S));
    cpupTestTask.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_CPUP;
    cpupTestTask.pcName       = "TestCpupTsk";                           /* 测试任务名称 */
    cpupTestTask.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    cpupTestTask.usTaskPrio   = 5;
    cpupTestTask.uwResved   = LOS_TASK_STATUS_DETACHED;

    ret = LOS_TaskCreate(&g_cpuTestTaskId, &cpupTestTask);
    if(ret != LOS_OK) {
        printf("cpupTestTask create failed.\n");
        return LOS_NOK;
    }
    usleep(100);

    /* 系统中运行的任务或者中断数量 */
    UINT16 maxNum = MAXTASKNUM; 

    /* 获取系统所有任务或中断最近1s的CPU占用率 */
    cpupUse = LOS_AllCpuUsage(maxNum, cpupInfo, CPUP_LAST_ONE_SECONDS, 0);
    printf("the system cpu usage in last 1s: %d\n", cpupUse);

    /* 获取最近10s内系统(除idle任务外)总CPU占用率 */
    cpupUse = LOS_HistorySysCpuUsage(CPUP_LAST_TEN_SECONDS);
    printf("the history system cpu usage in last 10s: %d\n", cpupUse);

    /* 获取指定任务在最近1s的CPU占用率,该测试例程中指定的任务为上面创建的CPUP测试任务 */
    cpupUse = LOS_HistoryTaskCpuUsage(g_cpuTestTaskId, CPUP_LAST_ONE_SECONDS);
    printf("cpu usage of the cpupTestTask in last 1s:\n TaskID: %d\n usage: %d\n", g_cpuTestTaskId, cpupUse);

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

the system cpu usage in last 1s: 15
the history system cpu usage in last 10s: 3
cpu usage of the cpupTestTask in last 1s:
TaskID: 10
usage: 0

Trace

概述

基本概念

Trace实时记录系统行为,类似系统“录像”功能。在系统发生异常后,能辅助用户查看历史事件,定位问题。

运作机制

LiteOS的Trace采用静态代码打桩和缓冲区记录方式,在桩被执行时,获取事件发生的上下文,并写入到缓冲区。

在插桩函数的入参中需要提供监测的事件类型、事件操作的主体对象和事件的参数,这些信息会被写入缓冲区,同时缓冲区中也会记录事件发生的时间、系统中的任务信息等事件上下文信息。

开发指导

使用场景

  • 通过Trace了解系统运转的路径,理解系统运行机制。

  • 通过Trace分析系统发生异常前的操作,定位死机问题。

功能

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

功能分类

接口名

描述

配置Trace缓冲区

LOS_TraceInit

配置Trace缓冲区的地址和大小。

开启/停止Trace事件记录

LOS_TraceStart

开启事件记录。

LOS_TraceStop

停止事件记录。

操作Trace记录的数据

LOS_TraceRecordDump

输出Trace缓冲区数据。

LOS_TraceRecordGet

获取Trace缓冲区的首地址。

LOS_TraceReset

清除Trace缓冲区中的事件。

过滤Trace记录的模块

LOS_TraceEventMaskSet

设置事件掩码,仅记录某些模块的事件。

屏蔽特定中断号事件

LOS_TraceHwiFilterHookReg

注册过滤特定中断号事件的钩子函数。

插桩函数

LOS_TRACE_EASY

简易插桩。

LOS_TRACE

标准插桩。

说明:

  1. LOS_TRACE_EASY(TYPE, IDENTITY, params...) 简易插桩。

  • 一句话插桩,用户在目标源代码中插入该接口即可。

  • TYPE有效取值范围为[0, 0xF],表示不同的事件类型。

  • IDENTITY类型UINTPTR,表示事件操作的主体对象。

  • Params类型UINTPTR,表示事件的参数。 示例:

LOS_TRACE_EASY(1, userId0, userParam1, userParam2);
LOS_TRACE_EASY(2, userId0);
LOS_TRACE_EASY(1, userId1, userParam1);
LOS_TRACE_EASY(2, userId1);
LOS_TRACE_EASY(3, userParma1, userParam2, userParam3); // parmas 可复用IDENTITY字段(当IDENTITY不必要时)
  1. LOS_TRACE(TYPE, IDENTITY, params...) 标准插桩。

  • 相比简易插桩,支持动态过滤事件和参数裁剪,但使用上需要用户按规则来扩展。

  • TYPE用于设置具体的事件类型,可以在头文件los_trace.h中的enum LOS_TRACE_TYPE中自定义事件类型。定义方法和规则可以参考其他事件类型。

  • IDENTITY和Params的类型及含义同简易插桩。 示例:

1.在enum LOS_TRACE_MASK中定义事件掩码,即模块级别的事件类型。定义规范为TRACE_#MOD#_FLAG,#MOD#表示模块名,例如:
  TRACE_FS_FLAG = 0x2000
2.在enum LOS_TRACE_TYPE中定义具体事件类型。定义规范为#TYPE# = TRACE_#MOD#_FLAG | NUMBER,例如:
  FS_READ  = TRACE_FS_FLAG | 0; // 读文件
  FS_WRITE = TRACE_FS_FLAG | 1; // 写文件
3.定义事件参数。定义规范为#TYPE#_PARAMS(IDENTITY, parma1...) IDENTITY, ...
  其中的#TYPE#就是上面2中的#TYPE#,例如:
  #define FS_READ_PARAMS(fp, fd, flag, size)    fp, fd, flag, size
  宏定义的参数对应于Trace缓冲区中记录的事件参数,用户可对任意参数字段进行裁剪;
  当定义为空时,表示不监测该类型事件:
  #define FS_READ_PARAMS(fp, fd, flag, size) // 不监测文件读事件
4.在适当位置插入代码桩。定义规范为LOS_TRACE(#TYPE#, #TYPE#_PARAMS(IDENTITY, parma1...))
  LOS_TRACE(FS_READ, fp, fd, flag, size); // 读文件的代码桩,#TYPE#之后的入参就是上面3中的FS_READ_PARAMS函数的入参。
  1. LiteOS预置的Trace事件及参数均可以通过上述方式进行裁剪,参数详见“kernel_lite/include/los_trace.h”。

  2. Trace Mask事件过滤接口LOS_TraceEventMaskSet(UINT32 mask),其入参mask仅高28位生效(对应LOS_TRACE_MASK中某模块的使能位),仅用于模块的过滤,暂不支持针对某个特定事件的细粒度过滤。例如:LOS_TraceEventMaskSet(0x202),则实际设置生效的mask为0x200(TRACE_QUE_FLAG),QUE模块的所有事件均会被采集。一般建议使用的方法为: LOS_TraceEventMaskSet(TRACE_EVENT_FLAG | TRACE_MUX_FLAG | TRACE_SEM_FLAG | TRACE_QUE_FLAG);

  3. 如果仅需要过滤简易插桩事件,通过设置Trace Mask为TRACE_MAX_FLAG即可。

  4. Trace缓冲区大小有限,事件写满之后会覆盖写,用户可通过LOS_TraceRecordDump中打印的CurEvtIndex识别最新事件。

Trace错误码

为快速定位可能导致Trace操作失败的原因,初始化Trace和启动Trace均需要返回对应的错误码。其他无返回值的接口如停止Trace、清除与Dump Trace 数据,错误原因均为Trace状态不合法,系统会直接打印错误原因。

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_TRACE_ERROR_STATUS

0x02001400

初始化Trace或启动Trace时状态不正确。

  • 删除重复的Trace初始化。
  • 启动Trace前先初始化Trace。

2

LOS_ERRNO_TRACE_NO_MEMORY

0x02001401

初始化Trace时,缓冲区申请失败。

解决方案有两个:

  • 配置LOSCFG_TRACE_BUFFER_SIZE减小Trace缓冲区的大小,可以通过menuconfig菜单项修改:Kernel ---> Enable Extend Kernel ---> Enable Trace Feature ---> Trace work mode (Offline mode) ---> Trace record buffer size。
  • 配置LOSCFG_BASE_CORE_TSK_LIMIT减少最大任务数,可以通过menuconfig菜单项修改:Kernel ---> Basic Config ---> Task Max Task Number。

3

LOS_ERRNO_TRACE_BUF_TOO_SMALL

0x02001402

初始化Trace时,缓冲区size设置过小。

配置LOSCFG_TRACE_BUFFER_SIZE增大Trace缓冲区的大小,menuconfig菜单项为:Kernel ---> Enable Extend Kernel ---> Enable Trace Feature ---> Trace work mode (Offline mode) ---> Trace record buffer size。

4

LOS_ERRNO_TRACE_BUF_IS_NULL

0x02001403

在离线模式下初始化Trace,且动态内存关闭,指定缓冲区为NULL。

有如下解决方案:

  • 初始化Trace时传入有效的缓冲区地址。
  • 开启动态内存。
  • 使用在线模式。

须知: 错误码定义见“错误码简介”。8~15位代表的所属模块为Trace模块,值为0x14。

开发流程

Trace的典型开发流程:

  1. 打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Trace Feature菜单,完成Trace的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_TRACE

    Trace模块的裁剪开关。

    YES/NO

    NO

    LOSCFG_KERNEL_EXTKERNEL

    LOSCFG_RECORDER_MODE_OFFLINE

    Trace工作模式为离线模式。

    YES/NO

    YES

    LOSCFG_KERNEL_TRACE

    LOSCFG_RECORDER_MODE_ONLINE

    Trace工作模式为在线模式。

    YES/NO

    NO

    LOSCFG_KERNEL_TRACE

    LOSCFG_TRACE_CLIENT_INTERACT

    使能与Trace IDE(LiteOS Studio)的交互,包括数据可视化和流程控制。

    YES/NO

    NO

    LOSCFG_KERNEL_TRACE

    LOSCFG_TRACE_PIPELINE_SERIAL

    选择串口作为IDE数据可视化通道。

    YES/NO

    YES

    LOSCFG_TRACE_CLIENT_INTERACT

    LOSCFG_TRACE_CONTROL_VIA_SHELL

    选择shell作为IDE流程控制的方式。

    YES/NO

    YES

    LOSCFG_TRACE_CLIENT_INTERACT && LOSCFG_SHELL

    LOSCFG_TRACE_CONTROL_AGENT

    选择agent作为IDE流程控制的方式。

    YES/NO

    NO

    LOSCFG_TRACE_CLIENT_INTERACT

    LOSCFG_TRACE_NO_CONTROL

    不启用IDE的流程控制。

    YES/NO

    NO

    LOSCFG_TRACE_CLIENT_INTERACT

    LOSCFG_TRACE_MSG_EXTEND

    记录系统更多的扩展信息。

    YES/NO

    NO

    LOSCFG_KERNEL_TRACE

    LOSCFG_TRACE_FRAME_CORE_MSG

    记录CPUID、中断状态、锁任务状态。

    YES/NO

    NO

    LOSCFG_TRACE_MSG_EXTEND

    LOSCFG_TRACE_FRAME_EVENT_COUNT

    记录事件的次序编号。

    YES/NO

    NO

    LOSCFG_TRACE_MSG_EXTEND

    LOSCFG_TRACE_FRAME_MAX_PARAMS

    配置记录事件的最大参数个数。

    INT

    3

    LOSCFG_KERNEL_TRACE

    LOSCFG_TRACE_BUFFER_SIZE

    配置Trace的缓冲区大小。

    INT

    2048

    LOSCFG_RECORDER_MODE_OFFLINE

  2. (可选)预置事件参数和事件桩(或使用系统默认的事件参数配置和事件桩)。

  3. (可选)调用LOS_TraceStop停止Trace后,清除缓冲区LOS_TraceReset(系统默认已启动trace)。

  4. (可选)调用LOS_TraceEventMaskSet设置需要监测的事件掩码(系统默认的事件掩码仅使能中断与任务切换),事件掩码参见“los_trace.h”中的LOS_TRACE_MASK定义。

  5. 在需要记录事件的代码起始点调用LOS_TraceStart。

  6. 在需要记录事件的代码结束点调用LOS_TraceStop。

  7. 调用LOS_TraceRecordDump输出缓冲区数据(函数的入参为布尔型,FALSE表示格式化输出,TRUE表示输出到Windows客户端)。

说明: 上述47中的接口,均封装有对应的shell命令,对应关系如下,命令介绍参见“Trace命令参考”:

  • LOS_TraceEventMaskSet —— trace_mask

  • LOS_TraceStart —— trace_start

  • LOS_TraceStop —— trace_stop

  • LOS_TraceRecordDump —— trace_dump

平台差异性

无。

注意事项

由于Trace会影响系统性能,同时考虑到一般只有在产品开发时才需要了解系统发生的事件,因此建议在产品发布时关闭Trace。

编程实例

实例描述

本实例实现如下功能:

  1. 创建一个用于Trace测试的任务。

  2. 设置事件掩码。

  3. 启动Trace。

  4. 停止Trace。

  5. 格式化输出Trace数据。

编程示例

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

代码实现如下:

#include "los_trace.h"

UINT32 g_traceTestTaskId;
VOID Example_Trace(VOID)
{
    UINT32 ret;

    LOS_TaskDelay(10);

    /* 开启trace */
    ret = LOS_TraceStart();
    if (ret != LOS_OK) {
        dprintf("trace start error\n");
        return;
    }

    /* 触发任务切换的事件 */
    LOS_TaskDelay(1);

    LOS_TaskDelay(1);

    LOS_TaskDelay(1);

    /* 停止trace */
    LOS_TraceStop();

    LOS_TraceRecordDump(FALSE);
}

UINT32 Example_Trace_test(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S traceTestTask;

    /* 创建用于trace测试的任务 */
    memset(&traceTestTask, 0, sizeof(TSK_INIT_PARAM_S));
    traceTestTask.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Trace;
    traceTestTask.pcName       = "TestTraceTsk";    /* 测试任务名称 */
    traceTestTask.uwStackSize  = 0x800;
    traceTestTask.usTaskPrio   = 5;
    traceTestTask.uwResved   = LOS_TASK_STATUS_DETACHED;
    ret = LOS_TaskCreate(&g_traceTestTaskId, &traceTestTask);
    if(ret != LOS_OK){
        dprintf("TraceTestTask create failed .\n");
        return LOS_NOK;
    }

    /* 系统默认情况下已启动trace, 因此可先关闭trace,然后清除缓存区后,再重启trace */
    LOS_TraceStop();
    LOS_TraceReset();

    /* 开启任务模块事件记录 */
    LOS_TraceEventMaskSet(TRACE_TASK_FLAG);

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

*******TraceInfo begin*******
clockFreq = 50000000
CurEvtIndex = 7
Index   Time(cycles)      EventType      CurTask   Identity      params    
0       0x366d5e88        0x45           0x1       0x0           0x1f         0x4          0x0
1       0x366d74ae        0x45           0x0       0x1           0x0          0x8          0x1f
2       0x36940da6        0x45           0x1       0xc           0x1f         0x4          0x9
3       0x3694337c        0x45           0xc       0x1           0x9          0x8          0x1f
4       0x36eea56e        0x45           0x1       0xc           0x1f         0x4          0x9
5       0x36eec810        0x45           0xc       0x1           0x9          0x8          0x1f
6       0x3706f804        0x45           0x1       0x0           0x1f         0x4          0x0
7       0x37070e59        0x45           0x0       0x1           0x0          0x8          0x1f
*******TraceInfo end*******

输出的事件信息包括:发生时间、事件类型、事件发生在哪个任务中、事件操作的主体对象、事件的其他参数。

  • Time(cycles):表示事件发生的时间,值是时钟的cycle数。

  • EventType:表示的具体事件可查阅头文件“los_trace.h”中的enum LOS_TRACE_TYPE。

  • CurTask:表示当前运行在哪个任务中,值为taskId。

  • Identity:表示事件操作的主体对象,可查阅头文件“los_trace.h”中的#TYPE#_PARAMS。

  • params:表示的事件参数可查阅头文件“los_trace.h“中的#TYPE#_PARAMS。

下面以序号为0的输出项为例,进行说明。

Index   Time(cycles)      EventType      CurTask   Identity      params 
0       0x366d5e88        0x45           0x1       0x0           0x1f         0x4          0x0
  • Time(cycles)可换算成时间,换算公式为cycles/clockFreq,单位为s。

  • 0x45为TASK_SWITCH事件,即任务切换事件,当前运行任务的taskId为0x1。

  • Identity和params的含义需要查看TASK_SWITCH_PARAMS宏定义:

    #define TASK_SWITCH_PARAMS(taskId, oldPriority, oldTaskStatus, newPriority, newTaskStatus) \
    taskId, oldPriority, oldTaskStatus, newPriority, newTaskStatus
    

    因为#TYPE#_PARAMS(IDENTITY, parma1...) IDENTITY, ...,所以Identity为taskId(0x0),第一个参数为oldPriority(0x1f)...

    说明: params的个数由menuconfig中Enable Trace Feature ---> Record max params配置,默认为3,超出的参数不会被记录,用户应自行合理配置该值。

综上所述,任务由0x1切换到0x0,0x1任务的优先级为0x1f,状态为0x4,0x0任务的优先级为0x0。

Perf

概述

基本概念

Perf为性能分析工具,依赖PMU(Performance Monitoring Unit)对采样事件进行计数和上下文采集,统计出其算法的热点(hot spot)分布和热路径(hot path)。

运作机制

基于事件采样原理,以性能事件为基础,当事件发生时,相应的事件计数器溢出发生中断,在中断处理函数中记录事件信息,包括当前的PC、当前运行的任务ID以及调用栈等信息。

开发指导

使用场景

通过Perf查找性能瓶颈和定位热点代码,更高效地优化系统性能。

功能

LiteOS的Perf模块为用户提供下面几种功能,接口详细信息可参见API参考。

功能分类

接口名

描述

初始化Perf

LOS_PerfInit

初始化PMU以及采样缓冲区。

开启/停止Perf采样

LOS_PerfStart

开启采样。

LOS_PerfStop

停止采样。

配置Perf采样事件

LOS_PerfConfig

配置采样事件的类型、周期等。

读取采样数据

LOS_PerfDataRead

读取采样数据到指定地址。

注册采样数据缓冲区的钩子函数

LOS_PerfNotifyHookReg

注册缓冲区水线到达的处理钩子。

LOS_PerfFlushHookReg

注册缓冲区刷cache的钩子。

去初始化Perf

LOS_PerfDeinit

去初始化PMU。

须知: 采样数据缓冲区为环形buffer,buffer中读过的区域可以覆盖写,未被读过的区域不能被覆盖写。

Perf错误码

为快速定位可能导致Perf操作失败的错误原因,Perf初始化和Perf采样配置接口均需要返回对应的错误码。具体描述如下表所示:

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_PERF_STATUS_INVALID

0x02002000

初始化Perf或配置采样事件时的状态不正确。

禁止重复初始化Perf,只能在Perf处于停止状态时才能配置采样事件。

2

LOS_ERRNO_PERF_HW_INIT_ERROR

0x02002001

初始化Perf时硬件PMU初始化失败。

确认平台是否具备硬件PMU单元,以及PMU的中断号是否正确。

3

LOS_ERRNO_PERF_TIMED_INIT_ERROR

0x02002002

初始化Perf时高精度Hrtimer PMU初始化失败。

确认平台是否具备高精度定时器单元。

4

LOS_ERRNO_PERF_SW_INIT_ERROR

0x02002003

初始化Perf时软件PMU初始化失败。

确认是否开启软件PMU功能。

5

LOS_ERRNO_PERF_BUF_ERROR

0x02002004

初始化Perf时buffer初始化失败。

配置的Perf buffer size过大或过小,可通过修改“los_config.h”中LOS_PERF_BUFFER_SIZE解决。

6

LOS_ERRNO_PERF_INVALID_PMU

0x02002005

配置采样事件的PMU未注册。

各类PMU可通过menuconfig进行选配, 确保对应的PMU已开启。

7

LOS_ERRNO_PERF_PMU_CONFIG_ERROR

0x02002006

配置采样事件的事件参数不合法。

检查LOS_PerfConfig中事件类型、事件ID、事件周期是否合法。

事件类型见enum PerfEventType,包括:

  • 硬件事件

    事件ID见enum PmuHwId;

    周期范围[0x101,0xFFFFFFFF];

    (注:armv8-m架构 cycle counter周期范围[0x101,0xFFFFFFFF],event counter周期范围[0x101,0xFFFF])

  • 软件事件

    事件ID见enum PmuSwId;

    周期范围>0;

  • 周期事件

    事件ID见enum PmuTimedId;

    周期范围>0;

8

LOS_ERRNO_PERF_CONFIG_NULL

0x02002007

配置采样事件的入参为空指针。

LOS_PerfConfig入参不能为空指针。

9

LOS_ERRNO_PERF_CONFIG_TASK_FILTER_ERROR

0x02002008

配置任务白名单失败。

检查拷贝白名单时memcpy_s入参是否正确。。

10

LOS_ERRNO_PERF_INIT_NO_MEMERY

0x02002009

初始化Perf时内存不足。

检查当前系统内存剩余或者关闭分任务统计功能以减少内存使用。。

须知: 错误码定义见“错误码简介”。8~15位代表的所属模块为Perf模块,值为0x20。

开发流程

Perf的典型开发流程:

  1. 打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Perf Feature菜单,完成Perf的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_PERF

    Perf模块的裁剪开关

    YES/NO

    NO

    LOSCFG_KERNEL_EXTKERNEL

    LOSCFG_PERF_CALC_TIME_BY_TICK

    Perf模块使用Tick作为计时单位

    YES/NO

    YES

    LOSCFG_KERNEL_PERF

    LOSCFG_PERF_CALC_TIME_BY_CYCLE

    Perf模块使用系统Cycle作为计时单位

    YES/NO

    NO

    LOSCFG_KERNEL_PERF

    LOSCFG_PERF_HW_PMU

    使能硬件PMU采样事件

    YES/NO

    NO

    LOSCFG_KERNEL_PERF&&

    LOSCFG_ARCH_PMU

    LOSCFG_PERF_TIMED_PMU

    使能高精度周期PMU采样事件

    YES/NO

    NO

    LOSCFG_KERNEL_PERF && LOSCFG_COMPAT_LINUX_HRTIMER

    LOSCFG_PERF_SW_PMU

    使能软件PMU采样事件

    YES/NO

    YES

    LOSCFG_KERNEL_PERF

    LOSCFG_KERNEL_PERF_PER_TASK

    Perf分任务统计模式开关

    YES/NO

    NO

    LOSCFG_KERNEL_PERF

    LOSCFG_KERNEL_PERF_SEPARATED_IRQ

    Perf独立中断统计模式开关

    YES/NO

    NO

    LOSCFG_KERNEL_PERF && LOSCFG_ARCH_INTERRUPT_TAKEOVER

  2. 调用LOS_PerfConfig配置需要采样的事件。

    Perf提供2种模式的配置, 及3大类型的事件配置:

    • 2种模式:计数模式(仅统计事件发生次数)、采样模式(收集上下文如任务ID、PC、backtrace等)。

    • 3种事件类型:CPU硬件事件(cycle、branch、icache、dcache等)、OS软件事件(task switch、mux pend、irq等)、高精度周期事件(cpu clock)。

  3. 在需要采样的代码起始点调用LOS_PerfStart。

  4. 在需要采样的代码结束点调用LOS_PerfStop。

  5. 调用输出缓冲区数据的接口LOS_PerfDataRead读取采样数据,并使用LiteOS Studio工具解析(单击调测工具里的性能分析tab页签)。

平台差异性

针对类型为硬件事件的采样,其依赖PMU硬件单元,且具有平台差异性。目前LiteOS Perf支持armv7 、armv8、risc-v、xea2 与LingLong架构的PMU。

注意事项

Perf用于性能调测,对系统性能有一定影响,建议在产品发布时,关闭Perf模块的裁剪开关LOSCFG_KERNEL_PERF。

编程实例

实例描述

本实例实现如下功能:

  1. 配置采样事件。

  2. 启动Perf。

  3. 执行需要统计的算法。

  4. 停止Perf。

  5. 输出统计结果。

编程示例

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

打开菜单,进入Kernel ---> Enable Extend Kernel ---> Enable Perf Feature,然后选中“Enable Hardware Pmu Events for Sampling, Enable Perf Per-Task mode”和“Enable Perf Separated IRQ mode”,使能PMU对硬件事件监测,并使能perf分任务统计以及中断独立统计。

代码实现如下:

#include "los_perf.h"
#include "los_memory.h"

#define TEST_PERF_BUFFER_SIZE    1024
#define OUTPUT_BUFFER_SIZE       0x200

#ifndef LOSCFG_KERNEL_MEM_ALLOC
STATIC CHAR g_buffer[TEST_PERF_BUFFER_SIZE] = {0};
#endif

STATIC VOID OsPrintBuff(const CHAR *buf, UINT32 num)
{
    UINT32 i;
    dprintf("num: ");
    for (i = 0; i < num; i++) {
        dprintf(" %02u", i);
    }
    dprintf("\n");
    dprintf("hex: ");
    for (i = 0; i < num; i++) {
        dprintf(" %02x", buf[i]);
    }
    dprintf("\n");
}

VOID PerfTestHWEvent(VOID)
{
    UINT32 ret;
    CHAR buf[OUTPUT_BUFFER_SIZE] = {0};
    UINT32 len;
    CHAR *buffer = NULL;

    PerfConfigAttr attr1 = {
        .eventsCfg = {
            .type        = PERF_EVENT_TYPE_HW,
            .events = {
                [0]      = {PERF_COUNT_HW_CPU_CYCLES, 0xFFFF},
                [1]      = {PERF_COUNT_HW_BRANCH_INSTRUCTIONS, 0xFFFFFF00},
            },
            .eventsNr    = 2,
            .predivided  = 1,             /* cycle counter increase every 64 cycles */
        },
        .taskIds         = {0},
        .taskIdsNr       = 0,            
        .needSample      = 0,
        .sampleType      = PERF_RECORD_IP | PERF_RECORD_CALLCHAIN,
    };
#ifdef LOSCFG_KERNEL_MEM_ALLOC
    buffer = (CHAR *)LOS_MemAlloc(m_aucSysMem1, TEST_PERF_BUFFER_SIZE);
#else
    buffer = g_buffer;
#endif

    ret = LOS_PerfInit(buffer, TEST_PERF_BUFFER_SIZE);
    if (ret != LOS_OK) {
        PRINT_ERR("perf init error %u\n", ret);
        return;
    }

    ret = LOS_PerfConfig(&attr1);
    if (ret != LOS_OK) {
        PRINT_ERR("perf config error %u\n", ret);
        return;
    }

    PRINTK("------count mode------\n");
    LOS_PerfStart(0);
    test();          /* code snippets concerned by users */
    LOS_PerfStop();

    PRINTK("--------sample mode------ \n");
    attr1.needSample = 1;
    LOS_PerfConfig(&attr1);
    LOS_PerfStart(2);
    test();         /* code snippets concerned by users */
    LOS_PerfStop();

    len = LOS_PerfDataRead(buf, OUTPUT_BUFFER_SIZE); /* get sample data */
    OsPrintBuff(buf, len); /* print data */
    memset(buf, 0, sizeof(buf));
#ifdef LOSCFG_KERNEL_MEM_ALLOC
    (VOID)LOS_MemFree(m_aucSysMem1, buffer);
#endif
    LOS_PerfDeinit();
}

结果验证

编译运行得到的结果为:

------count mode------
[EMG] perf count [tid 0 / Swt_Task]:
[EMG] [cycles] eventType: 0xff count: 0
[EMG] [branches] eventType: 0xc count: 0
[EMG] perf count [tid 1 / IdleCore000]:
[EMG] [cycles] eventType: 0xff count: 0
[EMG] [branches] eventType: 0xc count: 0
[EMG] perf count [tid 2 / app_Task]:
[EMG] [cycles] eventType: 0xff count: 40043481344
[EMG] [branches] eventType: 0xc count: 10002153167
[EMG] perf count [tid 3 / Swt_Task]:
[EMG] [cycles] eventType: 0xff count: 0
[EMG] [branches] eventType: 0xc count: 0
[EMG] perf count [tid 4 / IdleCore000]:
[EMG] [cycles] eventType: 0xff count: 100718720
[EMG] [branches] eventType: 0xc count: 5182967
[EMG] perf count [tid 5 / SerialShellTask]:
[EMG] [cycles] eventType: 0xff count: 0
[EMG] [branches] eventType: 0xc count: 0
[EMG] perf count [tid 6 / SerialEntryTask]:
[EMG] [cycles] eventType: 0xff count: 0
[EMG] [branches] eventType: 0xc count: 0
[EMG] perf count [irq@core 0]:
[EMG] [cycles] eventType: 0xff count: 891017
[EMG] [branches] eventType: 0xc count: 2948096
[EMG] perf count [irq@core 1]:
[EMG] [cycles] eventType: 0xff count: 889686
[EMG] [branches] eventType: 0xc count: 2948154
[EMG] time used: 44668000(us)
--------sample mode------
[EMG] time used: 44673000(us)
num:  00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ...
hex:  00 ef ef ef 00 00 00 00 14 00 00 00 60 00 00 00 02 00 00 00 90 2d 20 80 02 00 00 00 d4 5f 20 80 54 55 21 80 90 2d 20 80 02 ...

针对计数模式,系统在perf stop后会打印如下信息:

  • 当开启分任务统计功能后,会先打印每个任务的头信息,其后的事件计数为此任务的计数

    perf count [tid 2 / app_Task]
    

    任务ID:2,任务名称:app_Task。

  • 当关闭分任务统计功能后,会先打印每个核的头信息,其后的事件计数为此核的计数

    perf count [core 0]
    

    核ID:core 0。

  • 当开启中断独立统计功能后,会先打印每个核中断的头信息,其后的事件计数为此核中断的计数

    perf count [irq@core 0]
    

    核ID:core 0。

  • 事件计数的信息:

    [cycles] eventType: 0xff count: 40043481344
    

    事件名称:cycles、事件类型:0xff、事件发生的次数:40043481344。

    说明: 当采样事件为硬件PMU事件时,打印的事件类型为实际的硬件事件ID,非enum PmuHwId中定义的抽象类型。硬件事件ID详见各架构手册规定。

  • 针对采样模式,系统会将采样数据记录到用户指定的内存中,即LOS_PerfInit的入参。用户可以自行打印该内存的地址,然后通过JTAG口导出该片内存,再使用LiteOS Studio线下工具解析。用户也可以通过LOS_PerfDataRead将数据读到指定地址,进行查看或进一步处理。示例中OsPrintBuff为测试接口,其按字节打印Read到的采样数据,num表示第几个字节, hex表示该字节中的数值。

    须知:

    • 在RISC-V架构中,使用采样模式需要在定时中断处理函数中调用OsPerfCheckEventCount()函数。一般使用Tick中断处理函数。

    • Perf独立中断统计模式下,PMU中断不会被单独统计。另外,由于Timed PMU同样依赖于中断工作,计数可能与预期不一致。如果需要使用Timed PMU,请关闭Perf独立中断。

C++支持

概述

基本概念

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

运作机制

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

开发指导

功能

功能分类

接口名

描述

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

LOS_CppSystemInit

初始化C++构造函数

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

  • 第一个参数:init_array段的起始地址。

  • 第二个参数:init_array段的结束地址。

  • 第三个参数:标记调用C++特性时的场景,包括BEFORE_SCATTER(在分散加载快速启动阶段使用C++特性)、AFTER_SCATTER(在分散加载非快速启动阶段使用C++特性)、NO_SCATTER(在非分散加载特性中使用C++特性,或者在分散加载中不使用C++特性)。

开发流程

  1. 打开菜单,选择Kernel ---> Enable Extend Kernel ---> C++ Support,使能C++。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_KERNEL_CPPSUPPORT

    C++特性的裁剪开关

    YES/NO

    YES

    LOSCFG_KERNEL_EXTKERNEL

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

    由于在分散加载应用场景下,C++初始化中涉及到的相关代码、数据段加载的时机会有所不同,所以在开启和不开启分散加载这两种情况下,C++初始化函数LOS_CppSystemInit的调用有所不同。其中构造函数的初始化存在init_array这个段中,段区间通过变量__init_array_start、__init_array_end传递。

    • 不开启分散加载特性

      在不开启分散加载特性的情况下,需要在调用C++代码前,以NO_SCATTER(表示并未开启分散加载特性)参数调用LOS_CppSystemInit:

      LOS_CppSystemInit((unsigned long)&__init_array_start, (unsigned long)&__init_array_end, NO_SCATTER);
      
    • 开启分散加载特性

      • 如果需要在分散加载的快速启动阶段调用相关C++代码:

        在分散加载的快速启动阶段调用C++代码之前,以BEFORE_SCATTER(表示在分散加载快速启动阶段调用)参数调用LOS_CppSystemInit:

        LOS_CppSystemInit((unsigned long)&__init_array_start, (unsigned long)&__init_array_end, BEFORE_SCATTER);
        

        在分散加载的非快速启动阶段,再以AFTER_SCATTER(表示在分散加载非快速启动阶段调用)参数调用LOS_CppSystemInit:

        LOS_CppSystemInit((unsigned long)&__init_array_start, (unsigned long)&__init_array_end, AFTER_SCATTER);
        
      • 如果无需在分散加载的快速启动阶段调用相关C++代码:

        以NO_SCATTER参数调用一次LOS_CppSystemInit:

        LOS_CppSystemInit((unsigned long)&__init_array_start, (unsigned long)&__init_array_end, NO_SCATTER);
        
  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 */
    

注意事项

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

  • C++的cout函数依赖console功能。

编程实例

实例描述

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

  1. 编写C++代码。

  2. 在运行C++代码之前,先在app_init函数里初始化C++构造函数。此处未开启分散加载特性,所以只需以NO_SCATTER参数调用一次LOS_CppSystemInit即可。

  3. 在LOS_CppSystemInit之后调用编写好的C++代码。

编程示例

前提条件:打开菜单使能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

控制台

概述

基本概念

控制台是用于输入/输出系统信息的设备,可以向用户发送文本输出,并从用户接收文本输入。

开发指导

功能

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

功能分类

接口名

描述

初始化

system_console_init

初始化控制台。

virtual_serial_init

初始化虚拟串口。

去初始化

system_console_deinit

去初始化控制台。

virtual_serial_deinit

去初始化虚拟串口。

注意事项

  • 控制台功能依赖于文件系统。

  • 控制台输出任务的优先级为30。

  • 异常输出不经过控制台。

文件系统

功能概述

本章以Hi3556V200为例,介绍LiteOS目前支持的文件系统,包括VFS、FAT、YAFFS2和LITTLEFS。

各个文件系统功能概述

文件系统

功能特点概述

VFS

VFS是Virtual File System(虚拟文件系统)的缩写。通过采用标准的Unix系统接口,VFS可以访问位于不同物理介质上的不同文件系统,为各类文件系统提供一个统一的操作方式。

FAT

FAT文件系统是File Allocation Table(文件配置表)的简称,有FAT12、FAT16、FAT32。在可移动存储介质(U盘、SD卡、移动硬盘等)上使用FAT文件系统,可以使设备在Windows、Linux等桌面系统之间保持很好的兼容性。

YAFFS2

YAFFS是Yet Another Flash File System的简称,是一种针对Nand Flash的开源嵌入式文件系统,适用于大容量的存储设备,使得Nand Flash具有高效性和健壮性。

YAFFS2为文件系统提供了损耗平衡和掉电保护,保证在对文件系统修改的过程中,不会因发生意外而造成数据受损。LiteOS的YAFFS2支持多分区。

LITTLEFS

LITTLEFS(Little File System)是一种基于块的故障保护文件系统,使用小日志存储元数据,使用较大的写时复制(COW)结构存储文件数据,主要应用于NOR Flash闪存的文件管理。

LITTLEFS为文件系统提供了磨损平衡和掉电保护,保证在对文件系统修改的过程中,不会因发生意外而造成数据受损。

VFS

概述

基本概念

VFS是Virtual File System(虚拟文件系统)的缩写,是一个异构文件系统之上的软件粘合层,为用户提供统一的Unix文件操作接口。

由于不同类型的文件系统接口不统一,若系统中有多个文件系统类型,访问不同的文件系统则需要使用不同的非标准接口。但是,通过在系统中添加VFS层来提供统一的抽象接口,便可以屏蔽底层异构类型的文件系统差异,使得访问文件系统时无需关注底层的存储介质和文件系统类型,从而提高开发效率。

LiteOS中,VFS框架通过在内存中的树结构实现,树的每个结点都是一个inode结构体。设备注册和文件系统挂载后会根据路径在树中生成相应的结点。

VFS的两大主要功能如下:

  • 查找节点。

  • 统一调用(标准)。

运作机制

通过VFS层,可以使用标准的Unix文件操作函数(如open、read、write等)实现对不同介质上不同文件系统的访问。

VFS框架内存中的inode的关键在于u和i_private字段,一个是函数方法结构体的指针,一个是数据指针。inode树结点有三种类型:

  • 虚拟结点:作为VFS框架的虚拟文件,保持树的连续性,如/bin、/bin/vs、/dev。

  • 设备结点:/dev目录下,对应一个设备,如/dev/mmc0。

  • 挂载点:调用mount函数后生成,如/bin/vs/sd、/ramfs、/yaffs。

图 1 VFS树节点结构

系统初始化过程中会调用los_vfs_init(),将“/”作为root_inode。

文件描述符

本设计中文件描述符分两种,采用动态内存数组的形式来对普通文件描述符进行管理,采用全局数组管理网络描述符,这两个数组在内存上并不连续。

  • File描述符,即普通文件描述符,其中0保留作为系统stdin标准输入、1保留作为系统stdout标准输出、2保留作为系统stderr标准错误输出。用户可以分配的文件描述符从3开始到CONFIG_NFILE_DESCRIPTORS-1。

  • Socket描述符,该描述符的分配从CONFIG_NFILE_DESCRIPTORS开始。

开发指导

接口说明

头文件

接口名

描述

open_source/incubator-nuttx/liteos/fs/include/fs/fs.h

register_driver

注册字符驱动到文件系统。

unregister_driver

取消字符驱动注册。

register_blockdriver

注册块设备驱动到文件系统。

unregister_blockdriver

取消块设备驱动注册。

open_blockdriver

打开块设备。

close_blockdriver

关闭块设备。

find_blockdriver

查找块设备。

fs/include/los_fs.h

chattr

改变文件系统中的文件属性,目前仅FAT文件系统支持,修改后的文件属性目前支持4种:F_RDO(只读)、F_HID(隐藏)、F_SYS(系统文件)、F_ARC(存档)。

ls

列出目录内容。

rindex

从指定字符串的右面开始搜索第一次出现指定字符的位置。

说明: 关于文件属性:

  • 当前只支持修改FAT文件系统的文件属性,其他文件系统对只读等属性有各自的处理方式。

  • F_RDO(只读)、F_HID(隐藏)、F_SYS(系统文件)和F_ARC(存档)这4种属性并不冲突,可以任意修改。

  • 只读属性文件/目录不允许被删除。

  • 只读属性文件/目录允许重命名。

  • 只读文件不允许以O_CREAT、O_TRUNC,以及有含有写权限的方式打开。

  • 在LiteOS中隐藏属性文件可见,在Windows中不可见(不显示隐藏文件属性的情况下)。

  • 在LiteOS中文件加上隐藏属性,在Windows中只能通过命令行找到(在显示/不显示隐藏文件属性的情况下,该文件在Windows中都不可见)。

开发流程

推荐驱动开发人员使用VFS框架来注册/卸载设备,在应用层使用open()、read()操作字符设备文件来调用驱动。

  1. 打开菜单,选择FileSystem ---> Enable VFS,使能VFS。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_FS_VFS

    VFS框架的裁剪开关

    YES/NO

    YES

  2. 调用register_driver()、register_blockdriver()接口生成设备结点,调用mount()接口生成文件系统挂载结点。生成设备节点的开发流程详见适配文件操作的“开发指导”。

注意事项

  • VFS下所有文件系统创建的目录名和文件名长度最多为255个字符,若超过则创建失败。

  • VFS层支持的文件和目录最大全路径长度为259个字符,若超过,则创建失败

  • 所有文件系统中的文件名和目录名中只仅支持“-” 与“_”两种特殊字符,使用其他特殊字符可能造成不可预知的后果,请谨慎为之。

  • VFS下的所有文件系统,在创建模式或只写模式打开文件,此时文件权限为open接口mode入参值默认为0666,因VFS下的文件系统对文件权限模式支持不全, 推荐使用0666入参。

  • VFS在优化迭代过程中,对部分POSIX接口(rename、dup、opendir等)的异常错误分支的errno返回值会有更改,以便更贴近于POSIX标准,业务在使用errno返回值时请谨慎处理。

  • 对于块设备节点(例如“/dev/mmc0”),如果块设备驱动的bpos实现unlink回调函数且返回值为OK,则误调用unlink(“/dev/mmc0”)会导致块设备节点被删除和节点消失,并影响正常业务功能;如果想保持块设备节点常驻,不实现unlink回调即可。

  • ls命令会显示块设备节点文件的size,size值为块设备的容量值,如果不想显示对应的设备容量,块设备驱动的bpos不实现geometry回调函数即可。

  • VFS可以直接递归创建多级目录。

  • 设备分为字符设备和块设备,为了设备上的数据安全,需挂载相应文件系统后通过文件系统接口操作数据。

  • los_vfs_init() 只能调用一次,多次调用将会造成文件系统异常。

  • open打开一个文件时,参数O_RDWR、O_WRONLY、O_RDONLY互斥,只能出现一个,若出现2个或以上作为open的参数,会出现不可预知的错误,禁止使用。

  • 不建议open一个目录直接获取目录数据。如果要获取目录信息,可以调用opendir接口打开目录。

  • open()、read()、write()等为非线程安全接口,在多线程场景下,建议使用fopen()、fread()、fwrite()等接口。

  • 挂载文件系统请严格按照手册进行,错误挂载可能损坏设备及系统。

  • 挂载点必须为空目录,不能重复挂载至同一挂载点或挂载至其他挂载点下的目录或文件。

  • 不要在休眠唤醒或者分散加载阶段挂载一个不存在的文件系统。

  • 在unregister_driver卸载设备之前,要确保字符设备已经正确的close,即open和close的次数要对应,否则会卸载失败。

  • 在umount之前,需确保此文件系统中的所有目录及文件全部关闭,否则umount会失败。如果强制umount,可能导致包括但不限于文件系统损坏、设备损坏等问题,对此华为不承担任何责任。

  • SD卡移除前,需确保所有目录及文件已经全部关闭,并进行umount操作。如果强制拔卡,可能导致包括但不限于SD数据丢失、SD卡损坏等问题,对此华为不承担任何责任。

FAT

概述

基本概念

FAT文件系统是File Allocation Table(文件配置表)的简称,FAT文件系统有FAT12、FAT16、FAT32。FAT文件系统将硬盘分为MBR区、DBR区、FAT区、DIR区、DATA区等5个区域。

FAT文件系统支持多种介质,特别是在可移动存储介质(U盘、SD卡、移动硬盘等)上广泛使用。而且,FAT文件系统可以使嵌入式设备和Windows、Linux等桌面系统保持很好的兼容性,方便用户管理操作文件。

LiteOS的FAT文件系统的特点有:

  • 代码量和资源占用小、可裁切、支持多种物理介质。

  • 与Windows、Linux等系统保持兼容。

  • 支持多设备、多分区识别等功能。

  • 支持硬盘多分区,可以在主分区以及逻辑分区上进行文件操作。

  • 支持识别出硬盘上其他类型的文件系统,比如NTFS。

  • 支持基于FAT32 的虚拟分区特性。

    当开启虚拟分区特性时,用户可通过调用虚拟分区的设置接口los_set_virpartparam(),对特定的物理分区配置虚拟分区的数量、空间百分比大小、虚拟分区的入口名等,并会根据设置的入口名,在根目录建立对应的文件夹作为虚拟分区入口。

    • 在对应的虚拟分区内部进行操作,即视为在对应的虚拟分区内进行操作。

    • 若所有虚拟分区的空间百分比总和未达到100%,则允许在虚拟分区入口目录外进行写入操作,此时所有虚拟分区入口外部的总可写入空间,即为百分比总和的剩余空间。若所有虚拟分区的空间百分比达到了100%,则在虚拟分区入口目录外部将会以空间不足的理由,拒绝写入操作。

开发指导

FAT文件系统的接口说明

头文件

接口名

描述

fs/include/los_fs.h

getlabel

获取盘符

set_label

设置盘符

format

格式化磁盘

los_set_systime_status

设置FAT文件当前系统时间有效或无效

LOS_SetSyncThreadPrio

设置FAT文件系统缓存异步刷新任务的优先级

LOS_SetSyncThreadInterval

设置FAT文件系统缓存异步刷新的时间间隔

LOS_GetSyncThreadInterval

获取FAT文件系统缓存异步刷新的时间间隔

LOS_SetDirtyRatioThreshold

设置FAT文件系统缓存异步刷新的脏数据块比例阈值

LOS_GetDirtyRatioThreshold

获取FAT文件系统缓存异步刷新的脏数据块比例阈值

LOS_SetBlockExpireInterval

设置FAT文件系统块的最大过期时间间隔

LOS_GetBlockExpireInterval

获取FAT文件系统块的最大过期时间间隔

LOS_FatSetMemPool

设置FAT文件系统独立内存池

LOS_BcacheSetMemPool

设置FAT文件系统缓存独立内存池

vfs_files.h

LOS_PartitionFormat

格式化磁盘,仅部分平台定制接口

LOS_DiskPartition

按比例创建磁盘分区,仅部分平台定制接口

说明:

  • 使能FAT文件系统缓存(Cache)异步刷新后,系统会创建一个任务刷Cache,线程被触发唤醒进行脏数据刷新的条件如下:

  1. 周期性5s唤醒。

  2. 脏数据停留时间超过3s。

  3. 脏数据量超过50%时,触发任何一个条件,线程都会立刻把脏数据写回磁盘。

  • 在“open_source/FatFs/source/ffconf.h”文件中可以配置FAT文件系统,其中FF_FS_LOCK为最多支持同时打开的文件(文件夹)数。

虚拟分区的接口说明

头文件

接口名

接口功能描述

fs/include/los_fs.h

los_set_virpartparam

设置虚拟分区的参数

virstatfs

获取指定虚拟分区的状态

虚拟分区的调配结构体定义如下所示:

#define _MAX_ENTRYLENGTH  16 
#define _MAX_VIRVOLUMES   5

typedef struct virtual_partition_info
{
    char *devpartpath; 
    int  virpartnum;
    double virpartpercent[_MAX_VIRVOLUMES];
    char virpartname[_MAX_VIRVOLUMES][_MAX_ENTRYLENGTH + 1];
} virpartinfo;

成员

描述

注意事项

devpartpath

设备节点路径

所选定的设备节点的名称。必须是/dev下的设备节点路径,上限是DISK_NAME。

virpartnum

虚拟分区数量

最小值为1,最大值为_MAX_VIRVOLUMES,即为5。

virpartpercent

虚拟分区百分比

总和必须大于0.00,小于等于1.00。每个成员的值必须大于0.00,小于1.00。

virpartname

虚拟分区入口名

入口名有效字符最多为_MAX_ENTRYLENGTH ,即16个字符,不得为空。传入的集合不可以有重名项。入口名不得含有非法名称字符。

须知:

  • 在LiteOS中,虚拟分区特性仅支持特定的单个物理分区进行,可通过调配虚拟分区参数接口设置设备节点。

  • 设置指定物理设备节点后,会对设置进行上锁,不允许再次更改设置。挂载时就会采用设置的参数进行配置。当卸载后,才会对设置进行解锁。

  • 卸载后,需要通过调配虚拟分区参数接口重新设置虚拟分区参数。

  • 当插入的物理分区中已经设置了虚拟分区,但是其配置参数与当前系统通过调配接口所设置的参数不一致时,则将按照已有的配置参数对此分区进行管理。

虚拟分区涉及的接口行为变化

开启虚拟分区特性后,部分接口的行为会在虚拟分区下有部分变化。下述这些接口的行为变化只对于已成功应用了虚拟分区特性的物理分区,对于未能应用虚拟分区的基本FAT文件系统的物理分区,以及其他的文件系统,这些接口行为不涉及变化。

下述的接口只是在执行功能上的行为针对虚拟分区有所变化,函数的调用方式、参数等均不受影响。

序号

接口名

行为变化

返回值与错误码说明

1

mount

挂载了基本FAT分区后,会尝试识别当前物理分区是否已经应用过虚拟分区,并尝试对该物理分区按照其配置的信息加载虚拟分区,检查并尝试修复虚拟分区入口;若当前物理分区未应用过虚拟分区并符合启动虚拟分区的条件,则会自动对该物理分区应用虚拟分区,并建立虚拟分区。

返回值为-1表示基本FAT分区挂载失败,此时会触发libc错误码指示发生了何种错误;

返回值为0表示基本FAT分区挂载成功,此时错误码若为0表示虚拟分区挂载成功,若错误码为VIRERR_MODIFIED、VIRERR_CHAIN_ERR、VIRERR_OCCUPIED、VIRERR_NOTCLEAR、VIRERR_NOTFIT、VIRERR_NOPARAM表示虚拟分区挂载失败

2

format

当FAT分区已挂载后,若格式化成为FAT32文件系统,则会在格式化完成后对该物理分区应用虚拟分区;若非FAT32文件系统,则会完成格式化操作,并清除该物理分区的虚拟分区;若FAT分区未挂载,无论何种文件系统,则均会完成格式化操作后清除该物理分区的虚拟分区。

返回值为-1表示基本FAT分区格式化失败,此时会触发libc错误码指示发生了何种错误;

返回值为0表示基本FAT分区格式化完成。若此时分区并未挂载,则错误码则会为VIRERR_NOTMOUNT;若格式化的分区非调配接口所设定设备节点路径,或者调配接口并未设置参数,则此时会指示VIRERR_NOPARAM。若分区已挂载,错误码为0表示应用或重新应用虚拟分区成功,或者错误码可能为VIRERR_NOTFIT。

3

open

请参阅虚拟分区的兼容性的说明。

4

mkdir

请参阅虚拟分区的兼容性的说明。

5

rmdir

当物理分区已经成功应用虚拟分区时,不允许删除虚拟分区入口目录。

6

remove

当物理分区已经成功应用虚拟分区时,不允许删除虚拟分区入口目录。

7

rename

当物理分区已经成功应用虚拟分区时,不允许重命名虚拟分区入口目录。

虚拟分区的错误码

应用虚拟分区发生错误,只会发生于挂载时或者格式化时。当基本FAT文件系统的对应操作完成后,进行虚拟分区的配置与应用。此时若发生某种问题或者触发某种条件,虚拟分区会将错误码传递给上层,以分辨应用虚拟分区时发生了什么情况。同时该分区将会以基本FAT文件系统进行管理,而不会在此基础上采用虚拟分区管理。

说明: 对于挂载和格式化操作的返回值,指示的是基本FAT文件系统的成功或失败。

  • 当接口的返回值为0时,表示对应的基本FAT文件系统操作成功,此时可通过访问errno获取虚拟分区的错误码,以检查应用虚拟分区过程中是否有异常产生。

  • 当接口的返回值为-1时,表示于对应的基本FAT文件系统操作失败,此时的errno为libc的错误码,表示基本FAT操作失败的原因,不再表示虚拟分区的提示信息。

序号

定义

实际数值

描述

触发情况

1

VIRERR_MODIFIED

0x10000001

虚拟分区保留扇区信息遭到破坏

是否被重新分区、是否调整过分区大小、或者SD卡是否曾遭到破坏。

2

VIRERR_CHAIN_ERR

0x10000002

虚拟分区入口目录已被移出所在的虚拟分区

虚拟分区入口目录在其他平台上被删除后重建,有一定概率会触发该问题。

3

VIRERR_OCCUPIED

0x10000003

虚拟分区入口被同名文件占据

物理分区根目录有同名文件,阻碍了虚拟分区入口的创建。

4

VIRERR_NOTCLEAR

0x10000004

物理分区非空

在对新物理分区应用虚拟分区时,分区非空。

5

VIRERR_NOTFIT

0x10000005

物理分区非FAT32文件系统

物理分区是否为FAT32文件系统。

6

VIRERR_NOTMOUNT

0x10000006

设备未挂载

执行格式化时设备未挂载。

7

VIRERR_INTER_ERR

0x10000007

意外错误

应用虚拟分区时发生意外错误。

8

VIRERR_NOPARAM

0x10000008

未对虚拟分区进行全局配置

在执行挂载/格式化操作之前,需要先调用调配接口。

9

VIRERR_PARMLOCKED

0x10000009

设置已上锁

设置已上锁,不允许再次设置。

10

VIRERR_PARMNUMERR

0x1000000A

设置接口VirPartNum字段错误

检查VirPartNum的设定值是否正确。

11

VIRERR_PARMPERCENTERR

0x1000000B

设置接口VirPartPercent字段错误

检查VirPartPercent字段的值是否正确。

12

VIRERR_PARMNAMEERR

0x1000000C

设置接口VirPartName字段错误

检查VirPartName字段的设定是否正确。

13

VIRERR_PARMDEVERR

0x1000000D

设置接口DevPartPath字段错误

检查DevPartPath字段是否正确。

虚拟分区的兼容性

虚拟分区特性是LiteOS独有的基于FAT的文件管理模式。在其他平台上对已经应用过虚拟分区的物理分区进行操作时,务必了解以下兼容性特征,以免使用时发生问题。

  • 重新对存储介质分区、调整存储介质的大小等操作,会直接导致物理分区的虚拟分区特性失效。

  • 在其他平台上对已经应用虚拟分区的物理分区建立文件夹时,需要注意:

    • 若建立的文件夹在虚拟分区入口外,且全部虚拟分区的百分比总和为100%,则下次在LiteOS中在该文件夹内不允许创建新的文件或者文件夹。

  • 在其他平台上对已经应用虚拟分区的物理分区写文件时,需要注意:

    • 若写入的文件在虚拟分区入口外,且全部虚拟分区的百分比总和为100%,则下次在LiteOS中这些文件只允许以只读方式访问。

    • 若写入的文件在虚拟分区入口内,而且文件整体的簇链均在对应的虚拟分区的簇链管理范围内,则下次在LiteOS中这些文件会以正常权限访问。

    • 若写入的文件在虚拟分区入口内,而且文件整体的簇链有一部分或者整体不在虚拟分区的管理范围内,则下次在LiteOS中这些文件只允许以只读权限访问。

  • 在其他平台上删除虚拟分区入口目录,会导致对应的虚拟分区意外丢失。下次在LiteOS中进行挂载时,会尝试重建丢失的虚拟分区。

    • 若删除后用户创建一个同名的文件夹,且文件夹的整体簇链均落在了对应的虚拟分区的管理范围内,则下次在LiteOS中进行挂载时,该虚拟分区入口将被正常识别。

    • 若删除后用户创建一个同名的文件夹,且整体簇链一部分或者整体落在了对应虚拟分区的管辖范围外,则下次在LiteOS中进行挂载时,会导致虚拟分区入口校验失败,拒绝此次的虚拟分区应用。

  • 在其他平台上重命名虚拟分区入口目录,会导致对应的虚拟分区意外丢失。下次在LiteOS中进行挂载时,会尝试将丢失的虚拟分区重建。原有的文件不会丢失,但这些文件将不会再受到虚拟分区特性的管理,只允许以只读权限访问。

开发流程

使用FAT功能,涉及以下几个步骤,其中对虚拟分区的操作为可选部分,只有在FAT使用虚拟分区功能的情况下才涉及,请根据实际业务情况选择(各步骤详细操作见下述分解):

  1. 打开菜单,进入FileSystem ---> Enable FAT菜单,完成FAT文件系统的配置。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_FS_FAT

    使能FAT文件系统的开关

    YES/NO

    YES

    LOSCFG_FS_VFS && LOSCFG_DRIVER_DISK

    LOSCFG_FS_FAT_CACHE

    使能FAT文件系统缓存

    YES/NO

    YES

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_CACHE_SYNC_THREAD

    使能FAT文件系统缓存异步刷新

    YES/NO

    YES

    LOSCFG_FS_FAT_CACHE

    LOSCFG_FS_FAT_CACHE_PROC

    使能FAT文件系统缓存统计

    YES/NO

    YES

    LOSCFG_FS_FAT_CACHE

    LOSCFG_FS_FAT_CACHE_INDEPENDENT_MEMORY_POOL

    使能FAT文件系统缓存独立内存池

    YES/NO

    NO

    LOSCFG_FS_FAT_CACHE

    LOSCFG_FS_FAT_CHINESE

    使能FAT文件系统支持中文

    YES/NO

    YES

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_CHINESE_GB2312

    使用GB2312中文编码方式

    YES/NO

    YES

    LOSCFG_FS_FAT_CHINESE

    LOSCFG_FS_FAT_CHINESE_UTF_8

    使用UTF-8中文编码方式

    YES/NO

    NO

    LOSCFG_FS_FAT_CHINESE

    LOSCFG_FS_FAT_DCACHE

    使能FAT文件系统目录项缓存

    YES/NO

    YES

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_TRIM

    使能FAT文件系统介质TRIM

    YES/NO

    YES

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_UNI_TRIM

    使能FAT文件系统介质合并TRIM

    YES/NO

    YES

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_DIRECT_TRIM

    使能FAT文件系统介质直接TRIM

    YES/NO

    NO

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_VIRTUAL_PARTITION

    使能FAT文件系统支持虚拟分区

    YES/NO

    NO

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_FTL

    使能FAT文件系统运行在flash上

    YES/NO

    NO

    LOSCFG_FS_FAT &&

    LOSCFG_DRIVERS_MTD

    LOSCFG_FS_FAT_FTL_COMPRESS

    使能FAT文件系统运行在flash上读写压缩数据。

    YES/NO

    NO

    LOSCFG_FS_FAT_FTL

    LOSCFG_FS_FAT_INDEPENDENT_MEMORY_POOL

    使能FAT文件系统独立内存池

    YES/NO

    NO

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_STACK_LFN

    LFN 512字节使用栈内存

    YES/NO

    YES

    LOSCFG_FS_FAT

    LOSCFG_FS_FAT_HEAP_LFN

    LFN 512字节使用堆内存

    YES/NO

    NO

    LOSCFG_FS_FAT

    说明:

    • 当虚拟分区特性启用后,整个LiteOS的FAT文件系统将优先采用虚拟分区特性进行管理,虚拟分区不支持FAT目录项缓存。

    • 当启用FAT目录项缓存后,会消耗一部分内存用来缓存FAT文件系统的目录项,从而减少部分接口的再次访问耗时。

    • 当启用FAT Trim功能后,可以有效减缓磁盘的write amplification行为,提升数据写入速率的稳定性。

    • 当启用FTL功能后,FAT文件系统可以在Nor Flash上使用,使用add_mtd_partition添加分区生成spinorblk0ftlp0。

    • 当使用format格式化设备后,mount即可得到一个干净的FAT文件系统。

  2. 制作基于FTL的FAT文件系统镜像(可选)

  3. 识别设备

  4. 挂载FAT文件系统

  5. 卸载FAT文件系统

  6. 检查虚拟分区的可操作条件(可选)

  7. 调配虚拟分区(可选)

  8. 挂载虚拟分区(可选)

  9. 卸载虚拟分区(可选)

  10. 创建虚拟分区(可选)

  11. 删除虚拟分区(可选)

制作基于FTL的FAT文件系统镜像(可选)

FTL支持文件系统镜像的预制作,使用mkftl2image目录下的工具源码进行make编译,生成“mkfs.ftl”来进行制作,当修改压缩选项时,需重新生成“mkfs.ftl”。命令格式如下:mkfs.ftl [inDir] [partitionSize] [blockSize] [pageSize] [outFile]。

./mkfs.ftl appfs 0x400000 0x10000 0x200 appfs.ftl

参数

含义

inDir

要制作成镜像的源目录

partitionSize

分区大小

blockSize

Flash器件的块大小

pageSize

Flash器件的页大小

outFile

输出的文件系统镜像文件

识别设备

在“open_source/FatFs/source/ffconf.h”文件中可以使能多分区和多设备。使能多分区后,可以操作设备上的多个分区,否则只能操作第一个分区。使能多设备后,可以将多个设备挂载成FAT文件系统。具体配置项为:

  • 配置FF_MULTI_PARTITION为1,使能多分区功能。

  • 配置FF_VOLUMES大于1时,使能多设备功能。

系统自动识别插入的SD卡,自动注册设备节点,如下所示。其中的mmcblk0和mmcblk1为卡0和卡1,是独立的主设备,mmcblk0p0和mmcblk0p1为卡0的两个分区,mmcblk1p0为卡1的分区,这些分区可作为分区设备使用。存在分区设备的情况下,禁止使用主设备

LiteOS# ls
Directory /dev:
acodec                  0
adec                    0
aenc                    0
ai                      0
ao                      0
console                 0
fb0                     0
hi_gpio                 0
hi_mipi                 0
hi_rtc                  0
hi_tde                  0
i2c-0                   0
i2c-1                   0
i2c-2                   0
isp_dev                 0
lcd                     0
logmpp                  0
mem                     0
mmcblk0                 0
mmcblk0p0               0
mmcblk0p1               0
mmcblk1                 0
mmcblk1p0               0

可以在Shell中使用partinfo命令查看设备的分区信息:

LiteOS # partinfo /dev/sdap0
part info :
disk id          : 3
part_id in system: 0
part no in disk  : 0
part no in mbr   : 1
part filesystem  : 0C
part dev name    : sdap0
part sec start   : 2048
part sec count   : 167794688

挂载FAT文件系统

调用mount()函数实现设备节点的挂载。

ret = mount("/dev/mmcblk0p0", "/bin/vs/sd", "vfat", 0, NULL);
if (ret) {
    dprintf("mount fat filesystem err %d\n", ret);
}

说明: mount()函数有五个参数:

  • 第一个参数表示设备节点。

  • 第二个参数表示挂载点。

  • 第三个参数表示文件系统类型。

  • 第四个参数表示挂载标志,默认为0。

  • 最后一个参数表示私有数据,默认为NULL。

除了在源码中调用mount()函数,也可以在Shell中使用mount命令实现挂载,前面3个参数同mount()函数,最后两个参数不需要设置。得到如下回应信息,表明挂载成功:

LiteOS# mount /dev/mmcblk0p0 /bin/vs/sd vfat
mount ok

卸载FAT文件系统

调用umount()函数卸载分区,只需要给出正确的挂载点即可。也可以在Shell中使用umount命令实现,得到如下回应信息,表明卸载成功:

LiteOS# umount /bin/vs/sd
umount ok

检查虚拟分区的可操作条件(可选)

  • 对于未在LiteOS中应用过虚拟分区特性的存储设备(如SD卡、U盘、移动硬盘等)物理分区,需要保证:

    • 该物理分区为FAT32分区。

    • 该物理分区下无任何文件。

  • 对于曾经在LiteOS中已经应用过虚拟分区特性的存储设备物理分区,需要保证:

    • 该物理分区下的虚拟分区入口目录没有被删除、重命名或者其他形式的破坏。

    • 该物理分区的保留扇区内数据完整。

若有任一条件不满足,则无法对该物理分区应用虚拟分区特性,只能应用常规FAT文件系统。

调配虚拟分区(可选)

虚拟分区特性针对的是某个特定场景下的物理分区,需要通过调配接口los_set_virpartparam()指定分区设备,并设置该分区的配置参数。调配虚拟分区参数接口的主要目的是允许用户建立符合场景的虚拟分区。

系统开机后,若想应用虚拟分区特性,需要先调用los_set_virpartparam(),对当前系统的虚拟分区数量、对应虚拟分区的关键参数进行配置。若未配置,则将导致后续虚拟分区应用失败。

  • 对未在LiteOS中应用过虚拟分区特性的存储设备物理分区,在进行虚拟分区应用之前,可以通过调配接口设置需要的参数。正常情况下,当成功调配后,对该物理分区执行挂载操作,即可完成虚拟分区的应用。

  • 若操作的物理分区已经应用过虚拟分区特性,只要该分区已设置的虚拟分区校验正确,则可忽略调配接口设置的参数,而无条件遵循该分区内部的参数对其管理。

挂载虚拟分区(可选)

在完成挂载FAT文件系统后会自动对已经挂载的分区进行虚拟分区的应用。若虚拟分区应用成功,则会启动虚拟分区对该物理分区进行管理;若虚拟分区应用失败,则会以普通FAT文件系统对该物理分区进行管理,并传出特定的错误码表明在应用虚拟分区时发生了何种错误。

虚拟分区成功挂载后,会在分区根目录下建立对应数量以及对应名称的目录,即作为虚拟分区入口。在虚拟分区入口内部创建文件,即相当于在对应的虚拟分区内创建文件。每个虚拟分区入口内的最大可用空间,即为设置的虚拟分区百分比大小。

卸载虚拟分区(可选)

卸载虚拟分区可以认为是挂载的逆过程,不再进行重复。

创建虚拟分区(可选)

当对一个新物理分区应用虚拟分区时,执行挂载操作即可完成虚拟分区的创建,并且可以在LiteOS中进行重复使用。创建的虚拟分区的基本参数,以调配接口所设置的参数为准。

某些情况下,需要对某个分区重新应用虚拟分区时,可在已经挂载的分区上格式化该磁盘为FAT32文件系统。在格式化操作完成后,则会对该物理分区重新应用虚拟分区。

须知: 执行格式化操作会导致用户数据丢失,在执行此操作时请小心。

删除虚拟分区(可选)

当某些情况下,需要撤销对物理分区使用虚拟分区特性,可以在未挂载的分区上进行格式化,即可清除该分区上的虚拟分区信息。

须知: 执行格式化操作会导致用户数据丢失,在执行此操作时请小心。

注意事项

  • FAT文件系统中,单个文件不能大于4GB。

  • 带绝对路径的文件名,除去挂载点之后的长度总和不能大于252Byte。

  • 目前只支持FAT32格式,如果使用其他文件格式,需要先将介质格式化为FAT32再对接到LiteOS。

  • FAT文件系统支持的操作有:open,close,read,write,seek,sync,opendir,closedir,readdir,rewinddir,statfs,virstatfs,unlink,mkdir,rmdir,rename,stat,lstat,stat64,utime,chattr,seek64,getlabel,set_label,format,fallocate,fallocate64,truncate,truncate64,fscheck。

  • 以可写方式打开一个文件后,未close前再次打开会失败。同时打开同一文件,必须全部以只读方式打开。长时间打开一个文件,没有close时数据会丢失,必须close才能保存。

  • open打开一个文件,参数有O_TRUNC时,会将文件中的内容清空。

  • 若open未使用mode入参,则无法通过mode入参来控制文件权限。

  • 目前FAT文件系统不建议open带O_DIRECTORY属性的操作,打开目录请使用opendir()。

  • FAT文件系统的读写指针没有分离,所以以O_APPEND(追加写)方式打开文件后,读指针也在文件尾,读文件前需要用户手动置位。

  • FAT文件系统的stat及lstat函数获取的文件时间只是文件的修改时间,暂不支持获取文件创建时间和最后访问时间。微软FAT协议不支持1980年以前的时间。

  • 在format操作之前,若FAT文件系统已挂载,需确保所有目录及文件已经全部关闭,否则format会失败。

  • FAT支持挂载只读属性:

    • 当mount函数的入参为MS_RDONLY时,FAT将开启只读属性,所有带写入操作的接口(format接口除外),如write、mkdir、unlink,以及通过非O_RDONLY属性打开的文件,将均被拒绝,并传出EACCESS错误码。

    • 当mount函数的入参为MS_NOSYNC时,FAT不会主动将Cache的内容写回存储器件。FAT的如下接口(open、close、 unlink、rename、mkdir、rmdir、chattr、truncate)不会自动进行sync操作,虽然可以提升速度,但是需要上层主动调用sync来同步数据,否则下电可能会导致数据丢失。

  • 当前Cache的默认大小为16个块,每个块256个扇区。可以在“fs/include/vfs_config.h”文件中修改Cache的块数量CONFIG_FS_FAT_BLOCK_NUMS 和每个块的扇区数CONFIG_FS_FAT_SECTOR_PER_BLOCK,注意CONFIG_FS_FAT_SECTOR_PER_BLOCK必须为32的倍数,且不能大于2048。

  • 当有两张SD卡插槽时,卡0和卡1不固定,先插入的为卡0,后插入的为卡1。

  • 为避免SD卡使用异常和内存泄漏,在SD卡使用过程中拔卡,必须先关闭正处于打开状态的文件和目录,然后umount挂载节点。

YAFFS2

概述

基本概念

YAFFS是Yet Another Flash File System的简称,是一种开源的、针对Nand Flash的嵌入式文件系统。在YAFFS中,最小的存储单位为page。

目前YAFFS文件系统有YAFFS和YAFFS2两个版本,主要区别在于page读写size的大小,YAFFS2可支持到2K per page,远高于YAFFS的512 bytes,因此YAFFS2能够更好地支持大容量的Nand Flash芯片。此外YAFFS2还有64bytes的SPARE区域,用于存储坏块信息、ECC校验等。

  • YAFFS2专为Nand Flash设计,适用于大容量的存储设备,同时也使得Nand Flash具有高效性和健壮性。

  • YAFFS2实现对2K per page的读写支持,同时在内存空间占用、垃圾回收速度、读写速度等方面均有大幅提升。

  • YAFFS2为文件系统提供了损耗平衡和掉电保护,保证在修改文件系统中的数据时,即使发生意外也不损坏数据。

  • LiteOS的YAFFS2文件系统支持多分区,采用双向链表结构实现。

开发指导

接口说明

头文件

接口名

描述

fs/include/mtd_partition.h

add_mtd_partition

添加YAFFS2分区,该函数会自动为设备节点命名,对于YAFFS2,其命名规则是“/dev/nandblk”加上分区号。

delete_mtd_partition

删除已经卸载的分区。

说明:

  1. add_mtd_partition添加YAFFS2分区时,系统会自动对起始地址和分区大小根据设备block大小进行对齐。该函数有四个参数:

  • 第一个参数表示介质,支持“nand”和“spinor”。YAFFS2分区在“nand”上使用,littlefs在“spinor”上使用。

  • 第二个参数表示分区的起始地址,以16进制的形式传入。

  • 第三个参数表示分区大小,以16进制的形式传入。

  • 最后一个参数表示分区号,有效值为0~19。

  1. delete_mtd_partition函数有两个参数:

  • 第一个参数是分区号。

  • 第二个参数为介质类型,该函数与add_mtd_partition()函数对应。

开发流程

使用LiteOS的YAFFS2文件系统,涉及以下几个步骤(各步骤详细操作见下述分解):

  1. 打开菜单,选择FileSystem ---> Enable YAFFS2,使能YAFFS2文件系统。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_FS_YAFFS

    使能YAFFS2的开关

    YES/NO

    YES

    LOSCFG_FS_VFS && LOSCFG_DRIVERS_MTD_NAND

  2. 制作YAFFS2文件系统镜像并烧录(可选)

  3. 调用add_mtd_partition创建分区

  4. 调用mount函数挂载分区

  5. 调用umount函数卸载分区

  6. 调用delete_mtd_partition函数删除分区

制作YAFFS2文件系统镜像并烧录(可选)

目前支持使用mkyaffs2image工具制作文件系统镜像,可以参考如下命令制作镜像:

./mkyaffs2image rootfs rootfs.yaffs2 1 2

参数

含义

rootfs

要制作成镜像的源目录

rootfs.yaffs2

镜像名称

1

Nand Flash器件的页大小,1表示2KB pagesize

2

Nand Flash器件的ECC校验类型,2表示4bit/512

说明: 用户可根据实际情况修改命令中参数值。

制作好镜像后,就可以烧录此镜像了。

调用add_mtd_partition创建分区

创建分区的示例代码如下:

if (ret = add_mtd_partition("nand", 0x900000, 0x200000, 0) < 0) {
    dprintf("add yaffs partition 0 failed, return %d\n", ret);
}

if (ret = add_mtd_partition("nand", 0xb00000, 0x200000, 1) < 0) {
    dprintf("add yaffs partition 1 failed, return %d\n", ret);
}

创建成功后,在Shell中可以使用“partition nand”命令查看Nand Flash的分区信息:

LiteOS# partition nand
nand partition num:0, dev name:/dev/nandblk0, mountpt:(null), startaddr:0x0900000,length:0x0200000
nand partition num:1, dev name:/dev/nandblk1, mountpt:(null), startaddr:0x0b00000,length:0x0200000

调用mount函数挂载分区

调用mount()函数实现设备节点的挂载。

ret = mount("/dev/nandblk0", "/yaffs0", "yaffs", 0, NULL);
if (ret) {
    dprintf("mount yaffs err %d\n", ret);
}

说明: mount()函数有五个参数:

  • 第一个参数表示设备节点,这个参数需要和add_mtd_partition()函数对应起来。

  • 第二个参数表示挂载点。

  • 第三个参数表示文件系统类型。

  • 第四个参数表示挂载标志,默认为0。

  • 最后一个参数表示私有数据,默认为NULL。

除了在源码中调用mount()函数,也可以在Shell中使用mount命令实现挂载,前面3个参数同mount()函数,最后两个参数不需要给出,回显“mount ok”表明挂载成功:

LiteOS# mount /dev/nandblk1 /yaffs1 yaffs
mount ok

挂载成功后,在Shell中可以使用partition nand命令看到设备节点的挂载点信息:

LiteOS# partition nand
nand partition num:0, dev name:/dev/nandblk0, mountpt:/yaffs0, startaddr:0x0900000,length:0x0200000
nand partition num:1, dev name:/dev/nandblk1, mountpt:/yaffs1, startaddr:0x0b00000,length:0x0200000

调用umount函数卸载分区

调用umount()函数卸载分区,只需要正确给出挂载点即可。这一操作也可以在Shell中使用umount命令实现,回显“umount ok”表明命令执行成功:

LiteOS# umount /yaffs1
umount ok

LiteOS# umount /yaffs0
umount ok

卸载成功后,在Shell中可以使用“partition nand”命令看到设备节点的挂载点变为空。

LiteOS# partition nand
nand partition num:0, dev name:/dev/nandblk0, mountpt:(null), startaddr:0x0900000,length:0x0200000
nand partition num:1, dev name:/dev/nandblk1, mountpt:(null), startaddr:0x0b00000,length:0x0200000

调用delete_mtd_partition函数删除分区

调用delete_mtd_partition删除分区前要卸载分区。

ret = delete_mtd_partition(1, "nand");
if (ret != 0) {
    printf("delte yaffs error\n");
} else {
    printf("delete yaffs ok\n");
}

注意事项

  • 对于YAFFS2的多分区特性,分区起始地址可以灵活配置,但分区间地址不能重叠,用户需要根据Flash使用情况,合理配置空间,防止与其它文件系统或其它不适合挂载YAFFS2文件系统的空间发生冲突。

  • 最小可添加的分区为一个block大小,但是最小可挂载的分区为9个block大小(由YAFFS2特性决定),要注意区分这两个概念。

  • 建议分区使用前先进行擦除操作。

  • LiteOS的YAFFS2文件系统只允许连续打开20个目录。

  • YAFFS2文件系统支持的操作有:open,close,read,write,seek,sync,dup,opendir,closedir,readdir,rewinddir,statfs,unlink,mkdir,rmdir,rename,stat,stat64,seek64。

  • open打开一个文件,参数有O_TRUNC时,必须同时拥有写的权限,才会将文件中的内容清空。

  • YAFFS2当前不支持获取文件的atime、mtime、ctime,使用stat接口获取出来的值为0。

  • 目前YAFFS2不建议open带 O_DIRECTORY属性的操作,打开目录请使用opendir()。

  • YAFFS2支持挂载只读属性,当mount函数的入参为MS_RDONLY时,YAFFS2将开启只读属性,所有带写操作的接口,如write、mkdir、unlink,以及通过非O_RDONLY属性打开的文件,将均被拒绝,并传出EACCES错误码。

  • open打开一个文件,o_flags必须带有O_RDONLY、O_WRONLY、O_RDWR中的一种属性,o_flags设置的读写权限必须与创建文件时设置的mode匹配。

LITTLEFS

概述

基本概念

LITTLEFS是LITTLE File System(微型文件系统)的缩写,是一种专门为微控制器设计的故障保护文件系统,主要应用于NOR Flash闪存,其特点包括:

  • 可读写。

  • 提供损耗平衡。

  • 提供掉电安全保护,在修改文件系统中的数据时,即使发生意外也不损坏数据。

  • 内存和代码很精简,占用很少的RAM和FLASH。

LITTLEFS是基于块的文件系统,磁盘被分成大小均匀的块阵列,作为存储的逻辑单元,这些块在使用时由公共块分配器进行分配。LITTLEFS使用小日志存储元数据,并使用较大的写时复制(COW)结构存储文件数据。

LiteOS的LITTLEFS主要应用于NOR Flash闪存的文件管理,且支持多分区。

开发指导

接口说明

头文件

接口名

描述

fs/include/mtd_partition.h

add_mtd_partition

添加文件系统分区,该函数会自动为设备节点命名,对于LITTLEFS,其命名规则是“/dev/spinorblk0”。

delete_mtd_partition

删除已经卸载的分区。

open_source/musl/include/sys/mount.h

mount

挂载文件系统。

umount

卸载文件系统。

umount2

强制卸载文件系统,部分场景下存在内存泄露,需要重启后继续使用文件系统。

开发流程

  1. 使能LITTLEFS

  2. 添加LITTLEFS分区

  3. 挂载LITTLEFS分区

  4. 卸载LITTLEFS分区

  5. 删除LITTLEFS分区

使能LITTLEFS

打开菜单,选择FileSystem ---> Enable LITTLEFS,使能LITTLEFS文件系统。

配置项

含义

取值范围

默认值

依赖

LOSCFG_FS_LITTLEFS

使能LITTLEFS的开关

YES/NO

YES

LOSCFG_FS_VFS && LOSCFG_DRIVERS_MTD_SPI_NOR

添加LITTLEFS分区

调用add_mtd_partition()函数添加LITTLEFS分区。

if (add_mtd_partition("spinor", 0x100000, 0x500000, 0) != 0) {
    dprintf("add spinor partition failed\n");
}

说明: add_mtd_partition()添加LITTLEFS分区时,系统会自动根据设备block大小将分区起始地址和大小对齐。 该函数有四个参数:

  • 第一个参数表示介质,支持“nand”和“spinor”。LITTLEFS分区在“spinor”上使用。

  • 第二个参数表示分区的起始地址,以16进制的形式传入。

  • 第三个参数表示分区大小,以16进制的形式传入。

  • 最后一个参数表示分区号,有效值为0~19。

成功后,可以在Shell中使用“partition spinor”命令查看spinor flash分区信息,更多请详见“partition”。

LiteOS# partition spinor
spinor partition num:0, dev name:/dev/spinorblk0, mountpt:(null), startaddr:0x0100000, length:0x0500000

挂载LITTLEFS分区

调用mount()函数实现设备节点的挂载。

ret = mount("/dev/spinorblk0", "/littlefs0", "littlefs", 0, NULL);
if (ret != 0) {
    dprintf("ERROR:mount littlefs failed\n");
}

说明: mount()函数有五个参数:

  • 第一个参数表示设备节点,这个参数须与add_mtd_partition()函数对应。

  • 第二个参数表示挂载点。

  • 第三个参数表示文件系统类型。

  • 第四个参数表示挂载标志,默认为0。

  • 最后一个参数表示私有数据,默认为NULL。

调用mount()函数是否成功,可以在Shell中执行“ls”查看当前根目录下的目录。

LiteOS# ls
Directory /:
bin                  <DIR>
dev                  <DIR>
littlefs0            <DIR>

挂载成功后,可以对NOR Flash进行读写操作。

卸载LITTLEFS分区

调用umount()函数卸载分区,只需要给出正确的挂载点即可。

ret = umount("/littlefs0");
if (ret != 0) {
    dprintf("umount littlefs err\n");
}

说明: umount()函数只有一个“表示挂载点”的参数。

删除LITTLEFS分区

调用delete_mtd_partition()删除已经卸载的分区。

delete_mtd_partition(0, "spinor");

说明: delete_mtd_partition()函数有两个参数:

  • 第一个参数是分区号。

  • 第二个参数为介质类型,该参数与add_mtd_partition()函数对应。

注意事项

  • 如果以O_TRUNC方式open一个文件时,会将文件中的内容清空。

  • 若open未使用mode入参,则无法通过mode入参来控制文件权限。

  • LITTLEFS文件系统的读写指针分离,以O_APPEND(追加写)方式打开文件后,读指针在文件头,写指针在文件尾。

  • LITTLEFS文件系统用于NOR Flash,最终调用NOR Flash驱动接口,因此使用LITTLEFS文件系统之前需要保证存在NOR Flash闪存,且驱动已初始化成功(spinor_init()返回0)。

  • 目前LITTLEFS文件系统支持的操作有:open,close,read,write,seek,opendir,closedir,readdir,rewinddir,unlink,mkdir,rmdir,rename,stat,stat64,seek64。

  • 当前LITTLEFS读写cache的默认大小为1024Byte,可以在“fs/littlefs/include/vfs_littlefs.h”文件中修改LFS_CACHE_SIZE的大小,注意必须为LFS_MIN_CACHE_SIZE的倍数,且不能大于65536Byte。

  • 打开文件并写入数据,若在未close的情况下rename,数据会丢失。

  • 目前access操作只能判断文件是否存在,不能判断文件是否具有指定的权限。

  • 目前stat操作只能获取文件名、大小、文件类型(文件或目录),无法获取文件权限。

  • 目前LITTLEFS不建议open带O_DIRECTORY属性的操作,打开目录请使用opendir()。

  • 若同时打开同一文件,必须全部以只读方式打开。若以可写方式同时打开一个文件并写入数据,必须close才能保存,且只保存最后一次close的数据。

  • LITTLEFS支持挂载只读属性,当mount函数的入参mountflags为MS_RDONLY时,LITTLEFS将开启只读属性,所有带有写操作的接口,如write、mkdir、unlink,以及通过非O_RDONLY属性打开的文件,均将被拒绝,并传出EACCESS错误码。

兼容接口

概述

图 1 兼容接口示意图

LiteOS提供了一套自有OS接口,同时也支持musl libc库、C++标准库、CMSIS接口和Linux接口。上层APP建议调用musl libc库等兼容接口,不建议直接调用LiteOS的自有OS接口。下面章节将列出LiteOS支持的兼容接口。

须知: LitesOS适配的libc库函数,遵循开源musl libc的实现形式,musl开源代码遵循标准C规范ISO/IEC 9899:1999和POSIX标准1003.1™-2008:作为最简单的底层库函数,内部不对指针类型参数做非空判断,由使用者保证入参的合法性。 其中LiteOS实现的获取时间类接口都是自系统起调度算起,与接口标准定义不一致,使用时需特别注意。

POSIX接口

POSIX支持接口

LiteOS提供一套POSIX适配接口,具体的规格参见下表。

头文件

接口名

类型

说明

ctype.h

isalnum_l

数据判断

判断入参是否为字母或数字

ctype.h

isalpha_l

数据判断

判断入参是否为字母

ctype.h

isascii

数据判断

判断入参是否为ascii码

ctype.h

isblank

数据判断

判断入参是否为空格或tab

ctype.h

isblank_l

数据判断

判断入参是否为空格或tab

ctype.h

iscntrl_l

数据判断

判断入参是否为控制字符

ctype.h

isdigit_l

数据判断

判断入参是否为十进制数

ctype.h

isgraph_l

数据判断

判断入参是否有图形表示

ctype.h

islower_l

数据判断

判断入参是否为小写字母

ctype.h

isprint_l

数据判断

判断入参是否可以打印

ctype.h

ispunct_l

数据判断

判断入参是否为标点符号

ctype.h

isspace_l

数据判断

判断入参是否为空格

ctype.h

isupper_l

数据判断

判断入参是否为大写字母

ctype.h

isxdigit_l

数据判断

判断入参是否为十六进制数

ctype.h

toascii

数据转换函数

将字符转换成ascii码

ctype.h

tolower_l

数据转换函数

将大写字母转换成小写字母

ctype.h

toupper_l

数据转换函数

将小写字母转换成大写字母

dirent.h

alphasort

目录操作

依字母顺序排序目录结构

dirent.h

closedir

目录操作

关闭目录

dirent.h

opendir

目录操作

打开目录

dirent.h

readdir

目录操作

读取目录

dirent.h

rewinddir

目录操作

重设读取目录的位置为开头位置

dirent.h

scandir

目录操作

扫描目录完成条件过滤

dirent.h

seekdir

目录操作

设置下回读取目录的位置

dirent.h

telldir

目录操作

取得目录流的读取位置

dlfcn.h

dlclose

动态加载

卸载一个模块

dlfcn.h

dlopen

动态加载

动态加载一个so模块

dlfcn.h

dlsym

动态加载

在模块或者系统符号表中查找符号

fcntl.h

creat

文件操作函数

创建一个文件

fcntl.h

fcntl

文件操作函数

文件描述词操作

fcntl.h

open

文件操作函数

打开文件

langinfo.h

nl_langinfo

系统函数

返回指定的本地信息

langinfo.h

nl_langinfo_l

系统函数

返回指定的本地信息

libgen.h

basename

文件路径

返回文件名

mqueue.h

mq_close

消息队列

关闭消息队列

mqueue.h

mq_getattr

消息队列

获取消息队列属性

mqueue.h

mq_open

消息队列

打开消息队列

mqueue.h

mq_receive

消息队列

接受一个消息队列中的消息

mqueue.h

mq_send

消息队列

发送一个消息到消息队列

mqueue.h

mq_setattr

消息队列

设置消息队列属性

mqueue.h

mq_timedreceive

消息队列

定时接收消息

mqueue.h

mq_timedsend

消息队列

定时发送消息

mqueue.h

mq_unlink

消息队列

移除消息队列

net/if.h

if_freenameindex

网络函数

通过if_nameindex()获取完毕接口名称与索引后,调用该函数以释放动态分配的内存区域

net/if.h

if_indextoname

数据转换函数

将网卡序号转为网卡名

net/if.h

if_nameindex

网络函数

返回动态分配的struct if_nameindex结构数组,数组中的每一个元素分别对应一个本地网络接口

net/if.h

if_nametoindex

数据转换函数

将网卡名转为网卡序号

netdb.h

freeaddrinfo

网络函数

释放getaddrinfo申请的内存空间

netdb.h

getaddrinfo

网络函数

将主机名和服务名转换到socket地址

netdb.h

getnameinfo

网络函数

将socket地址转换到主机名和服务名

poll.h

poll

文件操作函数

监视文件描述符

pthread.h

pthread_attr_destroy

pthread

删除线程的属性

pthread.h

pthread_attr_getdetachstate

pthread

获取脱离状态的属性

pthread.h

pthread_attr_getinheritsched

pthread

获取任务调度方式

pthread.h

pthread_attr_getschedparam

pthread

获取任务调度参数

pthread.h

pthread_attr_getschedpolicy

pthread

获取任务调度策略属性,目前仅支持SCHED_RR调度策略,不支持SCHED_OTHER、SCHED_FIFO

pthread.h

pthread_attr_getscope

pthread

获取任务范围属性,任务使用范围目前只支持PTHREAD_SCOPE_SYSTEM,不支持PTHREAD_SCOPE_PROCESS

pthread.h

pthread_attr_getstackaddr

pthread

获取任务堆栈的起始地址

pthread.h

pthread_attr_getstacksize

pthread

获取任务属性堆栈大小

pthread.h

pthread_attr_init

pthread

初始化任务属性

pthread.h

pthread_attr_setdetachstate

pthread

设置任务属性分离状态

pthread.h

pthread_attr_setinheritsched

pthread

设置任务调度方式

pthread.h

pthread_attr_setschedparam

pthread

设置任务调度参数,注意在LiteOS中,任务优先级的值越小,任务在系统中的优先级就越高,与标准库函数相反,且优先级仅支持0~31。注意:需要将pthread_attr_t任务属性的inheritsched字段设置为PTHREAD_EXPLICIT_SCHED,否则设置的任务调度优先级将不会生效,系统默认设置为PTHREAD_INHERIT_SCHED

pthread.h

pthread_attr_setschedpolicy

pthread

设置任务调度策略属性,目前仅支持SCHED_RR调度策略,不支持SCHED_OTHER、SCHED_FIFO

pthread.h

pthread_attr_setscope

pthread

设置任务范围属性,任务使用范围目前只支持PTHREAD_SCOPE_SYSTEM,不支持PTHREAD_SCOPE_PROCESS

pthread.h

pthread_attr_setstackaddr

pthread

设置任务堆栈的起始地址

pthread.h

pthread_attr_setstacksize

pthread

设置任务属性堆栈大小

pthread.h

pthread_cancel

pthread

取消任务,仅支持先设置PTHREAD_CANCEL_ASYNCHRONOUS状态,再调用pthread_cancel取消任务

pthread.h

pthread_cond_broadcast

pthread

唤醒所有被阻塞在条件变量上的线程

pthread.h

pthread_cond_destroy

pthread

释放条件变量

pthread.h

pthread_cond_init

pthread

初始化条件变量

pthread.h

pthread_cond_signal

pthread

释放被阻塞在条件变量上的一个线程

pthread.h

pthread_cond_timedwait

pthread

超时时限内等待一个条件变量,当超时等待时间为相对时间,LiteOS不能处理早已超时的情况

pthread.h

pthread_cond_wait

pthread

等待一个条件变量

pthread.h

pthread_condattr_destroy

pthread

删除存储并使属性对象无效

pthread.h

pthread_condattr_getclock

pthread

获取任务时钟

pthread.h

pthread_condattr_getpshared

pthread

获取条件变量属性,目前只支持获取PTHREAD_PROCESS_PRIVATE条件变量属性

pthread.h

pthread_condattr_init

pthread

初始化条件变量属性

pthread.h

pthread_condattr_setclock

pthread

设置任务时钟,只支持CLOCK_MONOTONIC或CLOCK_REALTIME模式

pthread.h

pthread_condattr_setpshared

pthread

设置条件变量属性,只支持PTHREAD_PROCESS_PRIVATE属性

pthread.h

pthread_create

pthread

创建任务

pthread.h

pthread_detach

pthread

分离任务

pthread.h

pthread_equal

pthread

判断是否为同一任务

pthread.h

pthread_exit

pthread

任务退出

pthread.h

pthread_getschedparam

pthread

获取任务优先级及调度参数,目前仅支持SCHED_RR调度策略,不支持SCHED_OTHER、SCHED_FIFO

pthread.h

pthread_getspecific

pthread

获取调用线程的键绑定

pthread.h

pthread_join

pthread

阻塞任务

pthread.h

pthread_key_create

pthread

分配用于标识进程中线程特定数据的键

pthread.h

pthread_key_delete

pthread

销毁现有线程特定数据键

pthread.h

pthread_mutex_destroy

pthread

删除互斥锁

pthread.h

pthread_mutex_getprioceiling

pthread

获取互斥锁的优先级上限

pthread.h

pthread_mutex_init

pthread

初始化互斥锁

pthread.h

pthread_mutex_lock

pthread

申请互斥锁(阻塞操作)

pthread.h

pthread_mutex_setprioceiling

pthread

设置互斥锁的优先级上限

pthread.h

pthread_mutex_timedlock

pthread

申请互斥锁(只在设定时间内阻塞)

pthread.h

pthread_mutex_trylock

pthread

尝试申请互斥锁(非阻塞)

pthread.h

pthread_mutex_unlock

pthread

释放互斥锁

pthread.h

pthread_mutexattr_destroy

pthread

销毁互斥锁属性对象

pthread.h

pthread_mutexattr_getprioceiling

pthread

获取互斥锁属性的优先级上限

pthread.h

pthread_mutexattr_getprotocol

pthread

获取互斥锁属性的协议属性

pthread.h

pthread_mutexattr_gettype

pthread

获取互斥锁的类型属性

pthread.h

pthread_mutexattr_init

pthread

初始化互斥锁属性对象

pthread.h

pthread_mutexattr_setprioceiling

pthread

设置互斥锁属性的优先级上限

pthread.h

pthread_mutexattr_setprotocol

pthread

设置互斥锁属性的协议属性

pthread.h

pthread_mutexattr_settype

pthread

设置互斥锁的类型属性

pthread.h

pthread_once

pthread

一次性操作任务

pthread.h

pthread_self

pthread

获取任务ID

pthread.h

pthread_setcancelstate

pthread

任务cancel功能开关

pthread.h

pthread_setcanceltype

pthread

设置任务cancel类型

pthread.h

pthread_setschedparam

pthread

设置任务优先级及调度策略,目前仅支持SCHED_RR调度策略,不支持SCHED_OTHER和SCHED_FIFO,任务优先级仅支持0~31

pthread.h

pthread_setschedprio

pthread

设置任务优先级,任务优先级仅支持0~31

pthread.h

pthread_setspecific

pthread

设置线程数据

pthread.h

pthread_testcancel

pthread

cancel任务

sched.h

sched_get_priority_max

调度函数

获取系统支持的最大的优先级值

sched.h

sched_get_priority_min

调度函数

获取系统支持的最小的优先级值

sched.h

sched_yield

调度函数

使当前线程放弃占用CPU

semaphore.h

sem_destroy

信号量

销毁无名信号量

semaphore.h

sem_getvalue

信号量

获取指定信号量的值

semaphore.h

sem_init

信号量

初始化无名信号量

semaphore.h

sem_post

信号量

释放一个指定的无名信号量

semaphore.h

sem_timedwait

信号量

申请一个超时等待的无名信号量,当超时等待时间为相对时间时,LiteOS不能处理早已超时的情况

semaphore.h

sem_trywait

信号量

尝试申请一个无名信号量

semaphore.h

sem_wait

信号量

申请等待一个无名信号量

stdio.h

dprintf

标准IO操作

打印函数

stdio.h

fdopen

文件操作函数

取一个现存的文件描述符,并使一个标准的I/O流与该描述符相结合

stdio.h

fileno

文件操作函数

取得参数stream指定的文件流所使用的文件描述符,如果文件流已经关闭,则返回-1,并设置错误码

stdio.h

fseeko

文件操作函数

移动文件流的读写位置

stdio.h

ftello

文件操作函数

获取文件流的读取位置

stdlib.h

initstate

随机数函数

初始化随机数生成状态

stdlib.h

lrand48

随机数函数

取得一个正的长整型的随机数

stdlib.h

mkstemp

文件处理函数

建立唯一的临时文件

stdlib.h

nrand48

随机数函数

产生正随机数

stdlib.h

posix_memalign

内存操作

对齐申请内存,返回内存指针

stdlib.h

random

随机数函数

产生0~RAND_MAX范围内的随机数

stdlib.h

realpath

文件处理函数

相对路径转换成绝对路径,入参resolved_path的长度必须不小于PATH_MAX

stdlib.h

seed48

随机数函数

设置48位随机数种子

stdlib.h

setstate

随机数函数

设置随机数状态

stdlib.h

srand48

随机数函数

设置48位随机数种子

stdlib.h

srandom

随机数函数

产生随机数种子

string.h

stpcpy

字符串处理函数

赋值字符串到数组

string.h

stpncpy

字符串处理函数

赋值字符串到数组

string.h

strcoll_l

字符串处理函数

根据 LC_COLLATE 比较字符串

string.h

strdup

字符串处理函数

拷贝字符串到新申请内存中

string.h

strerror_l

字符串处理函数

返回一个指向错误消息字符串的指针

string.h

strerror_r

字符串处理函数

返回一个指向错误消息字符串的指针(可重入)

string.h

strnlen

字符串处理函数

返回字符串长度

string.h

strtok_r

字符串处理函数

分割字符串

string.h

strxfrm_l

字符串处理函数

根据LC_COLLATE来转换字符串src,若传入n的长度小于字符串src的长度,则不作转换,直接返回字符串src的长度

strings.h

ffs

位操作函数

查找一个整数中的第一个置位值

strings.h

strcasecmp

字符串处理函数

字符串比较

strings.h

strcasecmp_l

字符串处理函数

字符串比较

strings.h

strncasecmp

字符串处理函数

字符串比较

strings.h

strncasecmp_l

字符串处理函数

字符串比较

sys/ioctl.h

ioctl

文件操作函数

执行指定操作

sys/select.h

select

文件操作函数

监视文件描述符,用户需要保证待监测的文件描述符为小于FD_SETSIZE的正值,否则可能产生不确定的行为

sys/stat.h

fstat

文件操作接口

获取文件属性

sys/stat.h

lstat

文件操作接口

获取文件属性

sys/stat.h

mkdir

目录操作接口

创建目录

sys/stat.h

stat

文件操作接口

获取文件属性

sys/time.h

gettimeofday

时间函数

获取当前时间,以秒和微秒的格式返回

sys/times.h

times

时间函数

获取cpu tick到buff中

sys/uio.h

readv

文件操作函数

读文件

sys/uio.h

writev

文件操作函数

写文件

sys/utsname.h

uname

系统函数

获取系统信息,可配置LOSCFG_LIB_VENDORNAME选项改变返回值的nodename成员,不返回硬件标识符和域名信息

syslog.h

syslog

系统函数

输出系统日志,不支持第一个入参按等级输出日志

time.h

asctime_r

时间函数

将时间和日期以字符串形式表示

time.h

clock_getres

时间函数

获取对应时钟类型能够提供的时间精确度

time.h

clock_gettime

时间函数

获取指定时钟的时间

time.h

clock_settime

时间函数

设置指定时钟的时间

time.h

clock_nanosleep

时间函数

具有可指定时钟的高分辨率睡眠(但是目前只支持CLOCK_REALTIME)

time.h

ctime_r

时间函数

将时间和日期以字符串形式表示

time.h

gmtime_r

时间函数

取得目前的时间和日期

time.h

localtime_r

时间函数

取得当地目前的时间和日期

time.h

nanosleep

时间函数

进程以纳秒为单位休眠,但目前只支持tick(默认10ms)休眠,第二个参数不支持,且传参所设置的秒数不能大于4292秒

time.h

strftime_l

时间函数

格式化日期和时间,格式化字符不支持%z或%Z的格式化转换

time.h

strptime

时间函数

按照特定时间格式将字符串转换为时间类型

time.h

timer_create

时间函数

创建定时器,只支持SIGEV_THREAD在线程内处理(LiteOS有两种实现方式,在线程或者在中断中处理timer的回调函数,具体采用哪种方式由宏开关LOSCFG_BASE_CORE_SWTMR_IN_ISR控制);clock_id只支持CLOCK_REALTIME

time.h

timer_delete

时间函数

删除定时器

time.h

timer_getoverrun

时间函数

获取定时器超时次数

time.h

timer_gettime

时间函数

获得一个定时器剩余时间

time.h

timer_settime

时间函数

初始化或者撤销定时器,it_interval参数为0时,定时器使用完毕同样需要手动删除

unistd.h

access

文件操作函数

判断文件权限

unistd.h

chdir

目录操作接口

改变当前工作目录

unistd.h

close

文件操作接口

关闭文件

unistd.h

dup

文件操作函数

复制文件

unistd.h

dup2

文件操作函数

复制文件

unistd.h

fsync

文件操作函数

文件同步

unistd.h

ftruncate

文件操作函数

改变文件大小

unistd.h

getcwd

目录操作接口

获取当前工作目录

unistd.h

getopt

系统函数

分析命令行参数

unistd.h

getpid

系统函数

获取任务ID

unistd.h

isatty

文件操作函数

判断文件是否为终端设备

unistd.h

lseek

文件操作函数

改变文件读写指针

unistd.h

pread

文件操作函数

读文件

unistd.h

pwrite

文件操作函数

写文件

unistd.h

read

文件操作函数

读文件

unistd.h

rmdir

目录操作接口

删除目录

unistd.h

sleep

时间函数

以秒为单位阻塞进程

unistd.h

sync

文件操作函数

文件同步

unistd.h

sysconf

系统函数

获取系统参数

unistd.h

unlink

文件操作函数

删除文件

unistd.h

write

文件操作函数

写文件

utime.h

utime

文件操作函数

修改文件时间

wchar.h

mbsnrtowcs

宽字符处理函数

多字节序列转换为宽字符(有最大长度限制)

wchar.h

wcscoll_l

宽字符处理函数

采用目前区域的字符排列次序来比较宽字符串

wchar.h

wcsnlen

宽字符处理函数

返回多字节字符的长度(有最大长度限制)

wchar.h

wcsnrtombs

转换函数

把数组中存储的编码转换为多字节字符(有最大长度限制)

wchar.h

wcsxfrm_l

宽字符处理函数

根据程序当前的区域选项中的LC_COLLATE来转换宽字符串的前n个字符

wctype.h

iswalnum_l

宽字符处理函数

判断宽字符是否字母或数字

wctype.h

iswalpha_l

宽字符处理函数

判断宽字符是否为英文字母

wctype.h

iswblank

宽字符处理函数

判断宽字符是否为TAB或者空格

wctype.h

iswblank_l

宽字符处理函数

判断宽字符是否为TAB或者空格

wctype.h

iswcntrl_l

宽字符处理函数

判断宽字符是否为控制字符

wctype.h

iswctype_l

宽字符处理函数

判断是否为宽字符类型

wctype.h

iswdigit_l

宽字符处理函数

判断宽字符是否为阿拉伯字符0到9

wctype.h

iswgraph_l

宽字符处理函数

判断宽字符是否为除空格外的可打印字符

wctype.h

iswlower_l

宽字符处理函数

判断宽字符是否为小写英文字母

wctype.h

iswprint_l

宽字符处理函数

判断宽字符是否为可打印字符

wctype.h

iswpunct_l

宽字符处理函数

判断宽字符是否为标点符号或特殊符号

wctype.h

iswspace_l

宽字符处理函数

判断宽字符是否为空格字符

wctype.h

iswupper_l

宽字符处理函数

判断宽字符是否为大写英文字母

wctype.h

iswxdigit_l

宽字符处理函数

判断宽字符是否为16进制数字

wctype.h

towlower_l

宽字符处理函数

将宽字符转换成小写字母

wctype.h

towupper_l

宽字符处理函数

将宽字符转换成大写字母

wctype.h

wctype_l

宽字符处理函数

判断是否为宽字符类型

POSIX NP支持接口

LiteOS还提供了一套POSIX NP(nonportable)适配接口,以支持部分SMP(多核)的NP接口。NP接口为非POSIX标准接口,作为POSIX接口的补充。以下接口只有在开启多核模式下可以操作亲和性,在单核模式下直接返回ENOERR。

文件名

接口名

说明

pthread.h

pthread_attr_setaffinity_np

设置attr亲和性属性

pthread.h

pthread_attr_getaffinity_np

获取attr亲和性属性

pthread.h

pthread_setaffinity_np

设置pthread任务的亲和性

pthread.h

pthread_getaffinity_np

获取pthread任务的亲和性

pthread.h

pthread_setname_np

设置线程名称

pthread.h

pthread_getname_np

获取线程名称

POSIX不支持接口

LiteOS的POSIX接口中,有一些未支持,具体参见下表。

头文件

接口名

aio.h

aio_cancel

aio.h

aio_error

aio.h

aio_fsync

aio.h

aio_read

aio.h

aio_return

aio.h

aio_suspend

aio.h

aio_write

aio.h

lio_listio

dirent.h

dirfd

dirent.h

fdopendir

dirent.h

readdir_r

dlfcn.h

dlerror

fcntl.h

openat

fcntl.h

posix_fadvise

fcntl.h

posix_fallocate

fmtmsg.h

fmtmsg

fnmatch.h

fnmatch

ftw.h

ftw

ftw.h

nftw

glob.h

glob

glob.h

globfree

grp.h

endgrent

grp.h

getgrent

grp.h

getgrgid

grp.h

getgrgid_r

grp.h

getgrnam

grp.h

getgrnam_r

grp.h

setgrent

iconv.h

iconv

iconv.h

iconv_close

iconv.h

iconv_open

inttypes.h

imaxabs

inttypes.h

imaxdiv

libgen.h

dirname

locale.h

duplocale

locale.h

freelocale

locale.h

newlocale

locale.h

uselocale

monetary.h

strfmon

monetary.h

strfmon_l

mqueue.h

mq_notify

netdb.h

endhostent

netdb.h

endnetent

netdb.h

endprotoent

netdb.h

endservent

netdb.h

gai_strerror

netdb.h

gethostent

netdb.h

getnetbyaddr

netdb.h

getnetbyname

netdb.h

getnetent

netdb.h

getprotobyname

netdb.h

getprotobynumber

netdb.h

getprotoent

netdb.h

getservbyname

netdb.h

getservbyport

netdb.h

getservent

netdb.h

sethostent

netdb.h

setnetent

netdb.h

setprotoent

netdb.h

setservent

nl_types.h

catclose

nl_types.h

catgets

nl_types.h

catopen

pthread.h

pthread_atfork

pthread.h

pthread_attr_getguardsize

pthread.h

pthread_attr_getstack

pthread.h

pthread_attr_setguardsize

pthread.h

pthread_attr_setstack

pthread.h

pthread_barrier_destroy

pthread.h

pthread_barrier_init

pthread.h

pthread_barrier_wait

pthread.h

pthread_barrierattr_destroy

pthread.h

pthread_barrierattr_getpshared

pthread.h

pthread_barrierattr_init

pthread.h

pthread_barrierattr_setpshared

pthread.h

pthread_getattr_default_np

pthread.h

pthread_getattr_np

pthread.h

pthread_getconcurrency

pthread.h

pthread_getcpuclockid

pthread.h

pthread_getname_np

pthread.h

pthread_mutex_consistent

pthread.h

pthread_mutexattr_getpshared

pthread.h

pthread_mutexattr_getrobust

pthread.h

pthread_mutexattr_setpshared

pthread.h

pthread_mutexattr_setrobust

pthread.h

pthread_rwlock_destroy

pthread.h

pthread_rwlock_init

pthread.h

pthread_rwlock_rdlock

pthread.h

pthread_rwlock_timedrdlock

pthread.h

pthread_rwlock_timedwrlock

pthread.h

pthread_rwlock_tryrdlock

pthread.h

pthread_rwlock_trywrlock

pthread.h

pthread_rwlock_unlock

pthread.h

pthread_rwlock_wrlock

pthread.h

pthread_rwlockattr_destroy

pthread.h

pthread_rwlockattr_getpshared

pthread.h

pthread_rwlockattr_init

pthread.h

pthread_rwlockattr_setpshared

pthread.h

pthread_setattr_default_np

pthread.h

pthread_setconcurrency

pthread.h

pthread_setname_np

pthread.h

pthread_spin_destroy

pthread.h

pthread_spin_init

pthread.h

pthread_spin_lock

pthread.h

pthread_spin_trylock

pthread.h

pthread_spin_unlock

pthread.h

pthread_timedjoin_np

pthread.h

pthread_tryjoin_np

pwd.h

endpwent

pwd.h

getpwent

pwd.h

getpwnam

pwd.h

getpwnam_r

pwd.h

getpwuid

pwd.h

getpwuid_r

pwd.h

setpwent

regex.h

regcomp

regex.h

regerror

regex.h

regexec

regex.h

regfree

setjmp.h

siglongjmp

setjmp.h

sigsetjmp

sched.h

sched_getparam

sched.h

sched_getscheduler

sched.h

sched_rr_get_interval

sched.h

sched_setparam

sched.h

sched_setscheduler

search.h

hcreate

search.h

hdestroy

search.h

hsearch

search.h

insque

search.h

lfind

search.h

lsearch

search.h

remque

search.h

tdelete

search.h

tfind

search.h

tsearch

search.h

twalk

semaphore.h

sem_close

semaphore.h

sem_open

semaphore.h

sem_unlink

signal.h

kill

signal.h

killpg

signal.h

psiginfo

signal.h

psignal

signal.h

pthread_kill

signal.h

pthread_sigmask

signal.h

sigaction

signal.h

sigaddset

signal.h

sigaltstack

signal.h

sigdelset

signal.h

sigemptyset

signal.h

sigfillset

signal.h

sighold

signal.h

sigignore

signal.h

siginterrupt

signal.h

sigismember

signal.h

sigpause

signal.h

sigpending

signal.h

sigprocmask

signal.h

sigqueue

signal.h

sigrelse

signal.h

sigsuspend

signal.h

sigtimedwait

signal.h

sigwait

signal.h

sigwaitinfo

spawn.h

posix_spawn

spawn.h

posix_spawn_file_actions_addclose

spawn.h

posix_spawn_file_actions_adddup2

spawn.h

posix_spawn_file_actions_addopen

spawn.h

posix_spawn_file_actions_destroy

spawn.h

posix_spawn_file_actions_init

spawn.h

posix_spawnattr_destroy

spawn.h

posix_spawnattr_getflags

spawn.h

posix_spawnattr_getpgroup

spawn.h

posix_spawnattr_getschedparam

spawn.h

posix_spawnattr_getschedpolicy

spawn.h

posix_spawnattr_getsigdefault

spawn.h

posix_spawnattr_getsigmask

spawn.h

posix_spawnattr_init

spawn.h

posix_spawnattr_setflags

spawn.h

posix_spawnattr_setpgroup

spawn.h

posix_spawnattr_setschedparam

spawn.h

posix_spawnattr_setschedpolicy

spawn.h

posix_spawnattr_setsigdefault

spawn.h

posix_spawnattr_setsigmask

spawn.h

posix_spawnp

stdio.h

ctermid

stdio.h

flockfile

stdio.h

fmemopen

stdio.h

ftrylockfile

stdio.h

funlockfile

stdio.h

getc_unlocked

stdio.h

getchar_unlocked

stdio.h

getdelim

stdio.h

getline

stdio.h

open_memstream

stdio.h

pclose

stdio.h

popen

stdio.h

putc_unlocked

stdio.h

putchar_unlocked

stdio.h

renameat

stdio.h

tempnam

stdlib.h

_Exit

stdlib.h

a64l

stdlib.h

drand48

stdlib.h

erand48

stdlib.h

exit

stdlib.h

getsubopt

stdlib.h

grantpt

stdlib.h

jrand48

stdlib.h

l64a

stdlib.h

lcong48

stdlib.h

mkdtemp

stdlib.h

mrand48

stdlib.h

posix_openpt

stdlib.h

ptsname

stdlib.h

putenv

stdlib.h

rand_r

stdlib.h

setenv

stdlib.h

setkey

stdlib.h

unlockpt

stdlib.h

unsetenv

string.h

memccpy

string.h

strndup

string.h

strsignal

stropts.h

isastream

sys/ipc.h

ftok

sys/mman.h

mlock

sys/mman.h

mlockall

sys/mman.h

mmap

sys/mman.h

mprotect

sys/mman.h

msync

sys/mman.h

munlock

sys/mman.h

munlockall

sys/mman.h

munmap

sys/mman.h

posix_madvise

sys/mman.h

shm_open

sys/mman.h

shm_unlink

sys/msg.h

msgctl

sys/msg.h

msgget

sys/msg.h

msgrcv

sys/msg.h

msgsnd

sys/resource.h

getpriority

sys/resource.h

getrlimit

sys/resource.h

getrusage

sys/resource.h

setpriority

sys/resource.h

setrlimit

sys/select.h

pselect

sys/sem.h

semctl

sys/sem.h

semget

sys/sem.h

semop

sys/shm.h

shmat

sys/shm.h

shmctl

sys/shm.h

shmdt

sys/shm.h

shmget

sys/socket.h

sockatmark

sys/socket.h

socketpair

sys/stat.h

chmod

sys/stat.h

fchmod

sys/stat.h

fchmodat

sys/stat.h

fstatat

sys/stat.h

futimens

sys/stat.h

mkdirat

sys/stat.h

mkfifo

sys/stat.h

mkfifoat

sys/stat.h

mknod

sys/stat.h

mknodat

sys/stat.h

umask

sys/stat.h

utimensat

sys/time.h

getitimer

sys/time.h

setitimer

sys/time.h

utimes

sys/wait.h

wait

sys/wait.h

waitid

sys/wait.h

waitpid

syslog.h

closelog

syslog.h

openlog

syslog.h

setlogmask

termios.h

cfgetispeed

termios.h

cfgetospeed

termios.h

cfsetispeed

termios.h

cfsetospeed

termios.h

tcdrain

termios.h

tcflow

termios.h

tcflush

termios.h

tcgetattr

termios.h

tcgetsid

termios.h

tcsendbreak

termios.h

tcsetattr

time.h

clock_getcpuclockid

time.h

getdate

time.h

tzset

ulimit.h

ulimit

unistd

_exit

unistd.h

alarm

unistd.h

chown

unistd.h

confstr

unistd.h

crypt

unistd.h

ctermid

unistd.h

encrypt

unistd.h

execl

unistd.h

execle

unistd.h

execlp

unistd.h

execv

unistd.h

execve

unistd.h

execvp

unistd.h

faccessat

unistd.h

fchdir

unistd.h

fchown

unistd.h

fchownat

unistd.h

fdatasync

unistd.h

fexecve

unistd.h

fork

unistd.h

fpathconf

unistd.h

getegid

unistd.h

geteuid

unistd.h

getgid

unistd.h

getgroups

unistd.h

gethostid

unistd.h

gethostname

unistd.h

getlogin

unistd.h

getlogin_r

unistd.h

getpgid

unistd.h

getpgrp

unistd.h

getppid

unistd.h

getsid

unistd.h

getuid

unistd.h

lchown

unistd.h

link

unistd.h

linkat

unistd.h

nice

unistd.h

pathconf

unistd.h

pause

unistd.h

pipe

unistd.h

readlink

unistd.h

readlinkat

unistd.h

setegid

unistd.h

seteuid

unistd.h

setgid

unistd.h

setpgid

unistd.h

setpgrp

unistd.h

setregid

unistd.h

setreuid

unistd.h

setsid

unistd.h

setuid

unistd.h

swab

unistd.h

symlink

unistd.h

symlinkat

unistd.h

tcgetpgrp

unistd.h

tcsetpgrp

unistd.h

truncate

unistd.h

ttyname

unistd.h

ttyname_r

unistd.h

unlinkat

utmpx.h

endutxent

utmpx.h

getutxent

utmpx.h

getutxid

utmpx.h

getutxline

utmpx.h

pututxline

utmpx.h

setutxent

wchar.h

open_wmemstream

wchar.h

wcpcpy

wchar.h

wcpncpy

wchar.h

wcscasecmp

wchar.h

wcscasecmp_l

wchar.h

wcsdup

wchar.h

wcsncasecmp

wchar.h

wcsncasecmp_l

wchar.h

wcswidth

wchar.h

wcwidth

wctype.h

towctrans_l

wctype.h

wctrans_l

wordexp.h

wordexp

wordexp.h

wordfree

Libc/Libm/Libmat接口

Libc支持接口

LiteOS支持部分Libc接口,具体如下表所示。

头文件

接口名

类型

说明

arpa/inet.h

inet_aton

网络函数

转换字符串IP为数字格式,并存储在传参地址中

arpa/inet.h

htonl

网络函数

将主机数转换成无符号长整型的网络字节顺序

arpa/inet.h

htons

网络函数

将整型变量从主机字节顺序转变成网络字节顺序

arpa/inet.h

ntohl

网络函数

将一个无符号长整形数从网络字节顺序转换为主机字节顺序

arpa/inet.h

ntohs

网络函数

将一个16位数由网络字节顺序转换为主机字节顺序

assert.h

assert

诊断宏函数

如果它的条件返回错误,则终止程序执行,用于调测

ctype.h

isalnum

数据判断

判断入参是否为字母或数字

ctype.h

isalpha

数据判断

判断入参是否为字母

ctype.h

iscntrl

数据判断

判断入参是否为控制字符

ctype.h

isdigit

数据判断

判断入参是否为十进制数

ctype.h

isgraph

数据判断

判断入参是否有图形表示

ctype.h

islower

数据判断

判断入参是否为小写字母

ctype.h

isprint

数据判断

判断入参是否可以打印

ctype.h

ispunct

数据判断

判断入参是否为标点符号

ctype.h

isspace

数据判断

判断入参是否为空格

ctype.h

isupper

数据判断

判断入参是否为大写字母

ctype.h

isxdigit

数据判断

判断入参是否为十六进制数

ctype.h

tolower

数据转换函数

将大写字母转换成小写字母

ctype.h

toupper

数据转换函数

将小写字母转换成大写字母

err.h

err

错误提示

输出错误信息

err.h

errx

错误提示

输出错误信息

err.h

verr

错误提示

输出错误信息

err.h

verrx

错误提示

输出错误信息

err.h

vwarn

错误提示

输出警告信息

err.h

vwarnx

错误提示

输出警告信息

err.h

warn

错误提示

输出警告信息

err.h

warnx

错误提示

输出警告信息

errno.h

errno

错误信息

错误类型

errno.h

get_errno

错误信息

获取错误类型

errno.h

set_errno

错误信息

设置错误类型

fcntl.h

creat64

文件操作函数

创建一个文件(64位)

fcntl.h

fallcoate

文件操作函数

为文件预分配物理空间

fcntl.h

fallcoate64

文件操作函数

为文件预分配物理空间(64位)

fcntl.h

open64

文件操作函数

打开文件(64位)

ifaddrs.h

freeifaddrs

网络函数

释放本地IP地址

ifaddrs.h

getifaddrs

网络函数

获取本地IP地址

inttypes.h

strtoimax

数据转换函数

解析字符串中的整数值

inttypes.h

strtoumax

数据转换函数

解析字符串中的整数值

inttypes.h

wcstoimax

数据转换函数

解析字符串中的整数值

inttypes.h

wcstoumax

数据转换函数

解析字符串中的整数值

locale.h

setlocale

系统函数

设置或读取地域化信息。读取地域信息时返回C.UTF-8,因为LiteOS不支持国际化,所以设置地域信息无效。

malloc.h

calloc

内存操作

申请内存,返回内存指针

malloc.h

free

内存操作

释放原先分配的动态内存

malloc.h

malloc

内存操作

申请内存,返回内存指针

malloc.h

memalign

内存操作

对齐申请内存,返回内存指针

malloc.h

realloc

内存操作

调整已经申请的内存,返回内存指针

malloc.h

zalloc

内存操作

申请内存并清零内存区域,返回内存指针

netdb.h

gethostbyname

网络函数

获取对应于给定主机名的主机信息

netdb.h

gethostbyname_r

网络函数

获取对应于给定主机名的主机信息,可重入

poll.h

notify_poll

文件操作函数

监视文件描述符

poll.h

notify_poll_with_key

文件操作函数

监视文件描述符

resolv.h

dn_comp

网络函数

压缩域名

sched.h

sched_getaffinity

调度函数

获取指定任务对的CPU亲和性

sched.h

sched_setaffinity

调度函数

设置指定任务对的CPU亲和性

setjmp.h

longjmp

系统函数

长跳转

setjmp.h

setjmp

系统函数

承担非局部标号和goto作用

stdio.h

asprintf

格式化字符串函数

格式化字符串的复制

stdio.h

clearerr

错误处理函数

清除错误标志(加锁)

stdio.h

clearerr_unlocked

错误处理函数

清除错误标志(不加锁)

stdio.h

fclose

文件操作函数

关闭文件,文件关闭后再操作,将操作失败,并且设置错误码

stdio.h

feof

文件操作函数

检查文件流是否读到了文件尾

stdio.h

feof_unlocked

文件操作函数

检查文件流是否读到了文件尾(不加锁)

stdio.h

ferror

文件操作函数

检查文件流是否有错误

stdio.h

ferror_unlocked

文件操作函数

检查文件流是否有错误(不加锁)

stdio.h

fflush

文件操作函数

更新缓冲区

stdio.h

fflush_unlocked

文件操作函数

更新缓冲区(不加锁)

stdio.h

fgetc

文件操作函数

从文件中读取一个字符

stdio.h

fgetpos

文件操作函数

取得文件流的读取位置

stdio.h

fgets

文件操作函数

从文件中读取一个字符串

stdio.h

fgets_unlocked

文件操作函数

从文件中读取一个字符串(不加锁)

stdio.h

fileno_unlocked

文件操作函数

取得参数stream指定的文件流所使用的文件描述符(不加锁),

如果文件流已经关闭,则返回-1,并设置错误码

stdio.h

fopen

文件操作函数

打开文件,不支持以rx模式打开一个文件,否则返回NULL

stdio.h

fopen64

文件操作函数

打开文件(64位)

stdio.h

fprintf

文件操作函数

格式化输出数据至文件

stdio.h

fputc

文件操作函数

将字符写入文件流中

stdio.h

fputs

文件操作函数

将字符串写入文件流中

stdio.h

fputs_unlocked

文件操作函数

将字符串写入文件流中(不加锁)

stdio.h

fread

文件操作函数

从文件流读取数据,读取文件的size未做对齐处理,需用户自行保证size对齐

stdio.h

fread_unlocked

文件操作函数

从文件流读取数据(不加锁)

stdio.h

freopen

文件操作函数

关闭已打开的文件,按照传入的模式打开新文件后将新文件流关联到旧文件流

stdio.h

freopen64

文件操作函数

关闭已打开的文件,按照传入的模式打开新文件后将新文件流关联到旧文件(64位)

stdio.h

fscanf

文件操作函数

读取格式化输入

stdio.h

fseek

文件操作函数

移动文件流的读取位置

stdio.h

fseeko64

文件操作函数

移动文件流的读写位置(64位)

stdio.h

fsetpos

文件操作函数

移动文件流的读取位置

stdio.h

fsetpos64

文件操作函数

移动文件流的读取位置(64位)

stdio.h

ftell

文件操作函数

获取文件流的读取位置

stdio.h

ftello64

文件操作函数

获取文件流的读取位置(64位)

stdio.h

fwrite

文件操作函数

将数据写入文件流,写入文件的size未做对齐处理,需用户自行保证size对齐,有入参合法性校验

stdio.h

fwrite_unlocked

文件操作函数

将数据写入文件流(不加锁)

stdio.h

getc

标准IO操作

由文件中读取一个字符

stdio.h

getchar

标准IO操作

由文件中读取一个字符

stdio.h

gets

标准IO操作

由文件中读取一个字符串

stdio.h

perror

标准IO操作

向标准错误流输出错误信息

stdio.h

printf

标准IO操作

格式化输出数据

stdio.h

putc

标准IO操作

将一指定字符写入文件

stdio.h

putchar

标准IO操作

将一指定字符写标准输出流

stdio.h

puts

标准IO操作

将指定的字符串写到标准输出流

stdio.h

putw

标准IO操作

将一个整数写入文件

stdio.h

remove

文件操作函数

删除

stdio.h

rename

文件操作函数

重命名

stdio.h

rewind

文件操作函数

移动文件流的读取位置到起始位置

stdio.h

scanf

格式化输入输出函数

格式化字符串输入

stdio.h

setbuf

文件操作函数

设置文件流的缓冲区

stdio.h

setbuffer

文件操作函数

设置文件流的缓冲区

stdio.h

setvbuf

文件操作函数

设置文件流的缓冲区,当入参size小于0时,返回-1,并设置错误码。当入参buf为NULL,则会动态申请默认大小空间作为文件缓冲区

stdio.h

snprintf

格式化输入输出函数

格式化字符串

stdio.h

sprintf

格式化输入输出函数

格式化字符串

stdio.h

sscanf

格式化输入输出函数

格式化输入字符串

stdio.h

tmpnam

文件操作函数

返回指向唯一文件名的指针

stdio.h

ungetc

文件操作函数

将一指定字符写回文件流中

stdio.h

vasprintf

格式化输入输出函数

格式化字符串

stdio.h

vfprintf

格式化输入输出函数

格式化输出数据至文件

stdio.h

vfscanf

格式化输入输出函数

从文件流读取字符串,根据参数format转化并格式化数据

stdio.h

vprintf

格式化输入输出函数

格式化输出

stdio.h

vscanf

格式化输入输出函数

格式化字符串输入

stdio.h

vsnprintf

格式化输入输出函数

格式化字符串

stdio.h

vsprintf

格式化输入输出函数

格式化字符串

stdio.h

vsscanf

格式化输入输出函数

格式化输入字符串

stdlib.h

abort

系统函数

产生异常终止系统

stdlib.h

abs

数学计算函数

求整形绝对值

stdlib.h

atof

数据转换函数

将字符串转换成浮点型数

stdlib.h

atoi

数据转换函数

将字符串转换成整型数

stdlib.h

atol

数据转换函数

将字符串转换成长整型数

stdlib.h

atoll

数据转换函数

将字符串转换成长长整型数

stdlib.h

bsearch

数据结构函数

二元搜索

stdlib.h

labs

数学计算函数

求长整形绝对值

stdlib.h

llabs

数学计算函数

求长整形绝对值

stdlib.h

mblen

多字节字符函数

多字节字符字符串长度

stdlib.h

mbstowcs

多字节字符函数

转换多字节字符串到宽字符字符串

stdlib.h

mbtowc

多字节字符函数

转换多字节字符到宽字符字符

stdlib.h

mkostemps

文件处理函数

产生唯一的临时文件名

stdlib.h

mkstemps

文件处理函数

建立唯一的临时文件

stdlib.h

qsort

排序函数

利用快速排序法排列数组

stdlib.h

qsort_r

排序函数

利用快速排序法排列数组

stdlib.h

rand

随机数函数

产生随机数

stdlib.h

srand

随机数函数

设置随机数种子

stdlib.h

strtod

数据转换函数

将字符串转换成浮点型数

stdlib.h

strtof

字符串处理函数

字符串转单精度浮点型数

stdlib.h

strtol

数据转换函数

将字符串转换成长整型数

stdlib.h

strtold

字符串处理函数

字符串转长双精度浮点型数

stdlib.h

strtoll

字符串处理函数

字符串转双长整型数

stdlib.h

strtoul

数据转换函数

将字符串转换成无符号长整型数

stdlib.h

strtoull

字符串处理函数

字符串转无符号双长整数

stdlib.h

wctomb

多字节字符函数

转换宽字符字符到多字节字符

string.h

memchr

字符串处理函数

在某一内存范围内查找一特定字符

string.h

memcmp

字符串处理函数

比较内存内容

string.h

memcpy

字符串处理函数

拷贝内存内容

string.h

memmove

字符串处理函数

拷贝内存内容

string.h

memrchr

字符串处理函数

查找字符首次出现位置

string.h

memset

字符串处理函数

将一段内存空间填入某值

string.h

strcasestr

字符串处理函数

在一字符串中查找指定字符串,忽略大小写

string.h

strcat

字符串处理函数

连接两个字符串

string.h

strchr

字符串处理函数

查找字符首次出现位置

string.h

strchrnul

字符串处理函数

查找字符首次出现位置

string.h

strcmp

字符串处理函数

比较字符串

string.h

strcoll

字符串处理函数

根据 LC_COLLATE 比较字符串

string.h

strcpy

字符串处理函数

拷贝字符串

string.h

strcspn

字符串处理函数

入参1字符串中连续有几个字符都不属于入参2字符串

string.h

strerror

字符串处理函数

返回一个指向错误消息字符串的指针

string.h

strlcpy

字符串处理函数

拷贝字符串

string.h

strlen

字符串处理函数

返回字符串长度

string.h

strncat

字符串处理函数

连接两个字符串

string.h

strncmp

字符串处理函数

比较字符串

string.h

strncpy

字符串处理函数

拷贝字符串

string.h

strpbrk

字符串处理函数

查找字符串中第一个出现的指定字符

string.h

strrchr

字符串处理函数

查找字符串中最后出现的指定字符

string.h

strsep

字符串处理函数

分解字符串为一组字符串

string.h

strspn

字符串处理函数

返回字符串中连续不含指定字符串内容的字符数

string.h

strstr

字符串处理函数

在一字符串中查找指定字符串

string.h

strtok

字符串处理函数

分割字符串

string.h

strxfrm

字符串处理函数

根据LC_COLLATE来转换字符串src,若传入n的长度小于字符串src的长度,则不作转换,直接返回字符串src的长度

strings.h

bcmp

内存操作

同memcmp

strings.h

bcopy

内存操作

同memmove

strings.h

bzero

内存操作

同memset

strings.h

rindex

字符处理函数

字符处理

sys/mount.h

mount

文件系统操作

挂载文件系统

sys/mount.h

umount

文件系统操作

卸载文件系统

sys/prctl.h

prctl

pthread

线程操作,目前只支持PR_SET_NAME操作修改线程名

sys/stat.h

fstat64

文件操作接口

获取文件属性(64位)

sys/statfs.h

statfs

文件操作接口

获取文件系统属性

sys/time.h

adjtime

时间函数

按时间值delta来微调系统时间

sys/time.h

settimeofday

时间函数

设置系统时间

threads.h

tss_get

pthread

获取调用线程的键绑定,同pthread_getspecific

time.h

asctime

时间函数

将时间和日期以字符串形式表示

time.h

clock

时间函数

获取处理器时钟所使用的时间

time.h

ctime

时间函数

将时间和日期以字符串形式表示

time.h

difftime

时间函数

返回两个时间之间的差(秒)

time.h

gmtime

时间函数

取得目前的时间和日期

time.h

localtime

时间函数

取得当地目前的时间和日期

time.h

mktime

时间函数

将时间结构数据转换成经过的秒数

time.h

stime

时间函数

设置时间

time.h

strftime

时间函数

格式化日期和时间,格式化字符不支持%z或%Z的格式化转换

time.h

time

时间函数

获取当前的系统时间

time64.h

asctime64

时间函数

将时间和日期以字符串形式表示

time64.h

asctime64_r

时间函数

将时间和日期以字符串形式表示

time64.h

ctime64

时间函数

将时间和日期以字符串形式表示

time64.h

ctime64_r

时间函数

将时间和日期以字符串形式表示

time64.h

gmtime64

时间函数

取得目前的时间和日期

time64.h

gmtime64_r

时间函数

取得目前的时间和日期

time64.h

gettimeofday64

时间函数

取得当前系统时间,LiteOS重新实现了该接口,用于在32位平台下获取2038年以后的系统时间

time64.h

localtime64

时间函数

取得当地目前的时间和日期

time64.h

localtime64_r

时间函数

取得当地目前的时间和日期

time64.h

mktime64

时间函数

将时间结构数据转换成经过的秒数

time64.h

settimeofday64

时间函数

设置当前时间,LiteOS重新实现了该接口,用于在32位平台下设置当前系统时间为2038年以后的时间

tzdst.h

settimezone

时间函数

设置时区

tzdst.h

dst_disable

时间函数

失能夏令时

tzdst.h

dst_enable

时间函数

使能夏令时

tzdst.h

dst_inquire

时间函数

查询夏令时

uchar.h

c32rtomb

宽字符处理函数

将32位宽字符转换为多字节字符

uchar.h

mbrtoc32

宽字符处理函数

将多字节字符转换为32位宽字符

unistd.h

ftruncate64

文件操作函数

改变文件大小

unistd.h

getentropy

随机数函数

获取指定长度的随机数,若硬件不能提供随机数,则返回失败

unistd.h

getpagesize

系统函数

获取页大小

unistd.h

lseek64

文件操作函数

改变文件读写指针

unistd.h

pread64

文件操作函数

读文件

unistd.h

pwrite64

文件操作函数

写文件

unistd.h

usleep

时间函数

以微秒为单位阻塞进程

wchar.h

btowc

宽字符处理函数

把单个字节转换为宽字符

wchar.h

fgetwc

宽字符处理函数

把单个字节转换为宽字符

wchar.h

fgetwc_unlocked

宽字符处理函数

把单个字节转换为宽字符(不加锁)

wchar.h

fputwc

宽字符处理函数

写入一个宽字符到文件流

wchar.h

fputwc_unlocked

宽字符处理函数

写入一个宽字符到文件流(不加锁)

wchar.h

fwide

文件操作函数

设置查询文件流的方向

wchar.h

getwc

宽字符处理函数

从文件中读取一个宽字符

wchar.h

getwc_unlocked

宽字符处理函数

从文件中读取一个宽字符(不加锁)

wchar.h

mbrlen

宽字符处理函数

返回多字节字符的长度

wchar.h

mbrtowc

宽字符处理函数

多字节序列转换为宽字符

wchar.h

mbsinit

宽字符处理函数

测试初始的转换状态

wchar.h

mbsrtowcs

宽字符处理函数

多字节序列转换为宽字符

wchar.h

putwc

宽字符处理函数

将一指定宽字符写入文件中

wchar.h

putwc_unlocked

宽字符处理函数

将一指定宽字符写入文件中(不加锁)

wchar.h

swprintf

格式化输入输出函数

格式化宽字符串

wchar.h

ungetwc

宽字符处理函数

将宽字符写回指定的文件流

wchar.h

vfwprintf

格式化输入输出函数

格式化输出宽字符数据至文件

wchar.h

vfwscanf

格式化输入输出函数

从输入流中格式化宽字符串

wchar.h

vswprintf

格式化输入输出函数

格式化宽字符串

wchar.h

wcrtomb

宽字符处理函数

将宽字符代码转换为字符

wchar.h

wcschr

宽字符处理函数

查找宽字符串中第一个出现的指定宽字符

wchar.h

wcscmp

宽字符处理函数

比较宽字符串

wchar.h

wcscoll

宽字符处理函数

采用目前区域的字符排列次序来比较宽字符串

wchar.h

wcsftime

宽字符处理函数

格式化时间

wchar.h

wcsftime_l

宽字符处理函数

格式化时间

wchar.h

wcslen

宽字符处理函数

返回宽字符串长度

wchar.h

wcsncmp

宽字符处理函数

比较两个宽字符串指定个数的字符

wchar.h

wcsncpy

宽字符处理函数

复制宽字符串

wchar.h

wcsrtombs

转换函数

把数组中存储的编码转换为多字节字符

wchar.h

wcsstr

宽字符处理函数

在一个宽字符串中搜索另一个宽字符串

wchar.h

wcstol

宽字符处理函数

将宽字符串转换为长整数

wchar.h

wcstoll

宽字符处理函数

将宽字符串转换为长长整数

wchar.h

wcstoul

宽字符处理函数

将宽字符串转换为无符号长整数

wchar.h

wcstoull

宽字符处理函数

将宽字符串转换为无符号长长整数

wchar.h

wcsxfrm

宽字符处理函数

根据程序当前的区域选项中的LC_COLLATE来转换宽字符串的前n个字符

wchar.h

wctob

宽字符处理函数

把宽字符转换为单字节字符

wchar.h

wmemchr

宽字符处理函数

在一个宽字符数组中搜索

wchar.h

wmemcmp

宽字符处理函数

比较两个宽字符数组

wchar.h

wmemcpy

宽字符处理函数

拷贝宽字符数组

wchar.h

wmemmove

宽字符处理函数

拷贝宽字符数组

wchar.h

wmemset

宽字符处理函数

填充一个宽字符数组

wctype.h

iswalnum

宽字符处理函数

判断宽字符是否字母或数字

wctype.h

iswalpha

宽字符处理函数

判断宽字符是否为英文字母

wctype.h

iswcntrl

宽字符处理函数

判断宽字符是否为控制字符

wctype.h

iswctype

宽字符处理函数

判断是否为宽字符类型

wctype.h

iswdigit

宽字符处理函数

判断宽字符是否为阿拉伯字符0到9

wctype.h

iswgraph

宽字符处理函数

判断宽字符是否为除空格外的可打印字符

wctype.h

iswlower

宽字符处理函数

判断宽字符是否为小写英文字母

wctype.h

iswprint

宽字符处理函数

判断宽字符是否为可打印字符

wctype.h

iswpunct

宽字符处理函数

判断宽字符是否为标点符号或特殊符号

wctype.h

iswspace

宽字符处理函数

判断宽字符是否为空格字符

wctype.h

iswupper

宽字符处理函数

判断宽字符是否为大写英文字母

wctype.h

iswxdigit

宽字符处理函数

判断宽字符是否为16进制数字

wctype.h

towlower

宽字符处理函数

将宽字符转换成小写字母

wctype.h

towupper

宽字符处理函数

将宽字符转换成大写字母

wctype.h

wctype

宽字符处理函数

判断是否为宽字符类型

Libm支持接口

LiteOS提供一套Libm开源接口,具体的规格参见下表。

须知: Libm不支持设置错误返回码。

头文件

接口名

类型

说明

complex.h

cabs

复数计算函数

计算复数的绝对值

math.h

acos

数学计算函数

取反余弦函数值

math.h

acosf

数学计算函数

求反余弦函数

math.h

acosh

数学计算函数

求反双曲余弦值

math.h

acoshf

数学计算函数

求反双曲余弦值

math.h

acoshl

数学计算函数

求反双曲余弦值

math.h

acosl

数学计算函数

求反余弦函数

math.h

asin

数学计算函数

取反正弦函数值

math.h

asinf

数学计算函数

求反正弦函数

math.h

asinh

数学计算函数

求反双曲正弦值

math.h

asinhf

数学计算函数

求反双曲正弦值

math.h

asinhl

数学计算函数

求反双曲正弦值

math.h

asinl

数学计算函数

求反正弦函数

math.h

atan

数学计算函数

取反正切函数值

math.h

atan2

数学计算函数

取得反正切函数值

math.h

atan2f

数学计算函数

求反正切的值(以弧度表示)

math.h

atan2l

数学计算函数

求反正切的值(以弧度表示)

math.h

atanf

数学计算函数

求反正切函数

math.h

atanh

数学计算函数

求反双曲线正切函数

math.h

atanhf

数学计算函数

求反双曲线正切函数

math.h

atanhl

数学计算函数

求反双曲线正切函数

math.h

atanl

数学计算函数

求反正切函数

math.h

cbrt

数学计算函数

求立方根函数

math.h

cbrtf

数学计算函数

求立方根函数

math.h

cbrtl

数学计算函数

求立方根函数

math.h

ceil

数学计算函数

计算最小整数值

math.h

ceilf

数学计算函数

计算最小整数值

math.h

ceill

数学计算函数

计算最小整数值

math.h

copysign

数学计算函数

返回x,但是符号位变为y的符号位

math.h

copysignf

数学计算函数

返回x,但是符号位变为y的符号位

math.h

copysignl

数学计算函数

返回x,但是符号位变为y的符号位

math.h

cos

数学计算函数

求余弦函数

math.h

cosf

数学计算函数

求余弦函数

math.h

cosh

数学计算函数

求双曲线余弦函数

math.h

coshf

数学计算函数

求双曲线余弦函数

math.h

coshl

数学计算函数

求双曲线余弦函数

math.h

cosl

数学计算函数

求余弦函数

math.h

drem

数学计算函数

求余数(double)

math.h

dremf

数学计算函数

求余数(float)

math.h

erf

数学计算函数

求误差函数

math.h

erfc

数学计算函数

求误差补函数

math.h

erfcf

数学计算函数

求误差补函数

math.h

erfcl

数学计算函数

求误差补函数

math.h

erff

数学计算函数

求误差函数

math.h

erfl

数学计算函数

求误差函数

math.h

exp

数学计算函数

计算指数

math.h

exp10

数学计算函数

计算以10为底的x次方值

math.h

exp10l

数学计算函数

计算以10为底的x次方值

math.h

exp2

数学计算函数

计算以2为底的x次方值

math.h

exp2f

数学计算函数

计算以2为底的x次方值

math.h

exp2l

数学计算函数

计算以2为底的x次方值

math.h

expf

数学计算函数

计算以e为底的x次方值

math.h

expl

数学计算函数

计算以e为底的x次方值

math.h

expm1

数学计算函数

exp(x) - 1.0

math.h

expm1f

数学计算函数

exp(x) - 1

math.h

expm1l

数学计算函数

exp(x) - 1

math.h

fabs

数学计算函数

计算浮点型数的绝对值

math.h

fabsf

数学计算函数

求浮点数的绝对值

math.h

fabsl

数学计算函数

求浮点数的绝对值

math.h

fdim

数学计算函数

计算入参的差值的绝对值

math.h

fdimf

数学计算函数

计算入参的差值的绝对值

math.h

fdiml

数学计算函数

计算入参的差值的绝对值

math.h

finite

数学计算函数

判断数据是否有效

math.h

finitef

数学计算函数

判断数据是否有效

math.h

floor

数学计算函数

向下取整

math.h

floorf

数学计算函数

向下取整

math.h

floorl

数学计算函数

向下取整

math.h

fma

数学计算函数

浮点运算(x · y) + z

math.h

fmaf

数学计算函数

浮点运算(x · y) + z

math.h

fmal

数学计算函数

浮点运算(x · y) + z

math.h

fmax

数学计算函数

返回两个浮点入参的较大值

math.h

fmaxf

数学计算函数

返回两个浮点入参的较大值

math.h

fmaxl

数学计算函数

返回两个浮点入参的较大值

math.h

fmin

数学计算函数

返回两个浮点入参的较小值

math.h

fminf

数学计算函数

返回两个浮点入参的较小值

math.h

fminl

数学计算函数

返回两个浮点入参的较小值

math.h

fmod

数学计算函数

计算余数

math.h

fmodf

数学计算函数

计算余数

math.h

fmodl

数学计算函数

计算余数

math.h

frexp

数学计算函数

将浮点型数分为尾数与指数

math.h

frexpf

数学计算函数

把浮点数分解成尾数和指数

math.h

frexpl

数学计算函数

把浮点数分解成尾数和指数

math.h

hypot

数学计算函数

返回直角三角形斜边长度

math.h

hypotf

数学计算函数

返回直角三角形斜边长度

math.h

hypotl

数学计算函数

返回直角三角形斜边长度

math.h

ilogb

数学计算函数

与logb相同,但是返回有符号数的整数

math.h

ilogbf

数学计算函数

与logb相同,但是返回有符号数的整数

math.h

ilogbl

数学计算函数

与logb相同,但是返回有符号数的整数

math.h

isnan

数据判断

判断参数是否为不可表示的值

math.h

j0

数学计算函数

返回x的一类0阶的Bessel函数

math.h

j0f

数学计算函数

返回x的一类0阶的Bessel函数

math.h

j1

数学计算函数

返回x的一类1阶的Bessel函数

math.h

j1f

数学计算函数

返回x的一类1阶的Bessel函数

math.h

jn

数学计算函数

返回x的一类n阶的Bessel函数

math.h

jnf

数学计算函数

返回x的一类n阶的Bessel函数

math.h

ldexp

数学计算函数

返回x乘以2的exp次幂

math.h

ldexpf

数学计算函数

返回x乘以2的exp次幂

math.h

ldexpl

数学计算函数

返回x乘以2的exp次幂

math.h

lgamma

数学计算函数

返回gamma函数的绝对值的自然对数,gamma函数为:gamma (x) = integral from 0 to ∞ of t^(x-1) e^-t dt

math.h

lgamma_r

数学计算函数

lgamma_r与lgamma类似,但它将中间结果的符号存储在第二个入参指向的变量中,而不是存储在全局变量中。这个接口是可重入的。

math.h

lgammaf

数学计算函数

返回gamma函数的绝对值的自然对数,gamma函数为:gamma (x) = integral from 0 to ∞ of t^(x-1) e^-t dt

math.h

lgammaf_r

数学计算函数

lgamma_r与lgamma类似,但它将中间结果的符号存储在第二个入参指向的变量中,而不是存储在全局变量中。这个接口是可重入的。

math.h

lgammal

数学计算函数

返回gamma函数的绝对值的自然对数,gamma函数为:gamma (x) = integral from 0 to ∞ of t^(x-1) e^-t dt

math.h

lgammal_r

数学计算函数

lgamma_r与lgamma类似,但它将中间结果的符号存储在第二个入参指向的变量中,而不是存储在全局变量中。这个接口是可重入的。

math.h

llrint

数学计算函数

根据当前的舍入方式,将x舍入为整数值

math.h

llrintf

数学计算函数

根据当前的舍入方式,将x舍入为整数值

math.h

llrintl

数学计算函数

根据当前的舍入方式,将x舍入为整数值

math.h

llround

数学计算函数

四舍五入为整数

math.h

llroundf

数学计算函数

四舍五入为整数

math.h

llroundl

数学计算函数

四舍五入为整数

math.h

log

数学计算函数

计算以e为底的对数值

math.h

log10

数学计算函数

计算以10为底的对数值

math.h

log10f

数学计算函数

计算以10为底的x对数值

math.h

log10l

数学计算函数

计算以10为底的x对数值

math.h

log1p

数学计算函数

log(1+x)

math.h

log1pf

数学计算函数

log(1+x)

math.h

log1pl

数学计算函数

log(1+x)

math.h

log2

数学计算函数

计算以2为底的x对数值

math.h

log2f

数学计算函数

计算以2为底的x对数值

math.h

log2l

数学计算函数

计算以2为底的x对数值

math.h

logb

数学计算函数

使用FLT_RADIX作为对数的底数,返回x绝对值的对数

math.h

logbf

数学计算函数

使用FLT_RADIX作为对数的底数,返回x绝对值的对数

math.h

logbl

数学计算函数

使用FLT_RADIX作为对数的底数,返回x绝对值的对数

math.h

logf

数学计算函数

计算以e为底的x对数值

math.h

logl

数学计算函数

计算以e为底的x对数值

math.h

lrint

数学计算函数

根据当前的舍入方式,将x舍入为整数值,返回值为long int型

math.h

lrintf

数学计算函数

根据当前的舍入方式,将x舍入为整数值,返回值为long int型

math.h

lrintl

数学计算函数

根据当前的舍入方式,将x舍入为整数值,返回值为long int型

math.h

lround

数学计算函数

根据当前的舍入方式,将x舍入为整数值,返回值为long int型

math.h

lroundf

数学计算函数

根据当前的舍入方式,将x舍入为整数值,返回值为long int型

math.h

lroundl

数学计算函数

根据当前的舍入方式,将x舍入为整数值,返回值为long int型

math.h

modf

数学计算函数

将浮点型数分解成整数与小数,返回小数部分

math.h

modff

数学计算函数

将一个浮点值分为整数和小数部分

math.h

modfl

数学计算函数

将一个浮点值分为整数和小数部分

math.h

nan

数据判断

返回非数

math.h

nanf

数据判断

返回非数

math.h

nanl

数据判断

返回非数

math.h

nearbyint

数学计算函数

根据当前舍入模式,将x舍入为整数值

math.h

nearbyintf

数学计算函数

根据当前舍入模式,将x舍入为整数值

math.h

nearbyintl

数学计算函数

根据当前舍入模式,将x舍入为整数值

math.h

nextafter

数学计算函数

取两个双精度浮点数之间与第一个数相邻的浮点数,两数相等,则返回第二个数

math.h

nextafterf

数学计算函数

取两个浮点数之间与第一个数相邻的浮点数,两数相等,则返回第二个数

math.h

nextafterl

数学计算函数

返回x在y方向上的下一个可表示的数

math.h

nexttoward

数学计算函数

返回x在y方向上的上一个可表示的数

math.h

nexttowardf

数学计算函数

返回x在y方向上的上一个可表示的数

math.h

nexttowardl

数学计算函数

返回x在y方向上的上一个可表示的数

math.h

pow

数学计算函数

计算次方值

math.h

pow10

数学计算函数

计算10的x次方

math.h

pow10f

数学计算函数

计算10的x次方

math.h

pow10l

数学计算函数

计算10的x次方

math.h

powf

数学计算函数

求 x 的 y 次幂(次方)

math.h

powl

数学计算函数

求 x 的 y 次幂(次方)

math.h

remainder

数学计算函数

计算余数,舍入到最接近的整数

math.h

remainderf

数学计算函数

计算余数,舍入到最接近的整数

math.h

remainderl

数学计算函数

计算余数,舍入到最接近的整数

math.h

remquo

数学计算函数

计算余数

math.h

remquof

数学计算函数

计算余数

math.h

remquol

数学计算函数

计算余数

math.h

rint

数学计算函数

将浮点数舍入到最接近的整数

math.h

rintf

数学计算函数

将浮点数舍入到最接近的整数

math.h

rintl

数学计算函数

将浮点数舍入到最接近的整数

math.h

round

数学计算函数

返回x的四舍五入整数值

math.h

roundf

数学计算函数

返回x的四舍五入整数值

math.h

roundl

数学计算函数

返回x的四舍五入整数值

math.h

scalb

数学计算函数

返回x * 2exp

math.h

scalbf

数学计算函数

返回x * 2exp

math.h

scalbln

数学计算函数

返回x * 2exp

math.h

scalblnf

数学计算函数

返回x * 2exp

math.h

scalblnl

数学计算函数

返回x * 2exp

math.h

scalbn

数学计算函数

返回x乘以FLT_RADIX的n次幂

math.h

scalbnf

数学计算函数

返回x乘以FLT_RADIX的n次幂

math.h

scalbnl

数学计算函数

返回x乘以FLT_RADIX的n次幂

math.h

significand

数学计算函数

返回范围[1,2)的尾数,scalb (x, (double) -ilogb (x)).

math.h

significandf

数学计算函数

返回范围[1,2)的尾数,scalb (x, (double) -ilogb (x)).

math.h

sin

数学计算函数

取正弦函数值

math.h

sincos

数学计算函数

取正弦余弦值

math.h

sincosf

数学计算函数

取正弦余弦值

math.h

sincosl

数学计算函数

取正弦余弦值

math.h

sinf

数学计算函数

求正弦函数

math.h

sinh

数学计算函数

取双曲线正弦函数值

math.h

sinhf

数学计算函数

求双曲线正弦函数

math.h

sinhl

数学计算函数

求双曲线正弦函数

math.h

sinl

数学计算函数

求正弦函数

math.h

sqrt

数学计算函数

计算平方根值

math.h

sqrtf

数学计算函数

计算平方根值

math.h

sqrtl

数学计算函数

计算平方根值

math.h

tan

数学计算函数

求正切函数

math.h

tanf

数学计算函数

求正切函数

math.h

tanh

数学计算函数

取双曲线正切函数值

math.h

tanhf

数学计算函数

求双曲线正切函数

math.h

tanhl

数学计算函数

求双曲线正切函数

math.h

tanl

数学计算函数

求正切函数

math.h

tgamma

数学计算函数

参数为arg的gamma函数返回

math.h

tgammaf

数学计算函数

参数为arg的gamma函数返回

math.h

tgammal

数学计算函数

参数为arg的gamma函数返回

math.h

trunc

时间函数

截取日期或数字,返回指定的值

math.h

truncf

时间函数

截取日期或数字,返回指定的值

math.h

truncl

时间函数

截取日期或数字,返回指定的值

math.h

y0

数学计算函数

返回x的第二类0阶Bessel函数

math.h

y0f

数学计算函数

返回x的第二类0阶Bessel函数

math.h

y1

数学计算函数

返回x的第二类1阶Bessel函数

math.h

y1f

数学计算函数

返回x的第二类1阶Bessel函数

math.h

yn

数学计算函数

返回x的第二类n阶Bessel函数

math.h

ynf

数学计算函数

返回x的第二类n阶Bessel函数

Libmat支持接口

LiteOS支持部分Libmat接口,具体如下表所示。

头文件

接口名

类型

说明

lib/libmat/libmat.h

matrix_inv_f32

矩阵函数

32位浮点矩阵乘法

lib/libmat/libmat.h

matrix_inv_f64

矩阵函数

64位浮点矩阵乘法,

lib/libmat/libmat.h

matrix_mul_f32

矩阵函数

32位浮点矩阵求逆

lib/libmat/libmat.h

matrix_mul_f64

矩阵函数

64位浮点矩阵求逆

开发流程

打开菜单,进入Lib ---> Enable lib matrix菜单,完成矩阵模块的配置。

配置项

含义

取值范围

默认值

依赖

LOSCFG_LIB_LIBMAT

使能矩阵库功能

YES/NO

NO

NULL

须知: 矩阵运算:

  1. 需要用户保证入参的矩阵是存在的,行列数是自身矩阵的行列数。

  2. 未做数据溢出判断,需要用户自己确保计算结果不会溢出 矩阵乘法: 需要用户保证入参的结果矩阵是空矩阵。

Libc/Libm/Libmat不支持接口

LiteOS的Libc/Libm/Libmat接口中,有一些未支持,具体参见下表。

头文件

接口名

alloca.h

alloca

arpa/inet.h

inet_lnaof

arpa/inet.h

inet_makeaddr

arpa/inet.h

inet_netof

arpa/inet.h

inet_network

arpa/nameser.h

ns_get16

arpa/nameser.h

ns_get32

arpa/nameser.h

ns_initparse

arpa/nameser.h

ns_name_uncompress

arpa/nameser.h

ns_parserr

arpa/nameser.h

ns_put16

arpa/nameser.h

ns_put32

arpa/nameser.h

ns_skiprr

byteswap.h

bswap_16

byteswap.h

bswap_32

byteswap.h

bswap_64

complex.h

cabsf

complex.h

cabsl

complex.h

cacos

complex.h

cacosf

complex.h

cacosh

complex.h

cacoshf

complex.h

cacoshl

complex.h

cacosl

complex.h

carg

complex.h

cargf

complex.h

cargl

complex.h

casin

complex.h

casinf

complex.h

casinh

complex.h

casinhf

complex.h

casinhl

complex.h

casinl

complex.h

catan

complex.h

catanf

complex.h

catanh

complex.h

catanhf

complex.h

catanhl

complex.h

catanl

complex.h

ccos

complex.h

ccosf

complex.h

ccosh

complex.h

ccoshf

complex.h

ccoshl

complex.h

ccosl

complex.h

cexp

complex.h

cexpf

complex.h

cexpl

complex.h

cimag

complex.h

cimagf

complex.h

cimagl

complex.h

clog

complex.h

clogf

complex.h

clogl

complex.h

conj

complex.h

conjf

complex.h

conjl

complex.h

cpow

complex.h

cpowf

complex.h

cpowl

complex.h

cproj

complex.h

cprojf

complex.h

cprojl

complex.h

creal

complex.h

crealf

complex.h

creall

complex.h

csin

complex.h

csinf

complex.h

csinh

complex.h

csinhf

complex.h

csinhl

complex.h

csinl

complex.h

csqrt

complex.h

csqrtf

complex.h

csqrtl

complex.h

ctan

complex.h

ctanf

complex.h

ctanh

complex.h

ctanhf

complex.h

ctanhl

complex.h

ctanl

crypt.h

crypt

crypt.h

crypt_r

dirent.h

getdents

dirent.h

versionsort

dlfcn.h

dladdr

dlfcn.h

dlinfo

fcntl.h

lockf

fcntl.h

lockf64

fcntl.h

name_to_handle_at

fcntl.h

open_by_handle_at

fcntl.h

openat64

fcntl.h

posix_fadvise64

fcntl.h

posix_fallocate64

fcntl.h

readahead

fcntl.h

splice

fcntl.h

sync_file_range

fcntl.h

tee

fcntl.h

vmsplice

fenv.h

feclearexcept

fenv.h

fegetenv

fenv.h

fegetexceptflag

fenv.h

fegetround

fenv.h

feholdexcept

fenv.h

feraiseexcept

fenv.h

fesetenv

fenv.h

fesetexceptflag

fenv.h

fesetround

fenv.h

fetestexcept

fenv.h

feupdateenv

getopt.h

getopt_long

getopt.h

getopt_long_only

libintl.h

bind_textdomain_codeset

libintl.h

bindtextdomain

libintl.h

dcgettext

libintl.h

dcngettext

libintl.h

dgettext

libintl.h

dngettext

libintl.h

gettext

libintl.h

ngettext

libintl.h

textdomain

link.h

dl_iterate_phdr

locale.h

localeconv

malloc.h

malloc_usable_size

malloc.h

valloc

mntent.h

addmntent

mntent.h

endmntent

mntent.h

getmntent

mntent.h

getmntent_r

mntent.h

hasmntopt

mntent.h

setmntent

netdb.h

gethostbyaddr

netdb.h

gethostbyaddr_r

netdb.h

gethostbyname2

netdb.h

gethostbyname2_r

netdb.h

getservbyname_r

netdb.h

getservbyport_r

netdb.h

herror

netdb.h

hstrerror

netinet/ether.h

ether_aton

netinet/ether.h

ether_aton_r

netinet/ether.h

ether_hostton

netinet/ether.h

ether_line

netinet/ether.h

ether_ntoa

netinet/ether.h

ether_ntoa_r

netinet/ether.h

ether_ntohost

poll.h

ppoll

pty.h

forkpty

pty.h

openpty

pwd.h

fgetpwent

pwd.h

putpwent

resolv.h

dn_expand

resolv.h

dn_skipname

resolv.h

res_init

resolv.h

res_mkquery

resolv.h

res_query

resolv.h

res_querydomain

resolv.h

res_search

resolv.h

res_send

sched.h

clone

sched.h

sched_getcpu

sched.h

setns

sched.h

unshare

search.h

hcreate_r

search.h

hdestroy_r

search.h

hsearch_r

search.h

tdestroy

shadow.h

endspent

shadow.h

fgetspent

shadow.h

getspent

shadow.h

getspnam

shadow.h

getspnam_r

shadow.h

lckpwdf

shadow.h

putspent

shadow.h

setspent

shadow.h

sgetspent

shadow.h

ulckpwdf

signal.h

raise

signal.h

sigandset

signal.h

sigisemptyset

signal.h

sigorset

spawn.h

posix_spawn

spawn.h

posix_spawn_file_actions_addchdir_np

spawn.h

posix_spawn_file_actions_addclose

spawn.h

posix_spawn_file_actions_adddup2

spawn.h

posix_spawn_file_actions_addfchdir_np

spawn.h

posix_spawn_file_actions_addopen

spawn.h

posix_spawn_file_actions_destroy

spawn.h

posix_spawn_file_actions_init

spawn.h

posix_spawnattr_destroy

spawn.h

posix_spawnattr_getflags

spawn.h

posix_spawnattr_getpgroup

spawn.h

posix_spawnattr_getschedparam

spawn.h

posix_spawnattr_getschedpolicy

spawn.h

posix_spawnattr_getsigdefault

spawn.h

posix_spawnattr_getsigmask

spawn.h

posix_spawnattr_init

spawn.h

posix_spawnattr_setflags

spawn.h

posix_spawnattr_setpgroup

spawn.h

posix_spawnattr_setschedparam

spawn.h

posix_spawnattr_setschedpolicy

spawn.h

posix_spawnattr_setsigdefault

spawn.h

posix_spawnattr_setsigmask

spawn.h

posix_spawnp

stdio.h

cuserid

stdio.h

fgetc_unlocked

stdio.h

fgetln

stdio.h

fopencookie

stdio.h

fputc_unlocked

stdio.h

getw

stdio.h

setlinebuf

stdio.h

tmpfile

stdio.h

tmpfile64

stdlib.h

aligned_alloc

stdlib.h

at_quick_exit

stdlib.h

div

stdlib.h

ecvt

stdlib.h

fcvt

stdlib.h

gcvt

stdlib.h

getenv

stdlib.h

getloadavg

stdlib.h

ldiv

stdlib.h

lldiv

stdlib.h

mkdtemp

stdlib.h

mkostemp

stdlib.h

ptsname_r

stdlib.h

quick_exit

stdlib.h

reallocarray

stdlib.h

system

stdlib.h

tcgetwinsize

stdlib.h

tcsetwinsize

stdlib.h

valloc

stdlib.h

wcstombs

string.h

explicit_bzero

string.h

memmem

string.h

mempcpy

string.h

strlcat

string.h

strverscmp

strings.h

ffsl

strings.h

ffsll

strings.h

index

sys/acct.h

acct

sys/auxv.h

getauxval

sys/cachectl.h

cachectl

sys/cachectl.h

cacheflush

sys/epoll.h

epoll_create

sys/epoll.h

epoll_create1

sys/epoll.h

epoll_ctl

sys/epoll.h

epoll_pwait

sys/epoll.h

epoll_wait

sys/eventfd.h

eventfd

sys/eventfd.h

eventfd_read

sys/eventfd.h

eventfd_write

sys/fanotify.h

fanotify_init

sys/fanotify.h

fanotify_mark

sys/file.h

flock

sys/fsuid.h

setfsgid

sys/fsuid.h

setfsuid

sys/inotify.h

inotify_add_watch

sys/inotify.h

inotify_init

sys/inotify.h

inotify_init1

sys/inotify.h

inotify_rm_watch

sys/io.h

ioperm

sys/io.h

iopl

sys/klog.h

klogctl

sys/membarrier.h

membarrier

sys/mman.h

madvise

sys/mman.h

memfd_create

sys/mman.h

mincore

sys/mman.h

mlock2

sys/mman.h

mremap

sys/mman.h

remap_file_pages

sys/mount.h

umount2

sys/personality.h

personality

sys/ptrace.h

ptrace

sys/quota.h

quotactl

sys/random.h

getrandom

sys/reboot.h

reboot

sys/resource.h

prlimit

sys/sem.h

semtimedop

sys/sendfile.h

sendfile

sys/signalfd.h

signalfd

sys/socket.h

accept4

sys/socket.h

recvmmsg

sys/socket.h

sendmmsg

sys/stat.h

fstatat64

sys/stat.h

lchmod

sys/stat.h

lstat64

sys/statfs.h

fstatfs

sys/statvfs.h

fstatvfs

sys/statvfs.h

statvfs

sys/swap.h

swapoff

sys/swap.h

swapon

sys/sysinfo.h

get_avphys_pages

sys/sysinfo.h

get_nprocs

sys/sysinfo.h

get_nprocs_conf

sys/sysinfo.h

get_phys_pages

sys/sysinfo.h

sysinfo

sys/time.h

futimes

sys/time.h

futimesat

sys/time.h

lutimes

sys/timeb.h

ftime

sys/timerfd.h

timerfd_create

sys/timerfd.h

timerfd_gettime

sys/timerfd.h

timerfd_settime

sys/timex.h

adjtimex

sys/timex.h

clock_adjtime

sys/uio.h

preadv

sys/uio.h

process_vm_readv

sys/uio.h

process_vm_writev

sys/uio.h

pwritev

sys/wait.h

wait3

sys/wait.h

wait4

sys/xattr.h

fgetxattr

sys/xattr.h

flistxattr

sys/xattr.h

fremovexattr

sys/xattr.h

fsetxattr

sys/xattr.h

getxattr

sys/xattr.h

lgetxattr

sys/xattr.h

listxattr

sys/xattr.h

llistxattr

sys/xattr.h

lremovexattr

sys/xattr.h

lsetxattr

sys/xattr.h

removexattr

sys/xattr.h

setxattr

syslog.h

vsyslog

termios.h

cfmakeraw

termios.h

cfsetspeed

threads.h

call_once

threads.h

cnd_broadcast

threads.h

cnd_destroy

threads.h

cnd_init

threads.h

cnd_signal

threads.h

cnd_timedwait

threads.h

cnd_wait

threads.h

mtx_destroy

threads.h

mtx_init

threads.h

mtx_lock

threads.h

mtx_timedlock

threads.h

mtx_trylock

threads.h

mtx_unlock

threads.h

thrd_create

threads.h

thrd_current

threads.h

thrd_detach

threads.h

thrd_equal

threads.h

thrd_exit

threads.h

thrd_join

threads.h

thrd_sleep

threads.h

thrd_yield

threads.h

tss_create

threads.h

tss_delete

threads.h

tss_set

time.h

timegm

time.h

timespec_get

time64.h

timegm64

time64.h

timelocal64

uchar.h

c16rtomb

uchar.h

mbrtoc16

ucontext.h

getcontext

ucontext.h

makecontext

ucontext.h

setcontext

ucontext.h

swapcontext

unistd.h

_Fork

unistd.h

acct

unistd.h

brk

unistd.h

chroot

unistd.h

copy_file_range

unistd.h

daemon

unistd.h

dup3

unistd.h

eaccess

unistd.h

endusershell

unistd.h

execvpe

unistd.h

get_current_dir_name

unistd.h

getdomainname

unistd.h

getdtablesize

unistd.h

gettid

unistd.h

getusershell

unistd.h

issetugid

unistd.h

pipe2

unistd.h

posix_close

unistd.h

sbrk

unistd.h

setdomainname

unistd.h

setresgid

unistd.h

setresuid

unistd.h

setusershell

unistd.h

syncfs

unistd.h

syscall

unistd.h

truncate64

unistd.h

ualarm

unistd.h

vfork

unistd.h

vhangup

utmp.h

endutent

utmp.h

getutent

utmp.h

getutid

utmp.h

getutline

utmp.h

login_tty

utmp.h

pututline

utmp.h

setutent

utmp.h

updwtmp

utmp.h

utmpname

utmpx.h

updwtmpx

utmpx.h

utmpxname

wchar.h

fgetws

wchar.h

fgetws_unlocked

wchar.h

fputws

wchar.h

fputws_unlocked

wchar.h

fwprintf

wchar.h

fwscanf

wchar.h

getwchar

wchar.h

getwchar_unlocked

wchar.h

putwchar

wchar.h

putwchar_unlocked

wchar.h

swscanf

wchar.h

vswscanf

wchar.h

vwprintf

wchar.h

vwscanf

wchar.h

wcscat

wchar.h

wcscpy

wchar.h

wcscspn

wchar.h

wcsncat

wchar.h

wcspbrk

wchar.h

wcsrchr

wchar.h

wcsspn

wchar.h

wcstod

wchar.h

wcstof

wchar.h

wcstok

wchar.h

wcstold

wchar.h

wcswcs

wchar.h

wprintf

wchar.h

wscanf

wctype.h

towctrans

wctype.h

wctrans

须知: 自LiteOS V200R005C00版本起不再支持“stdlib.h”头文件中提供的atexit()。

CMSIS接口

CMSIS v1.0

本部分简要介绍LiteOS适配CMSIS v1.0接口的情况,更多关于接口声明、入参介绍、返回值类型、接口使用条件等详细介绍,请参见《CMSIS v1.0接口官方手册》。

CMSIS v1.0适配接口

LiteOS目前已支持大部分CMSIS v1.0接口,接口声明在“self_src/compat/cmsis/cmsis_os1.h”,接口描述详见下表。

接口名

类型

描述

osKernelInitialize

内核类接口

初始化操作系统

osKernelStart

内核类接口

启动操作系统

osKernelRunning

内核类接口

判断系统是否启动

osKernelSysTick

内核类接口

获取系统启动后时间(单位:tick)

osThreadCreate

任务/线程类接口

创建一个任务

osThreadGetId

任务/线程类接口

获取当前的任务句柄

osThreadTerminate

任务/线程类接口

终止某个任务

osThreadYield

任务/线程类接口

切换至同优先级的就绪任务

osThreadSetPriority

任务/线程类接口

设置任务优先级

osThreadGetPriority

任务/线程类接口

获取任务优先级

osDelay

延时类接口

任务延时处理

osTimerCreate

定时器类接口

创建定时器

osTimerStart

定时器类接口

启动定时器(若定时器正在计时会先停止该定时器)

osTimerStop

定时器类接口

停止定时器

osTimerDelete

定时器类接口

删除定时器

osSignalSet

信号类接口

设置信号

osSignalClear

信号类接口

清除信号

osSignalWait

信号类接口

等待信号

osMutexCreate

互斥锁类接口

创建互斥锁

osMutexWait

互斥锁类接口

获取互斥锁(在规定时间内阻塞等待)

osMutexRelease

互斥锁类接口

释放互斥锁

osMutexDelete

互斥锁类接口

删除互斥锁

osSemaphoreCreate

信号量类接口

创建信号量

osSemaphoreWait

信号量类接口

获取信号量(阻塞等待)

osSemaphoreRelease

信号量类接口

释放信号量

osSemaphoreDelete

信号量类接口

删除信号量

osPoolCreate

块状内存类接口

创建块状内存池

osPoolAlloc

块状内存类接口

申请内存

osPoolCAlloc

块状内存类接口

申请内存并清零

osPoolFree

块状内存类接口

释放内存

osMessageCreate

指针消息类接口

创建消息队列(不带内容,一般为数据指针)

osMessagePut

指针消息类接口

往消息队列里放入消息

osMessageGet

指针消息类接口

从消息队列里获取消息

osMailCreate

内容消息类接口

创建消息队列(带内容,可理解为在osMessage的基础上增加块状内存创建)

osMailAlloc

内容消息类接口

申请内存(用于存放消息内容)

osMailCAlloc

内容消息类接口

申请内存并清零(用于存放消息内容)

osMailPut

内容消息类接口

往消息队列里放入消息

osMailGet

内容消息类接口

从消息队列里获取消息

osMailFree

内容消息类接口

释放已申请的内存

osWait

延时类接口

等待任一信号或消息发生

CMSIS v1.0不支持接口

无。

CMSIS v1.0标准接口适配差异

考虑接口的易用性和LiteOS内部机制与CMSIS标准接口的差异,在适配CMSIS v1.0接口时,对部分接口进行了修改,详见下表。

接口名

类型

描述

osTimerStart

定时器类接口

增加一种定时器类型osTimerDelay,与osTimerOnce同为单次定时器,差别在于osTimerOnce超时后会删除定时器,但osTimerDelay不会,可以通过osTimerStart重复启动。

osBinarySemaphoreCreate

信号量类接口

新增接口,用于创建二值信号量。

osTimerRestart

定时器类接口

重新启动定时器。

CMSIS v1.0非标准接口

接口名

类型

描述

osMailClear

内容消息类接口

清空指定mail内容

osMailDelete

内容消息类接口

删除指定mailQ

CMSIS v2.0

本部分简要介绍LiteOS适配CMSIS v2.0接口的情况,更多关于接口声明、入参介绍、返回值类型、接口使用条件等详细介绍,请参见《CMSIS v2.0接口官方手册》。

CMSIS v2.0适配接口

LiteOS目前已支持大部分CMSIS v2.0接口,接口声明在“open_source/CMSIS/CMSIS/RTOS2/Include/cmsis_os2.h“”,接口描述详见下表。

接口名

类型

描述

osKernelInitialize

内核类接口

初始化操作系统

osKernelGetInfo

内核类接口

获取系统版本信息

osKernelGetState

内核类接口

获取系统状态(具体状态可参考osThreadState_t)

osKernelStart

内核类接口

启动操作系统

osKernelLock

内核类接口

锁内核(锁调度)

osKernelUnlock

内核类接口

解锁内核(解锁调度)

osKernelRestoreLock

内核类接口

恢复内核锁状态

osKernelGetTickCount

内核类接口

获取系统启动后时间(单位:tick)

osKernelGetTickFreq

内核类接口

获取每秒的tick数

osKernelGetSysTimerCount

内核类接口

获取系统启动后时间(单位:cycle)

osKernelGetSysTimerFreq

内核类接口

获取每秒的CPU cycle数

osThreadNew

任务/线程类接口

创建任务

osThreadJoin

任务/线程类接口

等待指定的任务并回收任务资源(需要开启内核LOSCFG_TASK_JOINABLE配置)

osThreadDetach

任务/线程类接口

设置任务为detached属性(需要开启内核LOSCFG_TASK_JOINABLE配置)

osThreadGetName

任务/线程类接口

获取任务名

osThreadGetId

任务/线程类接口

获取任务句柄

osThreadGetState

任务/线程类接口

获取任务状态

osThreadGetStackSize

任务/线程类接口

获取任务栈大小

osThreadGetStackSpace

任务/线程类接口

获取未使用过的任务栈空间

osThreadSetPriority

任务/线程类接口

设置任务优先级

osThreadGetPriority

任务/线程类接口

获取任务优先级

osThreadYield

任务/线程类接口

切换至同优先级的就绪任务

osThreadSuspend

任务/线程类接口

挂起任务(恢复前无法得到调度)

osThreadResume

任务/线程类接口

恢复任务

osThreadTerminate

任务/线程类接口

终止任务(建议不要主动终止任务)

osThreadGetCount

任务/线程类接口

获取已创建的任务数量

osThreadFlagsSet

任务事件类接口

写入指定事件

osThreadFlagsClear

任务事件类接口

清除指定事件

osThreadFlagsGet

任务事件类接口

获取当前任务事件

osThreadFlagsWait

任务事件类接口

等待指定事件

osDelay

延时类接口

任务延时(单位:tick)

osDelayUntil

延时类接口

延时至某一时刻(单位:tick)

osTimerNew

定时器类接口

创建定时器

osTimerGetName

定时器类接口

获取定时器名称(目前固定返回NULL)

osTimerStart

定时器类接口

启动定时器(若定时器正在计时会先停止该定时器)

osTimerStop

定时器类接口

停止定时器

osTimerIsRunning

定时器类接口

定时器是否在计时中

osTimerDelete

定时器类接口

删除定时器

osEventFlagsNew

事件类接口

创建事件(与任务事件ThreadFlags的差别在于有独立的句柄和控制块)

osEventFlagsGetName

事件类接口

获取事件名称(目前固定返回NULL)

osEventFlagsSet

事件类接口

写入指定事件

osEventFlagsClear

事件类接口

清楚指定事件

osEventFlagsGet

事件类接口

获取当前事件值

osEventFlagsWait

事件类接口

等待指定事件

osEventFlagsDelete

事件类接口

删除事件

osMutexNew

互斥锁类接口

创建互斥锁

osMutexGetName

互斥锁类接口

获取互斥锁名称(目前固定返回NULL)

osMutexAcquire

互斥锁类接口

获取互斥锁(阻塞等待)

osMutexRelease

互斥锁类接口

释放互斥锁

osMutexGetOwner

互斥锁类接口

获取持有该互斥锁的任务句柄

osMutexDelete

互斥锁类接口

删除互斥锁

osSemaphoreNew

信号量类接口

创建信号量

osSemaphoreGetName

信号量类接口

获取信号量名称(目前固定返回NULL)

osSemaphoreAcquire

信号量类接口

获取信号量(阻塞等待)

osSemaphoreRelease

信号量类接口

释放信号量

osSemaphoreGetCount

信号量类接口

获取信号量的计数值

osSemaphoreDelete

信号量类接口

删除信号量

osMessageQueueNew

消息队列类接口

创建消息队列

osMessageQueueGetName

消息队列类接口

获取消息队列名称(目前固定返回NULL)

osMessageQueuePut

消息队列类接口

往消息队列里放入消息

osMessageQueueGet

消息队列类接口

从消息队列里获取消息

osMessageQueueGetCapacity

消息队列类接口

获取消息队列节点数量

osMessageQueueGetMsgSize

消息队列类接口

获取消息队列节点大小

osMessageQueueGetCount

消息队列类接口

获取当前消息队列里的消息数量

osMessageQueueGetSpace

消息队列类接口

获取当前消息队列里的剩余消息数量

osMessageQueueDelete

消息队列类接口

删除消息队列

CMSIS v2.0不支持接口

CMSIS v2.0不支持接口描述如下表所示。

接口名

类型

描述

osKernelSuspend

内核类接口

挂起内核阻止调度,一般用于低功耗处理,目前LiteOS已提供Tickless、Runstop等低功耗机制,暂未适配该接口。

osKernelResume

内核类接口

同osKernelSuspend描述。

osThreadExit

任务/线程类接口

LiteOS任务支持自删除,任务退出前不需要调用osThreadExit。

osThreadEnumerate

任务/线程类接口

获取已创建的任务列表,目前未适配该接口,用户可以调用LiteOS的LOS_TaskInfoGet等维测接口获取任务状态。

osMemoryPoolAlloc

块状内存类接口

接口需要支持超时时间内申请内存块,目前LiteOS暂未提供这种机制。

osMemoryPoolFree

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolNew

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolGetName

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolGetCapacity

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolGetBlockSize

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolGetCount

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolGetSpace

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMemoryPoolDelete

块状内存类接口

由于osMemoryPoolAlloc未实现,剩余osMemPool类接口暂不实现。

osMessageQueueReset

消息队列类接口

操作系统暂不提供清空队列接口,由用户对队列内容进行操作,避免资源泄漏或其他异常。

CMSIS v2.0标准接口适配差异

考虑接口的易用性和LiteOS内部机制与CMSIS标准接口的差异,在适配CMSIS v2.0接口时,对部分接口进行了修改,详见下表。

接口名

类型

描述

osKernelGetTick2ms

内核类接口

新增接口,用于获取系统启动后时间(单位:ms)。接口声明在“compat/cmsis/cmsis_os.h”。

osMs2Tick

内核类接口

新增接口,用于毫秒与系统Tick转换。接口声明在“compat/cmsis/cmsis_os.h”。

osThreadNew

任务/线程类接口

优先级范围仅支持[osPriorityLow3, osPriorityHigh]。

osThreadSetPriority

任务/线程类接口

优先级范围仅支持[osPriorityLow3, osPriorityHigh]。

CMSIS v2.0非标准接口

接口名

类型

描述

osThreadGetArgument

任务/线程类接口

获取线程参数。

osTimerExtNew

定时器类接口

创建定时器(可指定定时器是否对齐)。

linux适配

LiteOS目前适配的是linux-4.19.90版本。Linux适配支持裁剪,如果需要使用Linux适配功能,可以打开菜单,选择Compat ---> Enable Linux以使能Linux适配模块。

配置项

含义

取值范围

默认值

依赖

LOSCFG_COMPAT_LINUX

Linux适配模块的裁剪开关

YES/NO

YES

完成量(completion)

概述

基本概念

完成量(completion)是一种轻量级任务同步机制。完成量是对信号量的一种补充,Linux的信号量机制,在同一个信号量上允许并发执行up()和down(),在多CPU运行场景下,up()可能试图访问一个不存在的信号量,为了防止这种错误,专门设计了完成量这种机制。

当一个任务需要等待另一个任务完成某操作后才能执行时,通过完成量允许被等待任务在完成指定操作后通知等待任务,从而唤醒等待任务,实现任务同步。

开发指导

使用场景

完成量应用于多任务同步场合,实现任务间的同步。

功能

功能分类

接口名

描述

兼容性

初始化完成量

init_completion

初始化一个完成量

不完全兼容,如果入参为空指针会退出,不做初始化

等待完成量

wait_for_completion

永久等待完成量

不完全兼容,如果入参为空指针或接口使用场景不正确会退出

wait_for_completion_timeout

超时等待完成量,超时时间单位为Tick

不完全兼容,接口实现中增加了非法判断,使用时请查看API参考

唤醒等待完成量的任务

complete

唤醒等待该完成量的任务队列中的首个任务

不完全兼容,如果入参为空会退出

complete_all

唤醒所有等待该完成量的任务

不完全兼容,如果入参为空会退出

开发流程

完成量的典型开发流程:

  1. 打开菜单,选择Compat ---> Enable Linux ---> Enable completion,使能完成量。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_COMPAT_LINUX_COMPLETION

    使能Linux完成量

    YES/NO

    NO

  2. 初始化完成量init_completion,该接口会创建一个完成量。

  3. 等待完成量wait_for_completion_timeout/wait_for_completion。

  4. 唤醒等待完成量的任务complete/complete_all。

注意事项

  • 完成量类似于信号量机制,参考信号量机制“注意事项”,即不能在中断中以阻塞模式(永久阻塞和定时阻塞)操作完成量,因为中断不能被阻塞。

  • 需要用户保证完成量接口的完成量指针入参的合法性,即入参必须为合法的完成量指针。

编程实例

实例描述

示例中,任务Example_TaskEntry创建了任务Example_Completion。Example_Completion等待完成量而阻塞,Example_TaskEntry唤醒该完成量。通过打印的先后顺序可以理解完成量操作时伴随的任务切换。

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

  2. 在任务Example_Completion中等待完成量,阻塞,发生任务切换,执行任务Example_TaskEntry。

  3. 在任务Example_TaskEntry中唤醒完成量,发生任务切换,执行任务Example_Completion。

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

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

编程示例

前提条件:在menuconfig菜单中已经使能Linux适配模块。

代码实现如下:

#include "linux/completion.h"
#include "los_task.h"

/* 任务ID */
UINT32 g_testTaskId;

/* 完成量 */
struct completion exampleCompletion;

/* 用例任务入口函数 */
VOID Example_Completion(VOID)
{
    UINT32 ret;

    /* 以超时等待方式等待完成量,超时时间为100Ticks */
    printf("Example_Completion wait completion\n");
    ret = wait_for_completion_timeout(&exampleCompletion, 100);

    if(ret == 0) {
        printf("Example_Completion, wait completion timeout\n");
    } else {
        printf("Example_Completion, wait completion success\n");
    }
    return;
}

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

    /* 完成量初始化 */
    init_completion(&exampleCompletion);

    /* 创建任务 */
    memset(&task, 0, sizeof(TSK_INIT_PARAM_S));
    task.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Completion;
    task.pcName       = "EventTsk1";
    task.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task.usTaskPrio   = 8;
    ret = LOS_TaskCreate(&g_testTaskId, &task);
    if(ret != LOS_OK) {
        printf("task create failed \n");
        return LOS_NOK;
    }

    /* 唤醒完成量 */
    printf("Example_TaskEntry complete\n");
    complete(&exampleCompletion);

    printf("Delete Task.\n");
    /* 删除任务 */
    ret = LOS_TaskDelete(g_testTaskId);
    if(ret != LOS_OK) {
        printf("task delete failed\n");
        return LOS_NOK;
    }

    return LOS_OK;
}

结果验证

编译运行得到的结果为:

Example_Completion wait completion
Example_TaskEntry complete
Example_Completion, wait completion success
Delete Task.

工作队列(workqueue)

概述

基本概念

工作队列提供一种机制:任何系统模块都可以将工作项挂载到工作队列,由工作队列按照FIFO方式处理挂载在上面的工作项。

工作队列通过工作队列任务处理挂载到工作队列中的工作项。队列处理任务允许被重新调度和睡眠,从而节省系统资源。

LiteOS的工作队列机制提供了丰富的对外接口以管理工作队列。当有工作项挂载到工作队列中时,工作队列处理任务将被唤醒去处理工作队列中的工作项,直到所有工作项都被处理完,工作队列处理任务将重新进入睡眠状态。

开发指导

使用场景

工作队列运用在对时间要求不高的场景,原因是工作队列里的工作只有在工作队列处理任务得到运行时才能被逐一处理,而工作队列处理任务是否能被及时唤醒调度取决于其他内核任务,另外在中断里也不能调度,只有在退出中断后才能得到调度。

功能

功能分类

接口名

描述

兼容性

创建/销毁工作队列

create_singlethread_workqueue

在当前CPU上创建一个工作队列

完全兼容

create_workqueue

LiteOS只在当前CPU上创建一个工作队列,而对于多CPU场景,linux会在每个CPU上都创建一个工作队列

不完全兼容

destroy_workqueue

销毁一个工作队列

完全兼容

初始化工作

INIT_WORK

初始化一个工作,将该工作与指定的处理函数绑定。当该工作被执行时,就会执行这个处理函数

完全兼容

INIT_DELAYED_WORK

初始化一个延迟工作,将该工作与指定的处理函数绑定,该工作将延迟一段时间后才会被执行。当该工作被执行时,就会执行这个处理函数

完全兼容

挂载工作项到工作队列

queue_work

挂载一个工作项到指定工作队列

不完全兼容,有参数非法性检查,详见API参考

queue_delayed_work

挂载一个延迟工作项到指定工作队列

不完全兼容,有参数非法性检查,详见API参考

schedule_work

挂载一个工作项到系统默认工作队列

完全兼容

schedule_delayed_work

挂载一个延迟工作项到系统默认工作队列

不完全兼容,有参数非法性检查,详见API参考

查询工作的状态

work_busy

查询一个指定工作当前的状态

不完全兼容,返回值不同,详见API参考

执行工作

flush_work

阻塞等待一个指定的非延迟工作项执行,完成后返回

完全兼容

flush_delayed_work

取消延迟,并阻塞等待这个工作项执行,完成后返回

完全兼容

取消工作

cancel_delayed_work

取消一个延迟工作,如果有其他任务正在取消该工作,则返回失败

不完全兼容,返回值不同

cancel_delayed_work_sync

取消一个延迟工作项,须等待工作项执行结束再返回。如果有其他任务正在取消该工作,那么等待其执行完毕

不完全兼容,返回值不同

cancel_work_sync

取消一个工作项,须等待工作项执行结束再返回。如果有其他任务正在取消该工作,那么等待其执行完毕

不完全兼容,返回值不同

说明:

  • 创建工作队列时,会初始化信号量,用于工作队列处理任务的睡眠和唤醒,以及初始化重要结构体并搭建工作队列链表。

  • 销毁工作队列时,系统先对资源进行加锁操作,再删除工作队列处理任务,释放信号量资源,释放内存空间等,最后解锁。

  • 有两种类型的工作:非延迟工作和延迟工作。工作可以立即得到挂载,而延迟工作需要延时指定时间后才能被挂载到工作队列中。

开发流程

工作队列的典型流程:

  1. 打开菜单,选择Compat ---> Enable Linux ---> Enable workqueue,使能工作队列。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_COMPAT_LINUX_WORKQUEUE

    使能Linux工作队列

    YES/NO

    NO

    LOSCFG_COMPAT_LINUX_TIMER

  2. 调用create_workqueue接口创建工作队列。

  3. 调用INIT_WORK/INIT_DELAYED_WORK初始化工作。

  4. 调用queue_work/queue_delayed_work接口挂载工作项到工作队列。

  5. 调用flush_work/flush_delayed_work接口立即执行工作。

  6. 调用cancel_delayed_work_sync/cancel_work_sync接口取消工作。

  7. 调用destroy_workqueue接口删除工作队列。

注意事项

  • 挂载到工作队列的工作项由工作队列处理任务进行处理,而任务的调度运行在时间上具有不确定性,所以对时间要求高的工作不建议使用工作队列进行处理。

  • LiteOS使用工作队列名对工作队列进行标识,所以不能存在相同名称的工作队列。

  • 可以使用schedule_work/schedule_delayed_work接口向系统默认的工作队列挂载工作,而不用再创建工作队列。

编程实例

实例描述

本实例实现如下功能:

  1. 创建名称为wq_test的工作队列。

  2. 分配工作内存并且初始化工作。

  3. 向工作队列中挂载工作。

  4. 立即执行工作。

  5. 销毁工作队列。

编程示例

前提条件:在menuconfig菜单中已经使能Linux适配模块。

代码实现如下:

#include "los_printf.h"
#include  "linux/workqueue.h"

static void Work_Func(struct work_struct *work)
{
    int i;
    for (i = 0; i < 2; i++) {
        dprintf("workqueue function is been called!..%d..%d..\n", i, work->work_status);
    }
}

UINT32 Example_Workqueue(VOID)
{
    struct workqueue_struct *wq;
    struct work_struct *work;
    UINT32 ret = LOS_OK;

    wq = create_workqueue("test1008");
    dprintf("create the workqueue successfully!\n\n");

    work =(struct work_struct *)malloc(sizeof(struct work_struct));
    if (!work) {
        ret = LOS_NOK;
    }
    dprintf("create work ok!\n\n");

    INIT_WORK(work, Work_Func);
    dprintf("init the work ok!\n\n");

    ret = queue_work(wq, work);
    dprintf("mount the work into workqueue successfully!\n\n");

    ret = flush_work(work);
    dprintf("flush the work ok!\n\n");

    destroy_workqueue(wq);
    dprintf("destroy the work ok!\n\n");

    return ret;
}

结果验证

编译运行得到的结果为:

create the workqueue successfully!

create work ok!

init the work ok!

mount the work into workqueue successfully!

workqueue function is been called!..0..3..
workqueue function is been called!..1..3..
flush the work ok!

destroy the work ok!

等待队列(waitqueue)

概述

基本概念

等待队列常用于线程等待资源。

当一个线程必须获取某些资源后才能进行下一步操作,且不占用CPU资源的时候,可以将该线程放入等待队列中,然后系统会切换到其他进程,等资源准备好后再唤醒等待队列。等待队列可以概括为两种等待情况:超时等待和循环等待。

LiteOS的等待队列机制提供了丰富的对外接口以管理等待队列,可以满足linux系统等待队列的绝大多数功能。

开发指导

使用场景

与工作队列相比,等待队列运用在对时间要求较高的场景,尤其是超时等待机制,常见场景有:同步系统资源访问、中断底半部和异步事件通知等。

功能

功能分类

接口名

描述

兼容性

注意事项

初始化等待队列头

DECLARE_WAIT_QUEUE_HEAD

定义并初始化等待队列头

完全兼容

不涉及

init_waitqueue_head

初始化等待队列头

完全兼容

不涉及

获取等待队列合法性

waitqueue_active

判断等待队列是否可用

完全兼容

用户保证入参是合法指针,不能是空指针

等待事件

wait_event

等待事件

完全兼容

不涉及

wait_event_interruptible

等待事件

不完全兼容,该事件不可被信号中断

不涉及

wait_event_interruptible_timeout

超时等待事件

不完全兼容,该事件不可被信号中断,返回值不同,入参范围有限制,详见API参考

不涉及

唤醒事件

wake_up_interruptible

唤醒事件

不完全兼容,入参合法时直接返回

不涉及

wake_up_interruptible_poll

唤醒事件

完全兼容

不涉及

wake_up

唤醒事件

完全兼容

不涉及

说明: 有两种类型的等待队列:

  • 循环等待,只有等到资源后才会退出等待。

  • 超时等待,即使未等到资源,在等待时间到达之后,也会退出等待。

开发流程

等待队列的典型开发流程:

  1. 打开菜单,选择Compat ---> Enable Linux ---> Enable waitqueue,使能等待队列。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_COMPAT_LINUX_WAITQUEUE

    使能Linux等待队列

    YES/NO

    NO

    LOSCFG_BASE_IPC_EVENT

  2. 调用DECLARE_WAIT_QUEUE_HEAD/init_waitqueue_head接口初始化等待队列头。

  3. 调用wait_event/wait_event_interruptible/wait_event_interruptible_timeout等待资源。

  4. 调用wake_up/wake_up_interruptible/wake_up_interruptible_poll唤醒等待队列。

注意事项

  1. LiteOS的等待队列不支持linux内核signal打断的机制。

  2. LiteOS不支持在一个等待队列中增加多个等待项,一个等待队列中只能有一个等待项。

编程实例

实例描述

本实例实现如下功能:

  1. 定义并初始化一个等待队列头。

  2. 创建一个任务,在任务中挂起事件,等待被唤醒。

  3. 唤醒事件。

编程示例

前提条件:在menuconfig菜单中已经使能Linux适配模块。

代码实现如下:

#include "los_printf.h"
#include "los_task.h"
#include "linux/wait.h.h"

static UINT32 g_TestTaskID;
static DECLARE_WAIT_QUEUE_HEAD(g_waitQueue);
static BOOL g_condition = FALSE;

static VOID Example_Task(VOID)
{
    UINT32 timeout = 10; // 10 ticks timeout
    UINT32 ret;

    ret = wait_event_interruptible_timeout(g_waitQueue, g_condition, timeout);
    if (ret > 0) {
        g_TestCount++;
    }
}
UINT32 Example_Waitqueue(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S stTask = {0};

    stTask.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Task;
    stTask.usTaskPrio   = TSK_PRIOR_HI;
    stTask.pcName       = "wait_task";
    stTask.uwStackSize  = LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE;
    stTask.uwResved     = LOS_TASK_STATUS_DETACHED;

    g_TestCount = 0;

    ret = LOS_TaskCreate(&g_TestTaskID, &stTask);
    if (ret != LOS_OK) {
        printf("Example_Task create Failed!\r\n");
        return LOS_NOK;
    }

    LOS_TaskDelay(2);
    g_condition = TRUE;
    wake_up_interruptible(&g_waitQueue);
    LOS_TaskDelay(1);

    if (g_TestCount == 1) {
        printf("Wakeup succeeded!\r\n");
        return LOS_OK;
    } else {
        printf("Wakeup failed!\r\n");
        return LOS_NOK;
    }
}

结果验证

编译运行得到的结果为:

Wakeup succeeded!

中断

概述

基本概念

Linux内核中有专门的中断函数。为提高系统使用的友好度,LiteOS基于自身的中断机制,适配了Linux中的相关中断接口。

LiteOS的中断支持:

  • 中断申请

  • 中断删除

  • 中断使能

  • 中断屏蔽

  • 中断底半部(基于workqueue)

中断顶半部和底半部的概念:

  • 中断处理程序耗时应尽可能短,以满足中断的快速响应,为了平衡中断处理程序的性能与工作量,将中断处理程序分解为两部分:顶半部和底半部。

  • 顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中的中断状态并清除中断标志位,即进行“登记工作”,将耗时的底半部处理程序挂到系统的底半部执行队列中去。

开发指导

功能

LiteOS中断模块适配的linux接口如下表,接口详细信息可以查看API参考。

接口名

描述

兼容性

request_irq

申请中断,将中断处理程序注册到指定中断号对应的链表中,支持一个中断号注册多个中断处理程序

不完全兼容,详见API参考

free_irq

删除中断

完全兼容

enable_irq

使能指定中断

完全兼容

disable_irq

屏蔽指定中断

完全兼容

说明: 中断底半部的功能,可利用workqueue实现,在上半部中初始化一个工作项,将其添加到workqueue中,系统会在空闲时执行workqueue里的底半部,详细使能用法请查阅“工作队列(workqueue)”。

开发流程

  1. 打开菜单,选择Compat ---> Enable Linux ---> Enable irq interrupt,使能中断。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_COMPAT_LINUX_IRQ

    使能Linux中断

    YES/NO

    NO

    LOSCFG_ARCH_INTERRUPT_TAKEOVER

  2. 调用申请中断接口request_irq。

  3. 调用disable_irq接口屏蔽指定中断。

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

  5. 调用free_irq接口删除中断。

注意事项

  • 调用request_irq()申请中断时,中断处理程序入参要符合(int, void*)标准格式。直接调用LOS_HwiCreate()创建中断时,中断处理程序入参可以为NULL。

  • 中断处理程序中禁止调用request_irq()和free_irq()接口。

  • 当中断ID为共享中断时,不能通过LOS_HwiCreate()创建中断,且request_irq()入参dev指针不能为NULL,应与处理程序一一对应。直接调用LOS_HwiCreate()创建的中断,不能通过free_irq()删除。

编程实例

实例描述

本实例实现如下功能:

  1. 申请中断。

  2. 删除中断。

编程示例

前提条件:

  • 在menuconfig菜单中使能Linux适配模块。

  • 在menuconfig菜单中配置中断使用最大数和可设置的中断优先级个数。

代码实现如下:

#include "los_hwi.h"
#include "linux/interrupt.h"

#define HWI_NUM_INT50 50
void Uart_Irqhandle(int irq, void *dev)
{
    printf("\nuart0:the function1 \n");
}
void Hwi_Test(void)
{
    int a = 1;
    void *dev = &a;
    unsigned long flags = 0;
    const char * name = "hwiTest";

    request_irq(HWI_NUM_INT50, Uart_Irqhandle, flags, name, dev); // 创建中断
    free_irq(HWI_NUM_INT50, dev);                                 // 删除中断
}

高精度定时器

概述

基本概念

高精度软件定时器利用一定的硬件定时器资源,通过算法实现更高精度的软件定时器,满足了对精确时间的需求。

LiteOS高精度软件定时器设计了一套软件架构,提供了微秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动。

开发指导

使用场景

内核软件定时器的定时精度为毫秒级别,定时精度并不高,无法满足对定时精度要求更高的场合。高精度软件定时器弥补了内核软件定时器在这方面的不足,提供了精度到达微秒级别的定时接口。

接口名

描述

兼容性

hrtimer_init

初始化高精度软件定时器资源,这部分功能已经在内核初始化阶段实现,所以这个接口只是适配linux接口,并无实现。

完全兼容

hrtimer_create

创建高精度软件定时器,设置定时时间及回调函数。

LiteOS自定义接口

hrtimer_start

启动高精度定时器。

不完全兼容,返回值不同,详见API参考

hrtimer_cancel

取消还未超时的高精度定时器。

不完全兼容,返回值不同,详见API参考

hrtimer_forward

修改还未超时的高精度定时器的定时时间,重新设定超时时间。

不完全兼容,入参不同,详见API参考

hrtimer_is_queued

如果高精度定时器已创建,则返回0,否则返回1。

不完全兼容,返回值不同,详见API参考

开发流程

高精度软件定时器使用的典型流程:

  1. 打开菜单,选择Compat ---> Enable Linux ---> Enable Linux Hrtimer,使能高精度定时器。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_COMPAT_LINUX_HRTIMER

    使能Linux高精度定时器

    YES/NO

    YES

    LOSCFG_COMPAT_LINUX

  2. 定义一个hrtimer变量和一个ktime变量。

  3. 接着根据定时时间,给ktime变量赋值,分别为sec及usec字段。

  4. 然后调用hrtimer_create接口,创建高精度定时器,设置其定时器时间及回调函数。

  5. 最后调用hrtimer_start接口,启动高精度软件定时器。

  6. 调用hrtimer_cancel与hrtimer_forward接口取消或修改定时器的超时时间。

  7. hrtimer_is_queued接口辅助用户判断高精度定时器是否已经创建。

注意事项

  • 高精度软件定时器接口涉及的hrtimer变量,需要用户提供,接口不会为其申请任何资源。

  • hrtimer_init接口没有实现,只是适配linux接口,所以使用时可不调用。

  • 目前高精度软件定时器只支持相对时间模式,不支持绝对时间模式。

  • 高精度软件定时器依赖硬件,使用前需要确认硬件是否支持。

  • 高精度定时器存在指令时间消耗,所以实际定时触发时间会大于设置的时间。

编程实例

实例描述

本实例实现如下功能:

  1. 用户定义swtmr变量及time变量。

  2. 调用hrtimer_create接口创建高精度定时器,设置其定时器时间及回调函数。

  3. 调用hrtimer_start接口启动定时器。

  4. 调用hrtimer_forward接口修改定时时间,由原来的80ms修改为40ms。

  5. 调用LOS_Msleep函数使任务延迟50ms。

  6. 再调用hrtimer_cancel接口取消定时器,发现此时定时器已超时不存在了。

编程实例

前提条件:已使能高精度定时器。

代码实现如下:

#include "los_base.h"
#include "linux/hrtimer.h"
 
static enum hrtimer_restart Hrtimer_Func(struct hrtimer *arg)
{
    dprintf("The hrtimer is timeout!!!\n");
    return HRTIMER_NORESTART;
}
 
static UINT32 TestCase(VOID)
{
    struct hrtimer swtmr;
    union ktime time;
    union ktime interval = { 0 };
    interval.tv.sec = 0;
    interval.tv.usec = 40000;
    int ret;
 
    dprintf("----->test start<-----\n");
 
    time.tv.sec = 0;
    time.tv.usec = 80000;
    ret = hrtimer_create(&swtmr, time, Hrtimer_Func);
    if (ret == 0) {
        dprintf("Hrtimer ctreate successfully!\n");
    }
 
    ret = hrtimer_start(&swtmr, time, HRTIMER_MODE_REL);
    if (ret == 0) {
        dprintf("Hrtimer start successfully!\n");
    }
 
    ret = hrtimer_forward(&swtmr, interval);
 
    LOS_Msleep(50);
 
    ret = hrtimer_cancel(&swtmr);
    if (ret == 0) {
        dprintf("Hrtimer already timeout!\n");
    }
 
    dprintf("----->test end<-----\n");
 
    return LOS_OK;
}

结果验证

编译运行得到的结果为:

----->test start<-----
Hrtimer ctreate successfully!
Hrtimer start successfully!
The hrtimer is timeout!!!
Hrtimer already timeout!
----->test end<-----

linux适配接口概览

LiteOS提供的linux适配接口如下表所示。

序号

文件名

接口名

说明

兼容性

注意事项

1

clock.h

sched_clock

获取系统时间

完全兼容

不涉及

2

completion.h

complete

唤醒一个等待completion的线程

不完全兼容,如果入参为空会退出

不涉及

3

completion.h

complete_all

唤醒所有等待completion的线程

不完全兼容,如果入参为空会退出

不涉及

4

completion.h

init_completion

初始化completion结构

不完全兼容,如果入参为空指针会退出,不做初始化

不涉及

5

completion.h

wait_for_completion

等待completion

不完全兼容,如果入参为空指针或接口使用场景不正确会退出

不涉及

6

completion.h

wait_for_completion_timeout

等待completion,若超时则结束等待

不完全兼容,接口实现中增加了非法判断,详见API参考

不涉及

7

crc32.h

crc32

计算CRC

完全兼容

不涉及

8

delay.h

msleep

延时,程序暂停若干时间(单位:ms)

完全兼容

不涉及

9

delay.h

udelay

忙等延迟。只用于极小时间(微秒级)的延迟

完全兼容

不涉及

10

delay.h

mdelay

忙等延迟,延迟时间单位:ms

完全兼容

不涉及

11

hrtimer.h

hrtimer_cancel

取消计时器并等待处理程序完成

不完全兼容,返回值不同,而且依赖平台,需要适配后才能实现,详见API参考

不涉及

12

hrtimer.h

hrtimer_create

设置时间段并创建回调函数

LiteOS自定义接口,依赖平台,需要适配后才能实现

不涉及

13

hrtimer.h

hrtimer_forward

转发计时器到期

不完全兼容,入参不同,而且依赖平台,需要适配后才能实现,详见API参考

不涉及

14

hrtimer.h

hrtimer_init

将计时器初始化为给定的时钟

依赖平台,需要适配后才能实现

不涉及

15

hrtimer.h

hrtimer_is_queued

检查计时器是否在队列之一上

不完全兼容,返回值不同,而且依赖平台,需要适配后才能实现,详见API参考

不涉及

16

hrtimer.h

hrtimer_start

(重新)在当前CPU上启动hrtimer

不完全兼容,返回值不同,而且依赖平台,需要适配后才能实现,详见API参考

不涉及

17

interrupt.h

disable_irq

屏蔽中断

完全兼容

不涉及

18

interrupt.h

enable_irq

使能中断

完全兼容

不涉及

19

interrupt.h

free_irq

释放中断服务

完全兼容

不涉及

20

interrupt.h

request_irq

注册中断服务

完全兼容

不涉及

21

io.h

readb

从I/O读取1字节

完全兼容

不涉及

22

io.h

readl

从I/O读取4字节

完全兼容

不涉及

23

io.h

readw

从I/O读取2字节

完全兼容

不涉及

24

io.h

writeb

向I/O写入1字节

完全兼容

不涉及

25

io.h

writel

向I/O写入4字节

完全兼容

不涉及

26

io.h

writew

向I/O写入2字节

完全兼容

不涉及

27

jiffies.h

get_jiffies_64

获取自系统启动以来的滴答数

完全兼容

不涉及

28

jiffies.h

jiffies_to_msecs

将滴答数转换成毫秒

功能完全兼容,参数范围限制

不涉及

29

jiffies.h

msecs_to_jiffies

将毫秒转换成滴答数

不完全兼容,支持频率有限制

不涉及

30

kernel.h

div_s64

除法运算

完全兼容

不涉及

31

kernel.h

div_s64_rem

除法运算

完全兼容

保证入参合法

32

kernel.h

div_u64

除法运算

完全兼容

不涉及

33

kernel.h

div_u64_rem

除法运算

完全兼容

保证入参合法

34

kernel.h

div64_s64

除法运算

完全兼容

不涉及

35

kernel.h

div64_u64

除法运算

完全兼容

不涉及

36

kernel.h

div64_u64_rem

除法运算

完全兼容

保证入参合法

37

kernel.h

do_div_imp

除法运算

完全兼容

不涉及

38

kernel.h

do_div_s64_imp

除法运算

完全兼容

不涉及

39

kernel.h

ERR_PTR

错误码操作

完全兼容

不涉及

40

kernel.h

IS_ERR

错误码判断

完全兼容

不涉及

41

kernel.h

PTR_ERR

错误码操作

完全兼容

不涉及

42

list.h

LIST_HEAD_INIT

初始化双链表(非指针)

完全兼容

不涉及

43

list.h

LIST_HEAD

声明并初始化链表头

完全兼容

不涉及

44

list.h

INIT_LIST_HEAD

初始化双链表(指针)

不完全兼容,变更为宏或内联

保证入参合法

45

list.h

list_add

在节点后添加新节点

完全兼容

保证入参合法

46

list.h

list_add_tail

在节点前添加新节点

完全兼容

保证入参合法

47

list.h

list_is_last

判断是否为尾节点

完全兼容

保证入参合法

48

list.h

list_empty

判断链表是否为空

完全兼容

保证入参合法

49

list.h

list_entry

获取结构体地址

完全兼容

保证入参合法

50

list.h

list_first_entry

获取首节点结构体指针

完全兼容

保证入参合法

51

list.h

list_for_each

遍历链表

完全兼容

保证入参合法

52

list.h

list_for_each_entry

遍历链表获取结构体

完全兼容

保证入参合法

53

list.h

list_for_each_entry_reverse

反向遍历获取结构体

完全兼容

保证入参合法

54

list.h

list_for_each_entry_safe

安全遍历(带删除)

完全兼容

保证入参合法

55

list.h

list_for_each_safe

安全遍历链表

完全兼容

保证入参合法

56

list.h

list_del

删除节点

完全兼容

保证入参合法

57

list.h

list_move

移动节点到另一链表

完全兼容

保证入参合法

58

list.h

list_del_init

删除并重新初始化

完全兼容

保证入参合法

59

platform_device.h

platform_device_register

注册设备

完全兼容

不涉及

60

platform_device.h

platform_device_unregister

注销设备

完全兼容

不涉及

61

platform_device.h

platform_driver_register

注册驱动

完全兼容

不涉及

62

platform_device.h

platform_driver_unregister

注销驱动

不完全兼容,返回值不同

不涉及

63

platform_device.h

platform_get_drvdata

获取驱动私有数据

完全兼容

不涉及

64

platform_device.h

platform_get_irq

获取设备中断号

完全兼容

不涉及

65

platform_device.h

platform_get_resource

获取设备资源

完全兼容

不涉及

66

platform_device.h

platform_ioremap_resource

获取设备内存基址

完全兼容

不涉及

67

platform_device.h

platform_set_drvdata

设置驱动私有数据

完全兼容

不涉及

68

rtc.h

is_leap_year

是否是闰年

完全兼容

不涉及

69

rtc.h

rtc_time_to_tm

绝对时间转年月日

完全兼容

不涉及

70

rtc.h

rtc_tm_to_time

年月日转绝对时间

完全兼容

不涉及

71

rwsem.h

DECLARE_RWSEM

定义并初始化读写锁

完全兼容

不涉及

72

rwsem.h

init_rwsem

初始化读写锁

完全兼容

不涉及

73

rwsem.h

down_read

获取读锁(睡眠)

完全兼容

不涉及

74

rwsem.h

down_read_trylock

尝试获取读锁

完全兼容

不涉及

75

rwsem.h

down_write

获取写锁(睡眠)

完全兼容

不涉及

76

rwsem.h

down_write_trylock

尝试获取写锁

完全兼容

不涉及

77

rwsem.h

up_read

释放读锁

完全兼容

不涉及

78

rwsem.h

up_write

释放写锁

完全兼容

不涉及

79

rwsem.h

downgrade_write

写锁降级为读锁

完全兼容

不涉及

80

scatterlist.h

sg_init_one

初始化集散序列

完全兼容

保证入参合法

81

scatterlist.h

sg_init_table

初始化集散表格

完全兼容

保证入参合法

82

scatterlist.h

sg_mark_end

标记序列结束

完全兼容

保证入参合法

83

scatterlist.h

sg_set_buf

设置集散序列缓冲区

完全兼容

保证入参合法

84

sched.h

schedule_timeout

任务延时唤醒

完全兼容

不涉及

85

semaphore.h

DEFINE_SEMAPHORE

定义并初始化信号量

完全兼容

不涉及

86

semaphore.h

sema_init

动态初始化信号量

不完全兼容,计数值有限制

不涉及

87

semaphore.h

down

获取信号量(不可唤醒睡眠)

不完全兼容,不支持计数值>1

不涉及

88

semaphore.h

down_interruptible

获取信号量(可中断睡眠)

不完全兼容,不支持计数值>1

不涉及

89

semaphore.h

down_trylock

尝试获取信号量

不完全兼容,不支持计数值>1

不涉及

90

semaphore.h

up

释放信号量

不完全兼容,不支持计数值>1

不涉及

91

semaphore.h

sema_destory

销毁信号量

非标准接口

不涉及

92

slab.h

kfree

释放内存

完全兼容

不涉及

93

slab.h

kmalloc

分配内存

完全兼容

不涉及

94

slab.h

kzalloc

分配并清零内存

完全兼容

不涉及

95

slab.h

vfree

释放虚拟内存

完全兼容

不涉及

96

slab.h

vmalloc

分配虚拟内存

完全兼容

不涉及

97

spinlock_types.h

DEFINE_SPINLOCK

定义并初始化自旋锁

完全兼容

不涉及

98

spinlock.h

spin_lock_init

动态初始化自旋锁

完全兼容

不涉及

99

spinlock.h

spin_lock

获取自旋锁

完全兼容

不涉及

100

spinlock.h

spin_unlock

释放自旋锁

完全兼容

不涉及

101

spinlock.h

spin_lock_irqsave

关中断并加锁

完全兼容

不涉及

102

spinlock.h

spin_unlock_irqrestore

开中断并解锁

完全兼容

不涉及

103

string.h

strlcpy

拷贝字符串

完全兼容

不涉及

104

timer.h

add_timer

添加定时器

功能完全兼容,参数有限制

不涉及

105

timer.h

del_timer

删除定时器

完全兼容

不涉及

106

timer.h

del_timer_sync

同步删除定时器

完全兼容

不涉及

107

timer.h

init_timer

初始化定时器

完全兼容

不涉及

108

timer.h

mod_timer

修改定时器到期时间

完全兼容

不涉及

109

wait.h

init_waitqueue_head

初始化等待队列头

完全兼容

不涉及

110

wait.h

wait_event

等待事件

完全兼容

不涉及

111

wait.h

wait_event_interruptible

可中断等待事件

不完全兼容,不可被信号中断

不涉及

112

wait.h

wait_event_interruptible_timeout

带超时的等待事件

不完全兼容,入参与返回值差异

不涉及

113

wait.h

waitqueue_active

检查等待队列是否活跃

完全兼容

不涉及

114

wait.h

wake_up

唤醒任务

完全兼容

不涉及

115

wait.h

wake_up_interruptible

唤醒可中断任务

不完全兼容,无signal唤醒

不涉及

116

wait.h

wake_up_interruptible_poll

唤醒任务

完全兼容

不涉及

117

wait.h

DECLARE_WAIT_QUEUE_HEAD

定义并初始化等待队列头

完全兼容

不涉及

118

workqueue.h

cancel_delayed_work

取消延迟工作

完全兼容

不涉及

119

workqueue.h

cancel_delayed_work_sync

同步取消延迟工作

不完全兼容,返回值逻辑有别

不涉及

120

workqueue.h

cancel_work_sync

同步取消工作

不完全兼容,状态判断逻辑有别

不涉及

121

workqueue.h

create_singlethread_workqueue

创建单线程工作队列

完全兼容

不涉及

122

workqueue.h

create_workqueue

创建工作队列

不完全兼容,CPU创建逻辑不同

不涉及

123

workqueue.h

destroy_workqueue

销毁工作队列

不完全兼容,带参数检测

不涉及

124

workqueue.h

flush_delayed_work

刷新延迟工作

完全兼容

不涉及

125

workqueue.h

flush_work

刷新工作

完全兼容

不涉及

126

workqueue.h

INIT_DELAYED_WORK

初始化延迟工作项

完全兼容

不涉及

127

workqueue.h

INIT_WORK

初始化工作项

完全兼容

不涉及

128

workqueue.h

queue_delayed_work

提交延迟工作项

完全兼容

不涉及

129

workqueue.h

queue_work

提交工作项

不完全兼容,带参数检测

不涉及

130

workqueue.h

schedule_delayed_work

调度延迟工作项

不完全兼容,带参数检测

不涉及

131

workqueue.h

schedule_work

调度工作项

不完全兼容,带参数检测

不涉及

132

workqueue.h

work_busy

检查工作项状态

不完全兼容,带参数检测

不涉及

说明: 完全兼容表示功能与linux一致,错误码返回值要以实际代码为准,部分兼容表示部分功能与linux一致。

linux不支持接口概览

LiteOS的linux适配接口中,有一些未支持,包括但不限于下表所示。

头文件

接口名

说明

兼容性

bug.h

BUG

提供断言并输出信息

不支持

compiler.h

likely

判断语句,值为真可能性更大

不支持

compiler.h

unlikely

判断语句,值为假可能性更大

不支持

fb.h

fb_alloc_cmap

分配颜色表内存空间

不支持

fb.h

fb_cmap_to_user

复制颜色表

不支持

fb.h

fb_copy_cmap

复制颜色表

不支持

fb.h

fb_dealloc_cmap

释放颜色表内存空间

不支持

fb.h

fb_default_cmap

设置默认颜色表

不支持

fb.h

fb_pan_display

刷新操作屏

不支持

fb.h

fb_set_cmap

设定颜色表

不支持

fb.h

fb_set_user_cmap

设定颜色表

不支持

fb.h

fb_set_var

设置fbinfo显示模式和可变参数

不支持

fb.h

framebuffer_alloc

向内核申请一块空间

不支持

fb.h

framebuffer_release

向内核释放一块空间

不支持

fb.h

register_framebuffer

将fbinfo结构注册到内核

不支持

fb.h

unregister_framebuffer

注销fbinfo

不支持

kernel.h

copy_from_user

将用户空间的数据传送到内核空间

不支持

kernel.h

copy_to_user

从内核区中读取数据到用户区

不支持

kernel.h

ioremap_cached

内核虚拟地址映射

不支持

kernel.h

ioremap_nocache

内核虚拟地址映射

不支持

kernel.h

iounmap

取消ioremap函数所做的映射

不支持

module.h

module_put

减少模块使用计数

不支持

module.h

try_module_get

增加模块使用计数

不支持

pm.h

dpm_resume_end

resume所有非系统设备,即执行所有注册设备的resume回调函数

不支持

pm.h

dpm_suspend_start

suspend所有非系统设备,即调用所有注册设备的suspend回调函数

不支持

sched.h

cond_resched

调度一个新程序投入运行

不支持

sched.h

signal_pending

检查当前进程是否有信号处理

不支持

semaphore.h

init_MUTEX

初始化信号量值为1

不支持

semaphore.h

init_MUTEX_LOCKED

初始化信号量值为0

不支持

string.h

simple_strtol

将一个字符串转换成unsigend long long型数据

不支持

suspend.h

pm_resume

用于resume系统

不支持

suspend.h

pm_suspend

用于挂起系统

不支持

timer.h

timer_pending

判断一个处在定时器管理队列中的定时器对象是否已经被调度执行

不支持

wait.h

add_wait_queue

将进程插入队列尾部

不支持

wait.h

DECLARE_WAITQUEUE

定义并初始化一个等待队列

不支持

wait.h

remove_wait_queue

将等待队列wait从附属的等待队列头q指向的等待队列链表中移除

不支持

wakelock.h

wake_lock

激活锁

不支持

wakelock.h

wake_lock_active

判断锁当前是否有效

不支持

wakelock.h

wake_lock_init

初始化一个锁

不支持

wakelock.h

wake_unlock

解锁使之成为无效锁

不支持

RT-Thread适配

本部分简要介绍LiteOS适配RT-Thread v5.2.1接口的情况。

RT-Thread适配接口

接口名

类型

描述

rt_thread_init

线程管理

静态初始化线程句柄及用户提供的栈空间

rt_thread_create

线程管理

从堆中动态分配空间并创建新线程

rt_thread_startup

线程管理

启动线程,将其加入系统的就绪队列

rt_thread_detach

线程管理

脱离(卸载)一个静态定义的线程对象

rt_thread_delete

线程管理

彻底删除动态创建的线程并回收内存

rt_thread_self

线程管理

获取当前正在运行的线程句柄

rt_thread_yield

线程管理

线程自愿放弃CPU,让同优先级线程运行

rt_thread_sleep

线程管理

线程挂起指定的系统时钟节拍(Tick)数

rt_thread_delay

线程管理

线程延时指定节拍(功能同sleep)

rt_thread_mdelay

线程管理

线程延时指定的毫秒(ms)数

rt_thread_control

线程管理

更改或获取线程的动态运行参数

rt_thread_suspend

线程管理

将指定线程挂起,使其进入阻塞态

rt_thread_resume

线程管理

恢复被挂起的线程至就绪态

rt_schedule

线程管理

强制立即执行一次系统任务调度

rt_enter_critical

线程管理

进入临界区,锁定调度器防止线程切换

rt_exit_critical

线程管理

退出临界区,解锁调度器

rt_critical_level

线程管理

查询当前临界区嵌套的深度等级

rt_thread_idle_gethandler

线程管理

获取系统空闲线程的句柄对象

rt_tick_get

时钟管理

获取系统自启动以来的总时钟节拍数

rt_tick_increase

时钟管理

增加系统Tick数(通常在时钟中断调用)

rt_tick_from_millisecond

时钟管理

将毫秒时间值换算为对应的系统节拍数

rt_timer_init

定时器管理

静态初始化内核定时器对象

rt_timer_detach

定时器管理

卸载静态定义的定时器对象

rt_timer_create

定时器管理

动态创建并初始化一个软件定时器

rt_timer_delete

定时器管理

删除动态定时器并回收其内存空间

rt_timer_start

定时器管理

正式启动指定的定时器计数

rt_timer_stop

定时器管理

强制停止正在运行的定时器

rt_timer_control

定时器管理

修改定时器的超时阈值或触发模式

rt_interrupt_enter

中断管理

中断服务函数入口标记,管理中断嵌套

rt_interrupt_leave

中断管理

中断服务函数出口标记,触发必要调度

rt_interrupt_get_nest

中断管理

获取当前中断嵌套的总层数

rt_hw_interrupt_disable

中断管理

执行底层关中断指令,保存CPU状态

rt_hw_interrupt_enable

中断管理

执行底层开中断指令,恢复CPU状态

rt_hw_interrupt_init

中断管理

底层硬件中断控制器初始化

rt_hw_interrupt_mask

中断管理

屏蔽指定的硬件中断源

rt_hw_interrupt_umask

中断管理

取消屏蔽指定的硬件中断源

rt_hw_interrupt_install

中断管理

绑定中断向量号与服务函数

rt_hw_interrupt_get_irq

中断管理

获取当前正在处理的中断号

rt_hw_interrupt_ack

中断管理

向中断控制器发送中断确认信号

rt_hw_interrupt_set_pending

中断管理

手动设置中断挂起状态

rt_hw_interrupt_get_pending

中断管理

获取当前中断的挂起状态

rt_hw_interrupt_clear_pending

中断管理

清除指定中断的挂起标志

rt_hw_interrupt_set_priority

中断管理

设置特定中断源的优先级

rt_hw_interrupt_get_priority

中断管理

获取特定中断源的优先级

rt_hw_interrupt_set_priority_mask

中断管理

设置全局中断优先级遮罩

rt_hw_interrupt_get_priority_mask

中断管理

获取全局中断优先级遮罩值

rt_sem_init

信号量

静态初始化信号量控制块

rt_sem_detach

信号量

卸载静态定义的信号量对象

rt_sem_create

信号量

动态分配并创建信号量

rt_sem_delete

信号量

销毁动态信号量并释放内存

rt_sem_take

信号量

获取(锁定)信号量,支持阻塞

rt_sem_trytake

信号量

非阻塞式尝试获取信号量

rt_sem_release

信号量

释放信号量并唤醒等待线程

rt_mutex_init

互斥量

静态初始化互斥锁对象

rt_mutex_detach

互斥量

卸载静态定义的互斥锁

rt_mutex_create

互斥量

动态分配并创建互斥锁

rt_mutex_delete

互斥量

销毁动态互斥锁并释放内存

rt_mutex_take

互斥量

获取互斥锁,支持优先级继承

rt_mutex_release

互斥量

释放互斥锁并恢复持有者优先级

rt_event_init

事件

静态初始化多事件同步对象

rt_event_detach

事件

卸载静态定义的事件对象

rt_event_create

事件

动态分配并创建事件控制块

rt_event_delete

事件

销毁动态事件对象

rt_event_send

事件

发送指定的事件标志位

rt_event_recv

事件

接收事件标志,支持逻辑等待条件

rt_mb_init

邮箱

静态初始化线程间通信邮箱

rt_mb_detach

邮箱

卸载静态定义的邮箱对象

rt_mb_create

邮箱

动态分配并创建邮箱空间

rt_mb_delete

邮箱

销毁动态邮箱并释放内存

rt_mb_send

邮箱

向邮箱投递一封4字节邮件

rt_mb_send_wait

邮箱

投递邮件,支持队满时阻塞等待

rt_mb_recv

邮箱

从邮箱中读取并取出一封邮件

rt_mq_init

消息队列

静态初始化消息队列对象

rt_mq_detach

消息队列

卸载静态定义的消息队列

rt_mq_create

消息队列

动态分配并创建消息队列空间

rt_mq_delete

消息队列

销毁动态队列并释放内存

rt_mq_send

消息队列

向队列发送一条自定义长度消息

rt_mq_send_wait

消息队列

发送消息,支持队列满时限时等待

rt_mq_urgent

消息队列

发送紧急消息,将其排在队首

rt_mq_recv

消息队列

从消息队列中接收一条消息内容

rt_malloc

动态内存管理

从系统堆中分配连续内存块

rt_realloc

动态内存管理

重新调整已分配内存块的大小

rt_calloc

动态内存管理

分配内存并自动清零数据位

rt_malloc_align

动态内存管理

分配按指定字节对齐的内存块

rt_free

动态内存管理

释放动态分配的内存块并归还堆

rt_free_align

动态内存管理

释放对齐分配的内存块

rt_memheap_init

动态内存管理

初始化多个内存堆管理对象

rt_mp_init

内存池管理

静态初始化固定内存块池

rt_mp_detach

内存池管理

卸载静态定义的内存池

rt_mp_create

内存池管理

动态创建并分配固定内存块池

rt_mp_delete

内存池管理

销毁动态内存池并释放资源

rt_mp_alloc

内存池管理

从内存池中申请一个固定大小的块

rt_mp_free

内存池管理

将内存块释放回对应的内存池

rt_thread_idle_sethook

系统钩子

注册空闲线程执行时的钩子函数

rt_thread_idle_delhook

系统钩子

删除已注册的空闲线程钩子函数

rt_interrupt_enter_sethook

系统钩子

设置进入中断时的审计钩子函数

rt_interrupt_leave_sethook

系统钩子

设置退出中断时的审计钩子函数

rt_malloc_sethook

系统钩子

设置内存分配成功的钩子函数

rt_free_sethook

系统钩子

设置内存释放成功的钩子函数

rt_mp_alloc_sethook

系统钩子

设置从内存池分配块的钩子函数

rt_mp_free_sethook

系统钩子

设置释放内存池块的钩子函数

rt_scheduler_sethook

系统钩子

设置系统任务切换的钩子函数

rt_thread_suspend_sethook

系统钩子

设置线程挂起操作的钩子函数

rt_thread_resume_sethook

系统钩子

设置线程恢复操作的钩子函数

rt_thread_inited_sethook

系统钩子

设置线程初始化完成的钩子函数

rt_strstr

字符串操作

在字符串中查找子字符串位置

rt_strcasecmp

字符串操作

忽略大小写比较两个字符串

rt_strncpy

字符串操作

拷贝指定长度的字符串内容

rt_strncmp

字符串操作

比较指定长度的两个字符串

rt_strcmp

字符串操作

比较两个字符串的字面值

rt_strnlen

字符串操作

计算指定长度限制内的字符串长度

rt_strlen

字符串操作

计算以空字符结尾的字符串长度

rt_strdup

字符串操作

复制并动态分配一个字符串副本

rt_snprintf

字符串操作

安全地格式化字符串至缓冲区

rt_vsprintf

字符串操作

使用变量参数列表格式化字符串

rt_sprintf

字符串操作

格式化并打印字符串至缓冲区

rt_memset

内存操作

使用特定字节填充内存区域

rt_memcpy

内存操作

在内存区域间执行高效拷贝

rt_memmove

内存操作

处理重叠区域的安全内存拷贝

rt_memcmp

内存操作

比较两块内存区域的内容差异

rt_show_version

内核基础服务

在控制台显示内核版本及版权信息

rt_console_get_device

内核基础服务

获取当前控制台使用的设备句柄

rt_console_set_device

内核基础服务

设置系统控制台使用的通信设备

rt_kputs

内核基础服务

内核级基础字符串输出函数

rt_kprintf

内核基础服务

内核专用格式化打印输出函数

__rt_ffs

内核基础服务

快速查找二进制位中第一个1的位置

rt_get_errno

错误代码管理

获取当前线程最近一次发生的错误码

rt_set_errno

错误代码管理

设置当前线程的全局错误代码

rt_atomic_load

原子操作

执行硬件级不可中断的读取操作

rt_atomic_store

原子操作

执行硬件级不可中断的写入操作

rt_atomic_exchange

原子操作

执行硬件级不可中断的数据交换

rt_atomic_add

原子操作

执行原子加法运算

rt_atomic_sub

原子操作

执行原子减法运算

rt_atomic_xor

原子操作

执行原子异或运算

rt_atomic_and

原子操作

执行原子与运算

rt_atomic_or

原子操作

执行原子或运算

rt_atomic_flag_test_and_set

原子操作

测试并设置原子标志位

rt_atomic_flag_clear

原子操作

清除原子标志位状态

rt_atomic_compare_exchange_strong

原子操作

强原子比较并交换操作

rt_wqueue_wait

等待队列

将线程挂起在指定等待队列上

rt_wqueue_wakeup

等待队列

唤醒等待队列上的指定线程

rt_workqueue_destroy

工作队列

销毁指定的工作队列及其线程

rt_workqueue_dowork

工作队列

立即执行工作队列中的任务项

rt_workqueue_cancel_work

工作队列

取消尚未执行的工作任务项

rt_workqueue_cancel_work_sync

工作队列

同步取消并等待工作项完成

rt_work_init

工作队列

初始化一个异步工作项任务

socket

套接字

创建一个兼容BSD标准的网络套接字

bind

套接字

绑定套接字至特定的地址和端口

listen

套接字

设置套接字进入监测状态

accept

套接字

接受一个远端套接字的连接请求

connect

套接字

发起与远程服务器的网络连接

send

套接字

通过套接字发送网络数据内容

recv

套接字

从套接字中接收网络数据内容

sendto

套接字

向指定地址发送UDP数据报文

recvfrom

套接字

从指定地址接收UDP数据报文

closesocket

套接字

关闭套接字并释放相关资源

shutdown

套接字

断开套接字的发送或接收通道

setsockopt

套接字

设置套接字的网络通信参数

getsockopt

套接字

获取套接字的网络通信参数

getpeername

套接字

获取与套接字连接的对端地址

getsockname

套接字

获取套接字本地绑定的地址

ioctlsocket

套接字

执行套接字的输入输出控制操作

gethostbyname

套接字

通过域名解析获取主机的 IP 地址

rt_ringbuffer_init

环形缓冲区

静态初始化环形缓冲区结构

rt_ringbuffer_reset

环形缓冲区

重置环形缓冲区,清除所有数据

rt_ringbuffer_put

环形缓冲区

向环形缓冲区压入指定数据内容

rt_ringbuffer_put_force

环形缓冲区

强制存入数据(覆盖旧数据)

rt_ringbuffer_putchar

环形缓冲区

向缓冲区存入单个字符

rt_ringbuffer_putchar_force

环形缓冲区

强制存入单个字符

rt_ringbuffer_get

环形缓冲区

从环形缓冲区提取并读出数据

rt_ringbuffer_getchar

环形缓冲区

从缓冲区读出单个字符

rt_ringbuffer_data_len

环形缓冲区

获取缓冲区中当前已存储的数据长度

rt_ringbuffer_create

环形缓冲区

动态创建并分配环形缓冲区空间

rt_ringbuffer_destroy

环形缓冲区

销毁动态缓冲区并释放内存

rt_ringbuffer_get_size

环形缓冲区

获取环形缓冲区的总容量大小

rt_rbb_init

环形块状缓冲区

静态初始化块状缓冲区管理结构

rt_rbb_create

环形块状缓冲区

动态创建环形块状缓冲区对象

rt_rbb_destroy

环形块状缓冲区

销毁块状缓冲区并释放资源

rt_rbb_get_buf_size

环形块状缓冲区

获取块状缓冲区的内部总长度

rt_rbb_blk_alloc

环形块状缓冲区

从缓冲区分配一个数据块句柄

rt_rbb_blk_put

环形块状缓冲区

将数据块标记为已就绪状态

rt_rbb_blk_get

环形块状缓冲区

从缓冲区获取已就绪的数据块

rt_rbb_blk_free

环形块状缓冲区

释放数据块并回收缓冲区空间

rt_rbb_blk_queue_get

环形块状缓冲区

从块队列中获取一个数据块

rt_rbb_blk_queue_len

环形块状缓冲区

获取当前队列中块的数量

rt_rbb_blk_queue_buf

环形块状缓冲区

获取块队列关联的缓冲区地址

rt_rbb_blk_queue_free

环形块状缓冲区

释放整个块队列相关的资源

rt_rbb_next_blk_queue_len

环形块状缓冲区

获取下一个就绪块的数据长度

rt_completion_init

完成信号量

静态初始化单次同步完成量

rt_completion_wait

完成信号量

阻塞线程直至收到任务完成信号

rt_completion_done

完成信号量

发送完成信号,唤醒等待的线程

rt_pipe_open

管道

打开指定的流式通信管道

rt_pipe_close

管道

关闭已打开的流式通信管道

rt_pipe_read

管道

从管道中读取流式数据内容

rt_pipe_write

管道

向管道中写入流式数据内容

rt_pipe_create

管道

创建用于线程间通信的虚拟管道

rt_pipe_delete

管道

删除指定的通信管道并释放资源

ulog_init

日志组件

初始化ULOG日志管理框架

ulog_deinit

日志组件

反初始化并关闭日志框架

ulog_flush

日志组件

强制刷新并输出日志缓冲区内容

ulog_hexdump

日志组件

以十六进制格式打印内存数据日志

ulog_raw

日志组件

输出不带日志头格式的原始数据

dlopen

动态模块

打开并加载一个动态库模块

dlsym

动态模块

在动态库中查找特定符号的地址

dlclose

动态模块

关闭并卸载指定的动态库模块

rt_spin_unlock_irqrestore

自旋锁与多核

释放自旋锁并恢复之前的中断状态

rt_cpu_self

自旋锁与多核

获取当前执行代码的CPU核心对象

rt_cpu_index

自旋锁与多核

获取当前CPU核心的逻辑索引号

RT-Thread接口适配差异

考虑接口的易用性和LiteOS内部机制与RT-Thread标准接口的差异,在适配RT-Thread接口时,对部分接口进行了修改,详见下表。

接口名

类型

描述

INIT_EXPORT

初始化

不支持RT-Thread自带的INIT_EXPORT,如果想系统启动自动初始化函数,可参考LiteOS的宏 LOS_SYS_INIT。

rt_mq_init

消息队列

不支持RT_IPC_FLAG_PRIO模式。

rt_mq_create

消息队列

不支持RT_IPC_FLAG_PRIO模式。

object相关接口

object

暂不支持。

rt_spin_lock

自旋锁

使用全局锁。

rt_spin_unlock

自旋锁

使用全局锁。

rt_spin_lock_irqsave

自旋锁

使用全局锁。

rt_spin_unlock_irqrestore

自旋锁

使用全局锁。

rt_cpu_self

自旋锁

仅支持单核。

rt_workqueue_cancel_work_sync

工作队列

不支持同步等待机制。

rt_event_init

事件

接口中的参数name实际并未使用。

rt_event_create

事件

接口中的参数name实际并未使用。

rt_timer_init

定时器

接口中的参数name实际并未使用。

rt_timer_control

定时器

相比原生接口,新增支持命令RT_TIMER_CTRL_GET_STATE(获取定时器状态)、RT_TIMER_CTRL_GET_REMAIN_TIME(获得软件定时器剩余Tick数)。

rt_sem_init

信号量

不支持PRIO。

rt_sem_detach

信号量

不支持PRIO。

rt_mq_init

消息队列

不支持PRIO。

rt_mq_create

消息队列

不支持PRIO。

rt_mutex_init

互斥锁

接口中的参数flag实际并未使用。

rt_mutex_create

互斥锁

接口中的参数flag实际并未使用。

涉及任务优先级相关接口

任务

在RT-Thread里优先级级别是根据数字从大到小排序,在LiteOS里是从小到大排序。

FreeRTOS适配

本部分简要介绍LiteOS适配FreeRTOS v11.1.0接口的情况。

FreeRTOS适配接口

接口名

类型

描述

portENTER_CRITICAL

中断管理

进入硬件级临界区,禁止所有可屏蔽中断

portEXIT_CRITICAL

中断管理

退出硬件级临界区,恢复中断处理

portCLEAR_INTERRUPT_MASK_FROM_ISR

中断管理

在中断服务函数中恢复中断遮罩状态

portSET_INTERRUPT_MASK_FROM_ISR

中断管理

在中断服务函数中屏蔽中断并保存当前状态

xTaskGetTickCount

任务管理

获取系统启动后的总时钟节拍数(Tick

xTaskGetCurrentTaskHandle

任务管理

获取当前正在运行的任务句柄

xTaskCreate

任务管理

动态创建一个新任务并分配堆栈

xTaskCreateStatic

任务管理

静态初始化任务(使用用户提供的内存)

vTaskDelete

任务管理

删除一个任务并释放其占用的资源

vTaskDelay

任务管理

任务进入阻塞态,延时指定的时钟节拍

vTaskDelayUntil

任务管理

任务延时至绝对时间点,用于周期性执行

xTaskDelayUntil

任务管理

任务延时至绝对时间点的函数变体

uxTaskPriorityGet

任务管理

获取指定任务的当前运行优先级

vTaskPrioritySet

任务管理

动态修改指定任务的运行优先级

vTaskSuspend

任务管理

挂起指定任务,使其停止运行

vTaskResume

任务管理

恢复被挂起的任务进入就绪态

xTaskResumeFromISR

任务管理

在中断服务函数中恢复被挂起的任务

xTaskAbortDelay

任务管理

强制中止任务的延时或阻塞状态

uxTaskPriorityGetFromISR

任务管理

在中断中安全获取任务的优先级

uxTaskBasePriorityGet

任务管理

获取任务的基础优先级(不受优先级继承影响)

uxTaskBasePriorityGetFromISR

任务管理

在中断中获取任务的基础优先级

uxTaskGetSystemState

任务管理

获取系统中所有任务的状态快照

vTaskGetInfo

任务管理

获取单个任务的详细信息

xTaskGetApplicationTaskTag

任务管理

获取与任务关联的标签值

uxTaskGetStackHighWaterMark

任务管理

获取任务栈运行时的最小剩余空间

xTaskCallApplicationTaskHook

任务管理

调用任务关联的钩子回调函数

vTaskSetApplicationTag

任务管理

为任务分配一个应用标签值

vTaskSetThreadLocalStoragePointer

任务管理

设置任务的线程本地存储(TLS)指针

pvTaskGetThreadLocalStoragePointer

任务管理

获取任务的线程本地存储(TLS)指针

vTaskSetTimeOutState

任务管理

初始化一个超时状态结构体

xTaskCheckForTimeOut

任务管理

检查任务阻塞是否已达到超时时间

eTaskConfirmSleepModeStatus

任务管理

确认系统是否可以进入低功耗睡眠模式

taskYIELD

任务管理

强制执行任务切换,让出CPU

taskENTER_CRITICAL

任务管理

任务级进入临界区(锁定调度器)

taskEXIT_CRITICAL

任务管理

任务级退出临界区(解锁调度器)

taskENTER_CRITICAL_FROM_ISR

任务管理

中断级进入临界区安全接口

taskEXIT_CRITICAL_FROM_ISR

任务管理

中断级退出临界区安全接口

vTaskStartScheduler

任务管理

启动任务调度器,系统开始运行

vTaskEndScheduler

任务管理

停止任务调度器运行

vTaskSuspendAll

任务管理

挂起调度器,禁止所有任务切换

xTaskResumeAll

任务管理

恢复调度器运行

vTaskStepTick

任务管理

在低功耗模式唤醒后补偿时钟节拍

xTaskCatchUpTicks

任务管理

修正缺失的系统时钟节拍数

xTaskNotifyGive

任务通知

向指定任务发送一个简单的通知(增量)

xTaskNotifyGiveIndexed

任务通知

向指定索引的任务通知组发送通知

vTaskNotifyGiveFromISR

任务通知

在中断中向任务发送通知(增量)

vTaskNotifyGiveIndexedFromISR

任务通知

在中断中向指定索引发送通知

ulTaskNotifyTake

任务通知

等待任务通知(类似轻量级信号量)

ulTaskNotifyTakeIndexed

任务通知

等待指定索引的任务通知

xTaskNotify

任务通知

向任务发送带有特定值和动作的通知

xTaskNotifyIndexed

任务通知

向任务指定索引发送带值的通知

xTaskNotifyAndQuery

任务通知

发送通知并获取任务之前的通知状态

xTaskNotifyAndQueryIndexed

任务通知

对指定索引发送通知并查询状态

xTaskNotifyAndQueryFromISR

任务通知

中断中发送通知并查询状态的安全接口

xTaskNotifyAndQueryIndexedFromISR

任务通知

中断中对指定索引发送通知并查询

xTaskNotifyFromISR

任务通知

在中断中向任务发送带值的通知

xTaskNotifyIndexedFromISR

任务通知

中断中对指定索引发送通知的安全接口

xTaskNotifyWait

任务通知

阻塞等待任务通知到来

xTaskNotifyWaitIndexed

任务通知

阻塞等待指定索引的任务通知

xTaskNotifyStateClear

任务通知

清除任务的通知挂起状态

xTaskNotifyStateClearIndexed

任务通知

清除指定索引的任务通知状态

ulTasknotifyValueClear

任务通知

清除任务通知的具体数值

ulTaskNotifyValueClearIndexed

任务通知

清除指定索引的任务通知数值

uxQueueMessagesWaiting

队列管理

获取队列中当前存储的消息数量

uxQueueMessagesWaitingFromISR

队列管理

在中断中获取队列消息数量

uxQueueSpacesAvailable

队列管理

获取队列中剩余的可用空闲槽位

vQueueDelete

队列管理

删除队列并释放相关内存

xQueueCreate

队列管理

动态创建一个新的队列

xQueueCreateStatic

队列管理

静态初始化一个队列

xQueueGetStaticBuffers

队列管理

获取静态队列使用的缓冲区指针

xQueueIsQueueEmptyFromISR

队列管理

在中断中判断队列是否为空

xQueueIsQueueFullFromISR

队列管理

在中断中判断队列是否已满

xQueueOverwrite

队列管理

向队列写入数据(若满则覆盖旧数据)

xQueueOverwriteFromISR

队列管理

中断中执行覆盖写入的安全接口

xQueueReceive

队列管理

从队列中读取并移除一个消息

xQueueReceiveFromISR

队列管理

中断中接收消息的安全接口

xQueueReset

队列管理

重置队列至初始空状态

xQueueSend

队列管理

向队列尾部发送一个消息

xQueueSendFromISR

队列管理

中断中发送消息的安全接口

xQueueSendToBack

队列管理

向队列尾部发送消息(同Send

xQueueSendToBackFromISR

队列管理

中断中向队尾发送消息的安全接口

xQueueSendToFront

队列管理

向队列头部发送消息(插队)

xQueueSendToFrontFromISR

队列管理

中断中向队头发送消息的安全接口

xQueueCreateSet

队列集

创建一个队列集合用于监控多个对象

xQueueAddToSet

队列集

将队列或信号量添加到队列集中

xQueueRemoveFromSet

队列集

从队列集中移除指定成员

xQueueSelectFromSet

队列集

阻塞等待队列集中有对象变为可用

xQueueSelectFromSetFromISR

队列集

中断中选择可用队列成员的安全接口

xStreamBufferCreate

流缓冲区

动态创建用于字节流传输的缓冲区

xStreamBufferCreateStatic

流缓冲区

静态初始化一个流缓冲区

xStreamBufferSend

流缓冲区

向流缓冲区写入字节数据

xStreamBufferSendFromISR

流缓冲区

中断中写入字节流的安全接口

xStreamBufferReceive

流缓冲区

从流缓冲区读取字节数据

xStreamBufferReceiveFromISR

流缓冲区

中断中读取字节流的安全接口

vStreamBufferDelete

流缓冲区

删除流缓冲区并释放内存

xStreamBufferBytesAvailable

流缓冲区

获取当前可读取的字节数

xStreamBufferSpacesAvailable

流缓冲区

获取当前可写入的空闲字节数

xStreamBufferSetTriggerLevel

流缓冲区

设置解除阻塞所需的最小字节触发量

xStreamBufferReset

流缓冲区

清空并重置流缓冲区

xStreamBufferResetFromISR

流缓冲区

中断中重置流缓冲区的安全接口

xStreamBufferIsEmpty

流缓冲区

判断流缓冲区是否为空

xStreamBufferIsFull

流缓冲区

判断流缓冲区是否已满

xStreamBufferGetStaticBuffers

流缓冲区

获取静态流缓冲区的内部指针

uxStreamBufferGetStreamBufferNotificationIndex

流缓冲区

获取关联的任务通知索引号

vStreamBufferSetStreamBufferNotificationIndex

流缓冲区

设置关联的任务通知索引号

xStreamBatchingBufferCreate

流缓冲区

创建支持批处理模式的流缓冲区

xStreamBufferSendCompletedFromISR

流缓冲区

中断中标记发送完成的通知函数

xStreamBufferReceiveCompletedFromISR

流缓冲区

中断中标记接收完成的通知函数

xStreamBatchingBufferCreateStatic

流缓冲区

静态创建批处理模式的流缓冲区

xMessageBufferCreate

消息缓冲区

动态创建用于变长消息传输的缓冲区

xMessageBufferCreateStatic

消息缓冲区

静态初始化一个消息缓冲区

xMessageBufferSend

消息缓冲区

向缓冲区发送一条变长消息

xMessageBufferSendFromISR

消息缓冲区

中断中发送变长消息的安全接口

xMessageBufferReceive

消息缓冲区

从缓冲区接收一条变长消息

xMessageBufferReceiveFromISR

消息缓冲区

中断中接收变长消息的安全接口

vMessageBufferDelete

消息缓冲区

删除消息缓冲区并释放内存

xMessageBufferSpacesAvailable

消息缓冲区

获取缓冲区剩余可用消息空间

xMessageBufferReset

消息缓冲区

清空并重置消息缓冲区

xMessageBufferResetFromISR

消息缓冲区

中断中重置消息缓冲区的安全接口

xMessageBufferIsEmpty

消息缓冲区

判断消息缓冲区是否为空

xMessageBufferIsFull

消息缓冲区

判断消息缓冲区是否已满

xMessageBufferSendCompletedFromISR

消息缓冲区

中断中标记消息发送完成

xMessageBufferReceiveCompletedFromISR

消息缓冲区

中断中标记消息接收完成

xMessageBufferGetStaticBuffers

消息缓冲区

获取静态消息缓冲区的内部指针

xMessageBufferSpaceAvailable

消息缓冲区

获取消息缓冲区可用的字节空间

xSemaphoreCreateBinary

信号量

动态创建一个二值信号量

xSemaphoreCreateBinaryStatic

信号量

静态创建一个二值信号量

vSemaphoreCreateBinary

信号量

旧版本创建二值信号量的宏

xSemaphoreCreateCounting

信号量

动态创建一个计数信号量

xSemaphoreCreateCountingStatic

信号量

静态创建一个计数信号量

xSemaphoreCreateMutex

信号量

动态创建一个互斥锁(支持优先级继承)

xSemaphoreCreateMutexStatic

信号量

静态创建一个互斥锁

xSemaphoreCreateRecursiveMutex

信号量

动态创建一个递归互斥锁

xSemaphoreCreateRecursiveMutexStatic

信号量

静态创建一个递归互斥锁

xSemaphoreGetMutexHolder

信号量

获取当前持有互斥锁的任务句柄

uxSemaphoreGetCount

信号量

获取信号量的当前计数值

xSemaphoreTake

信号量

获取信号量/锁(支持阻塞等待)

xSemaphoreTakeFromISR

信号量

中断中获取信号量的安全接口

xSemaphoreTakeRecursive

信号量

获取递归互斥锁

xSemaphoreGive

信号量

释放信号量/

xSemaphoreGiveFromISR

信号量

中断中释放信号量的安全接口

xSemaphoreGiveRecursive

信号量

释放递归互斥锁

vSemaphoreDelete

信号量

删除信号量/锁并释放内存

xTimerCreate

软件定时器

动态创建一个软件定时器

xTimerCreateStatic

软件定时器

静态初始化一个软件定时器

xTimerIsTimerActive

软件定时器

判断定时器当前是否正在运行

xTimerStart

软件定时器

启动或重新启动定时器计数

xTimerStartFromISR

软件定时器

中断中启动定时器的安全接口

xTimerStop

软件定时器

停止定时器计数

xTimerStopFromISR

软件定时器

中断中停止定时器的安全接口

xTimerChangePeriod

软件定时器

动态修改定时器的超时周期

xTimerChangePeriodFromISR

软件定时器

中断中修改周期的安全接口

xTimerDelete

软件定时器

删除定时器并释放内存

xTimerReset

软件定时器

重置定时器的计数值

xTimerResetFromISR

软件定时器

中断中重置定时器的安全接口

pvTimerGetTimerID

软件定时器

获取分配给定时器的用户ID指针

vTimerSetTimerID

软件定时器

设置分配给定时器的用户ID指针

vTimerSetReloadMode

软件定时器

设置定时器模式(单次或自动重载)

xTimerGetTimerID

软件定时器

获取定时器的ID

xTimerGetReloadMode

软件定时器

获取定时器的重载模式设置

xTimerPendFunctionCall

软件定时器

将函数推迟到定时器任务中执行

xTimerPendFunctionCallFromISR

软件定时器

中断中推迟函数执行的安全接口

pcTimerGetName

软件定时器

获取定时器的名称字符串

xTimerGetPeriod

软件定时器

获取定时器的周期时间

xTimerGetExpiryTime

软件定时器

获取定时器下一次到期的时间点

xEventGroupCreate

事件组

动态创建一个事件标志组

xEventGroupCreateStatic

事件组

静态创建一个事件标志组

vEventGroupDelete

事件组

删除事件组并释放内存

xEventGroupWaitBits

事件组

阻塞等待事件组中特定位的组合状态

xEventGroupSetBits

事件组

设置(置位)事件组中的特定位

xEventGroupSetBitsFromISR

事件组

中断中设置事件位的安全接口

xEventGroupClearBits

事件组

清除事件组中的特定位

xEventGroupClearBitsFromISR

事件组

中断中清除事件位的安全接口

xEventGroupGetBits

事件组

获取当前事件组的所有位标志状态

xEventGroupGetBitsFromISR

事件组

中断中获取事件位状态的安全接口

xEventGroupSync

事件组

事件组同步(用于多个任务同步点)

xEventGroupGetStaticBuffer

事件组

获取静态事件组的内部指针

pvPortMalloc

内存管理

内核级动态内存分配函数

vPortFree

内存管理

释放由内核分配的内存块

FreeRTOS接口适配差异

考虑接口的易用性和LiteOS内部机制与FreeRTOS标准接口的差异,在适配FreeRTOS接口时,对部分接口进行了修改,详见下表。

接口名

类型

描述

portSET_INTERRUPT_MASK_FROM_ISR

中断管理

暂不支持按优先级屏蔽中断,该宏的效果等同于portDISABLE_INTERRUPTS。

xQueueAddToSet

队列集

当前接口新增约束:当队列不为空/互斥量被其他线程占用/信号量被其他线程持有时,无法将句柄添加到队列集。

xQueueRemoveFromSet

中断管理

当前接口约束补充说明:当队列不为空/互斥量被其他线程占用/信号量被其他线程持有时,无法将句柄从队列集移出。

xQueueSelectFromSetFromISR

中断管理

xTicksToWait参数为0

xTimerPendFunctionCall

定时器

当前接口改为同步立即执行,依赖异步与任务上下文的应用逻辑。

vTimerSetReloadMode

定时器

LiteOS不支持定时器模式的更改,因此该接口不能实际修改定时器模式。

xSemaphoreCreateCounting

信号量

FreeRTOS 中“队列长度”在这里用于设置信号量的初始计数(count),而不是最大计数。

xTaskGetTickCount

任务管理

自系统启动开始计数,原生是任务启动调度开始计数。

xTaskCreate

任务管理

原生FreeRtos不会自动释放任务资源,当前实现会自动释放任务资源。

xTaskCreateStatic

任务管理

原生FreeRtos不会自动释放任务资源,当前实现会自动释放任务资源。

通知值相关函数

任务管理

原生实现是将任务阻塞,当前适配机制是将任务挂起。

驱动开发指导

本章以Hi3556V200为例,使能开发者操作控制与ETH、USB2.0 DRD和SD/MMC/eMMC卡等驱动模块相连的LiteOS外围驱动设备。

说明: Hi3556V200/Hi3559V200无ETH模块。

基于驱动框架的开发指导

概述

基本概念

图1所示,驱动开发就是按一定的规格实现硬件功能并进行抽象,提供给开发者一套统一的设备访问接口,方便开发者进行上层业务开发。

  • 驱动设备

    系统将所有外部设备看成是一类特殊文件,称为“设备文件”。外部设备可分为三类:

    • 字符设备:设备以字符形式发送和接收数据,不支持随机访问。

    • 块设备:利用一块系统内存作为缓冲区,以整个数据缓冲区的形式传输数据。块设备主要针对Flash等慢速设备,能避免CPU耗费过多的时间等待操作完成。

    • 网络设备:所有对网络硬件的访问都通过接口进行。接口提供一个对所有类型的网络硬件一体化的操作集合,来发送和接收基本数据。

  • 驱动程序

    内核与外部设备之间的接口。驱动程序向应用程序屏蔽了硬件在实现上的细节。在应用程序看来,外部设备只是一个设备文件,使应用程序可以像操作普通文件一样来操作外部设备,如open ()、close ()、read ()、write () 等。

  • 驱动框架管理

    对驱动设备和驱动程序进行系统管理的模块。

图 1 基本设备驱动框架

运作机制

驱动框架涉及到2个比较重要的数据结构LosDevice(用于描述驱动设备)和LosDriver(用于描述驱动程序)。系统中有两个全局双向链表。一个是全局设备链表,管理挂载的所有驱动设备;另一个是全局驱动链表,管理所有挂载的驱动程序。

这两种数据结构介绍如下:

  • LosDevice

    LosDevice代表一个驱动设备。结构体中重要的成员包括:

    • const CHAR *name:设备名,用以跟驱动配对。

    • struct LosDriver *driver:表示该驱动设备的驱动程序。

    • struct LosDeviceConfig cfg:设备io起始地址、io大小及中断号配置。

    • LOS_DL_LIST driverNode:对应的驱动节点,用以挂载到该驱动结构体中的deviceList上。

    • LOS_DL_LIST deviceItem:该驱动设备节点,用以挂载到全局设备链表中。

  • LosDriver

    LosDriver代表一个设备驱动程序。结构体中重要的成员包括:

    • const CHAR *name:驱动名,用以跟设备配对。

    • LOS_DL_LIST deviceList:使用当前这个驱动程序的驱动设备的双向链表。

    • LOS_DL_LIST driverItem:该驱动程序节点,用以挂载到全局驱动链表里。

    • struct LosDriverOps ops:驱动实现的操作接口,如probe、remove等。

LosDevice和LosDriver之间的关系如图2所示。

图 2 LosDevice和LosDriver的关系图

LosDriver挂在全局驱动链表下,包含了一个deviceList链表,表示这个驱动程序操作(或控制)的所有设备。

LosDevice挂在全局设备链表下,包含了一个driver指针,表示这个设备对应的设备驱动程序。

一个驱动设备只对应一个驱动程序,而一个驱动程序可以支持多个驱动设备。当对应的设备/驱动挂载的时候,会根据名字去全局驱动链表/全局设备链表中寻找已经注册的对应的驱动/设备,执行注册过程。

开发指导

功能

LiteOS的驱动框架为用户提供下面几种功能,接口详细信息可以查看API参考。

功能分类

接口名

描述

操作驱动设备

LOS_DeviceRegister

注册一个设备,添加到全局设备链表

LOS_DeviceUnregister

注销一个设备,从全局设备链表中移除

LOS_DeviceDataGet

获取指定设备的私有数据

LOS_DeviceRegBaseGet

获取指定设备的io起始地址

LOS_DeviceRegSizeGet

获取指定设备的io大小

LOS_DeviceIrqNumGet

获取指定设备的中断号

注册/注销驱动程序

LOS_DriverRegister

注册设备驱动程序,并和相应的设备绑定

LOS_DriverUnregister

注销设备驱动程序

低功耗功能

LOS_PmSuspend

挂起所有设备

LOS_PmResume

恢复所有设备

开发流程

下面以注册USB驱动为例,讲解基于驱动框架的驱动开发流程。

  1. 打开菜单,选择Driver ---> Enable Driver Base,使能驱动框架。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_DRIVERS_BASE

    使能驱动框架的开关

    YES/NO

    YES

  2. 注册驱动设备。

    1. 定义USB驱动设备实例。

      struct LosDeviceRegs hiudc3_regs[] = {
          {
              .base = DWC_USB3_PORT1_BASE_ADDR,
              .size = DWC_USB3_PORT1_ADDR_OFFSET,
          },
      };
      static struct LosDevice hiudc3_device = {
          .name       = "hi_udc3",
          .id = -1,
          .cfg   = {
      
              .irqNum = NUM_HAL_INTERRUPT_USB_DEV,
      
              .numRegs = ARRAY_SIZE(hiudc3_regs),
              .regs = hiudc3_regs,
          },
      };
      
    2. 将驱动设备注册到全局设备链表。

      LOS_DeviceRegister(&hiudc3_device);
      
    3. 预加载处理。

      通过调用“linux/module.h”(在根目录“self_src/compat/linux/include”目录下)里面的预加载挂载函数,将驱动设备注册函数挂载到系统中。当系统启动时,系统会按照挂载函数优先级遍历预加载节点,从而注册驱动设备。系统提供如下预加载挂载函数,优先级从高到低:

      pure_initcall(f)       //0
      core_initcall(f)       //1
      postcore_initcall(f)   //2
      arch_initcall(f)       //3
      subsys_initcall(f)     //4
      fs_initcall(f)         //5
      device_initcall(f)     //6
      late_initcall(f)       //7
      

      具体调用请参见“编程实例”。

      须知: 执行预加载可能会影响到系统的某些功能,例如,当驱动程序初始化耗时较长时,会对快速启动(分散加载)的启动时间造成冲击。目前系统只支持优先级小于等于3的预加载函数。

  3. 注册设备驱动程序。

    1. 定义USB驱动程序实例。

      static struct LosDriver hiudc3_driver = {
      	.name = "hi_udc3",
      	.ops = {
      		.probe = hiudc3_probe,
      		.remove = hiudc3_remove,
      	},
      	.pmOps = {
      		.suspend = hiudc3_suspend,
      		.resume = hiudc3_resume,
      	},
      };
      

      参数描述如表所示。

      参数

      描述

      probe

      该函数参数一般用于初始化驱动程序

      remove

      该函数参数一般用于去初始化驱动程序

      name

      驱动程序的属性name必须和对应驱动设备的name一致

      suspend/resume

      用于挂起/恢复驱动设备

    2. 注册USB驱动程序。

      LOS_DriverRegister(&hiudc3_driver);
      
  4. 将加载的驱动注册到文件系统。

    为方便用户使用,将驱动实例注册到文件系统,使对驱动的操作抽象为对文件的操作。具体实现请参见“适配文件操作的驱动开发”。

注意事项

调用预加载处理的驱动函数需要在“build/make/liteos_tables_ldflags.mk”中的LITEOS_TABLES_DRIVER_LDFLAGS添加“-u”声明,否则可能会导致链接器优化掉驱动初始化模块,造成不可预估的错误。

LITEOS_TABLES_DRIVER_LDFLAGS := \
-ui2c_init \
-ugpio_init \
-uregulator_init \
-umtd_init_list \

编程实例

实例描述

添加USB驱动。

编程实例

前提条件:在menuconfig菜单中使能驱动框架。

  1. 在指定开发板的“board.c”目录下定义驱动设备。

    /* 定义驱动设备实例 */
    #ifdef LOSCFG_DRIVERS_USB3_DEVICE_CONTROLLER
    struct LosDeviceRegs hiudc3_regs[] = {
        {
            .base = DWC_USB3_PORT1_BASE_ADDR,
            .size = DWC_USB3_PORT1_ADDR_OFFSET,
        },
    };
    
    static struct LosDevice hiudc3_device = {
        .name       = "hi_udc3",
        .id = -1,
        .cfg   = {
            .irqNum = NUM_HAL_INTERRUPT_USB_DEV,
            .numRegs = ARRAY_SIZE(hiudc3_regs),
            .regs = hiudc3_regs,
        },
    };
    #endif
    
    int machine_init(void)
    {
    #ifdef LOSCFG_DRIVERS_USB3_DEVICE_CONTROLLER
        (void)LOS_DeviceRegister(&hiudc3_device);
    #endif
    }
    arch_initcall(machine_init);    //预加载驱动设备注册。内核初始化执行时,会预加载驱动设备注册
    
  2. 在“self_src/drivers/usb/adapt_liteos/controller/usb_device”目录下定义USB设备驱动程序。

    /* 驱动程序实现的操作接口 */
    static int hiudc3_probe(struct LosDevice *pdev)
    {
        ……
    }
    
    static int hiudc3_remove(struct LosDevice *pdev)
    {
        ……
    }
    
    static int hiudc3_suspend(struct LosDevice *pst_dev)
    {
        ……
    }
    
    static int hiudc3_resume(struct LosDevice *pst_dev)
    {
        ……
    }
    
    /* 定义设备驱动程序实例 */
    static struct LosDriver hiudc3_driver = {
        .name = "hi_udc3",
        .ops = {
            .probe = hiudc3_probe,
            .remove = hiudc3_remove,
        },
    	.pmOps = {
            .suspend = hiudc3_suspend,
            .resume = hiudc3_resume,
        },
    };
    
    /* 初始化驱动程序,即注册驱动程序 */
    int hiudc3_init(void)
    {
        return (int)LOS_DriverRegister(&hiudc3_driver);
    }
    
    /* 注销驱动程序 */
    int hiudc3_exit(void)
    {
        return (int)LOS_DriverUnregister(&hiudc3_driver);
    }
    

适配文件操作的驱动开发

概述

基本概念

驱动开发就是把硬件的功能按操作系统的规格实现并抽象出来,提供给应用程序开发人员调用。

当在新的芯片上移植系统时,需要根据该芯片特性对支持的外部设备进行驱动开发。

LiteOS的驱动初始化函数主要初始化设备对应的驱动结构,提供给上层应用来注册设备驱动的控制节点。

开发指导

开发流程

推荐驱动开发人员使用VFS框架来注册/卸载设备驱动,基于文件系统的驱动开发主要涉及下面几步:

  1. 打开菜单,选择FileSystem ---> Enable VFS,使能VFS。

    配置项

    含义

    取值范围

    默认值

    依赖

    LOSCFG_FS_VFS

    VFS框架的裁剪开关

    YES/NO

    YES

  2. 初始化驱动

  3. 操作驱动节点

初始化驱动

在LiteOS上进行驱动开发的第一步,是编写驱动初始化函数。驱动初始化函数用于初始化设备所需的驱动结构,以及生成驱动的控制节点。

驱动初始化函数中需要调用设备驱动注册函数,用于注册并生成驱动节点。

register_driver(FAR const char *path, FAR const struct file_operations_vfs *fops, mode_t mode, FAR void *priv)
register_blockdriver(FAR const char *path, FAR const struct block_operations *bops, mode_t mode, FAR void *priv)

参数描述如下表所示:

参数

描述

path

驱动节点路径,应用程序可通过该路径访问到驱动节点,进而访问设备驱动提供的操作接口。

fops/bops

驱动操作结构体,为应用程序提供操作函数集。fops和bops分别表示字符设备和块设备的驱动操作集。

mode

读写该驱动节点的权限,暂时无效,后续或提供支持。

priv

驱动节点注册过程需要传入的参数。

编写完驱动初始化函数后,需要在适当的地方引导该初始化函数执行。用户可以在app_init函数里调用已编写好的驱动初始化函数引导设备初始化。

操作驱动节点

驱动初始化后,生成的设备驱动节点为应用提供操作设备的接口,下面以I2C设备驱动程序为例,说明用户程序与驱动操作函数的调用关系。

驱动操作函数集对于应用程序与驱动操作函数的调用关系非常重要。在编写驱动程序时,操作函数集需要实现硬件设备的各项机制,并在设备驱动注册时传入。操作函数集会成为应用程序需求的最终实现。

I2C设备驱动提供的操作函数集如下表所示:

操作函数集

对应的应用层接口

i2cdev_open

open

i2cdev_release

close

i2cdev_read

read

i2cdev_write

write

i2cdev_ioctl

ioctl

  • open操作

    应用程序打开节点文件时,系统最终会在该驱动节点注册过程中调用驱动操作函数集中的open函数。

    open函数对目标驱动设备函数结构体先进行实例化,再进行必要的初始化。在成功打开节点文件后,应用程序能够获取到对应驱动节点的文件描述符,并通过该文件描述符对驱动程序进行访问。

  • read/write操作

    read/write操作是常用的访问设备的接口。不同类型的设备与驱动提供的read/write操作的功能互有差异。对于i2c设备,应用程序通过调用read/write接口可以实现对I2C设备进行读写。

    i2cdev_read(struct file * filep, char __user *buf, size_t count)
    i2cdev_write(struct file * filep, const char __user *buf, size_t count)
    

    参数描述如下表所示:

    参数

    描述

    filep

    文件描述结构体指针

    buf

    读出/写入数据的缓冲区指针

    count

    读出/写入数据的长度

  • ioctl操作

    ioctl操作提供对驱动设备函数的配置管理。通过执行相应的命令,完成对设备属性的配置或访问。

    I2C设备中,使用命令I2C_16BIT_REG、I2C_16BIT_DATA与I2C_TIMEOUT分别对应设置I2C设备的传输寄存器位宽、传输数据位宽与超时时间。

    i2cdev_ioctl(struct file *filep, int cmd, unsigned long arg)
    

    参数描述如下表所示:

    参数

    描述

    filep

    文件描述结构体指针

    cmd

    操作命令

    arg

    附加参数

  • close操作

    close操作对应着驱动操作函数集里的release函数。release函数对驱动程序的资源进行释放。

注意事项

无。

MMC驱动开发

概述

MMC(Multi Media Card)是 MMC协会订立、主要针对手机或平板电脑等产品的内嵌式存储器标准规格。MMC驱动用于处理对SD存储卡(Secure Digital Memory Card)和EMMC卡(Embedded Multi Media Card)的识别和读写等操作,并通过SDIO协议支持扩展外设(如蓝牙、WiFi、GPS等)。

LiteOS MMC驱动支持的设备包括TF卡、EMMC存储器和WiFi扩展外设。

  • TF(T-Flash)卡,又叫micro SD卡

    TF卡主要通过挂载为FAT文件系统实现对设备的读写操作,挂载之前首先需要完成设备的识别,也可以通过裸读写接口对TF卡进行裸读写。

    须知: 目前LiteOS不支持用户自定义TF卡分区。如有需要,用户需提前在PC端对TF卡做好分区操作。

  • EMMC存储器

    EMMC存储器主要通过挂载为FAT文件系统实现对设备的读写操作,挂载之前需要先调用接口自定义分区,然后进行设备的识别,也可以通过裸读写接口对EMMC存储器进行裸读写。

    支持LiteOS调度模块启动前完成MMC驱动初始化,支持系统启动前(系统资源初始化后、第一次任务调度前)和异常阶段调用MMC异常读写接口对MMC存储进行读写和格式化,需打开宏LOSCFG_DRIVERS_MMC_RW_IN_BOOT_AND_EXC。

    支持在LiteOS系统全局调度器启动前完成MMC低频切高频的tuning流程,支持EMMC器件在启动前运行在200MHZ频率。

    支持RPMB(Replay Protected Memory Block)分区,通过RPMB操作接口对RPMB分区进行KEY的写入、Write Counter读取、RPMB数据区数据的读写(仅数据透传不涉及加解密),需打开宏LOSCFG_DRIVERS_MMC_RPMB。

    提供发送CMD56命令的对外接口,获取EMMC存储器相关信息。

  • SDIO扩展外设

    目前支持的外围设备只有WiFi模组。设备通过WiFi模组进行通信和数据传输,设备识别前需要根据硬件要求做好管脚复用配置。

LiteOS MMC驱动模块包括协议层和控制层:

  • 协议层支持SD、EMMC、SDIO协议规范。

  • 控制层支持Himci和Sdhci控制器,不同的芯片平台根据硬件IP选择对应的控制器驱动。

开发指导

MMC设备识别

LiteOS中的MMC设备分为嵌入式和非嵌入式两类:

  • 嵌入式设备包括EMMC存储器和SDIO扩展外设,均不支持带电插拔。EMMC设备目前仅支持首次识别,不支持多次识别;SDIO扩展外设可以调用hisi_sdio_rescan接口实现对设备的卸载和加载识别(依赖于扩展外设的软件上下电)。

  • 非嵌入式设备为TF卡。TF卡支持热插拔,通过线程轮询方式实现设备的动态卸载和加载。

须知: 除SDIO扩展外设可以调用sdio_rescan接口实现设备识别外,其他MMC设备的识别均在同一线程内实现,当存在多个MMC设备时,将根据控制器ID的顺序串行识别设备。

驱动初始化接口

接口名

描述

SD_MMC_Host_init

遍历平台所有控制器并进行初始化

MMC_HostInitById

初始化平台指定控制器,控制器由入参ID确定(可减少MMC驱动初始化的整体耗时)

须知: MMC驱动初始化成功后,如果系统支持proc文件系统,会生成“proc/mci/mci_info”节点,通过“cat proc/mci/mci_info”可查看设备信息。此两个初始化接口不可混用,且MMC_HostInitById调用需保证单线程调用或者时间上互斥。

添加EMMC分区接口

接口名

接口参数

参数描述

add_mmc_partition

struct disk_divide_info *info

入参,是一个设备信息结构体,这里表示EMMC的分区信息,当前只支持传入全局变量struct disk_divide_info emmc

size_t sectorStart

入参,表示分区起始扇区

size_t sectorCount

入参,表示分区扇区数

设备信息结构体定义如下所示:

#define MAX_DIVIDE_PART_PER_DISK 16         // 每个设备可支持的最大逻辑分区个数
#define MAX_PRIMARY_PART_PER_DISK 4         // 每个设备可支持的最大主分区个数

struct disk_divide_info{
    UINT64   sector_count;                  // 分区块个数
    UINT32   part_count;                    // 分区号
    UINT32   sector_size;                   // 分区大小
    struct partition_info part[MAX_DIVIDE_PART_PER_DISK + MAX_PRIMARY_PART_PER_DISK];  // 分区信息结构体
};

裸读写接口

  • TF卡裸读写接口说明

    接口名

    接口参数

    参数描述

    mmc_direct_write

    mmc_direct_read

    uint32_t host_idx

    入参,表示TF卡所在的控制器编号

    char * buffer

    入参,表示读写数据保存的buffer地址(以CACHE_ALIGNED_SIZE对齐)

    uint32_t start_sector

    入参,表示读写的起始块地址

    uint32_t nsectors

    入参,表示读写的块数量

  • EMMC裸读写接口说明

    接口名

    接口参数

    参数描述

    emmc_raw_write

    emmc_raw_read

    char * buffer

    入参,表示读写数据保存的buffer地址(以CACHE_ALIGNED_SIZE对齐)

    uint32_t start_sector

    入参,表示读写的起始块

    uint32_t nsectors

    入参,表示读写的块数量

  • MMC系统启动前和异常中读写/初始化接口说明

    接口名

    接口参数

    参数描述

    mmc_write_in_exception

    mmc_read_in_exception

    uint32_t host_idx

    入参,表示mmc设备所在的控制器编号(支持TF卡与EMMC)

    char * buffer

    入参,表示读写数据保存的buffer地址(以CACHE_ALIGNED_SIZE对齐)

    uint32_t start_sector

    入参,表示读写的起始块地址

    uint32_t nsectors

    入参,表示读写的块数量

    mmc_earse_in_exception

    uint32_t host_idx

    入参,表示mmc设备所在的控制器编号

    uint32_t start_sector

    入参,表示要格式化的起始块地址

    uint32_t nsectors

    入参,表示要格式化的块数量

    须知: MMC异常(中断)上下文读写接口支持TF卡与EMMC但不支持SDIO设备,不推荐在非异常(中断)中调用,在调用前用户需保证系统不处于MMC逻辑执行流程中,否则可能会导致接口读写失败。

RPMB接口

RPMB数据帧结构体

typedef struct {
    uint8_t padding[RPMB_PADING_LEN];
    uint8_t mac_key[RPMB_MAC_KEY_LEN];
    uint8_t data[RPMB_DATA_LEN];
    uint8_t rand[RPMB_RAND_LEN];
    uint32_t write_cnt;
    uint16_t address;
    uint16_t block_cnt;
    uint16_t op_result;
    uint16_t req_resp;
} rpmb_data_t;

RPMB分区操作接口

接口名

接口参数

参数描述

mmc_rpmb_operate

uint32_t index

入参,表示MMC设备所在的控制器编号

uint32_t operType

入参,对RPMB分区的操作类型,支持四种操作类型设置key(RPMB_SET_KEY)、获取Write Counter(RPMB_READ_WRITE_COUNTER)、写数据(RPMB_WRITE_DATA)、读数据(RPMB_READ_DATA)

void *buf

入/出参,RPMB数据帧结构体

发送CMD56命令接口

接口名

接口参数

参数描述

mmc_general_cmd

uint32_t index

入参,表示MMC设备所在的控制器编号。

uint32_t arg

入参,与eMMC厂家有关,不同厂家参数不同。

unsigned char *buffer

出参,返回eMMC设备的健康信息、厂家信息。

SDIO接口

LiteOS提供了一套完整的SDIO对外接口,包括发送命令、收发数据等。SDIO扩展外设通过调用这套接口实现对扩展外设的操作。

接口名

描述

sdio_get_func

获得一个sdio_func,此sdio_func在SDIO设备识别后产生,通过func号、厂商ID、设备ID(此三个参数根据SDIO外设获得)唯一标识。

sdio_en_func

使能,该接口通过CMD52命令写0x2地址实现。

sdio_dis_func

禁止指定的sdio_func,该接口通过CMD52命令写0x2地址实现。

sdio_require_irq

给指定的sdio_func注册SDIO中断处理函数,该中断处理函数需要用户自行实现,函数类型定义为:void (sdio_irq_handler_t)(struct sdio_func *)。

sdio_release_irq

释放指定sdio_func中注册的SDIO中断处理函数。

sdio_read_byte

从指定sdio_func的指定地址addr读取一字节数据,并返回读取的数据。

sdio_read_byte_ext

从指定sdio_func的指定地址addr读取一字节数据,添加入参in到CMD参数中(暂不支持),并返回读取的数据。

sdio_read_incr_block

从指定sdio_func的指定地址addr读取指定长度的数据到内存指定地址dst中,读取时设备地址会依次增长(比如设备的内存地址)。

该接口通过CMD53命令实现。

sdio_read_fifo_block

从指定sdio_func的指定地址addr读取指定长度的数据到内存指定地址dst中,读取时设备地址固定不变(比如设备的FIFO)。

该接口通过CMD53命令实现。

sdio_write_incr_block

将指定地址src中size长度的数据写入指定sdio_func的指定地址addr中,写入时设备地址会依次增长(比如设备的内存地址)。

该接口通过CMD53命令实现。

sdio_write_fifo_block

将指定地址src中size长度的数据写入指定sdio_func的指定地址addr中,写入时设备地址固定不变(比如设备的FIFO)。

该接口通过CMD53命令实现。

sdio_write_byte

将一字节数据写入指定sdio_func的指定地址addr,该接口通过CMD52命令实现。

sdio_write_byte_raw

将一字节数据写入指定sdio_func的指定地址addr,写完后读回(设置RAW Flag),该接口通过CMD52命令实现。

sdio_func0_read_byte

从设备指定地址addr读取一字节数据,并返回读取的数据,该接口通过CMD52命令读取func0实现。

sdio_func0_write_byte

写入一字节数据到设备指定地址addr,该接口通过CMD52命令写入func0实现。

sdio_set_cur_blk_size

配置SDIO当前块大小,此块小大不应大于512Byte。

sdio_rescan

SDIO设备重新识别,实际执行操作为设备卸载和加载。

sdio_reset_comm

reset SDIO设备。

LiteOS基于控制器能力,提供数据的离散模式传输,并提供了一套专门的读写接口。SDIO扩展外设通过调用这套接口实现离散传输,即可以将多块buffer数据一次性发送或者一次性读取数据到多块buffer,以减少多块数据传输场景下的数据拷贝,提升数据传输性能。

接口名

描述

sdio_readv_incr_block

从指定sdio_func的指定地址addr读取数据,读取的数据长度和保存地址由入参struct mmc_sg *sg指定,sg中的数据个数由入参sg_nums决定,读取时设备地址会依次增长(比如设备的内存地址)。

该接口通过CMD53命令实现。

sdio_readv_fifo_block

从指定sdio_func的指定地址addr读取数据,读取的数据长度和保存地址由入参struct mmc_sg *sg指定,sg中的数据个数由入参sg_nums决定,读取时设备地址固定不变(比如设备的FIFO)。

该接口通过CMD53命令实现。

sdio_writev_incr_block

将入参struct mmc_sg *sg中的数据写入指定sdio_func的指定地址addr,sg中提供数据地址和长度,sg中的数据个数由入参sg_nums决定,写入时设备地址会依次增长(比如设备的内存地址)。

该接口通过CMD53命令实现。

sdio_writev_fifo_block

将入参struct mmc_sg *sg中的数据写入指定sdio_func的指定地址addr,sg中提供数据地址和长度,sg中的数据个数由入参sg_nums决定,写入时设备地址固定不变(比如设备的FIFO)。

该接口通过CMD53命令实现。

mmc_sg结构体如下所示:

struct mmc_sg {
    size_t length;  // 数据长度,不能大于buffer的内存空间大小
    void *data;     // buffer地址,必须cache line对齐,其指向的buffer内存空间大小也必须cache line对齐
};

EMMC存储器开发流程

  1. 添加EMMC分区

    size_t part0_start_sector = 16 * (0x100000/512);
    size_t part0_count_sector = 1024 * (0x100000/512);
    extern struct disk_divide_info emmc;
    add_mmc_partition(&emmc, part0_start_sector, part0_count_sector);  
    
  2. 初始化驱动

    MMC_HostInitById(emmc_host_id);
    

    说明: emmc_host_id为EMMC存储器对应的控制器编号。 emmc介质识别成功后会生成“/dev/mmcblkxPy”的节点,供文件系统挂载。(x是设备ID,一般是0或者1,y是分区ID,由设备分区个数决定,从0开始)。

  3. 设备识别后,读写设备

    1. 裸读写

      调用EMMC裸读写接口进行读写。

    2. 挂载为FAT文件系统进行读写

      mount("/dev/mmcblk0p0", "/sd", "vfat", 0, NULL);
      

      挂载后可通过文件系统标准接口对设备进行读写等操作。

      说明: 请根据实际情况传入“/dev/mmcblk0p0”为EMMC存储器被识别后注册的设备名。

TF卡开发流程

  1. 初始化驱动

    MMC_HostInitById(sd_host_id);
    

    说明: sd_host_id为TF卡对应的控制器编号。插入sd卡并识别成功后会生成“/dev/mmcblkxPy”的节点,供文件系统挂载。其中,x是设备ID,一般是0或者1,y是分区ID,由设备分区个数决定,从0开始。

  2. 设备识别后,读写设备

    1. 裸读写

      调用TF卡裸读写接口进行读写。

    2. 挂载为FAT文件系统读写

      mount("/dev/mmcblk0p0", "/sd", "vfat", 0, NULL);
      

      挂载后可通过文件系统标准接口对介质进行读写等操作。

      说明: “/dev/mmcblk0p0”为TF卡被识别后注册的设备名,请根据实际情况传入。

SDIO扩展外设开发流程

  1. 管脚复用

    配置IO寄存器,配置对应SDIO外围设备的CLK、CMD、DATA0、DATA1、DATA2、DATA3管脚寄存器。

  2. 初始化驱动

    MMC_HostInitById(sdio_host_id);
    

    说明: sdio_host_id为SDIO扩展外设对应的控制器编号。SDIO wifi设备识别成功后会生成“/dev/sdio0”的节点。

  3. 设备识别后获取func并使能func

    struct sdio_func* func;
    func = sdio_get_func(func_num,  manf_id, device_id);
    sdio_en_func(func);
    
  4. 设置当前block size

    sdio_set_cur_blk_size(func, blk_size);
    
  5. 注册中断处理函数

    void handler_sample(struct sdio_func *func)
    {
        dprintf(“%s,%d func:0x%x\n”,__FUNCTION__,__LINE__,func);
        return;
    }
    sdio_require_irq(func, handler_sample);
    

后续就可以通过SDIO对外读写接口进行读写等操作了。

注意事项

  • 正常操作过程中需要遵守的事项:

    • 保证卡的金属片与卡槽硬件接触充分良好(如果接触不好,会出现检测错误或读写数据错误),测试薄的MMC卡时,必要时可以用手按住卡槽的通讯端测试。

    • 读写过程中异常拔卡,驱动会直接卸载设备节点。在该场景下,用户必须释放正在操作的资源(比如打开的文件、目录等资源),并且对挂载点进行umount操作。否则,会造成资源泄漏或挂载点不能再次挂载。

    • SDIO外设业务调用sdio_require_irq注册中断后,在设备卸载前必须保证调用sdio_release_irq接口释放中断和相关资源,否则可能导致系统异常。

    • EMMC存储器进行驱动初始化前必须先进行添加分区,否则会导致设备识别时无法获取分区,设备识别后不支持动态添加和删除分区。

    • EMMC存储器挂载为FAT文件系统前需将dev设备格式化,否则可能导致挂载失败。

    • SDIO扩展外设进行驱动初始化前必须先进行管脚复用配置,否则会导致设备无法识别。

  • 被禁止的操作:

    • 读写SD卡时不要拔卡,否则会打印一些异常信息,并且可能会导致卡中文件或文件系统被破坏。

    • 格式化SD卡时禁止拔卡,否则可能会造成SD卡永久性损坏。

    • 上电状态下严禁拔掉SDIO扩展外设,否则可能会造成SDIO扩展外设或者硬件单板损坏。

  • 不同平台控制器对应的介质可能不同,关于控制器编号与所支持的介质类型,请参考各芯片平台用户指南文档中关于EMMC/SD/SDIO控制器的章节。

USB驱动开发

概述

USB(Universal Serial Bus,通用串行总线)是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。LiteOS USB驱动主要包含协议层、Composite层和控制器驱动三部分。

  • 协议层

    协议层是设备具体实现层,支持上层业务通过USB口进行数据收发。

  • Composite层

    Composite(复合设备)层连接协议层与控制器驱动,支持具体协议的加载、卸载及设备枚举过程。Composite作为虚拟设备,其他USB设备都需要挂载在Composite设备下面。由于Composite层封装了与控制端口交互的处理流程,USB设备只需要向Composite层注册相应的处理函数,具体协议的加载和设备枚举过程由Composite层执行。

  • 控制器驱动

    控制器驱动与硬件交互,主要支持数据收发和端口控制。

图 1 USB驱动基本框架

运作机制

USB协议驱动加载和数据收发主要流程如下:

  1. USB驱动加载

    USB设备驱动的加载通过Composite层封装统一的处理流程,由Composite调用具体协议的加载函数进行驱动程序的加载。待设备与主机通过USB线建立连接后,Composite层统一处理与主机之间的枚举过程,通过协议层注册的处理函数,进行端口申请和协议私有数据初始化。

  2. USB数据收发

    USB设备加载成功后,业务层通过协议层进行数据收发,协议层直接调用控制器驱动提供的端口控制接口进行端口数据收发处理。

开发指导

功能

功能

Hi35xx的USB功能支持Device模式,具体协议及功能参见下表。

功能

描述

使用说明

Device U盘

支持将挂载到系统的SD卡/MMC介质当做U盘,供USB Host端进行读写访问等操作。

  • 支持SD卡虚拟成U盘
  • 支持EMMC和RAM介质虚拟成U盘
  • 支持EMMC介质指定分区挂载识别
  • 支持ftl文件系统虚拟成U盘
  • 支持PC上右键盘符对U盘进行格式化、弹出等操作

UVC(USB Video Class)

支持通过USB口进行视频数据的发送。

  • 支持安卓机顶盒UVC扩展命令
  • 支持的视频规格:

    格式:H.265/H.264/MJPEG/YUYV/NV12/NV21

    分辨率@帧率:1920×1080@30fps、1280×720@30fps、640×480@30fps、640×360@30fps、352×288@30fps、320×240@30fps、176×144@30fps

UAC(USB Audio Class)

支持通过USB口进行音频数据的发送和接收。

  • 不支持USB3.0
  • 支持的PCM规格:48K、32K 、16K、8K
  • 支持的位深和声道数:16bit/1ch、24bit/1ch、24bit/2ch、24bit/4ch
  • 支持麦克风功能和扬声器功能
  • 支持UAC1.0和UAC2.0

UAV(UVC&UAC)

支持通过USB口进行音视频数据的发送。

  • 不支持USB3.0
  • 支持的规格参见UVC、UAC规格
  • 不支持UAC1.0扬声器功能和UAC2.0

DFU(Device Firmware Upgrade)

支持通过USB口对固件的升级,对接Host端DFU协议。

  • 不支持USB3.0
  • 支持PC升级
  • 支持安卓机顶盒升级
  • 支持升级状态查询

HID(Human Interface Device)

支持通过USB口对HID设备的数据进行接收和发送。

  • 不支持USB3.0
  • 不支持启动接口和启动协议
  • 支持USB鼠标、键盘和带有鼠标功能的键盘设备功能
  • 支持报告描述符动态可配置

虚拟网口

支持通过USB口进行网络数据的收发,模拟网口的功能。

  • 不支持二层套接字设置
  • 支持RNDIS协议网络传输
  • 支持ECM协议网络传输

虚拟串口

支持通过USB口进行串行通信,模拟串口的功能。

支持调测功能

复合设备 (虚拟网口&虚拟串口)

支持基于USB口同时模拟网口和串口的功能。

  • 支持调测功能
  • 不支持二层套接字设置

UAC&HID复合设备

支持通过USB口进行音频数据、HID数据的发送和接收。

  • 不支持USB3.0
  • 支持的规格参见UAC、HID规格
  • 支持UAC1.0麦克风、扬声器功能和HID键盘、鼠标功能
  • 不支持UAC2.0

说明: USB3.0 Device对接USB2.0 Host时,由于设备侧硬件限制不支持降速,无法识别。

LiteOS USB驱动由于受芯片硬件资源限制,支持的规格也不尽相同,详细请参见下表所示。

芯片平台

U 盘

UVC

UAC

UAV

DFU

HID

虚拟串口

虚拟网口RNDIS

虚拟网口

ECM

复合设备

SER_RNDIS和SER_ECM

UAC&HID

Hi3516DV300 (USB2.0)

  

说明: Hi3516DV300/Hi3556V200/Hi3559V200的USB2.0口,是基于USB3.0的控制器操作的,编译时需要选择3.0的控制器。

配置USB模块的内存

USB模块的正常运行需要一段uncached内存,所以需要单独为USB模块配置内存。

配置内存可通过定义以下函数实现:

void board_config(void)
{
    g_sys_mem_addr_end = 0x88000000;
    g_usb_mem_addr_start = g_sys_mem_addr_end;
    g_usb_mem_size = 0x100000;
}

须知:

  • 不可以修改void board_config(void)的声明,若应用层不定义void board_config(void)函数,LiteOS会按默认值分配OS可见内存大小。建议根据实际情况自行定义。

  • g_sys_mem_addr_end配置的是OS可见内存的结束位置。

  • g_usb_mem_addr_start配置的是USB内存的起始位置。

  • g_usb_mem_size配置的是USB内存的大小。

  • 若系统完全不支持USB相关功能,则可以将g_usb_mem_addr_start和g_usb_mem_size配置为0。

动态切换USB协议

动态切换时,先调用usb_deinit卸载当前使用的协议,再通过usb_init重新加载待切换的协议。

部分协议使用中不支持卸载(如:DFU、UVC、Camera、HID),需要先停用当前业务后再调用usb_deinit卸载。

模块编译

USB驱动的源码路径为drivers/usb。用户在LiteOS根目录下执行make即可编译出对应的USB模块库。编译成功后,会在“out/<platform>/lib”目录下生成名为“libusb_base.a”、“libusb_device.a”的库文件,目录中的platform为具体的平台名。

图1是LiteOS中USB驱动menuconfig配置项截图,在“Driver”配置项下,可以通过选中“Enable USB”开启 USB 驱动功能。

图 1 USB驱动menuconfig配置

Device端

配置项如为图1所示的1~4,对应描述如下:

  • 1表示是否打开USB驱动Device功能。

  • 2表示Device控制器种类,分别有Dwc2.0和3.0(可视具体板子的 USB 口类别而定)。

  • 3表示是否开启Device模式下的USB协议功能。

  • 4表示Device模式下的具体USB协议功能,支持的USB协议如下,menuconfig配置项如图2所示:

    • Device U盘

    • UVC

    • UAC

    • Camera(UVC&UAC)

    • DFU升级

    • 虚拟串口

    • 虚拟网口ECM

    • 虚拟网口RNDIS

    • 复合设备(虚拟串口&虚拟网口)

    • HID

    • UAC&HID复合设备

    图 2 USB驱动Device端配置

须知: 初始化Device模式也是调用usb_init(DEVICE, xxx) ,根据业务诉求开启需要的协议,也可以全部开启。另外,依赖的外围组件也需要提前加载好,具体参见“Device端”的编程实例。

编程实例

Device端

Device虚拟U盘

初始化

在初始化函数中初始化SD卡驱动和USB驱动:

SD_MMC_Host_init();
usbd_set_device_info(DEV_MASS, &str_manufacturer, &str_product, &str_serial_number, dev_id);
usb_init(DEVICE, DEV_MASS);   // dtype设置为DEV_MASS

说明:

  • 在多卡情况下(系统同时挂载2张卡及以上),USB2.0 Device默认绑定第一张卡设备节点,例如同时存在“/dev/mmcblk0p0”,“/dev/mmcblk1p0”时,USB2.0 Device会绑定“/dev/mmcblk0p0”,因此Host端通过USB口只能访问到一张SD/MMC卡。

  • USB2.0 Device连接Host端后,Host端可以通过USB端口访问到系统存储介质的文件。在Host端访问存储介质过程中,系统端请勿对MMC/SD Card存储介质进行读写、格式化等操作,避免Host端访问存储介质异常。

  • Device U盘对接PC进行格式化时,仅支持Windows PC进行快速格式化操作,请勿使用Windows PC的一般格式化操作或Linux的高级格式化命令,避免对分区信息进行修改,导致Device卸载虚拟U盘后mount失败。如果进行了此操作,mount失败后可重新format存储介质来解决。

  • 目前虚拟U盘支持的存储介质有4种,分别是:SD卡、MMC、RAM、FTL spi_nor。初始化流程一致,在加载USB之前,都需要先将对应介质的文件系统初始化,确保“dev/”目录下有可供虚拟U盘读写的节点。

注册Device U盘回调函数

应用层通过fmass_register_notify(void(*notify)(void* context, int status), void* context)函数注册notify回调,驱动会在识别到接入和拔出Host端时调用此回调函数,从而实现DeviceU盘拔插相关的处理。

请在USB模块初始化后,立马调用fmass_register_notify()实现Device回调函数的注册。

须知: Notify回调函数中,在USB Device链接的情况下,需通过fmass_partition_startup()将需要被PC端识别的SD卡分区节点启动,并且保证其接口传进来的设备节点是存在的,如果不存在,就默认将“/dev/mmcblk0p0”虚拟化为U盘,若“/dev/mmcblk0p0”节点不存在,则会失败。

开启额外串口

开启LOSCFG_DRIVERS_USB_MASS_STORAGE_EXT_SER_FUNC,即可在虚拟U盘的基础上额外增加串口的功能。当前支持配置为单串口和双串口。

注意事项

  • 在正常操作过程中需要遵守的事项:

    • Device U盘将SD卡交由USB Host处理,因此在Device U盘被Host端识别后,会通过回调函数通知APP软件模块,APP必须将所有录像抓拍等操作SD卡的业务停下,并umount SD卡分区,待USB Host离线后再mount和恢复相关业务。

    • USB Host端进行读写访问时,不要拔出单板的SD/MMC卡,否则会打印一些异常信息,并且可能会导致卡住文件或文件系统被破坏。

  • 在操作过程中出现异常时的操作:

    若拔出Device U盘后,再极其快速地插入Device U盘,可能会导致Host端检测不到U盘,因为单板Device U盘的检测注册/注销过程需要一定的时间。

编程示例

extern int fmass_register_notify(void(*notify)(void* context, int status), void* context);
extern int fmass_partition_startup(char* path);
void fmass_app_notify(void* conext, int status)
{
    char *path = "/dev/mmcblk0p0";
    if(status == 1)/*usb device connect*/
    {
        /* 停止录像抓拍等对/dev/mmcblk0p0分区的读写操作,并umount该分区 */
        fmass_partition_startup(path);    // startup fmass access partition
    }
    else
    {
        /* mount该分区,并通知其它模块该分区可以进行读写操作 */
    }
}
void app_init()
{
    uint32_t ret;
    int handle;

    const char manufacturer[38] = {
        'H', 0, 'i', 0, 'S', 0, 'i', 0, 'l', 0, 'i', 0, 'c', 0, 'o', 0, 'n', 0,
        '(', 0, 'S', 0, 'h', 0, 'a', 0, 'n', 0, 'g', 0, 'h', 0, 'a', 0, 'i', 0, ')', 0
    };
    struct device_string str_manufacturer = {
        .str = manufacturer,
        .len = 38
    };

    const char product[18] = {
        'U', 0, 'S', 0, 'B', 0, 'D', 0, 'E', 0, 'V', 0, 'I', 0,
        'C', 0, 'E', 0
    };
    struct device_string str_product = {
        .str = product,
        .len = 18
    };

    const char serial[16] = {
        '2', 0, '0', 0, '2', 0, '0', 0, '0', 0, '6', 0, '2', 0,
        '4', 0
    };
    struct device_string str_serial_number = {
        .str = serial,
        .len = 16
    };

    struct device_id dev_id = {
        .vendor_id = 0x3361,
        .product_id = 0x0108,
        .release_num = 0x0318
    };

    SD_MMC_Host_init();

    g_usb_mem_addr_start= g_sys_mem_addr_end;
    g_usb_mem_size= 0x40000; //recommend 256K nonCache for usb

    ret = usbd_set_device_info(DEV_MASS, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    if (ret != 0) {
        dprintf("set fail!\n");
        return;
    }
    
    ret = usb_init(DEVICE, DEV_MASS);  // usb_init must after SD_MMC_Host_init
    if(ret != 0) {
        dprintf("usb init failed!\n");
        return;
    }
    handle = fmass_register_notify(fmass_app_notify,NULL);
    if (handle == -1) {
        return;
    }
}

内存中Fat32文件系统虚拟为U盘

  1. 制作FAT格式的文件系统镜像(以Ubuntu系统为例)。

    1. 制作FAT格式磁盘镜像。

      mkfs.fat -s 64 -S 512 -f 2 -n "Internal SD" -F 32 -C usbdisk.tmp 2048
      

      其中2048表示磁盘空间大小,单位是KB。

    2. 挂载FAT格式磁盘镜像。

      mount -o loop -t vfat usbdisk.tmp test/
      

      其中test为挂载目录。

    3. 在test目录下存放所需文件,然后将文件同步到磁盘。

      sync test/
      
    4. 卸载磁盘镜像。

      umount test/
      
    5. 制作文件系统镜像。

      dd if=usbdisk.tmp of=udisk.img bs=1024 count=2048
      

      将“usbdisk.tmp”前面2MB的内容制作为文件系统镜像“udisk.img“”。

  2. 将制作好的文件系统镜像下载到flash上。

    将“udisk.img”文件放到指定路径FAT_PART_IMAGE_PATH(“/littlefs/udisk.img”)。

  3. 调用ram_mass_storage_init接口注册RAM_DEV_NAME(/dev/uram)设备节点。

  4. 调用fmass_register_notify接口指定设备节点“/dev/uram”虚拟为U盘。

  5. 调用usbd_set_device_info和usb_init接口加载虚拟U盘协议。

    须知: 本次制作的文件系统镜像申请的是2M容量,请根据实际业务需求调整容量大小,除了需要制定特定容量的文件系统镜像之外,还要修改对应的FAT_PART_SIZE大小。

Device UVC

Device UVC支持UVC1.0协议和UVC1.1协议版本的切换,打开菜单,进入Driver ---> Enable USB ---> Enable USB DWC Controller ---> Enable USB Gadget Support ---> USB Gadget Drivers ---> UVC Version可选择UVC1.0或UVC1.1版本。当前默认为UVC1.1协议,切换为UVC1.0可支持Windows xp系统,增加兼容性。

操作指导

  1. UVC上层应用在USB初始前设置UVC传输能力集,开启VI,打开UVC设备。

  2. 就绪后,等待底层UVC连接,启动VPSS和VENC编码并传输码流数据,直至UVC通道断开。

  3. 切换新码流格式时,应用须先关闭前一次编码并清空缓存,再启动编码。

操作示例

  • 方式1:直接调用device uvc相关接口控制数据传输。

  1. 通过fuvc_frame_descriptors_get接口设置UVC传输能力集(含:编码格式、分辨率和帧率)。

  2. 在初始化函数中调用usbd_set_device_info和usb_init接口实现USB驱动注册。

    usbd_set_device_info(DEV_UVC, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_UVC);     // dtype设置为DEV_UVC
    
  3. 开启VI后,调用uvc_open_device打开UVC设备,需指定分辨率和帧率。

  4. 通过uvc_wait_host等待板端与USB主机侧连接成功。

  5. 启动VPSS和VENC编码后,通过uvc_recv_pack开始执行视频数据传输。

  6. 通过uvc_video_stop停止视频数据传输,可调用uvc_close_device关闭USB设备。

  • 方式2:打开device uvc注册的设备节点,并通过系统调用控制数据传输。

  1. 在初始化函数中调用usbd_set_device_info和usb_init接口实现USB驱动注册。

    usbd_set_device_info(DEV_UVC, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_UVC);     // dtype设置为DEV_UVC
    
  2. 开启VI后,调用open打开UVC设备节点“/dev/usb_uvc-0”,并通过调用select函数监视设备连接情况。

  3. 当板端与USB主机侧连接成功并可以开始传输数据时,select系统调用能够接收到来自host端的消息,此时调用ioctl接口发送VIDIOC_DQEVENT命令,并提供结构体struct usbd_uvc_video_event来接收数据。根据接收的数据将视频的分辨率、帧率等信息发送给VPSS和VENC。

  4. 启动VPSS和VENC编码后,通过uvc_recv_pack开始执行视频数据传输。同时通过系统调用ioctl发送VIDIOC_STREAMON命令通知USB设备可以开始进行数据传输。

  5. 通过调用VIDIOC_STREAMOFF可以停止视频传输,再通过系统调用close关闭USB设备。

须知: 方式1与方式2都有打开设备的操作,分别是直接调用接口uvc_open_device和调用系统接口open。当同时使用这两个接口时,只能打开一次设备,后调用的接口将无法打开设备且其各种操作都将失效。

Device UAC

UAC的数据处理流程,麦克风业务具体步骤如下:

  1. 在初始化函数中调用usbd_set_device_info和usb_init接口实现USB驱动注册。

    usbd_set_device_info(DEV_UAC, str_manufacturer, str_product, str_serial_number, dev_id);
    usb_init(DEVICE, DEV_UAC);      // dtype设置为DEV_UAC
    
  2. 开启VI后,通过uac_wait_host等待板端与USB。

  3. 通过fuac_get_opts接口获取当前Device UAC与Host协商的采样率、声道数和位深信息,计算每次要发送的音频长度和需要缓存的音频个数。

  4. 通过3计算出的数值,调用fuac_reqbuf_init接口初始化UAC内存池。

  5. 启动VPSS和VENC编码前调用fuac_reqbuf_get接口获取空闲buf,把编码后的音频填充到此buf。

  6. 填充完的buf,通过调用fuac_send_message发送音频数据给Host,此后56往复循环执行。

  7. 结束音频传输时,把5中已获取的空闲buf,通过调用fuac_send_message接口,全部归还给USB,然后调用fuac_reqbuf_deinit接口释放UAC内存池。

  8. 调用usb_deinit接口卸载协议,注意卸载协议前必须保证UAC内存池的所有buf全部归还给USB层,否则会卸载失败。

扬声器业务具体步骤如下:

  1. 和麦克风业务中的12一样,依次实行加载UAC协议。

  2. 通过fuac_get_opts接口获取当前Device UAC与Host协商的采样率、声道数和位深信息,计算每次要接收的音频长度。

  3. 调用fuac_receive_message接收Host下发的音频数据。

  4. 停止调用fuac_receive_message接口,即停止音频数据传输。

  5. 调用usb_deinit接口卸载协议。

Device UAV(Camera)

UAV是同时支持UAC、UVC,相关操作流程,参见上面UVC、UAC使用示例。区别在于初始化时,传入的协议类型:

usbd_set_device_info(DEV_CAMERA, &str_manufacturer, &str_product, &str_serial_number, dev_id);
usb_init(DEVICE, DEV_CAMERA);   // dtype设置为 DEV_CAMERA
Device虚拟串口

虚拟串口配置说明如下:

配置项

含义

取值范围

默认值

依赖

LOSCFG_DRIVERS_USB_SERIAL_VFS_INTERFACE

使能VFS的读写接口控制虚拟串口的读写和ioctl

YES/NO

YES

LOSCFG_FS_VFS

LOSCFG_DRIVERS_USB_SERIAL_FUNC_INTERFACE

使能对外接口控制虚拟串口读写和ioctl

YES/NO

NO

LOSCFG_DRIVERS_USB_SERIAL_GADGET_DEFAULT

使能默认单串口

YES/NO

YES

LOSCFG_DRIVERS_USB_SERIAL_GADGET_MULT

使能多串口

YES/NO

NO

LOSCFG_DRIVERS_USB_SERIAL_COUNT

多串口使能时,设置串口具体数量

2-3

2

LOSCFG_DRIVERS_USB_SERIAL_GADGET_MULT

LOSCFG_DRIVERS_USB_SERIAL_NOTIFICATION_FUNC

使能串口通知功能(开启中断端点)

YES/NO

YES

操作示例

  • 方式一:初始化虚拟串口后,通过VFS 接口打开串口节点,并使用read、write、ioctl等接口控制串口。

    当系统支持文件系统时,以下就是依赖文件系统的虚拟串口的初始化和使用流程:

    在初始化函数中调用以下接口实现USB驱动注册:

    usbd_set_device_info(DEV_SERIAL, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_SERIAL);
    

    若需要基于USB虚拟的串口进行shell通信交互与调试信息打印,需要再进行console、Shell等与串口有依赖关系的模块初始化。

    virtual_serial_init("/dev/ttyGS0");
    system_console_init("/dev/serial");
    

    若单板端写数据发送到USB主机,则直接调用write接口;若单板端读取从USB主机发来的数据,open串口后先调用ioctl接口,然后再调用read接口,ioctl用法如下:

    ioctl(fd, CONSOLE_CMD_RD_BLOCK_SERIAL, 1);
    

    须知:

    • 虚拟串口功能不能与Uart串口同时使用。

    • 如果有对节点的多次open操作,需要在卸载前执行相同次数close操作,才能保证协议卸载时成功,否则会出现不可预知的错误。

    • 虚拟串口最大支持读写4096Byte数据。

    • 单串口模式下初始化后,在dev目录下会生成/dev/ttyGS0节点,在双串口模式下初始化后,在dev目录下会生成/dev/ttyGS0节点和/dev/ttyGS1节点。

  • 方式二:直接调用非VFS模块的对外接口控制虚拟串口

    当系统不支持文件系统时,虚拟串口和shell暂不兼容,初始化虚拟串口之后,需要调用usb_serial_ioctl接口,使虚拟串口可以正常读。用法如下:

    usbd_set_device_info(DEV_SERIAL, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_SERIAL);
    usb_serial_ioctl(0,CONSOLE_CMD_RD_BLOCK_SERIAL, 1); // index参数默认使用0,当开启双串口时,可以选0或1。
    

    然后根据业务需要调用usb_serial_read和usb_serial_write对虚拟串口进行读写操作。

    须知: 非VFS模式的虚拟串口,使用对外接口时,入参index默认使用0,当仅支持一个虚拟串口时,只能用0,当开启双串口时,可以选0或1。

Host端如何识别串口

如果Host端为Windows PC,那么首先在PC端放置驱动文件“linux-cdc-acm.inf”,该文件在发布包中的路径:01.software/pc/usb_tools。

通过USB数据线将单板与Host端相连,PC端会自动加载驱动,第一次一般会失败,需要自行安装驱动,方法为:

  1. 右击计算机,进入管理界面。

  2. 打开设备管理器。

  3. 点开其他设备,会看到Gadget Serial(COMx),双击。

  4. 打开驱动程序界面,点击更新驱动程序,进入浏览计算机以查找驱动程序软件(R)。

  5. 把路径指向驱动文件“linux-cdc-acm.inf”所在的目录,点击下一步,计算机会自动进行安装驱动程序,安装成功后,关闭界面。

    须知:

    • “01. software/pc/usb_tools”目录下有“linux-cdc-acm.inf”文件,通常Win7无法自动加载虚拟串口驱动,需要手动加载;Win10通常会自动加载虚拟串口驱动,无需手动加载。

    • 如果安装串口驱动失败,则需要修改“linux-cdc-acm.inf“文件,在[DeviceList]和[DeviceList.NTamd64]里添加“USB\VID_3361&PID_0102&REV_0318”,其中3361对应的是VID,0102对应的是PID,0318对应的是设备版本号,这3组数字是通过调用usbd_set_device_info接口来设置,请根据实际设备进行修改,需要和设置值保持一致。

    单板端重新上电启动,在PC端就可以作为真正的串口来使用。

Device虚拟网口RNDIS

RNDIS目前可以不依赖lwip,支持对接不同的网络协议栈。RNDIS初始化方式分为两种。

第一种就是在开启了LWIP配置项后,步骤如下:

  1. 在初始化函数中调用以下接口实现TCP/IP协议栈初始化和USB驱动注册。

    tcpip_init(NULL, NULL);   // TCP/IP协议栈初始化
    usbd_set_device_info(DEV_ETHERNET, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_ETHERNET);
    
  2. USB虚拟网口默认未配置IP地址,请根据网络规划自行配置IP地址。

  3. 如果host端为Windows 7或者Windows10,那么首先在PC端放置linux.inf驱动文件,见发布包路径:01.software/pc/usb_tools。然后修改“linux.inf”驱动文件,在[RndisDevices.NTx86]、[RndisDevices.NTamd64]和[RndisDevices.NTia64]添加“USB\VID_3361&PID_0102&REV_0318”,其中3361对应的是VID,0102对应的是PID,0318对应的是设备版本号,这3组数字是通过调用usbd_set_device_info接口来设置,请根据实际设备进行修改,要和设置值保持一致。

  4. 通过USB数据线将单板与Host端相连。

    • 如果Host端为windows7系统,通常自动加载驱动,一般会失败,需要自行安装驱动,具体操作为:

      1. 右击计算机,进入管理界面。

      2. 打开设备管理器。

      3. 点开其他设备,会看到“Linux USB Ethernet/RNDIS Gadget #3“”,双击。

      4. 打开驱动程序界面,点击更新驱动程序,进入浏览计算机以查找驱动程序软件(R)。

      5. 把路径指向“linux.inf”所在的目录,点击下一步,计算机会自动进行安装驱动程序,安装成功后,关闭界面。

    • 如果Host端为Windows10系统,通常会误识别为虚拟串口,需要手动更新驱动,具体操作为:

      1. 右击计算机,进入管理界面。

      2. 打开设备管理器。

      3. 点开端口设备,会看到USB串行设备(COMX)。

      4. 打开驱动程序界面,点击更新驱动程序,进入浏览计算机以查找驱动程序软件(R)。

      5. 把路径指向“linux.inf”所在的目录,点击下一步,计算机会自动进行安装驱动程序,安装成功后,会生成虚拟网口设备,对应虚拟串口设备消失,关闭界面。如果安装过程中提示第三方INF不包含数字签名信息,此关闭方法可在网上搜索,此处不再赘述。

  5. 在单板端,配置IP并添加路由。

  6. 当单板和PC通过USB数据线相连时,会在PC端生成USB网络节点,具体位置:打开网络和共享中心--->更改适配器设置--->Linux USB Ethernet /RNDIS Gadget #x。

  7. 建立网桥:

    1. 右键“Linux USB E thernet /RNDIS Gadget #x”节点,点击添加到桥选项;

    2. PC端连接大网的本地连接节点右键,选择添加到桥选项;

    3. 等待网桥建立完成,如图1所示。

    图 1 成功建立网桥

    此时在单板端,能正常访问大网,可作为真正的网口操作使用。

须知:

  • 在Win10 PC桥接网络,若网桥无法获取到IP地址时,请确定一下桥接顺序,桥接时请先选择物理网卡,再选择虚拟网卡,然后桥接。若配置网桥失败,请单击确定,然后再选择物理网卡右键添加到桥(这个步骤很重要),因为造成配置网桥失败的原因就是物理网卡没有添加到桥。

  • 当访问桥接的网络环境时需要权限,若出现能ping通IP地址但ping不通网关和服务器的问题,请排查单板端的MAC地址(IP和MAC地址有关联性)。

第二种初始化RNDIS的方式,就是在关闭LWIP的情况下,对接其他网络协议栈,步骤如下:

  1. 在初始化函数中进行USB驱动注册之前,需要调用RNDIS初始化其他网络协议栈钩子的函数。

    struct usb_eth_operations
    {
      void (*eth_register)(void);// 加载时调用,注册网卡
      void (*eth_unregister)(void);// 卸载时调用,注销网卡
      void (*eth_link_change)(bool connected);// 连接状态改变时调用,通知网络协议栈断连状态
      void (*eth_rx)(const uint8_t *buf, uint32_t buflen);// 收到数据时调用,将数据传入网络协议栈
    };// 此结构体定义在f_eth.h头文件中,需要包含此头文件
    

    创建struct usb_eth_operations类型ops,并使用其他网络协议栈api实现上述四个功能类型的函数,并将对应函数赋值给ops的各个函数指针成员。然后调用feth_init_ops函数初始化RNDIS内部的对应函数。

    // 默认其他网络协议栈已经实现相应函数指针,参考self_src/drivers/usb/gadget/usbd_eth_common.c文件中lwip实现对应函数指针的方式
    struct usb_eth_operations ops;
    ops.eth_register = other_net_reg;
    ops.eth_unregister = other_net_unreg;
    ops.eth_link_change = other_net_link;
    ops.eth_rx = other_net_rx;
    if (feth_init_ops(&ops) != 0) {
        dprintf("RNDIS init ops faild.\n");
        return;
    }
    dprintf("RNDIS init ops success.\n");// 执行到这里说明已经初始化RNDIS与网络协议栈对接的钩子
    
  2. 调用以下接口进行USB驱动注册。

    usbd_set_device_info(DEV_ETHERNET, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_ETHERNET);
    
  3. 至此已完成RNDIS的加载,其他网络协议栈需要发送数据到host,则调用feth_send_data接口即可。

    feth_send_data(buf, ETH_SEND_BUF);// 在需要处直接调用发送接口发送即可
    
  4. 与host对接后,如何识别问题,参照第一种RNDIS初始化方式中的方法即可。

须知: 当没开启lwip时,要求用feth_init_ops函数注册钩子函数,来对接一个网络协议栈,若没有注册钩子函数,RNDIS在加载后,只是一个无任何网络通信功能的虚拟网口,无法解析接收到的网络数据,也无法发送符合网络协议的网络数据。同时,RNDIS虽然也可以直接调用发送接口发送数据,但由于无法自己构造符合网络协议的网络数据,在host端将无任何反应。

Device虚拟网口ECM

虚拟网口ECM和虚拟网口RNDIS的加载过程是一样的,在与host对接之前的初始化加载流程可以参考RNDIS的加载过程,其中需要调整的代码如下,只是入参有区别,其他与RNDIS一致。

usbd_set_device_info(DEV_ECM, &str_manufacturer, &str_product, &str_serial_number, dev_id);
usb_init(DEVICE, DEV_ECM);

须知:

  • ECM对接的host是linux,要求linux开启支持ECM虚拟网口的配置。

  • ECM对接linux之后,需要先在linux系统中将对应网卡设置IP地址,然后再设置device端的ECM网卡IP地址,否则device端设置IP会失败。

  • ECM对接网络协议层的方式也与RNDIS一致,可以参考RNDIS。

Device复合设备(串口&网口)

复合设备支持单串口与网口复合以及双串口与网口复合,在配置项里,打开串口与网口复合的配置项后,默认是单串口与网口复合,若要双串口与网口复合,需要在串口模式中选择双串口即可。

以上两种复合设备的初始化过程一样,具体过程如下:

tcpip_init(NULL, NULL);       // TCP/IP协议栈初始化
usbd_set_device_info(DEV_SER_ETH, &str_manufacturer, &str_product, &str_serial_number, dev_id);
usb_init(DEVICE, DEV_SER_ETH);
virtual_serial_init("/dev/ttyGS0");
system_console_init("/dev/serial");

复合设备中串口和网口的使用同上面“Device虚拟串口”和”Device虚拟网口RNDIS”使用示例。

须知:

  • 虚拟串口功能不能与UART串口同时使用。

  • 由于网口分为RNDIS网口和ECM网口,复合设备初始化的区别在于传入的设备枚举值不同,RNDIS与串口复合时,传入DEV_SER_ETH;ECM与串口复合时,传入DEV_SER_ECM。最终生成的复合设备,用法上与单个的串口或者网口用法一致,可以参考虚拟串口和虚拟网口的介绍

Device DFU

目前DFU支持两种升级方式,默认使用方式一。

  • 方式一:通过分配足够大的空间来保存升级文件。

  • 方式二:无需分配足够大的空间,边下载边处理数据。

方式一的升级步骤如下:

  1. 在初始化函数中调用以下接口实现USB驱动注册。

    usbd_set_device_info(DEV_DFU, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_DFU);
    
  2. 升级开始前,调用usb_dfu_init_env_entities接口设置升级接口类型及环境变量。

  3. 调用usb_dfu_update_status,等待升级事件。

  4. 等到有升级事件后,调用usb_dfu_update_size_get获取升级文件大小,并对升级文件进行必要的校验,检查升级文件是否携带后缀字段(校验和、标志等关键字),如携带后缀比对校验和。

  5. 根据上述升级文件大小,为DFU缓存升级文件提供足够的内存空间,通过usb_dfu_read获取实际升级文件。

  6. 获取到实际升级文件后,即可对固件执行升级动作,写入指定Flash地址段。

  7. 最后需要调用usb_dfu_free_entities接口释放相关资源。

    说明: 给升级的文件提供足够的内存空间主要与厂商提供的升级镜像大小有关,请预留足够的升级的空间。

方式二的升级步骤如下:

  1. 在初始化函数中调用以下接口来实现USB驱动注册。

    usbd_set_device_info(DEV_DFU, &str_manufacturer, &str_product, &str_serial_number, dev_id);
    usb_init(DEVICE, DEV_DFU);
    
  2. 业务层需要自行实现弱函数usb_dfu_download_callback,该函数会在“控制传输完成回调函数”里被调用,将host端传来的数据作为入参传入该弱函数,在业务层直接处理每次传输进来的升级数据。

    void usb_dfu_download_callback(constuint8_t *buf, uint32_t len);  // 弱函数usb_dfu_download_callback的声明
    
  3. 最后调用usb_dfu_wait_transmit_finish函数,等待传输完成。

    说明: 弱函数usb_dfu_download_callback在中断里被调用,故自行实现的处理数据的代码中,不可使用引起调度的函数,如mutex锁的使用; 由于是边下载边升级,升级过程中需要确保USB中断不受影响,建议升级过程中关闭其他可能影响USB中断的模块或功能。

Device HID

HID上层应用在USB初始化之前,先调用usbd_set_device_info接口来设置厂商ID、产品ID、设备序列号以及字符串信息,然后调用hid_report_descriptor_info或者hid_add_report_descriptor接口来配置报告描述符信息,让USB主机识别为具体的HID设备,如键盘、鼠标、带有鼠标功能的键盘等

操作示例

  • 方式1:打开Device HID注册的设备节点,通过调用VFS模块的接口进行HID数据流传输。

  1. 调用usbd_set_device_info和hid_report_descriptor_info接口来填充各个描述符信息。

  2. 在初始化函数中调用usb_init接口实现USB驱动注册。

    usb_init(DEVICE, DEV_HID); // dtype设置为 DEV_HID
    
  3. 在上层应用中,调用open函数来打开指定的HID设备。

    fd = open("/dev/hid", O_RDWR, 0);
    
  4. 在上层应用中,按报告描述符规定的INPUT数据格式写到write函数中的入参buffer,长度也是按报告描述符规定的INPUT长度。若报告描述符定义了OUTPUT项目,则调用read函数去读取USB主机发给设备的数据,读取的大小是按报告描述符规定的OUTPUT长度,若要提高任务执行效率,可以调用poll机制,两者区别是read是永久阻塞模式,而poll机制是定时阻塞模式。

  5. 数据传输结束之后,若不使用设备,便调用close函数关闭设备。

  • 方式2:直接调用非VFS模块的对外接口进行HID数据流传输。

  1. 调用usbd_set_device_info和hid_add_report_descriptor接口来填充各个描述符信息。

  2. 在初始化函数中调用usb_init接口实现USB驱动注册。

    usb_init(DEVICE, DEV_HID); // dtype设置为 DEV_HID
    
  3. 在上层应用中,按报告描述符规定的INPUT数据格式和长度信息,调用fhid_send_data接口发送HID数据。若报告描述符定义了OUTPUT项目,则调用fhid_recv_data接口去读取USB主机发给设备的数据,读取的HID数据长度与报告描述符规定的OUTPUT长度一致。

须知:

  • hid_report_descriptor_info接口只能设置一个Report,并和VFS模块的接口配套使用;hid_add_report_descriptor接口可以最多设置两个Report(循环调用接口进行设置,设置前需要配置LOSCFG_DRIVERS_USB_HID_REPORT_MAP_NUM),并和非VFS模块的接口配套使用。这两种方式不允许混用。如果需要重新设置Report,则需要加载并卸载Device HID协议才能设置。

  • 如果报告描述符中没有Report ID项目(鼠标或者键盘报告表),默认的ID值是0,在数据传输过程中,第一个字节不需要填充0,若定义一个以上的Report ID(带有鼠标功能的键盘报告表),此时在传输报表中的第一个字节填充相应的Report ID,输入报表、输出报表与特征报表可以分享同一个Report ID。

  • 如果设置的报告描述符含有Output Report特性,则需要打开LOSCFG_DRIVERS_USB_HID_OUTPUT_REPORT配置才能进行Output传输,当USB主机发给设备数据的时候,建议及时把数据读走,否则USB主机第二次发来的数据会覆盖第一次的数据。

  • 若调用了open函数,此时无法卸载协议,只有调用close函数,才能卸载协议。当前Device HID只允许open一次,再次open就会失败。

  • 上层应用中调用VFS模块的read和write接口或者非VFS模块的fhid_send_data和fhid_recv_data接口时,请确保传进来的buffer和len大小要一致,否则会造成发送或者读取的数据有误。在进行数据流传输前需要配置LOSCFG_DRIVERS_USB_HID_INPUT_REPORT_LEN,即发送hid数据的最大长度,该值必须要做对齐操作,否则会出现数据传输错误,当前对齐策略有两种,

  1. 如果传输的内存是non-cache属性,该值必须满足四字节对齐;

  2. 如果传输的内存是cache属性,该值必须满足USB_CACHE_ALIGN_SIZE对齐。

  • USB2.0控制器支持通过SOF中断发送hid数据(需打开配置项LOSCFG_DRIVERS_USB_HID_POLLING_REPORTS),在高速模式下其中断是每隔125μs上报,其回调弱函数void usb_sof_intr_callback(void)需要在业务层实现具体操作,该接口操作时间不能超过125μs,否则会影响传输速率和其他中断操作。

  • 方式1和方式2不能同时使用,若使用方式1,需要打开VFS模块功能。HID业务运行过程中,不允许卸载HID协议,必须要停止应用层的业务操作后,才能卸载协议。

  • 方式1和方式2的接收HID数据接口只能在任务中调用,不能在中断回调调用,发送HID数据接口可以在中断回调调用。

Device UAC&HID&串口复合设备

UAC&HID&串口复合设备是最多同时支持麦克风、扬声器、键盘和鼠标、串口功能,扬声器功能可以在配置项里打开或者关闭,而键盘和鼠标功能则是通过调用hid_report_descriptor_info或者hid_add_report_descriptor接口来配置报告描述符信息。相关操作流程,参见上面UAC、HID使用示例。区别在于初始化时,传入的协议类型:

usbd_set_device_info(DEV_UAC_HID, &str_manufacturer, &str_product, &str_serial_number, dev_id);
usb_init(DEVICE, DEV_UAC_HID);   // dtype设置为 DEV_UAC_HID

串口功能可裁剪,默认关闭。通过打开LOSCFG_DRIVERS_USB_UAC_HID_EXT_SER_FUNC可开启额外的单串口功能。

API参考

该功能模块提供以下接口:

接口名

描述

usbd_set_device_info

设置device设备的VID、PID、设备版本号和厂商、产品、序列号字符串信息。

usb_init

初始化USB模块。

usb_deinit

卸载USB模块。

usb_is_devicemode

判断当前USB Device是否已连接USB Host。

usb_device_is_host_suspended

检测USB Host是否进入休眠状态。

usb_device_remote_wakeup

远程唤醒已进入休眠状态的USB Host

fmass_register_notify

注册Device notify回调函数。

fmass_unregister_notify

注销Device notify回调函数。

fmass_partition_startup

启动被PC端识别的SD卡、MMC介质或RAM介质分区节点。

ram_mass_storage_init

使能Fat32文件系统镜像内存映射,并注册/dev/uram设备节点。

ram_mass_storage_deinit

非使能Fat32文件系统镜像内存映射。

fuvc_frame_descriptors_get

设置UVC设备视频传输能力集。

uvc_open_device

打开UVC设备。

uvc_close_device

关闭UVC设备。

uvc_wait_host

UVC等待USB HOST连接。

uvc_get_state

获取UVC设备的运行状态。

uvc_recv_pack

启动UVC的视频数据传输,拷贝方式。

uvc_video_stop

停止UVC视频传输。

uvc_format_info_get

获取新码流格式详细信息。

uvc_format_status_check

码流格式切换事件。

hi_camera_register_cmd

应用注册UVC扩展命令,提供底层驱动调用。

uac_wait_host

UAC等待USB HOST连接。

fuac_reqbuf_init

初始化UAC(音频)内存池。

fuac_reqbuf_deinit

释放UAC(音频)内存池。

fuac_reqbuf_get

获取空闲的UAC(音频)内存。

fuac_send_message

音频数据传输。

fuac_receive_message

音频数据传输接收。

fuac_opts_set

设置uac1.0音频参数,包括通道数等。

fuac_get_opts

获取uac1.0音频参数,包括通道数等。

fuac_rate_get

获取uac1.0音频采样率。

fuac2_get_opts

获取音频2.0协议的参数,包括通道数等。

fuac2_set_opts

设置音频2.0协议的参数,包括通道数等。

usb_dfu_init_env_entities

初始化配置DFU接口功能,以及配置环境变量。

usb_dfu_update_status

获取升级事件。

usb_dfu_update_size_get

获取升级文件实际大小。

usb_dfu_get_entity

获取DFU列表中指定的DFU结构体变量。

usb_dfu_read

获取指定DFU列表中的一定大小的缓冲数据。

usb_dfu_free_entities

释放DFU相关资源。

usb_dfu_wait_transmit_finish

等待DFU传输完成。

hid_report_descriptor_info

配置报告描述符信息,设置报告数据格式和功能。

hid_add_report_descriptor

配置多个报告表描述符,设置报告表数据格式、功能和接口协议

fhid_send_data

HID数据传输发送。

fhid_recv_data

HID数据传输接收。

uvc_get_frame_buf

获取帧缓冲区的地址(自研接口)。

usb_serial_ioctl

往USB虚拟串口发送ioctl命令。

usb_serial_read

读取主机往USB虚拟串口发送的数据。

usb_serial_write

往USB虚拟串口发送数据。

feth_send_data

往USB虚拟网口发送的数据。

feth_init_ops

初始化USB虚拟网口与网络协议栈对接的钩子。

feth_change_connect_status

控制虚拟网口断连重连接口

usbd_set_device_info

【描述】

设置device设备的VID、PID、设备版本号和厂商、产品、序列号字符串信息。

【语法】

uint32_t usbd_set_device_info(device_type dtype,
                          const struct device_string *str_manufacturer,
                          const struct device_string *str_product,
                          const struct device_string *str_serial_number,
                          struct device_id dev_id);

【参数】

参数名称

描述

输入/输出

dtype

device 类型:

  • DEV_SERIAL,虚拟串口。
  • DEV_ETHERNET,虚拟网口。
  • DEV_SER_ETH,复合设备(串口&网口)。
  • DEV_DFU,USB固件升级。
  • DEV_MASS,虚拟U盘。
  • DEV_UVC,USB视频传输。
  • DEV_UAC,USB音频传输。
  • DEV_CAMERA,USB摄像头。
  • DEV_HID,USB HID设备输入。
  • DEV_UAC_HID,USB UAC&HID复合设备

输入

str_manufacturer

厂商字符串信息,结构体第一个成员变量是字符串指针,第二个成员变量是字符串的总长度。

输入

str_product

产品字符串信息,结构体成员同上。

输入

str_serial_number

产品序列号字符串信息,结构体成员同上。

输入

dev_id

设备ID号,结构体第一个成员变量是VID(厂商ID号),第二个成员变量是PID(产品ID号),第三个成员变量是release_num,即设备版本号。

输入

【返回值】

返回值

描述

LOS_OK

成功

LOS_NOK

失败

【需求】

  • 头文件:usb_init.h

  • 库文件:libusb_base.a

【注意】

  • 厂商ID号不可随意设置,需向USB协会申请,产品ID号和设备版本号可根据实际产品进行设置,如果同一个单板切换为不同的USB设备需要更换PID号,否则USB主机识别到的设备驱动会有问题。

  • 字符串长度要和字符串大小一致,且长度范围2~252,传入的入参长度必须是偶数,否则USB主机无法识别字符串信息。

【举例】

须知: 以下示例仅供参考,不可直接使用,特别是VID号,请务必修改为客户自己的厂商信息。

const char manufacturer[38] = {
    'H', 0, 'i', 0, 'S', 0, 'i', 0, 'l', 0, 'i', 0, 'c', 0, 'o', 0, 'n', 0,
    '(', 0, 'S', 0, 'h', 0, 'a', 0, 'n', 0, 'g', 0, 'h', 0, 'a', 0, 'i', 0, ')', 0 
};
struct device_string str_manufacturer = {
    .str = manufacturer,
    .len = 38
};
 
const char product[18] = {
    'U', 0, 'S', 0, 'B', 0, 'D', 0, 'E', 0, 'V', 0, 'I', 0,
    'C', 0, 'E', 0
};
struct device_string str_product = {
    .str = product,
    .len = 18
};
 
const char serial[16] = {
    '2', 0, '0', 0, '2', 0, '0', 0, '0', 0, '6', 0, '2', 0,
    '4', 0
};
struct device_string str_serial_number = {
    .str = serial,
    .len = 16
};
struct device_id dev_id = {
    .vendor_id = 0x3361,
    .product_id = 0x0102,
    .release_num = 0x0318
};
 
uint32_t ret;
ret = usbd_set_device_info(DEV_UVC, &str_manufacturer, &str_product, &str_serial_number, dev_id);
if (ret != 0) {
    dprintf("set fail!\n");
}

【相关主题】

无。

usb_init

【描述】

初始化USB模块。

【语法】

uint32_t usb_init(controller_type ctype, device_type dtype);

【参数】

参数名称

描述

输入/输出

ctype

枚举类型,描述了usb是host类型或device类型:

  • HOST表示host类型。
  • DEVICE表示device类型。

输入

dtype

枚举类型,

  • 作为host模式时,该入参无效,传入0。
  • 作为device时,表示device类型:
    • DEV_SERIAL,虚拟串口。
    • DEV_ETHERNET,虚拟网口。
    • DEV_SER_ETH,复合设备(串口&网口)。
    • DEV_DFU,USB固件升级。
    • DEV_MASS,虚拟U盘。
    • DEV_UVC,USB视频传输。
    • DEV_UAC,USB音频传输。
    • DEV_CAMERA,USB摄像头。
    • DEV_HID,USB HID设备输入。
    • DEV_UAC_HID,USB UAC&HID复合设备。

输入

【返回值】

返回值

描述

LOS_OK

成功。

LOS_NOK

失败。

【需求】

  • 头文件:usb_init.h

  • 库文件:libusb_base.a

【注意】

  • USB各协议使用时与其他模块的依赖关系,参考“编程实例”。

  • 不支持重复执行,切换协议需要调用usb_deinit卸载当前协议。

【举例】

参考“编程实例”。

【相关主题】

usb_deinit

usb_deinit

【描述】

卸载USB模块。

【语法】

uint32_t usb_deinit(void);

【参数】

【返回值】

返回值

描述

LOS_OK

成功。

LOS_NOK

失败。

【需求】

  • 头文件:usb_init.h

  • 库文件:libusb_base.a

【注意】

【举例】

参考“编程实例”。

【相关主题】

usb_init

usb_is_devicemode

【描述】

判断当前USB Device是否已连接USB Host。

【语法】

bool usb_is_devicemode(void);

【参数】

无。

【返回值】

返回值

描述

TRUE

当前USB Device已连接USB Host。

FALSE

当前USB Device未连接USB Host。

【需求】

  • 头文件:usb_init.h

  • 库文件:libusb_base.a

【注意】

此接口要在加载USB Device协议后再去调用。

【举例】

无。

【相关主题】

无。

usb_device_is_host_suspended

【描述】

检测USB Host是否进入休眠状态。

【语法】

bool usb_device_is_host_suspended(void);

【参数】

无。

【返回值】

返回值

描述

true

当前USB Host已进入休眠状态。

false

当前USB Host不在休眠状态。

【需求】

  • 头文件:usb_init.h

  • 库文件:libusb_base.a

【注意】

此接口要在加载USB2.0 控制器后再去调用。

【举例】

【相关主题】

usb_device_remote_wakeup

usb_device_remote_wakeup

【描述】

远程唤醒已进入休眠状态的USB Host。

【语法】

int usb_device_remote_wakeup(void);

【参数】

无。

【返回值】

返回值

描述

0

表示远程唤醒操作成功。

-1

表示远程唤醒操作失败。

【需求】

  • 头文件:usb_init.h

  • 库文件:libusb_base.a

【注意】

此接口要在加载USB2.0 控制器后再去调用。

【举例】

【相关主题】

usb_device_is_host_suspended

fmass_register_notify

【描述】

注册Device notify回调函数。

【语法】

int fmass_register_notify(void (*notify)(void *context, int status), void *context);

【参数】

参数名称

描述

输入/输出

notify

Notify回调函数指针。

输入

context

需要传入Notify回调函数的私有数据指针,该指针会传入给Notify回调函数的context。

输入

【返回值】

返回值

描述

0 ~ MAX_NOFIFY_NUM-1

成功。

-1

失败。

【需求】

  • 头文件:f_mass_storage.h

  • 库文件:libusb_base.a

【注意】

允许注册多个notify回调函数,USB2.0 Device拔插事件发生时驱动会调用每一个notify函数。

【举例】

参考编程实例。

【相关主题】

fmass_unregister_notify

【描述】

注销Device notify回调函数。

【语法】

int fmass_unregister_notify(int handle);

【参数】

参数名称

描述

输入/输出

handle

fmass_register_notify返回的句柄值。

输入

【返回值】

返回值

描述

0

成功。

-1

失败。

【需求】

  • 头文件:f_mass_storage.h

  • 库文件:libusb_base.a

【注意】

【举例】

参考“编程实例”。

【相关主题】

fmass_register_notify

fmass_partition_startup

【描述】

启动被PC端识别的SD卡、MMC介质或RAM介质分区节点。

【语法】

int fmass_partition_startup(const char *path);

【参数】

参数名称

描述

输入/输出

path

SD卡、MMC介质或RAM介质分区节点,该设备节点必须是有效的。

输入

【返回值】

返回值

描述

0

成功。

-1

失败。

【需求】

  • 头文件:f_mass_storage.h

  • 库文件:libusb_base.a

【注意】

【举例】

参考编程实例。

【相关主题】

fmass_register_notify

ram_mass_storage_init

【描述】

使能FAT32文件系统镜像内存映射,并注册/dev/uram设备节点。

【语法】

int ram_mass_storage_init(void);

【参数】

【返回值】

返回值

描述

0

成功。

-1

失败。

【需求】

  • 头文件:f_mass_storage_ram.h

  • 库文件:libusb_base.a

【注意】

无。

【举例】

参考“编程实例”。

【相关主题】

ram_mass_storage_deinit

ram_mass_storage_deinit

【描述】

禁用FAT32文件系统镜像的内存映射。

【语法】

int ram_mass_storage_deinit(void);

【参数】

无。

【返回值】

返回值

描述

0

成功。

-1

失败。

【需求】

  • 头文件:f_mass_storage_ram.h

  • 库文件:libusb_base.a

【注意】

【举例】

参考“编程实例”。

【相关主题】

ram_mass_storage_init

fuvc_frame_descriptors_get

【描述】

设置UVC设备视频传输能力集。

【语法】

void fuvc_frame_descriptors_get(const struct fuvc_format_info *format_info, uint32_t frames_count);

【参数】

参数名称

描述

输入/输出

format_info

UVC支持的视频码流格式以及分辨率。

输入

frames_count

视频格式对应的分辨率的总个数。

输入

【返回值】

无。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

参考“编程实例”。

【举例】

参考“编程实例”。

【相关主题】

uvc_open_device

【描述】

打开UVC设备。

【语法】

int uvc_open_device(uvc_t *hdl, const struct uvc_open_param *param);

【参数】

参数名称

描述

输入/输出

hdl

用于存储UVC设备句柄。

输出

param

打开UVC设备的初始能力,使用中根据实际需求可变化修改。

输入

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_NOMATCH

UVC状态不匹配。

UVC_ERROR_HANDLE

UVC句柄无效。

UVC_ERROR_PTR

指针非法。

UVC_ERROR_MEMORY

内存不足。

UVC_ERROR_VALUE

取值非法。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

参考“编程实例”。

【举例】

参考“编程实例”。

【相关主题】

uvc_close_device

uvc_close_device

【描述】

关闭UVC设备。

【语法】

int uvc_close_device(uvc_t hdl);

【参数】

参数名称

描述

输入/输出

hdl

用于存储UVC设备句柄。

输入

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_FATAL

致命错误。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

参考“编程实例”。

【举例】

参考“编程实例”。

【相关主题】

uvc_open_device

uvc_wait_host

【描述】

等待USB HOST连接。

【语法】

int uvc_wait_host(uvc_t hdl, int wait_option, int *connected);

【参数】

参数名称

描述

输入/输出

hdl

UVC设备句柄。

输入

wait_option

等待连接时的状态:

  • UVC_WAIT_HOST_NOP:立即返回当前状态;
  • UVC_WAIT_HOST_FOREVER:阻塞,直至连接再返回。

输入

connected

当前连接状态:

  • 0:未连接;
  • 1:已连接。

输出

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_NOMATCH

UVC状态不匹配。

UVC_ERROR_HANDLE

UVC句柄无效。

UVC_ERROR_PTR

指针非法。

UVC_ERROR_VALUE

取值非法。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

参考“编程实例”。

【举例】

参考“编程实例”。

【相关主题】

uvc_close_device

uvc_get_state

【描述】

获取UVC设备的运行状态。

【语法】

int uvc_get_state(uvc_t hdl, uint32_t *state);

【参数】

参数名称

描述

输入/输出

hdl

UVC设备句柄。

输入

state

UVC设备运行状态:

  • UVC_STATE_IDLE:空闲状态;
  • UVC_STATE_CONN:连接状态;
  • UVC_STATE_CLOSE:关闭状态。

输出

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_HANDLE

UVC句柄无效。

UVC_ERROR_PTR

指针非法。

UVC_ERROR_VALUE

取值非法。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

无。

【举例】

无。

【相关主题】

无。

uvc_recv_pack

【描述】

UVC零拷贝模式发送数据包。

【语法】

int uvc_recv_pack(const struct uvc_pack *pack);

【参数】

参数名称

描述

输入/输出

pack

指向UVC数据包的指针。

输入

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_MEMORY

内存申请失败。

UVC_ERROR_HANDLE

UVC句柄无效。

UVC_ERROR_PTR

指针非法。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

参考“编程实例”。

【举例】

参考“编程实例”。

【相关主题】

uvc_video_stop

uvc_video_stop

【描述】

停止UVC视频传输。

【语法】

int uvc_video_stop(uvc_t hdl);

【参数】

参数名称

描述

输入/输出

hdl

UVC设备句柄。

输入

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_NOMATCH

UVC状态不匹配。

UVC_ERROR_HANDLE

UVC句柄无效。

UVC_ERROR_PTR

指针非法。

UVC_ERROR_VALUE

取值非法。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

参考“编程实例”。

【举例】

参考“编程实例”。

【相关主题】

uvc_recv_pack

uvc_format_info_get

【描述】

获取新码流格式详细信息。

【语法】

int uvc_format_info_get(struct uvc_format_info *info);

【参数】

参数名称

描述

输入/输出

info

struct uvc_format_info{
    uint32_t width;  // 分辨率,宽
    uint32_t height; // 分辨率,高
    uint32_t format; // 码流格式
    uint32_t status; // 1-格式切换中;0-切换完成
} __attribute__((packed));

输出

【返回值】

返回值

描述

UVC_OK

成功。

UVC_ERROR_NOMATCH

UVC状态不匹配。

UVC_ERROR_PTR

指针非法。

UVC_ERROR_FATAL

致命错误。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

无。

【举例】

无。

【相关主题】

uvc_format_status_check

uvc_format_status_check

【描述】

码流格式切换事件。

【语法】

enum format_switch_status uvc_format_status_check(void);

【参数】

无。

【返回值】

返回值

描述

FORMAT_SWITCH_FINISH

格式切换完成。

FORMAT_SWITCH_PENDING

格式切换中。

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

无。

【举例】

无。

【相关主题】

uvc_format_info_get

hi_camera_register_cmd

【描述】

应用注册UVC扩展命令,提供底层驱动调用。

【语法】

int hi_camera_register_cmd(const struct uvc_camera_cmd *cmd);

【参数】

参数名称

描述

输入/输出

cmd

UVC扩展命令:

struct uvc_camera_cmd {
    uint32_t id;        // 事件码
    uint8_t name[32]; // 名称
    camera_control_func uvc_control_func;
    // 处理函数指针信息的数组
};
typedef uint32_t (*camera_control_func)(void *buf, uint32_t len, uint32_t event_id, uint32_t cmd_type);

输入

【返回值】

返回值

描述

0

成功

-1

失败

【需求】

  • 头文件:f_uvc.h

  • 库文件:libusb_base.a

【注意】

无。

【举例】

无。

【相关主题】

无。

uac_wait_host

【描述】

等待USB HOST连接。

【语法】

int uac_wait_host(int wait_option);

【参数】

参数名称

描述

输入/输出

wait_option

等待连接时的状态:

  • UAC_WAIT_HOST_NOP:立即返回当前状态;
  • UAC_WAIT_HOST_FOREVER:阻塞,直至连接再返回。

输入

【返回值】

返回值

描述

UAC_OK

成功。

UAC_ERROR_NOMATCH

状态不匹配。

UAC_ERROR_PTR

指针非法。

UAC_ERROR_VALUE

参数非法。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

fuac_send_message

fuac_reqbuf_init

【描述】

初始化UAC(音频)内存池。

【语法】

int fuac_reqbuf_init(uint32_t buf_count, uint32_t data_len);

【参数】

参数名称

描述

输入/输出

buf_count

音频内存缓存的个数。

输入

data_len

每个音频内存的长度。

输入

【返回值】

返回值

描述

UAC_OK

成功。

UAC_ERROR_MEMORY

内存不足。

UAC_ERROR_PTR

参数非法。

UAC_ERROR_VALUE

不正确的值。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

不能重复初始化,已初始化的内存池必须先调用释放接口再去初始化。

【举例】

无。

【相关主题】

fuac_reqbuf_deinit

fuac_reqbuf_deinit

【描述】

释放UAC(音频)内存池。

【语法】

int fuac_reqbuf_deinit(void);

【参数】

无。

【返回值】

返回值

描述

UAC_OK

成功。

UAC_ERROR_VALUE

不正确的值。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

释放之前必须先把申请到的内存全部归还给USB层,才能去释放,否则失败。一旦出现采样率或者声道数或者位深出现切换,则必须要释放内存然后重新初始化内存池。

【举例】

无。

【相关主题】

fuac_reqbuf_init

fuac_reqbuf_get

【描述】

获取空闲的UAC(音频)内存。

【语法】

uint8_t *fuac_reqbuf_get(uint32_t *buf_index);

【参数】

参数名称

描述

输入/输出

buf_index

获取到音频内存对应的索引。

输出

【返回值】

返回值

描述

UINTPTR

音频内存对应的地址。

NULL

空指针。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

不使用音频内存时,必须调用fuac_send_message接口,把获取到的内存归还给USB。

【举例】

无。

【相关主题】

fuac_send_message

fuac_send_message

【描述】

音频数据传输发送。

【语法】

int fuac_send_message(const uint8_t *buf, uint32_t len, uint32_t buf_index);

【参数】

参数名称

描述

输入/输出

buf

待传输的音频数据。

输入

len

待传输的音频数据长度。

输入

buf_index

待传输的音频数据buf的索引号。

输入

【返回值】

返回值

描述

UAC_OK

成功

UAC_ERROR_PTR

指针非法

UAC_ERROR_VALUE

不正确的值

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

入参buf_index是通过调用fuac_reqbuf_get接口返回值的buf_index。fuac_reqbuf_get和fuac_send_message接口一一对应,顺序申请和顺序发送,不要乱序发送,否则Host出现声音不正常。

【举例】

无。

【相关主题】

fuac_reqbuf_get

fuac_receive_message

【描述】

音频数据传输接收。

【语法】

int fuac_receive_message(void *buf, int len, int timeout);

【参数】

参数名称

描述

输入/输出

buf

待接收的音频数据缓存。

输入

len

接收音频数据缓存大小。

输入

timeout

接收音频数据的超时时间。

输入

【返回值】

返回值

描述

int

接收音频数据的实际大小。

-UAC_ERROR_MEMORY

内存不足。

-UAC_ERROR_PTR

指针非法。

-UAC_ERROR_DISCONNECT

设备已断开。

-UAC_ERROR_TIMEOUT

等待超时。

-UAC_ERROR_EVENT

错误事件。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

fuac_opts_set

【描述】

设置UAC1.0音频参数。

【语法】

int fuac_opts_set(const struct uac_opts *opts);

【参数】

参数名称

描述

输入/输出

opts

音频参数结构体。

输入

【返回值】

返回值

描述

0

成功。

-1

空指针。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

fuac_get_opts

fuac_get_opts

【描述】

获取UAC1.0音频参数。

【语法】

int fuac_get_opts(struct uac_opts *opts);

【参数】

参数名称

描述

输入/输出

opts

音频参数结构体。

输出

【返回值】

返回值

描述

0

成功。

-UAC_ERROR_PTR

指针非法。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

fuac_opts_set

fuac_rate_get

【描述】

获取uac1.0音频采样率。

【语法】

uint32_t fuac_rate_get(void);

【参数】

【返回值】

返回值

描述

uint32_t类型数值

UAC1.0当前音频采样率。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

fuac2_get_opts

【描述】

获取音频2.0协议的参数,包括通道数等。

【语法】

int fuac2_get_opts(struct uac2_opts *opts);

【参数】

参数名称

描述

输入/输出

opts

当前的音频参数结构体。

输出

【返回值】

返回值

描述

0

成功。

-UAC_ERROR_PTR

空指针。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

fuac2_set_opts

fuac2_set_opts

【描述】

设置UAC2.0音频参数。

【语法】

int fuac2_set_opts(const struct uac2_opts *opts);

【参数】

参数名称

描述

输入/输出

opts

音频参数结构体。

输入

【返回值】

返回值

描述

0

成功。

-UAC_ERROR_PTR

空指针。

-UAC_ERROR_VALUE

不支持的参数设置。

【需求】

  • 头文件:f_uac.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

fuac2_get_opts

usb_dfu_init_env_entities

【描述】

初始化配置DFU接口功能,配置环境变量(目前只支持RAM)。

【语法】

int usb_dfu_init_env_entities(const char *type, char *envstr, const char *devstr);

【参数】

参数名称

描述

输入/输出

type

DFU功能,只支持RAM。

输入

envstr

DFU环境参数。

输入

devstr

未使用。

输入

【返回值】

返回值

描述

0

成功。

-1

失败。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_dfu_update_status

【描述】

获取升级事件。

【语法】

uint32_t usb_dfu_update_status(void);

【参数】

无。

【返回值】

返回值

描述

0

未启动升级。

1

升级中。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_dfu_update_size_get

【描述】

获取升级文件实际大小。

【语法】

uint64_t *usb_dfu_update_size_get(void);

【参数】

无。

【返回值】

返回值

描述

uint64_t *

升级文件实际大小的保存地址。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_dfu_get_entity

【描述】

获取列表中指定的DFU结构体变量。

【语法】

struct usb_dfu_entity *usb_dfu_get_entity(int alter);

【参数】

参数名称

描述

输入/输出

alter

获取列表中存储变量的序列号。

输入

【返回值】

返回值

描述

struct usb_dfu_entity *

DFU结构体变量,存储当前使用的DFU各个参数值,参见结构体声明。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_dfu_read

【描述】

获取指定DFU列表中的一定大小的缓冲数据。

【语法】

int usb_dfu_read(struct usb_dfu_entity *dfu, void *buf, int size, uint32_t blk_seq_num);

【参数】

参数名称

描述

输入/输出

dfu

获取缓冲数据所在的DFU列表结构信息,通过usb_dfu_get_entity获取。

输入

buf

接收缓存buffer。

输入

size

接收数据大小。

输入

blk_seq_num

获取的次数计数,须是连续自增值。

输入

【返回值】

返回值

描述

int

获取到的数据实际长度。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_dfu_free_entities

【描述】

释放DFU相关资源。

【语法】

void usb_dfu_free_entities(void)

【参数】

无。

【返回值】

无。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_dfu_wait_transmit_finish

【描述】

等待DFU传输完成。

【语法】

int usb_dfu_wait_transmit_finish(uint32_t timeout);

【参数】

参数名称

描述

输入/输出

timeout

等待超时时间。

输入

【返回值】

返回值

描述

0

传输未完成。

-1

协议未加载或传输错误。

1

传输完成。

【需求】

  • 头文件:f_dfu.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

hid_report_descriptor_info

【描述】

配置报告描述符信息,设置报告数据格式和功能。

【语法】

int hid_report_descriptor_info(const void *report_desc, size_t report_desc_len);

【参数】

参数名称

描述

输入/输出

report_desc

报告描述符信息。

输入

report_desc_len

报告描述符总大小。

输入

【返回值】

返回值

描述

0

成功。

-1

失败。

【需求】

  • 头文件:f_hid.h

  • 库文件:libusb_device.a

【注意】

报告描述符的定义要遵循标准HID协议,与VFS模块接口配套使用。

【举例】

请参考HID官方文档:《Device Class Definition for Human interface Devices》和《HID Usage Tables》。

【相关主题】

无。

hid_add_report_descriptor

【描述】

配置多个报告表描述符,设置报告表数据格式、功能和接口协议。

【语法】

int hid_add_report_descriptor(const uint8_t *report_desc, size_t report_desc_len, uint8_t protocol);

【参数】

参数名称

描述

输入/输出

report_desc

报告描述符信息。

输入

report_desc_len

报告描述符总大小。

输入

protocol

默认值为0,如果需要支持启动接口,需要传入1或者2,1为键盘;2为鼠标。

输入

【返回值】

返回值

描述

report_index

成功(对应的报表索引值)。

-1

失败。

【需求】

  • 头文件:f_hid.h

  • 库文件:libusb_device.a

【注意】

报告描述符的定义要遵循标准HID协议,与fhid_send_data和fhid_recv_data接口配套使用。

【举例】

请参考HID官方文档:《Device Class Definition for Human interface Devices》和《HID Usage Tables》。

【相关主题】

无。

fhid_send_data

【描述】

HID数据传输发送。

【语法】

ssize_t fhid_send_data (uint8_t report_index, const char *buf, size_t buflen);

【参数】

参数名称

描述

输入/输出

report_index

报表索引值,通过hid_add_report_descriptor接口返回值得到。

输入

buf

需要发送的HID数据。

输入

buflen

HID数据的总大小。

输入

【返回值】

返回值

描述

ssize_t

已发送HID数据的实际大小。

-1

失败。

【需求】

  • 头文件:f_hid.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

fhid_recv_data

【描述】

HID数据传输接收。

【语法】

ssize_t fhid_recv_data(uint8_t report_index, char *buf, size_t buflen);

【参数】

参数名称

描述

输入/输出

report_index

报表索引值,通过hid_add_report_descriptor接口返回值得到。

输入

buf

需要接收HID数据的缓存。

输入

buflen

接收HID数据缓存的长度。

输入

【返回值】

返回值

描述

ssize_t

获取到的数据实际长度。

-1

失败。

【需求】

  • 头文件:f_hid.h

  • 库文件:libusb_device.a

【注意】

由于此接口存在读数据阻塞机制,所以只能在任务中调用接口,不能在中断回调调用。

【举例】

无。

【相关主题】

无。

uvc_get_frame_buf

【描述】

获取帧缓冲区的地址。

【语法】

uint8_t *uvc_get_frame_buf(struct file *filep, uint32_t index, uint32_t offset);

【参数】

参数名称

描述

输入/输出

filep

字符设备文件句柄,通过文件描述符fd进行获取。

输入

index

当前缓存帧的索引号。

输入

offset

帧数据内容的起点,通过VIDIOC_QUERYBUF查询申请。

输入

【返回值】

返回值

描述

uint8_t *

返回帧缓冲区的地址。

【需求】

  • 头文件:usb_v4l2.h

  • 库文件:libusb_base.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_serial_ioctl

【描述】

往USB虚拟串口发送ioctl命令。

【语法】

int usb_serial_ioctl(uint32_t index, int cmd, unsigned long arg);

【参数】

参数名称

描述

输入/输出

index

虚拟串口索引,单串口时只能是0,双串口时可以输入0或1。

输入

cmd

待发送命令,目前只支持值为0x104的命令,发送后此命令后可以控制read接口是否挂起等待的读。

输入

arg

发送命令附带对应的参数,值为0x104命令附带的参数值,目前可以传入0和非0数,非0为控制读接口挂起等待读取数据;传0则是不挂起等待的读取数据。

输入

【返回值】

返回值

描述

0

发送命令成功。

-25

入参cmd为未知命令。

-22

USB虚拟串口正在卸载或者入参索引无效。

【需求】

  • 头文件:usbd_acm.h

  • 库文件:libusb_device.a

【注意】

【举例】

usb_serial_ioctl(0, 0x104, 1); // 建议使用值为0x104的宏表示cmd命令,最后一个入参传1,表示控制虚拟串口读取数据时,等待挂起,直到接收到数据为止

usb_serial_ioctl(0, 0x104, 0); // 最后一个入参传0,表示控制虚拟串口调用读接口读取数据时,不管有没有收到数据,立即返回读结果

【相关主题】

无。

usb_serial_read

【描述】

读取来自主机往USB虚拟串口发送的数据。

【语法】

ssize_t usb_serial_read(uint32_t index, char *buffer, size_t buflen);

【参数】

参数名称

描述

输入/输出

index

虚拟串口索引,单串口时只能是0,双串口时可以输入0或1。

输入

buffer

需要接收串口数据的缓存。

输入

buflen

接收到的串口数据的缓存长度。

输入

【返回值】

返回值

描述

ssize_t

已接收串口数据的缓存的实际大小。

-1

接收失败。

-22

入参有误。

【需求】

  • 头文件:usbd_acm.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

usb_serial_write

【描述】

往USB虚拟串口发送的数据。

【语法】

ssize_t usb_serial_write(uint32_t index, const char *buffer, size_t buflen);

【参数】

参数名称

描述

输入/输出

index

虚拟串口索引,单串口时只能是0,双串口时可以输入0或1。

输入

buffer

需要发送的USB虚拟串口数据。

输入

buflen

已发送的USB虚拟串口数据大小。

输入

【返回值】

返回值

描述

ssize_t

已发送USB虚拟串口数据大小。

-1

发送失败。

-22

入参有误。

【需求】

  • 头文件:usbd_acm.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

feth_send_data

【描述】

往USB虚拟网口发送的数据。

【语法】

void feth_send_data(const uint8_t *buf, uint32_t buflen);

【参数】

参数名称

描述

输入/输出

buf

需要发送给host网络数据。

输入

buflen

需要发送给host网络数据长度。

输入

【返回值】

无。

【需求】

  • 头文件:f_eth.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

feth_init_ops

【描述】

初始化USB虚拟网口与网络协议栈对接的钩子。

【语法】

int feth_init_ops(struct usb_eth_operations* ops);

【参数】

参数名称

描述

输入/输出

ops

与网络协议栈对接函数指针结构体。

输入

【返回值】

返回值

描述

0

初始化成功。

-1

初始化失败。

【需求】

  • 头文件:f_eth.h

  • 库文件:libusb_device.a

【注意】

无。

【举例】

无。

【相关主题】

无。

feth_change_connect_status

【描述】

控制虚拟网口断连重连接口。

【语法】

void feth_change_connect_status(bool connect);

【参数】

参数名称

描述

输入/输出

connect

设置虚拟网口断开或连接的参数,true表示连接,false表示断开。

输入

【返回值】

无。

【需求】

  • 头文件:f_eth.h

  • 库文件:libusb_device.a

【注意】

此接口只是通知host,虚拟网口需要断开或连接,至于host端有没有真的将虚拟网口断开或连接,属于host的行为,device端并不知晓。

【举例】

无。

【相关主题】

无。

UART驱动开发

概述

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器),在嵌入式设计中,UART用来与PC进行通信,包括与监控调试器和其它器件,如EEPROM通信。

Huawei_LiteOS UART驱动模块的基本框架包括文件适配层、驱动抽象层、驱动实现层。

  • 文件适配层:采用LOSCFG_FS_VFS管控,可裁剪,提供通过文件方式控制uart。

  • 驱动抽象层:提供对外接口,提供通过struct LosDevice设备文件方式控制uart。

  • 驱动实现层:uart控制器硬件实现层,不提供对外接口。

图 1 UART驱动基本框架

运作机制

UART驱动主要开发流程如下:

  1. UART驱动加载:

    UART驱动的加载由驱动抽象层提供加载接口,系统在启动过程中注册驱动。

  2. UART设备加载:

    UART设备由系统根据需要及实际情况进行加载。

  3. UART数据收发:

    驱动抽象层提供裸读写接口,文件适配层提供VFS文件方式读写。

开发指导

裸读写UART

UART驱动抽象层提供如下的接口:

接口名

描述

struct LosDevice *UartDevGetByPort(int num)

通过串口号获取UART设备。

int UartOpen(struct LosDevice *dev)

打开指定的UART设备。

int UartClose(struct LosDevice *dev)

关闭指定的UART设备。

ssize_t UartRead(struct LosDevice *dev, char *buf, size_t count)

读取指定的UART设备的数据。

ssize_t UartWrite(struct LosDevice *dev, const char *buf, size_t count)

向指定的UART设备写数据。

int UartIoctl(struct LosDevice *dev, int cmd, unsigned long arg)

控制指定的UART设备(具体支持的控制方式由UART控制器的驱动实现层提供)。

int UartSuspend(struct LosDevice *dev)

挂起指定的UART设备。

int UartResume(struct LosDevice *dev)

恢复指定的UART设备。

VOID UartEarlyInit(VOID);

串口初始化。

VOID UartPuts(const CHAR *s, UINT32 len, BOOL isLock);

串口输出(系统默认)。

VOID UartPutsReg(UINTPTR regBase, const CHAR *s, UINT32 len, BOOL isLock);

串口输出(用户指定基地址)。

文件方式读写UART

参考“VFS”,支持open、close、read、write、ioctl操作。

模块配置

打开菜单,进入Driver → Uart Type菜单,完成UART模块的配置。

配置项

描述

取值范围

默认值

依赖

LOSCFG_DRIVERS_SIMPLE_UART

简单的UART,只支持输出。

YES/NO

YES

LOSCFG_DRIVERS_UART

完整的UART,支持输入输出。

YES/NO

NO

LOSCFG_DRIVERS_BASE

进入Driver → Main Uart Driver菜单,选择用于串口输出的默认串口驱动,可以选择如下配置:

配置项

描述

依赖

LOSCFG_DRIVERS_UART_MAIN_ARM_PL011

使用ARM PL011作为默认串口

LOSCFG_DRIVERS_UART_ARM_PL011

LOSCFG_DRIVERS_UART_MAIN_HQ_UART

使用HQ UART系列作为默认串口

LOSCFG_DRIVERS_UART_HQ_UART

LOSCFG_DRIVERS_UART_MAIN_VUART_RPQ

使用虚拟UART作为默认串口

LOSCFG_DRIVERS_UART_VUART_RPQ

编程实例

simple uart

使能LOSCFG_DRIVERS_SIMPLE_UART,simple uart只支持输出。

general uart

使能LOSCFG_DRIVERS_UART,general uart既支持输出,也支持输入。

裸读写UART

实例描述

  1. 通过串口号获取UART设备。

  2. 打开串口。

  3. 写串口。

  4. 挂起串口。

  5. 恢复串口。

  6. 关闭串口。

编程示例

#define BUF_SIZE 100

UINT32 TestCase(VOID)
{
    struct LosDevice *uartDev = NULL;
    int ret;
    ssize_t rtRet;
    char dataBuf[BUF_SIZE] = {"test uart\n"};

    uartDev = UartDevGetByPort(0);
    if (uartDev == NULL) {
        return LOS_NOK;
    }

    ret = UartOpen(uartDev);
    if (ret != 0) {
        return LOS_NOK;
    }

    rtRet = UartWrite(uartDev, dataBuf, BUF_SIZE);
    if (rtRet <= 0) {
        goto OUT_CLOSE;
    }

    ret = UartSuspend(uartDev);
    if (ret != 0) {
        goto OUT_CLOSE;
    }

    ret = UartResume(uartDev);
    if (ret != 0) {
        goto OUT_CLOSE;
    }

    ret = UartClose(uartDev);
    if (ret != 0) {
        return LOS_NOK;
    }

    return LOS_OK;

OUT_CLOSE:
    (VOID)UartClose(uartDev);
    return LOS_NOK;
}
文件读写UART

实例描述

  1. 使能LOSCFG_FS_VFS。

  2. 打开串口。

  3. 写串口。

  4. 挂起串口。

  5. 恢复串口。

  6. 关闭串口。

编程示例

#define BUF_SIZE 100
#define UART_DEV_PATH      "/dev/uartdev-0"

UINT32 TestCase(VOID)
{
    int ret;
    int fd;
    char dataBuf[BUF_SIZE] = {"test uart\n"};
    ssize_t writeLen;

    fd = open(UART_DEV_PATH, O_RDWR);
    if (fd == -1) {
        return LOS_NOK;
    }

    writeLen = write(fd, dataBuf, BUF_SIZE);
    if (writeLen <= 0) {
        goto OUT_CLOSE;
    }

    ret = ioctl(fd, Pl011_IOCTL_SUSPEND, 0);
    if (ret != 0) {
        goto OUT_CLOSE;
    }

    ret = ioctl(fd, Pl011_IOCTL_RESUME, 0);
    if (ret != 0) {
        goto OUT_CLOSE;
    }

    ret = close(fd);
    if (ret != 0) {
        return LOS_NOK;
    }

    return LOS_OK;

OUT_CLOSE:
    (VOID)close(fd);
    return LOS_NOK;
}

配置工具

概述

LiteOS使用Kconfig文件配置系统。所用的Kconfig语言是一种菜单配置语言,config.in和Kconfig都由该语言编写而成。LiteOS使用python kconfiglib来解析、展示Kconfig文件。解析Kconfig文件后,会在根目录下生成/更新“.config”文件,同时在开发板的include文件夹下生成“menuconfig.h”。使用menuconfig前,需要先安装python和kconfiglib。

安装工具

  • 安装python 2.7/3.2+。

    下面以python3.10为例介绍安装方法。

    • 命令行方式安装:

      sudo apt-get install python3.10
      
    • 源码包编译安装:

      1. 通过官网下载python源码包

      2. 解压源码包。

        参考如下命令完成解压,将压缩包名替换为实际下载的源码包名:

        tar -xf Python-3.10.2.tgz
        
      3. 检查依赖。

        解压后进入到目录中,执行“./configure”命令以检查编译与安装python所需的依赖:

        cd Python-3.10.2
        ./configure
        

        如果没有报错就继续下一步操作,如果存在报错就根据提示安装依赖。

      4. 编译&安装python。

        sudo make
        sudo make install
        
      5. 检查python版本并正确链接python命令。

        python --version
        

        如果显示的不是刚刚安装的python版本,则需要执行以下命令来正确链接python命令。

        1. 获取python目录,例如对于python 3.10.2,执行如下命令。

          which python3.10
          
        2. 链接python命令到刚刚安装的python包。

          将以下命令中的“python3.10-path“替换为“which python3.10”命令执行后的回显路径:

          cd /usr/bin
          sudo rm python3 && sudo ln -s "python3.10-path" python3
          sudo rm python && sudo ln -s python3 python
          
          
        3. 再次检查python版本。

          python --version
          python3 --version
          
  • 安装pip包管理工具。

    如果pip命令不存在,可以下载pip源码包进行安装。pip依赖setuptools,如果setuptools不存在,也需要安装。

    • 命令行方式安装:

      sudo apt-get install python3-setuptools python3-pip -y
      sudo pip3 install --upgrade pip
      
    • 源码包方式安装:

      1. 安装setuptools。

        点击setuptools源代码包下载地址,可以参考下面的命令进行安装:

        sudo unzip setuptools-50.3.2.zip
        cd setuptools
        sudo python setup.py install
        

        须知: setuptools最新版本不支持python 2.7,如果使用python 2.7,请下载setuptools 45.0.0版本以支持python 2.7。

      2. 安装pip。

        点击pip源代码包下载地址,可以参考下面的命令进行安装:

        sudo tar -xf pip-20.2.4.tar.gz
        cd pip-20.2.4
        sudo python setup.py install
        
  • 安装kconfiglib库。

    • 对于服务器可以联网的情况。

      可以直接使用如下命令安装kconfiglib:

      sudo pip install kconfiglib
      
    • 对于服务器不能联网的情况。

      可以采用离线的方式安装。首先在其他能联网的环境上下载kconfiglib,可以下载kconfiglib的wheel文件“kconfiglib-14.1.0-py2.py3-none-any.whl”或源代码文件“kconfiglib-14.1.0.tar.gz”,这里以14.1.0版本为例。

      1. wheel文件的安装,可以参考如下命令:

        sudo pip install kconfiglib-14.1.0-py2.py3-none-any.whl
        
      2. 源代码文件的安装,可以参考如下命令:

        sudo tar -zxvf kconfiglib-14.1.0.tar.gz
        cd kconfiglib-14.1.0
        sudo python setup.py install
        

使用指导

使用menuconfig配置工具前,请确保已经安装编译LiteOS的交叉编译器工具链,并加入环境变量。

为满足不同的使用场景,配置工具支持下述命令。根据使用场景,在根目录下执行下述其中一个命令即可。在执行命令前,先根据开发板拷贝“self_src/tools/build/config/”目录下的默认配置文件“${platform}.config”到根目录,并重命名为“.config”。除了“.config”文件外,还有一个重要的配置文件“build/menuconfig/config.in”文件(该文件包含了各个模块的Kconfig文件)。“.config”文件决定了各个配置项的默认值,“config.in”文件决定了展示图形化界面的配置项。

保存savemenuconfig菜单

对于Make编译工具:在LiteOS根目录下执行“make savemenuconfig”命令会解析根目录下的“.config”文件,并在对应的LiteOS开发板工程的子目录include下生成“menuconfig.h”文件。

对于CMake编译工具:在LiteOS根目录下执行“./build/build_cmake.sh savemenuconfig”会解析根目录下的“.config”文件,并在对应的LiteOS开发板工程的子目录“buildcmakeout/xxx/”目录下生成“menuconfig.h”文件。

make defconfig

在LiteOS根目录下执行“make defconfig”命令会解析根目录下的“.config”文件(cmake当前不支持该功能),使所有的配置项尽可能使用其默认配置,并更新“.config”,同时在对应的LiteOS开发板工程的子目录include下生成“menuconfig.h”。

make allyesconfig

在LiteOS根目录下执行“make allyesconfig”命令会解析根目录下的“.config”文件(cmake当前不支持该功能),使所有的配置项尽可能使能,即设置为Y,并更新“.config”,同时在对应的LiteOS开发板工程的子目录include下生成“menuconfig.h”。

make allnoconfig

在LiteOS根目录下执行“make allnoconfig”命令会解析根目录下的“.config”文件(cmake当前不支持该功能),使所有的配置项尽可能禁用,即设置为“is not set”,并更新“.config”,同时在对应的LiteOS开发板工程的子目录include下生成“menuconfig.h”。

配置项说明

配置项简介

运行“make menuconfig”,进入LiteOS Configuration主界面,目前有Compiler、Targets、Kernel、Debug、SSP等选项。下面对Compiler,Targets和SSP选项进行介绍,其余选项在各个模块章节中均有介绍,这里不再赘述。

Compiler配置

Compiler选项中包含了两个子菜单:Compiler交叉编译器工具链和Optimize Option编译优化选项。

  • 现有多种交叉编译器可选:包括“arm-himix410-linux”和“arm-none-eabi-”等,请选择实际使用的编译器。

  • 有三种编译优化选项可选:Optimize None(不做编译优化)、Optimize Speed(优化编译速度)和Optimize Size(优化编译体积)。

Targets配置

Targets选项中包含Family(产品)、Board(芯片)、Enable Floating Pointer Unit(使能浮点数)、Enable Access Permission Control(使能访问权限控制)。

  • Family:选择产品,其中BVT、DPT、AIOT等都是产品名,SECRET是保密项目,QEMU和STM32是LiteOS内部使用的。

  • Board:选择具体芯片,目前支持Hi3161、Hi3372、Hi3322,Hi1A21等。

  • Enable Access Permission Control:使能访问权限控制,可以根据不同架构使能对应的内存管理单元,包含RiscV下的PMP。

配置项描述

打开菜单,进入Targets菜单,相关配置项介绍如下,可根据配置项名称搜到其具体所在位置。

配置项

含义

取值范围

默认值

架构

LOSCFG_ARCH_SECURE_MONITOR_MODE

令OS处于EL3运行

YES/NO

NO

ARM64

LOSCFG_ARCH_NON_SECURE_OS_MODE

令OS处于非安全Guest OS模式下运行(EL1-NS)

YES/NO

NO

LOSCFG_ARCH_SECURE_OS_MODE

令OS处于安全Guest OS模式下运行(EL1-S)

YES/NO

NO

LOSCFG_ARCH_ARM_AARCH64_LSE_ATOMIC

使能ARMv8.1-LSE Atomic指令集扩展

YES/NO

YES

LOSCFG_ARCH_ARM_SVE2

使能ARMv8.2 SVE并行化功能

YES/NO

NO

ARM64

须知: OS运行模式需要target进行适配,具体可以使用的模式以交付的模式为准。

SSP配置

选择“Stack Smashing Protector (SSP) Compiler Feature”选项,该选项可以打开和关闭堆栈保护功能。

  • -fno-stack-protector:关闭堆栈保护。

  • -fstack-protector:启用堆栈保护,若C或C++函数的局部变量中含有字符(char)数组(数组大小大于等于4个字节的字符数组),并且不会被优化掉,编译器会为函数插入保护代码。

  • -fstack-protector-strong:启用堆栈保护,若C或C++函数中有数组定义并且不会被优化掉,或者存在局部变量的地址引用时,编译器会为函数插入保护代码(默认选项)。

  • -fstack-protector-all:为所有C或C++函数插入保护代码,相比上一个,可能会大幅增加性能开销。

    推荐使用“-fstack-protector”选项,在兼顾性能的同时,增强了安全性。

  • ArchStackGuardInit:liteos值提供伪随机数金丝雀值,需要厂商适配真随机,以保证安全性。

须知: LiteOS提供弱函数ArchStackGuardInit,采用伪随机数方式初始化“__stack_chk_guard”,如果用户需要达到真随机数,需重定义ArchStackGuardInit。另外部分架构没有随机种子,无法达成伪随机数,因此,用户必须自行验证“__stack_chk_guard”的随机性。

NX配置

“Enable Data Sec NX Feature”选项可以打开和关闭数据段不可执行功能。

  • 选择该选项建议配合使用系统提供的链接脚本,不使用系统链接脚本时需要参考系统链接脚本编写私有链接脚本。

  • 该选项只在支持MMU的架构上起效,不支持MMU的架构请在初始化过程中自行实现NX。

可维可测

Shell

概述

基本概念

LiteOS提供Shell命令行,它能够以命令行交互的方式访问操作系统的功能或服务:它接收并解析用户输入的命令,并处理操作系统的输出结果。

LiteOS Shell

LiteOS提供的Shell作为在线调试工具,支持常用的基本调试功能,包含系统、文件、网络、动态加载和Trace等相关命令。同时LiteOS的Shell可以根据需求新增定制命令。

  • 系统相关命令:可以查询系统任务、内核信号量、系统软件定时器、CPU占用率、当前中断等相关信息,详见“系统命令参考”。

  • 文件相关命令:除了支持基本的ls、cd等功能外,还支持sync。sync用来同步缓存数据(文件系统的数据)到SD卡或Flash中,详见“文件命令参考”。

  • 动态加载相关命令:加载obj文件或so文件,并通过查找已加载obj或so的函数地址,直接调用相关函数,同时用户也可以卸载obj文件或so文件。

  • Trace相关命令:可以开启/停止Trace、设置Trace事件、输出Trace缓冲区数据等,详见“Trace命令参考”。

开发指导

使用场景

Shell命令可以通过串口,Telnet或者自定义工具输入。新增定制的命令,需重新编译链接后才能执行。

功能

  1. LiteOS提供的Shell命令参见后面“系统命令参考”章节。

  2. LiteOS的Shell模块为用户提供下面几个接口,接口详细信息可以查看API参考。

    功能分类

    接口名

    描述

    注册命令

    SHELLCMD_ENTRY

    静态注册命令

    osCmdReg

    动态注册命令

    说明: 静态注册命令方式一般用于注册系统常用命令,动态注册命令方式一般用于注册用户命令。 静态注册命令有5个入参,动态注册命令有4个入参。下面除去第一个入参是静态注册独有的,剩余的四个入参两个注册命令是一致的。

    • 第一个入参:命令变量名,用于设置链接选项(“build/make/liteos_tables_ldflags.mk”的LITEOS_TABLES_LDFLAGS变量)。例如变量名为ls_shellcmd,链接选项就应该设置为:LITEOS_TABLES_LDFLAGS += -uls_shellcmd。这个入参是静态注册独有的,动态注册中没有这个入参。

    • 第二个入参:命令类型,目前支持两种命令类型。

    • CMD_TYPE_EX:不支持标准命令参数输入,会把用户填写的命令关键字屏蔽掉。例如:输入“ls /ramfs”,传入给命令处理函数的参数只有“/ramfs”,对应于命令处理函数中的argv[0],而ls命令关键字并不会被传入。

    • CMD_TYPE_STD:支持的标准命令参数输入,所有输入的字符都会通过命令解析后被传入。例如:输入“ls /ramfs”,“ls”和“/ramfs”都会被传入命令处理函数,分别对应于命令处理函数中的argv[0]和argv[1]。

    • 第三个入参:命令关键字,是命令处理函数在Shell中对应的名称。命令关键字必须唯一,即两个不同的命令项不能有相同的命令关键字,否则只会执行其中一个。Shell在执行用户命令时,如果存在多个命令关键字相同的命令,只会执行在“help”命令中排在最前面的那个。

    • 第四个入参:命令处理函数的入参最大个数。

    • 静态注册命令暂不支持设置。

    • 动态注册命令支持设置不超过32的入参最大个数,或者设置为XARGS(其在代码中被定义为0xFFFFFFFF)表示不限制参数个数。

    • 第五个入参:命令处理函数名,即在Shell中执行命令时被调用的函数。

配置项

打开menuconfig,进入Debug ---> Enable a Debug Version ---> Enable Shell菜单,完成Shell的配置。

配置项

含义

取值范围

默认值

依赖

LOSCFG_SHELL

Shell功能的裁剪开关

YES/NO

YES

LOSCFG_DEBUG_VERSION(=y) && LOSCFG_DRIVERS_UART(=y)

LOSCFG_SHELL_CONSOLE(开源版本无该配置项)

设置Shell直接与Console交互

YES/NO

YES

LOSCFG_SHELL(=y)

LOSCFG_SHELL_UART

设置Shell直接与uart驱动交互

YES/NO

NO

LOSCFG_DRIVERS_UART(=y)

LOSCFG_SHELL_VENDOR

设置Shell使用自定义方式交互

YES/NO

NO

LOSCFG_SHELL(=y)

新增命令开发流程

下面以注册系统命令ls为例,介绍新增Shell命令的典型开发流程。

  1. 定义Shell命令处理函数。

    Shell命令处理函数用于处理注册的命令。例如定义一个命令处理函数osShellCmdLs,处理ls命令,并在头文件中声明命令处理函数原型。

    int osShellCmdLs(int argc, const char **argv);
    

    须知: 命令处理函数的参数与C语言中main函数的参数类似,包括两个入参:

    • argc:Shell命令的参数个数。个数中是否包括命令关键字,和注册命令时的命令类型有关。

    • argv:为指针数组,每个元素指向一个字符串,该字符串就是执行shell命令时传入命令处理函数的参数。参数中是否包括命令关键字,和注册命令时的命令类型有关。

  2. 注册命令。

    有静态注册命令和系统运行时动态注册命令两种注册方式。

    • 静态注册ls命令:

      #include "shcmd.h"
      SHELLCMD_ENTRY(ls_shellcmd, CMD_TYPE_EX, "ls", XARGS, (CMD_CBK_FUNC)osShellCmdLs);
      
    • 动态注册ls命令:

      #include "shell.h"
      osCmdReg(CMD_TYPE_EX, "ls", XARGS, (CMD_CBK_FUNC)osShellCmdLs);
      
  3. 对于静态注册命令方式,在“build/make/liteos_tables_ldflags.mk”中设置链接选项(LITEOS_TABLES_LDFLAGS变量)。

  4. 打开菜单使能Shell,详见配置项

  5. 编译烧录系统后,可以执行新增的Shell命令。

注意事项

  • 默认模式下支持英文输入。如果在UTF8格式下输入了中文字符,只能通过回退三次来删除。

  • 支持Shell命令、文件名及目录名的Tab键联想补全,若有多个匹配项则补全共同字符,并打印多个匹配项。对于过多的匹配项(打印多于24行),将会进行打印询问(Display all num possibilities?(y/n)),可输入y选择全部打印,或输入n退出打印,选择全部打印并打印超过24行后,会进行“--More--”提示,此时回车键继续打印,q键退出(支持Ctrl+c退出)。

  • 不建议使用Shell命令操作“/dev”目录下的设备文件,可能会引起不可预知的结果。

  • Shell属于调测功能,默认配置为关闭,正式商用产品中禁止使用该功能。

  • 免责声明:华为不承担在正式商用产品中使用该功能的任何风险。

静态注册编程实例

实例描述

本实例演示如何使用静态注册命令方式新增一个名为test的Shell命令。编译工具以Make为例介绍 。

  1. 定义一个新增命令所要调用的命令处理函数cmd_test。

  2. 使用SHELLCMD_ENTRY函数添加新增命令项。

  3. 在liteos_tables_ldflags.mk中添加链接该新增命令项参数。

  4. 打开菜单使能Shell。

  5. 重新编译代码后运行。

编程示例

  1. 定义命令所要调用的命令处理函数cmd_test:

    #include "shell.h"
    #include "shcmd.h"
    
    int cmd_test(void)
    {
        printf("hello everybody!\n");
        return 0;
    }
    
  2. 添加新增命令项:

    SHELLCMD_ENTRY(test_shellcmd, CMD_TYPE_EX, "test", 0, (CMD_CBK_FUNC)cmd_test);
    
  3. 在链接选项中添加链接该新增命令项参数:

    在“build/make/liteos_tables_ldflags.mk”中LITEOS_TABLES_LDFLAGS项下添加“-utest_shellcmd”。

  4. 打开菜单使能Shell,即设置LOSCFG_SHELL=y。

  5. 重新编译代码:

    make clean;make
    

结果验证

烧录新系统镜像后,重启系统。使用help命令查看当前系统中所有注册的命令,可以看到test命令已经注册。

动态注册编程实例

实例描述

本实例演示如何使用动态注册命令方式新增一个名为test的Shell命令。编译工具以Make为例介绍 。

  1. 定义一个新增命令所要调用的命令处理函数cmd_test。

  2. 使用osCmdReg函数添加新增命令项。

  3. 打开菜单使能Shell。

  4. 重新编译代码后运行。

编程示例

  1. 定义命令所要调用的命令处理函数cmd_test:

    #include "shell.h"
    #include "shcmd.h"
    
    int cmd_test(void)
    {
        printf("hello everybody!\n");
        return 0;
    }
    
  2. 在app_init函数中调用osCmdReg函数动态注册命令:

    void app_init(void)
    {
         ....
         ....
         osCmdReg(CMD_TYPE_EX, "test", 0,(CMD_CBK_FUNC)cmd_test);
         ....
    }
    
  3. 打开菜单使能Shell,即设置LOSCFG_SHELL=y。

  4. 重新编译代码:

    make clean;make
    

结果验证

烧录新系统镜像后,重启系统。使用help命令查看当前系统中所有注册的命令,可以看到test命令已经注册。

系统命令参考

使能系统命令

使用Shell中的系统命令前,需要打开menuconfig使能Shell,详见“配置项”。

help

命令功能

help命令用于显示当前操作系统内所有的Shell命令。

命令格式

help

使用实例

举例:输入“help”。

输出说明

执行help,输出当前系统内的所有Shell命令:

LiteOS # help
*******************shell commands:*************************

cpup          date          dlock         dmesg         free          help          hwi
log           memcheck      mutex         queue         sem           stack         swtmr
systeminfo    task          uname         watch

date

命令功能

date命令用于查询及设置系统时间。

命令格式

date

date --help

date +Format

date -s YY/MM/DD

date -s hh:mm:ss

date -r Filename(开源版本暂不支持该命令)

参数说明

参数

参数说明

取值范围

--help

使用帮助

N/A

+Format

根据Format格式打印时间

--help中列出的占位符

-s YY/MM/DD

设置系统时间,用“/”分割的年月日

>= 1970/01/01

-s hh:mm:ss

设置系统时间,用“:”分割的时分秒

N/A

-r Filename

查询指定文件的修改时间,需要使能LOSCFG_FS_VFS

N/A

使用指南

  • 该命令依赖于LOSCFG_SHELL_EXTENDED_CMDS,该宏开关可在menuconfig菜单项中开启“Enable Shell Ext CMDs”使能。

    Debug  ---> Enable a Debug Version ---> Enable Shell ---> Enable Shell Ext CMDs
    
  • date参数缺省时,默认显示当前系统时间。

  • --help、+Format、-s、-r不能混合使用。

使用实例

举例:

输入“date +%Y--%m--%d”。

输出说明

执行“date +%Y--%m--%d”,按其指定格式打印当前系统时间:

LiteOS # date +%Y--%m--%d
2021--01--20

uname

命令功能

uname命令用于显示操作系统的名称、系统编译时间、版本信息等。

命令格式

uname [-a | -s | -t | -v | --help]

参数说明

参数

参数说明

-a

显示全部信息。

-s

显示操作系统名称。

-t

显示系统的编译时间

-v

显示版本信息。

--help

显示uname命令的帮助信息。

使用指南

  • 参数缺省时,默认显示操作系统名称。

  • uname的参数不能混合使用。

使用实例

举例:

输入“uname -a”。

输出说明

执行“uname -a”,获取系统信息:

LiteOS # uname -a
LiteOS V200R002C00SPC050B011 LiteOS 3.2.2 Mar 30 2019 16:09:28

task

命令功能

task命令用于查询系统任务信息。

命令格式

task_ _[ID]

参数说明

参数

参数说明

取值范围

ID

任务ID号,输入形式以十进制表示或十六进制表示皆可。

[0, 0xFFFFFFFF]

使用指南

  • 参数缺省时,默认打印全部运行任务信息。

  • task后加ID,当ID参数在[0, 64]范围内时,返回指定ID号任务的任务名、任务ID、任务的调用栈信息(最大支持15层调用栈),其他取值时返回参数错误的提示。如果指定ID号对应的任务未创建,则提示。

  • 如果在task命令中,发现任务是Invalid状态,请确保pthread_create创建函数时有进行如下操作之一,否则资源无法正常回收。

    • 选择的是阻塞模式应该调用pthread_join()函数。

    • 选择的是非阻塞模式应该调用pthread_detach()函数。

    • 如果不想调用前面两个接口,就需要设置pthread_attr_t状态为PTHREAD_STATE_DETACHED,将attr参数传入pthread_create,此设置和调用pthread_detach函数一样,都是非阻塞模式。

使用实例

举例1:输入“task 6”。

举例2:输入“task”。

输出说明

执行“task 0xb”,查询ID号为b的任务信息:

LiteOS # task 0xb
TaskName = SerialEntryTask
TaskId = 0xb
*******backtrace begin*******
traceback 0 -- lr = 0x1d804    fp = 0xa86bc
traceback 1 -- lr = 0x1da40    fp = 0xa86e4
traceback 2 -- lr = 0x20154    fp = 0xa86fc
traceback 3 -- lr = 0x258e4    fp = 0xa8714
traceback 4 -- lr = 0x242f4    fp = 0xa872c
traceback 5 -- lr = 0x123e4    fp = 0xa8754
traceback 6 -- lr = 0x2a9d8    fp = 0xb0b0b0b

执行“task”,查询所有任务信息:

LiteOS # task
Name          TaskEntryAddr    TID   Priority  Status     StackSize  WaterLine  StackPoint  TopOfStack EventMask SemID  CPUUSE  CPUUSE10s  CPUUSE1s MEMUSE
----          ------------     ---   -------   --------   ---------  --------   ----------  ---------- -------  -----  -------  ---------  ------  -------
Swt_Task          0x40002770   0x0    0        QueuePend  0x6000     0x2cc     0x4015a318  0x401544e8  0x0      0xffff    0.0       0.0     0.0     0
IdleCore000       0x40002dc8   0x1    31       Ready      0x400      0x15c     0x4015a7f4  0x4015a550  0x0      0xffff   98.6      98.2    99.9     0
system_wq         0x400b80fc   0x3    1        Pend       0x6000     0x244     0x40166928  0x40160ab8  0x1      0xffff    0.0       0.0     0.0     0
SerialShellTask   0x40090158   0x5    9        Running    0x3000     0x55c     0x40174918  0x40171e70  0xfff    0xffff    1.2       1.7     0.0     48
SerialEntryTask   0x4008fe30   0x6    9        Pend       0x1000      0x2c4    0x40175c78  0x40174e88  0x1      0xffff    0.0       0.0     0.0     72

说明:

  • 输出项说明:

  • Name:任务名。

  • TID:任务ID。

  • Priority:任务的优先级。

  • Status:任务当前的状态。

  • StackSize:任务栈大小。

  • WaterLine:该任务栈已经被使用的内存大小。

  • StackPoint:任务栈指针,表示栈的起始地址。

  • TopOfStack:栈顶的地址。

  • EventMask:当前任务的事件掩码,没有使用事件,则默认任务事件掩码为0(如果任务中使用多个事件,则显示的是最近使用的事件掩码)。

  • SemID:当前任务拥有的信号量ID,没有使用信号量,则默认0xFFFF(如果任务中使用了多个信号量,则显示的是最近使用的信号量ID)。

  • CPUUSE:系统启动以来的CPU占用率。

  • CPUUSE10s:系统最近10秒的CPU占用率。

  • CPUUSE1s:系统最近1秒的CPU占用率。

  • MEMUSE:截止到当前时间,任务所申请的内存大小,以字节为单位显示。MEMUSE仅针对系统内存池进行统计,不包括中断中处理的内存和任务启动之前的内存。 任务申请内存,MEMUSE会增加,任务释放内存,MEMUSE会减小,所以MEMUSE会有正值和负值的情况。

    1. MEMUSE为0:说明该任务没有申请内存,或者申请的内存和释放的内存相同。

    2. MEMUSE为正值:说明该任务中有内存未释放。

    3. MEMUSE为负值:说明该任务释放的内存大于申请的内存。

  • 系统任务说明(LiteOS系统初始任务有以下几种):

  • Swt_Task:软件定时器任务,用于处理软件定时器超时回调函数。

  • IdleCore000:系统空闲时执行的任务。

  • system_wq:系统默认工作队列处理任务。

  • SerialEntryTask:从底层buf读取用户的输入,初步解析命令,例如tab补全,方向键等。

  • SerialShellTask:接收命令后进一步解析,并查找匹配的命令处理函数,进行调用。

  • 任务状态说明:

  • Ready:任务处于就绪状态。

  • Pend:任务处于阻塞状态。

  • PendTime:阻塞的任务处于等待超时状态。

  • Suspend:任务处于挂起状态。

  • Running:该任务正在运行。

  • Delay:任务处于延时等待状态。

  • SuspendTime:挂起的任务处于等待超时状态。

  • Invalid:非上述任务状态。

free

命令功能

free命令可显示系统内存的使用情况,同时显示系统的text段、data段、rodata段、bss段大小。

命令格式

free [_-k _| -m]

参数说明

参数

参数说明

取值范围

无参数

以Byte为单位显示。

N/A

-k

以KByte为单位显示。

N/A

-m

以MByte为单位显示。

N/A

使用指南

  • 输入free显示内存使用情况,total表示系统动态内存池的总大小,used表示已使用的内存大小,free表示空闲的内存大小。text表示代码段大小,data表示数据段大小,rodata表示只读数据段大小,bss表示未初始化全局变量占用的内存大小。

  • free命令能够以三种单位来显示内存使用情况,包括Byte、KByte和MByte。

使用实例

举例:

分别输入“free”、“free -k”、“free -m”。

输出说明

以三种单位显示内存使用情况的输出:

LiteOS # free

        total        used          free
Mem:    117631744    31826864      85804880

        text         data          rodata        bss
Mem:    4116480      423656        1204224       6659316

LiteOS # free -k

        total        used          free
Mem:    114874       31080         83793

        text         data          rodata        bss
Mem:    4020         423           1176         6503

LiteOS # free -m

        total        used          free
Mem:    112          30            81

        text         data          rodata        bss
Mem:    3            0             1             6

memcheck

命令功能

检查动态申请的内存块是否完整,是否存在内存越界造成的节点损坏。

命令格式

memcheck

使用指南

  • 打开内存完整性检查开关。

    • 目前只有bestfit内存管理算法支持该命令,需要使能LOSCFG_KERNEL_MEM_BESTFIT。

      Kernel ---> Memory Management ---> Dynamic Memory Management Algorithm ---> bestfit
      
    • 该命令依赖于LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK,使用时需要在菜单项中开启“Enable integrity check or not”。

      Debug  ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable integrity check or not
      
  • 当内存池所有节点完整时,输出“memcheck over, all passed!”。

  • 当内存池存在节点不完整时,输出被损坏节点的内存块信息。

使用实例

举例:

输入“memcheck”。

输出说明

  • 没有内存越界时,执行“memcheck”输出内容如下:

    LiteOS # memcheck
    system memcheck over, all passed!
    
  • 发生内存越界时,执行“memcheck”输出内容如下:

    LiteOS # memcheck
    [ERR][OsMemIntegrityCheck], 1145, memory check error!
    freeNodeInfo.pstPrev:(nil) is out of legal mem range[0x80353540, 0x84000000]
    
    broken node head: (nil)  (nil)  (nil)  0x0, pre node head: 0x7fc6a31b  0x8  0x80395ccc  0x80000110
    
    ---------------------------------------------
     dump mem tmpNode:0x80395df4 ~ 0x80395e34
    
     0x80395df4 :00000000 00000000 00000000 00000000
     0x80395e04 :cacacaca cacacaca cacacaca cacacaca
     0x80395e14 :cacacaca cacacaca cacacaca cacacaca
     0x80395e24 :cacacaca cacacaca cacacaca cacacaca
    
    ---------------------------------------------
     dump mem :0x80395db4 ~ tmpNode:0x80395df4
    
     0x80395db4 :00000000 00000000 00000000 00000000
     0x80395dc4 :00000000 00000000 00000000 00000000
     0x80395dd4 :00000000 00000000 00000000 00000000
     0x80395de4 :00000000 00000000 00000000 00000000
    
    ---------------------------------------------
    cur node: 0x80395df4
    pre node: 0x80395ce4
    pre node was allocated by task:SerialShellTask
    cpu0 is in exc.
    cpu1 is running.
    excType:software interrupt
    taskName = SerialShellTask
    taskId = 8
    task stackSize = 12288
    system mem addr = 0x80353540
    excBuffAddr pc = 0x80210b78
    excBuffAddr lr = 0x80210b7c
    excBuffAddr sp = 0x803b2d50
    excBuffAddr fp = 0x80280368
    R0         = 0x59
    R1         = 0x600101d3
    R2         = 0x0
    R3         = 0x8027a300
    R4         = 0x1
    R5         = 0xa0010113
    R6         = 0x80395e04
    R7         = 0x80317254
    R8         = 0x803b2de4
    R9         = 0x4
    R10        = 0x803afca4
    R11        = 0x80280368
    R12        = 0x1
    CPSR       = 0x600101d3
    

    以上各关键输出项含义如下:

    • “mem check error”,表示检测到了内存节点被破坏。

    • “cur node:”,表示该节点内存被踩,并打印内存地址。

    • “pre node:”,表示被踩节点前面的节点,并打印节点地址。

    • “pre node was allocated by task:SerialShellTask”,表示在SerialShellTask任务中发生了踩内存。

    • “taskName”和“taskId”,分别表示发生异常的任务名和任务ID。

memused

命令功能

memused命令用于查看当前系统used节点中保存的函数调用栈LR信息。通过分析数据可检测内存泄露问题。

命令格式

memused

使用指南

打开menuconfig菜单开启内存泄漏检测。

  • 目前只有bestfit内存管理算法支持该功能,需要使能LOSCFG_KERNEL_MEM_BESTFIT。

    Kernel ---> Memory Management ---> Dynamic Memory Management Algorithm ---> bestfit
    
  • 该命令依赖于LOSCFG_MEM_LEAKCHECK,可以在菜单项中开启“Enable Function call stack of Mem operation recorded”:

    Debug  ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable Function call stack of Mem operation recorded
    

    开启该菜单项后,会在内存操作时,将函数调用关系LR记录到内存节点中,若相同调用栈的内存节点随时间增长而不断增多,则可能存在内存泄露,通过LR可以追溯内存申请的位置。重点关注LR重复的节点。

    须知: 此配置打开时,会影响内存占用和内存操作性能,因此仅在检测内存泄露问题时打开。

使用实例

举例:

输入“memused”。

输出说明

LiteOS # memused
node         LR[0]       LR[1]       LR[2]
0x802d79e4:  0x8011d740  0x8011a990  0x00000000
0x802daa0c:  0x8011d5ec  0x8011d740  0x8011a990
0x802dca28:  0x8006e6f4  0x8006e824  0x8011d5ec
0x802e2a48:  0x8014daac  0x8014db4c  0x800f6da0
0x802e8a68:  0x8011d274  0x8011d654  0x8011d740
0x802e8a94:  0x8014daac  0x8014db4c  0x8011d494
0x802eeab4:  0x8011d4e0  0x8011d658  0x8011d740
0x802f4ad4:  0x8015326c  0x80152a20  0x800702bc
0x802f4b48:  0x8015326c  0x80152a20  0x800702bc
0x802f4bac:  0x801504d8  0x801505d8  0x80150834
0x802f4c08:  0x801504d8  0x801505d8  0x80150834
0x802f4c74:  0x801504d8  0x801505d8  0x80150834
0x802f4e08:  0x801504d8  0x801505d8  0x80150834
0x802f4e60:  0x801030e8  0x801504d8  0x801505d8
0x802f4e88:  0x80103114  0x801504d8  0x801505d8
0x802f4eb4:  0x801504d8  0x801505d8  0x80150834
0x802f4f7c:  0x801504d8  0x801505d8  0x80150834
0x802f5044:  0x801504d8  0x801505d8  0x80150834
0x802f510c:  0x800702bc  0x80118f24  0x00000000

hwi

命令功能

hwi命令用于查询当前中断信息。

命令格式

hwi

hwi status all

hwi status hwiNum [devId]

参数说明

参数

参数说明

取值范围

status all

打印所有已创建的中断状态。

hwiNum

中断号,十进制。

[0,0xFFFFFFFF]

devId

共享中断设备号,十六进制,缺省时表示查询的中断为非共享中断。

[0,0xFFFFFFFF]

使用指南

  • 使能hwi命令,在菜单项打开宏开关LOSCFG_DEBUG_HWI:

    Debug ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Hwi Debugging
    
  • 输入“hwi”即显示当前中断号、中断次数及注册中断名称。

  • 输入“hwi status all”显示当前所有已创建中断的状态。

  • 输入“hwi status hwiNum [devId]”查询特定的中断状态;共享中断需要devId参数,非共享中断不需要参数devId。

  • 若打开宏开关LOSCFG_CPUP_INCLUDE_IRQ,hwi命令还会显示各个中断的处理时间(cycles)、CPU占用率以及中断类型。该宏开关可在菜单项中开启“Enable Cpup include irq”使能:

    Kernel ---> Enable Extend Kernel ---> Enable Cpup ---> Enable Cpup include irq
    

    关于该宏开关更详细的介绍,参见CPU占用率“开发流程”。

使用实例

举例:

输入“hwi”。

输入“hwi status hwiNum devId”。

输入“hwi status hwiNum”。

输入“hwi status all”。

输出说明

  1. 输入“hwi”,显示中断信息(LOSCFG_CPUP_INCLUDE_IRQ关闭)。

    LiteOS # hwi
    InterruptNo     Count     Name
    35:             1364:
    36:             0:
    40:             79:       uart_pl011
    
  2. 输入“hwi”,显示中断信息(LOSCFG_CPUP_INCLUDE_IRQ打开)。

    LiteOS # hwi
    InterruptNo Count   Name   CYCLECOST  CPUUSE CPUUSE10s CPUUSE1s mode
            3:  1333            122       0.0    0.0       0.0    normal
            4:  0               0         0.0    0.0       0.0    normal
            5:  59   uart_pl011 305       0.0    0.0       0.0    normal
           12:  96      ETH     131       0.0    0.0       0.0    normal
    
  3. 输入“hwi status 3 0x1”,显示中断号3,设备号0x1共享中断的状态。

    LiteOS # hwi status 3 0x1
    InterruptNo     DevId   Priority    Affinity    Created     Enable      Pending
    -----------     -----   --------    --------    -------     ------      -------
    3               0x1     20          0x0         0           1           0
    
  4. 输入“hwi status 3”,显示中断号3的非共享中断的状态。

    LiteOS # hwi status 3
    InterruptNo     DevId   Priority    Affinity    Created     Enable      Pending
    -----------     -----   --------    --------    -------     ------      -------
    3               0x0     20          0x0         0           1           0
    
  5. 输入“hwi status all”,显示所有已创建中断的状态。

    LiteOS # hwi status all
    InterruptNo     DevId   Priority    Affinity    Created     Enable      Pending
    -----------     -----   --------    --------    -------     ------      -------
    0               0x0     20          0x0         1           1           0
    1               0x0     20          0x0         1           1           0
    2               0x0     20          0x0         1           1           0
    30              0x0     20          0x0         1           1           0
    33              0x0     0           0x1         1           1           0
    

queue

命令功能

queue命令用于查看队列的使用情况。

命令格式

queue

使用指南

该命令依赖于LOSCFG_DEBUG_QUEUE,该宏开关可在菜单项中开启“Enable Queue Debugging”使能。

Debug ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Queue Debugging

使用实例

举例:

输入“queue”。

输出说明

执行“queue”后得到队列的使用情况:

LiteOS # queue
used queues information:
Queue ID <0x0> may leak, queue len is 0x10, readable cnt:0x0, writeable cnt:0x10, TaskEntry of creator:0x0x80007d5, Latest operation time: 0x614271

以上各输出项含义如下:

输出项

说明

Queue ID

队列编号。

queue len

队列消息节点个数。

readable cnt

队列中可读的消息个数。

writeable cnt

队列中可写的消息个数。

TaskEntry of creator

创建队列的接口地址。

Latest operation time

队列最后操作时间。

sem

命令功能

sem命令用于查询系统内核信号量的相关信息。

命令格式

sem [ID]

sem fulldata

参数说明

参数

参数说明

取值范围

ID

信号量ID号,输入形式以十进制表示或十六进制表示皆可。

[0, 0xFFFFFFFF]

fulldata

查询所有在用的信号量信息,打印信息包括如下:SemID、Count、Original Count、Creater (TaskEntry)、Last Access Time

N/A

使用指南

  • 参数缺省时,显示所有信号量的使用数及信号量总数。

  • sem后加ID,当ID参数在[0, 1023]范围内时,返回指定ID号的信号量使用数(如果指定ID号的信号量未被使用则提示),其他取值时返回参数错误的提示。

  • sem的参数ID和fulldata不可以混用。

  • 参数fulldata依赖LOSCFG_DEBUG_SEMAPHORE,该宏开关可在菜单项中开启“Enable Semaphore Debugging”使能。

    Debug  ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Semaphore Debugging
    

使用实例

举例1:输入“sem 1”。

举例2:输入“sem”和“sem fulldata”。

输出说明

执行“sem 1”,查询指令结果:

LiteOS # sem 1
   SemID       Count
   ----------  -----
   0x00000001      0x1
No task is pended on this semphore!

执行“sem”和“sem fulldata”,查询所有在用的信号量信息:

LiteOS # sem
   SemID       Count
   ----------  -----
   0x00000000  1
   SemID       Count
   ----------  -----
   0x00000001  1
   SemID       Count
   ----------  -----
   0x00000002  1
   SemUsingNum   : 3

LiteOS # sem fulldata
Used Semaphore List:
   SemID   Count   OriginalCout   Creater(TaskEntry)   LastAccessTime
   ------  -----   ------------   -----------------    --------------
   0x2     0x1     0x1            0x80164d70           0x3
   0x0     0x1     0x1            0x0                  0x3
   0x1     0x1     0x1            0x0                  0x3

mutex

命令功能

mutex命令用于查看互斥锁的使用情况。

命令格式

mutex

使用指南

该命令依赖于LOSCFG_DEBUG_MUTEX,该宏开关可在菜单项中开启“Enable Mutex Debugging”使能。

Debug  ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Mutex Debugging

使用实例

举例:

输入“mutex”。

输出说明

执行“mutex”输出互斥锁使用情况:

LiteOS # mutex
used mutexs information: 
Mutex ID <0x0> may leak, Owner is null, TaskEntry of creator: 0x8000711,Latest operation time: 0x0

以上各主要输出项含义如下:

输出项

说明

Mutex ID

锁序号。

TaskEntry of creator

创建锁的接口地址。

Latest operation time

任务最后调度时间。

swtmr

命令功能

swtmr命令用于查询系统软件定时器的相关信息。

命令格式

swtmr [ID]

参数说明

参数

参数说明

取值范围

ID

软件定时器ID号,输入形式以十进制表示或十六进制表示皆可。

[0,0xFFFFFFFF]

使用指南

  • 使能swtmr命令,在菜单项打开宏开关LOSCFG_DEBUG_SWTMR:

    Debug ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Swtmr Debugging
    
  • 参数缺省时,默认显示所有软件定时器的相关信息。

  • swtmr后加ID号时,当ID在[0, 当前软件定时器个数-1]范围内时,返回对应ID的软件定时器的相关信息,其他取值时返回错误提示。

使用实例

举例:

输入“swtmr”和“swtmr 0”。

输出说明

执行“swtmr”查询软件定时器相关信息,输出如下:

LiteOS # swtmr

SwTmrID     State    Mode    Interval  Arg         handlerAddr
----------  -------  ------- --------- ----------  --------
0x00000000  Ticking  Period   1000     0x00000000  0x800442d

执行“swtmr 0”查询ID为0的软件定时器信息,输出如下:

LiteOS # swtmr 0

SwTmrID     State    Mode    Interval  Arg         handlerAddr
----------  -------  ------- --------- ----------  --------
0x00000000  Ticking  Period   1000     0x00000000  0x800442d

输出项

说明

SwTmrID

软件定时器ID。

State

软件定时器状态。

Mode

软件定时器模式。

Interval

软件定时器使用的Tick数。

Arg

传入的参数。

handlerAddr

回调函数的地址。

systeminfo

命令功能

systeminfo命令用于显示当前操作系统的资源使用情况,包括任务、信号量、互斥锁、队列、软件定时器等。

对于信号量、互斥锁、队列和软件定时器,如果在系统镜像中已经裁剪了这些模块,那么说明系统没有使用这些资源,该命令也就不会显示这些资源的情况。

命令格式

systeminfo

使用实例

举例:

输入“systeminfo”。

输出说明

执行“systeminfo”查看系统资源使用情况,输出如下:

LiteOS # systeminfo

   Module    Used      Total
--------------------------------
   Task      5         16
   Sem       0         20
   Mutex     1         20
   Queue     1         10
   SwTmr     1         16

以上各输出项含义如下:

输出项

说明

Module

模块名称。

Used

当前使用量。

Total

最大可用量。

log

命令功能

log命令用于修改&查询系统的日志打印等级。

命令格式

log level [levelNum]

参数说明

参数

参数说明

取值范围

levelNum

设置日志打印等级。

[0x0,0x5]

须知: log命令的帮助信息中还显示了另外两个参数module和path,但当前暂不支持这两个参数。

使用指南

  • 该命令依赖于LOSCFG_SHELL_LK,可在菜单项中开启“Enable Shell lk”使能。

    Debug  ---> Enable a Debug Version ---> Enable Shell ---> Enable Shell lK
    
  • log level命令用于设置系统的日志打印等级,包括6个等级。

    TRACE_EMG = 0

    TRACE_COMMOM = 1

    TRACE_ERROR = 2

    TRACE_WARN = 3

    TRACE_INFO = 4

    TRACE_DEBUG = 5

  • 若log level命令不加levelNum参数,则提示命令的使用方法。

使用实例

举例:

输入“log level 4”。

输出说明

执行“log level 4”,设置系统的日志打印等级为INFO级别:

LiteOS # log level 4
Set current log level INFO

dmesg

命令功能

dmesg命令用于控制内核dmesg缓存区。

命令格式

dmesg

dmesg [-c | -C | -D | -E | -L | -U]

dmesg -s <size>

dmesg -l <level>

dmesg > file(开源版本暂不支持该命令)

参数说明

参数

参数说明

取值范围

-c

打印缓存区内容并清空缓存区,此打印不受-D、-L影响。

N/A

-C

清空缓存区。

N/A

-D | -E

关闭/开启控制台打印,开源版本暂不支持该参数。

N/A

-L | -U

关闭/开启串口打印,开源版本暂不支持该参数。

N/A

-s <size>

设置缓存区大小。

0-4294967279(0 - 0xffffffef)

-l <level>

设置缓存区的日志打印等级。

0 -- 5

> file

将缓存区内容写入文件,开源版本暂不支持该参数。

N/A

须知:

  1. 写入文件需确保已挂载文件系统。

  2. 关闭串口打印会影响Shell使用,建议先连接Telnet再尝试关闭串口。

使用指南

  • 该命令依赖于LOSCFG_SHELL_DMESG,使用时可在菜单项中开启“Enable Shell dmesg”。

    Debug  ---> Enable a Debug Version---> Enable Shell---> Enable Shell dmesg
    
  • 参数缺省时,默认打印缓存区内容。

  • 各参数均不能混合使用。

使用实例

举例:

输入“dmesg”后,接着输入“dmesg -C”和“dmesg”。

输出说明

第一次执行“dmesg”后,可以看到输出了缓存区内容。接着执行“dmesg -C”清空缓存区内容,紧接着再次执行“dmesg”可以看到之前缓存区中的内容已经被清空:

LiteOS # dmesg

LiteOS # log level 4
Set current log level INFO
LiteOS # dmesg

LiteOS # dmesg -C
LiteOS # dmesg

LiteOS # dmesg

须知: “dmesg -C”是日志维测命令,只支持串口输出,不支持通过console输出到其他输出端口(Telnet、USB等)。

stack

命令功能

stack命令用于显示当前操作系统内所有栈的信息。

命令格式

stack

使用指南

该功能默认开启。

使用实例

举例:

输入“stack”。

输出说明

执行“stack”命令查看系统内所有栈的信息:

LiteOS # stack

 stack name    cpu id     stack addr     total size   used size
 ----------    ------     ---------      --------     --------
  udf_stack      3        0x3c800        0x28         0x0   
  udf_stack      2        0x3c828        0x28         0x0   
  udf_stack      1        0x3c850        0x28         0x0   
  udf_stack      0        0x3c878        0x28         0x0   
  abt_stack      3        0x3c8a0        0x28         0x0   
  abt_stack      2        0x3c8c8        0x28         0x0   
  abt_stack      1        0x3c8f0        0x28         0x0   
  abt_stack      0        0x3c918        0x28         0x0   
  fiq_stack      3        0x3ca40        0x40         0x0   
  fiq_stack      2        0x3ca80        0x40         0x0   
  fiq_stack      1        0x3cac0        0x40         0x0   
  fiq_stack      0        0x3cb00        0x40         0x0   
  svc_stack      3        0x3cb40        0x2000       0x524 
  svc_stack      2        0x3eb40        0x2000       0x524 
  svc_stack      1        0x40b40        0x2000       0x524 
  svc_stack      0        0x42b40        0x2000       0x528 
  irq_stack      3        0x3c940        0x40         0x0   
  irq_stack      2        0x3c980        0x40         0x0   
  irq_stack      1        0x3c9c0        0x40         0x0   
  irq_stack      0        0x3ca00        0x40         0x0   
  exc_stack      3        0x44b40        0x1000       0x0   
  exc_stack      2        0x45b40        0x1000       0x0   
  exc_stack      1        0x46b40        0x1000       0x0   
  exc_stack      0        0x47b40        0x1000       0x0

须知: stack是异常维测命令,只支持串口输出,不支持通过console输出到其他输出端口(USB等)。

cpup

命令功能

cpup命令用于查询系统CPU的占用率,并以百分比显示占用率。

命令格式

cpup [mode] [taskID]

参数说明

参数

参数说明

取值范围

mode

  • 缺省:显示系统最近10s的CPU占用率
  • 0:显示系统最近10s的CPU占用率
  • 1:显示系统最近1s的CPU占用率
  • 其他数字:显示系统启动至今总的CPU占用率

[0,0xFFFF]

或0xFFFFFFFF

taskID

任务ID号

[0,0xFFFF]

或0xFFFFFFFF

使用指南

  • 参数缺省时,显示系统最近10s的CPU占用率。

  • 只输入一个参数时,该参数为mode,显示系统相应时间的CPU占用率。

  • 输入两个参数时,第一个参数为mode,第二个参数为taskID,显示指定任务ID的任务在相应时间的CPU占用率。

  • 该功能需要使能CPU占用率模块,菜单路径为:

    Kernel ---> Enable Extend Kernel ---> Enable Cpup
    

    想更多的了解CPU占用率模块,参见“CPU占用率”。

使用实例

举例:

输入“cpup 1 1”。

输出说明

执行“cpup 1 1”,显示ID为1的任务最近1s的CPU占用率:

LiteOS # cpup 1 1
TaskId 1 CpuUsage in 1s: 78.7

watch

命令功能

watch命令用于周期性监测一个命令的运行结果。

命令格式

watch [-c | -n | -t | --count | --interval | -no-title] <command>

watch --over

参数说明

参数

参数说明

缺省值

取值范围

-c | --count

命令执行的总次数

0xFFFFFF

(0, 0xFFFFFF]

-n | --interval

命令周期性执行的时间间隔(s)

1s

(0, 0xFFFFFF]

-t | -no-title

关闭顶端的时间显示

N/A

N/A

command

需要监测的Shell命令

N/A

N/A

--over

关闭当前的监测

N/A

N/A

使用指南

  • 该命令依赖于LOSCFG_SHELL_EXTENDED_CMDS,该宏开关可在菜单项中开启“Enable Shell Ext CMDs”使能。

    Debug  ---> Enable a Debug Version ---> Enable Shell ---> Enable Shell Ext CMDs
    
  • command参数必须是Shell命令,对于非Shell命令,会有错误提示“command is not fount”。

  • 如果要监测命令,command是必填参数。

  • --over参数不能与其他参数混合使用。

使用实例

举例1:输入“watch -c 5 task 1”。

举例2:在不需要watch命令监测的情况下,执行“watch --over”。

输出说明

每个周期间隔1秒的执行“task 1”命令,共执行5次,watch命令监测到的结果如下所示:

LiteOS # watch -c 3 task 1

LiteOS # Thu Jan  1 16:26:26 1970

TaskName = Swt_Task
TaskId = 0x1
*******backtrace begin*******
traceback 1 -- lr = 0x08004006 -- fp = 0x0800045e
traceback 2 -- lr = 0x08004000 -- fp = 0x0800194c
traceback 3 -- lr = 0x080040da -- fp = 0x08003e50
traceback 4 -- lr = 0x080015c2 -- fp = 0x080040a8
traceback 5 -- lr = 0x0800396e -- fp = 0x08001598

Thu Jan  1 16:26:27 1970

TaskName = Swt_Task
TaskId = 0x1
*******backtrace begin*******
traceback 1 -- lr = 0x08004006 -- fp = 0x0800045e
traceback 2 -- lr = 0x08004000 -- fp = 0x0800194c
traceback 3 -- lr = 0x080040da -- fp = 0x08003e50
traceback 4 -- lr = 0x080015c2 -- fp = 0x080040a8
traceback 5 -- lr = 0x0800396e -- fp = 0x08001598

Thu Jan  1 16:26:28 1970

TaskName = Swt_Task
TaskId = 0x1
*******backtrace begin*******
traceback 1 -- lr = 0x08004006 -- fp = 0x0800045e
traceback 2 -- lr = 0x08004000 -- fp = 0x0800194c
traceback 3 -- lr = 0x080040da -- fp = 0x08003e50
traceback 4 -- lr = 0x080015c2 -- fp = 0x080040a8
traceback 5 -- lr = 0x0800396e -- fp = 0x08001598

schedstatstart

命令功能

schedstatstart命令用于开启调度统计功能。

命令格式

schedstatstart

使用指南

  • 目前该功能仅支持有浮点运算的平台,且该命令依赖于LOSCFG_DEBUG_SCHED_STATISTICS,该宏开关可在菜单项中开启“Enable Scheduler Statistics Debugging”使能。

    Debug ---> Enable a Debug Version ---> Enable DebugLiteOS Kernel Resource ---> Enable Scheduler Statistics Debugging。

  • schedstatstart、schedstatstop和schedstatinfo等3个命令一起配套使用。

使用实例

举例:输入“schedstatstart”。

输出说明

查看“输出说明”。

schedstatstop

命令功能

schedstatstop命令用于关闭调度统计功能,并输出各个CPU的调度统计信息。

命令格式

schedstatstop

使用指南

同“使用指南”。

使用实例

举例:输入“schedstatstop”。

输出说明

查看“输出说明”。

schedstatinfo

命令功能

schedstatinfo命令用于输出各个任务的CPU调度统计信息。

命令格式

schedstatinfo

使用指南

同“使用指南”。

使用实例

举例:

输入“schedstatinfo”。

输出说明

查看“输出说明”。

文件命令参考

使能文件系统命令

使用Shell中的文件命令前,需要打开menuconfig菜单使能Shell,详见“配置项”。

cd

命令功能

cd命令用于改变当前目录。

命令格式

cd [path]

参数说明

参数

参数说明

取值范围

path

目的目录。

用户必须具有目的目录的可执行权限。

使用指南

  • 未指定目录参数时,会跳转至根目录。

  • cd后加目录时,跳转至该目录。

  • 目录以 /(斜杠)开头时,表示根目录。

  • .(点)表示当前目录。

  • ..(点点)表示父目录。

使用实例

举例:

输入“cd ..”。

输出说明

图 1 显示结果如下

pwd

命令功能

pwd命令用于显示当前路径。

命令格式

pwd

使用指南

pwd 命令将当前目录的全路径名称(从根目录开始的绝对路径)写入标准输出。全部目录使用 /(斜线)分隔,第一个 / 表示根目录,最后一个目录是当前目录。

使用实例

举例:

输入“pwd”。

输出说明

图 1 查看当前路径

mkdir

命令功能

mkdir命令用于创建一个目录。

命令格式

mkdir <dir>

参数说明

参数

参数说明

取值范围

dir

需要创建的目录。

N/A

使用指南

  • mkdir后加所需要创建的目录名,则将在当前目录下创建目录。

  • mkdir后加带路径的目录名,则将在指定路径下创建目录。

使用实例

举例:

输入“mkdir share”。

输出说明

图 1 在当前目录下创建share目录

rmdir

命令功能

rmdir命令用于删除指定的目录。

命令格式

rmdir <dir>

参数说明

参数

参数说明

取值范围

dir

要删除的目录,该目录必须为空目录。

N/A

使用指南

  • rmdir命令只能用来删除目录。

  • rmdir一次只能删除一个目录。

  • rmdir只能删除空目录。

使用实例

举例:输入“rmdir dir”。

输出说明

图 1 删除一个名为dir的目录

rm

命令功能

rm命令用于删除文件或者非空目录。

命令格式

rm -r <dir>

rm_ _<file>

参数说明

参数

参数说明

取值范围

-r <dir>

dir为要删除的目录,支持删除非空目录和空目录。

N/A

file

要删除文件。

N/A

使用指南

  • rm命令一次只能删除一个文件。

  • rm -r命令可以删除非空目录。

须知: 如果使用rm删除系统关键资源(例如/dev),会造成系统死机等未知影响,需慎重使用该命令。

使用实例

举例:

  1. 输入“rm 1.c”。

  2. 输入“rm -r dir”。

输出说明

图 1 用rm命令删除文件1.c

图 2 用rm -r删除目录dir

touch

命令功能

touch命令用于在当前目录下创建一个空文件。

命令格式

touch <filename>

参数说明

参数

参数说明

取值范围

filename

需要创建的文件。

N/A

使用指南

  • touch命令创建的空文件,其默认权限为可读写。

  • touch命令一次只能创建一个文件。

  • touch命令操作已存在的文件会成功,不会更新时间戳。

须知: 在系统重要资源路径下使用touch命令创建文件,会对系统造成死机等未知影响,如在“/dev”路径下执行“touch uartdev-0”,会产生系统卡死现象。

使用实例

举例:

输入“touch file.c”。

输出说明

图 1 在当前目录下创建一个名为file.c的文件

cp

命令功能

cp命令用于拷贝文件,创建一份副本。

命令格式

cp <source path> <dest path>

参数说明

参数

参数说明

取值范围

source path

源文件。

目前只支持文件,不支持目录。

dest path

目的路径。

支持目录以及文件。

使用指南

  • 同一路径下,源文件与目的文件不能重名。

  • 源文件必须存在,且不为目录。

  • 源文件路径支持“*”和“?”通配符,“*”代表任意多个字符,“?”代表任意单个字符。目的路径不支持通配符。当源文件路径可匹配多个文件时,目的路径必须为目录。

  • 目的路径为目录时,该目录必须存在。此时目的文件以源文件名进行命名。

  • 目的路径为文件时,所在目录必须存在。目的文件名就是目的路径中指定的文件名。

  • 目前不支持多文件拷贝。参数大于2个时,只对前2个参数进行操作。

  • 目的文件不存在时创建新文件,已存在则覆盖。

须知: 拷贝系统重要资源文件时,会造成死机等重大未知影响,如拷贝“/dev/uartdev-0”文件时,会产生系统卡死现象。

使用实例

举例:

输入“cp 100HSCAM/FILE0087.MP4 .”。

输出说明

图 1 显示结果如下

dd

命令功能

dd命令用于文件读写性能统计。

命令格式

dd file=[PATHNAME] mode=[OPS] bs=[SIZE] count=[N]

参数说明

参数

参数说明

取值范围

PATHNAME

带文件路径的文件名。

非空

OPS

要测试的文件操作类型,包括读/写,取值1表示读,2表示写。

[1, 2]

SIZE

一次读/写的block字节数。

[0, 0xFFFFFFFF]

N

读/写的block个数。

[0, 0xFFFFFFFF]

使用指南

  1. dd命令可并发执行,一条dd命令对应一个线程,因此可以进行多线程的读写测试。

  2. dd命令测试较大的文件读写时,存在耗时较长的情况,可能不会立即输出性能结果,这是正常现象。

  3. bs字段和count字段可以不指定,系统将使用缺省值LOSCFG_DD_DEFAULT_BS 1024、LOSCFG_DD_DEFAULT_CNT 1。

  4. bs字段的SIZE不宜过大,系统将申请SIZE大小的动态内存,过大的SIZE会导致内存申请失败,导致dd命令无法正常执行。

  5. dd流程中仅会执行open、read/write、close操作,用户需自行保证操作dd前已正确挂载对应的文件系统,如有其他操作也需用户自行执行。

使用实例

举例,测试文件写性能如下:

dd file=/app/sd/testfile mode=2 bs=32768 count=32768

输出说明

输出写入文件的总字节数、总耗时以及速率。

ls

命令功能

ls命令用于显示当前目录的内容。

命令格式

ls [path]

参数说明

参数

参数说明

取值范围

path

  • 为空时,显示当前目录的内容。
  • 为无效文件名时,显示失败,提示:No such directory。
  • 为有效目录路径时,显示对应目录下的内容。
  1. 空;
  2. 有效目录路径。

使用指南

  • ls可以显示文件的大小。

  • proc下ls无法统计文件大小,显示为0。

使用实例

举例:

输入“ls”。

输出说明

图 1 查看当前系统路径下的内容显示

cat

命令功能

cat用于显示文本文件的内容。

命令格式

cat <pathname>

参数说明

参数

参数说明

取值范围

pathname

带文件路径的文件名,如果文件在当前路径下,可以直接输入文件名。

已存在的文件。

使用实例

举例:

输入“cat w“”。

输出说明

图 1 查看w文件的信息

lsfd

命令功能

lsfd命令用于显示当前已经打开的文件描述符及对应的文件名。

命令格式

lsfd

使用指南

lsfd命令显示当前已经打开文件的fd号以及文件的名字。

使用实例

举例:

输入“lsfd”。

输出说明

LiteOS # lsfd
   fd    filename
    3   /dev/shell

format

命令功能

format命令用于格式化磁盘。

命令格式

format <dev_inodename> <sectors> <option> [label]

参数说明

参数

参数说明

dev_inodename

要格式化的设备名。

sectors

分配的单元内存或扇区大小,取值必须为2的幂数,FAT32下最大值为128,取其他值表示自动选择合适的簇大小。不同size的分区,可用的簇大小范围不同。

option

格式化选项,用于选择文件系统的类型,有如下几种参数可供选择:
  • 0x01:FMT_FAT
  • 0x02:FMT_FAT32
  • 0x07:FMT_ANY
  • 0x08:FMT_ERASE(USB不支持该选项)

传入其他值皆为非法,将由系统自动选择格式化方式。若格式化U盘时低格位为1,会出现错误打印。

label(可选参数)

该参数为可选参数,输入值应为字符串,用来指定卷标名。当输入字符串“null”时,则把之前设置的卷标名清空。

使用指南

  • format指令用于格式化磁盘,设备名可以在dev目录下查找。format时必须安装存储卡。

  • format只能格式化U盘、SD卡和EMMC卡,无法格式化Nand Flash和Nor Flash。

  • sectors参数必须传入合法值,传入非法参数可能引发异常。

使用实例

举例:

输入“format /dev/mmcblk0p0 128 2”。

输出说明

LiteOS # format /dev/mmcblk0p0 128 2
Format to FAT32, 128 sectors per cluster.
format /dev/mmcblk0p0 Success

mount

命令功能

mount命令用于将设备挂载到指定目录。

命令格式

mount <device> <path> <name>

参数说明

参数

参数说明

取值范围

device

要挂载的设备。

系统拥有的设备。

path

设备要挂载到的目录,用户必须具有该目录的可执行权限。

N/A

name

要挂载的文件系统类型。

vfat,yaffs,ramfs,nfs,procfs,romfs

使用指南

mount后加需要挂载的设备信息、指定目录以及设备文件格式,就能成功挂载文件系统到指定目录。

使用实例

举例:

输入“mount /dev/mmc0 /bin/vs/sd vfat”。

输出说明

将“/dev/mmc0”以VFAT文件系统类型挂载到“/bin/vs/sd”目录。

LiteOS # mount /dev/mmc0 /bin/vs/sd vfat
mount ok

umount

命令功能

umount命令用于卸载已经挂载的文件系统。

命令格式

umount <dir>

参数说明

参数

参数说明

取值范围

dir

需要卸载的文件系统对应的挂载目录。

系统已挂载的文件系统的目录。

使用指南

umount后加上需要卸载的文件系统对应的挂载目录,即将指定文件系统卸载。

使用实例

举例:

输入“umount /bin/vs/sd”。

输出说明

将挂载在“/bin/vs/sd”目录上的文件系统卸载。

LiteOS # umount /bin/vs/sd
umount ok

partinfo

命令功能

partinfo命令用于查看系统识别的硬盘、U盘和SD卡的分区信息。

命令格式

partinfo <dev_inodename>

参数说明

参数

参数说明

取值范围

dev_inodename

要查看的分区名。

合法的分区名。

使用实例

举例:

输入“partinfo /dev/sdap0”。

输出说明

LiteOS # partinfo /dev/sdap0
part info :
disk id          : 3
part_id in system: 0
part no in disk  : 0
part no in mbr   : 1
part filesystem  : 0C
part dev name    : sdap0
part sec start   : 2048
part sec count   : 167794688

partition

命令功能

partition命令用于查看Flash分区信息。

命令格式

partition <nand | spinor>

参数说明

参数

参数说明

取值范围

nand

显示Nand Flash分区信息。

N/A

spinor

显示Spinor Flash分区信息。

N/A

使用指南

  • 当使能YAFFS文件系统时才可以查看Nand Flash分区信息。

  • 当使能JFFS或ROMFS文件系统时才可以查看Spinor Flash分区信息。

使用实例

举例:

输入partition nand。

输出说明

查看Nand Flash统分区信息。

LiteOS # partition nand
nand partition num:0, blkdev name:/dev/nandblk0, mountpt:/yaffs0, startaddr:0x00e00000, length:0x00200000

statfs

命令功能

statfs命令用于打印文件系统的信息,如该文件系统类型、总大小、可用大小等信息。

命令格式

statfs <directory>

参数说明

参数

参数说明

取值范围

directory

文件系统的挂载路径。

必须是存在的文件系统,并且其支持statfs命令。

使用指南

打印信息因文件系统而异。

使用实例

以YAFFS文件系统为例:

输入“statfs /yaffs0”。

输出说明

statfs got:
f_type         = 1497497427
cluster_size   = 2048
total_clusters = 704
free_clusters  = 640
avail_clusters = 640
f_namelen      = 255

/yaffs0
total size: 9830400 Bytes
free  size: 5890048 Bytes

virstatfs

命令功能

virstatfs命令用于打印虚拟分区下文件系统的信息,如总大小、可用大小等信息。

命令格式

virstatfs_ _<directory>

参数说明

参数

参数说明

取值范围

directory

虚拟分区入口目录的路径或者其子路径。

N/A

使用指南

  • virstatfs命令只有开启了虚拟分区特性时才有效(LOSCFG_FS_FAT_VIRTUAL_PARTITION=y)。

  • virstatfs的输入路径,必须是已经成功应用虚拟分区的物理分区上的虚拟分区入口目录,或者其子目录。如果输入路径是该物理分区的根目录,或者该物理分区未成功应用虚拟分区特性,或者路径指向非FAT32文件系统分区,virstatfs命令均会被拒绝执行。

  • 关于虚拟分区特性,详见FAT开发指导中“虚拟分区的接口说明”和“开发流程”。

使用实例

举例:

输入“virstatfs /bin/vs/sd/virpart0”。

输出说明

sync

命令功能

sync命令用于同步缓存数据(FAT文件系统中的数据)到SD卡。

命令格式

sync

使用指南

  • sync命令用来刷新缓存,当没有SD卡插入时不进行操作,有SD卡插入时缓存信息会同步到SD卡。成功时无显示信息。

  • 使用sync命令前,需要使能FAT文件系统,即设置LOSCFG_FS_FAT = y。

使用实例

举例:

输入“sync”,有SD卡时同步到SD卡,无SD卡时不操作。

输出说明

无。

writeproc

命令功能

writeproc命令用于向proc文件系统中的文件写入内容。

命令格式

writeproc <val> <operational mark> <path>

参数说明

参数

参数说明

取值范围

val

要写入文件的内容。

字符串。

operational mark

操作符,当操作符为“>>”时,表示将内容追加写到指定的文件。

目前只允许“>>”。

path

将要写入内容的文件。

必须为带绝对路径的文件。

使用指南

  • writeproc用于将指定内容写入proc文件系统中的文件,该文件必须是已经存在的文件,也可以是非用户创建的文件。

  • 当操作符为“>>”,同时文件存在时,将指定内容写入该文件。

  • 使用writeproc命令前,需要使能PROC文件系统,即设置LOSCFG_FS_PROC = y。

使用实例

举例:

输入“writeproc 'sys=2' >> /proc/umap/logmpp”。

输出说明

图 1 修改logmpp中的sys等级

Trace命令参考

使能Trace命令

使用Shell中的Trace命令前,需要先打开menuconfig菜单使能Shell,详见“配置项”。

同时配置Trace模块,详见Trace开发流程中的“开发指导”,下文中的“离线模式”在menuconfig中的菜单项为:Kernel ---> Enable Extend Kernel ---> Enable Trace Feature ---> Trace work mode ---> Offline mode”。关于Trace模块的详细介绍,详见“Trace”。

trace_mask

命令功能

设置事件过滤掩码。

命令格式

trace_mask [MASK]

参数说明

参数

参数说明

取值范围

MASK

Trace事件掩码。

[0, 0xFFFFFFFF]

使用指南

  • 如果不设置事件掩码,或者执行该命令时参数缺省,则默认仅开启任务和中断事件记录。

  • trace_mask后加MASK,则开启对应模块的事件记录。

  • 具体的模块掩码MASK参见“los_trace.h”中定义的LOS_TRACE_MASK。

使用实例

举例:

  1. 输入“trace_mask 0”

  2. 输入“trace_mask 0xFFFFFFFF”

输出说明

执行“trace_mask 0”,设置所有模块的事件都不记录,命令执行成功后,不会输出信息。

LiteOS # trace_mask 0

LiteOS #

执行“trace_mask 0xFFFFFFFF”,设置所有模块的事件都进行记录,命令执行成功后,不会输出信息。

LiteOS # trace_mask 0xFFFFFFFF

LiteOS #

trace_start

命令功能

开启Trace。

命令格式

trace_start

使用指南

输入“trace_start”即开启系统Trace功能,离线模式下会记录系统发生的事件并保存在指定buffer中。

使用实例

举例:开启系统Trace功能,输入“trace_start”。

输出说明

在离线模式下,成功执行“trace_start”命令后,不会输出信息。

LiteOS # trace_start

LiteOS #

trace_stop

命令功能

停止Trace。

命令格式

trace_stop

使用指南

输入“trace_stop”即终止系统Trace功能, 停止记录事件。

使用实例

举例:停止系统Trace功能,输入“trace_stop”。

输出说明

成功执行“trace_stop”命令后,不会输出信息。

LiteOS # trace_stop

LiteOS #

trace_dump

命令功能

在离线模式下,dump出Trace buffer的信息。

命令格式

trace_dump [1 | 0]

参数说明

参数

参数说明

取值范围

1

将Trace数据输出到客户端。

NA

0

将Trace数据格式化打印。

NA

使用指南

  • 只能在离线模式下使用trace_dump命令。

  • 参数缺省时将格式化打印Trace数据。

  • trace_dump命令打印的是trace_start和trace_stop之间的数据,所以需要先执行trace_stop停止Trace后,再执行trace_dump打印Trace buffer的信息。

使用实例

举例:

输入“trace_dump”。

输出说明

执行“trace_dump”命令,格式化打印缓存中的数据。

LiteOS # trace_dump
*******TraceInfo begin*******
clockFreq = 180000000
CurEvtIndex = 19
Index   Time(cycles)      EventType      CurTask   Identity      params    
0       0x7da8da5180      0x45           0x5       0x2           0x9          0x20         0x1f
1       0x7dde8c6980      0x45           0x2       0x5           0x1f         0x4          0x9
2       0x7e1431df20      0x45           0x5       0x2           0x9          0x20         0x1f
3       0x7e49e3f720      0x45           0x2       0x5           0x1f         0x4          0x9
4       0x7e7f896cc0      0x45           0x5       0x2           0x9          0x20         0x1f
5       0x7eb53b84c0      0x45           0x2       0x5           0x1f         0x4          0x9
6       0x7eeae0fa60      0x45           0x5       0x2           0x9          0x20         0x1f
7       0x7f20931260      0x45           0x2       0x5           0x1f         0x4          0x9
8       0x7f56388800      0x45           0x5       0x2           0x9          0x20         0x1f
9       0x7f8beaa000      0x45           0x2       0x5           0x1f         0x4          0x9
10      0x7fc19015a0      0x45           0x5       0x2           0x9          0x20         0x1f
11      0x7ff7422da0      0x45           0x2       0x5           0x1f         0x4          0x9
12      0x802ce7a340      0x45           0x5       0x2           0x9          0x20         0x1f
13      0x806299bb40      0x45           0x2       0x5           0x1f         0x4          0x9
14      0x80983f30e0      0x45           0x5       0x2           0x9          0x20         0x1f
15      0x80cdf148e0      0x45           0x2       0x5           0x1f         0x4          0x9
……
24      0x6c560a8d00      0x24           0x2       0x2d          0x0          0x0          0x0
25      0x6c8baf7600      0x25           0x2       0x2d          0x0          0x0          0x0
……
36      0x71fe6f2000      0x24           0x2       0x2d          0x0          0x0          0x0
37      0x7234140900      0x25           0x2       0x2d          0x0          0x0          0x0
38      0x7269c20250      0x45           0x2       0x1           0x1f         0x4          0x0
39      0x734055a650      0x45           0x1       0x2           0x0          0x8          0x1f
40      0x7380b52450      0x45           0x2       0x1           0x1f         0x4          0x0
……
48      0x77a6d3b300      0x24           0x2       0x2d          0x0          0x0          0x0 
49      0x77dc789c00      0x25           0x2       0x2d          0x0          0x0          0x0
50      0x7812269550      0x45           0x2       0x1           0x1f         0x4          0x0
……
*******TraceInfo end*******

trace_reset

命令功能

在离线模式下,清除Trace buffer中的事件数据。

命令格式

trace_reset

使用指南

只能在离线模式下使用trace_reset命令。

使用实例

举例:

输入“trace_reset”。

输出说明

执行“trace_reset”,清除事件数据:

LiteOS # trace_reset

LiteOS #

调度统计

功能说明

LiteOS提供调度统计维测功能,该功能用于统计CPU的一些调度信息,包括idle任务启动时间、idle任务运行时长、调度切换次数、中断次数、核间迁移次数(多核)等。

使用方法

  1. 在菜单项中使能Debug ---> Enable a Debug Version ---> Enable DebugLiteOS Kernel Resource ---> Enable Scheduler Statistics Debugging,可以开启调度统计维测功能(仅支持有浮点运算功能的平台)。

  2. 输入shell命令schedstatstart,开启调度统计功能。

  3. 输入shell命令schedstatstop,关闭调度统计功能,系统将输出各个CPU的调度统计信息。

  4. 输入shell命令schedstatinfo,系统将输出各个任务的CPU调度统计信息。

注意事项

shell指令执行顺序:schedstatstart -> schedstatstop -> schedstatinfo。

输出说明

  • 调用schedstatstart后系统输出如下图所示:

  • 调用schedstatstop后系统输出如下图所示:

    参数描述如下表所示:

    参数

    描述

    Passed Time

    调度统计功能运行时长。

    CPU

    CPU ID。

    Idle(%)

    Idle任务运行时长百分比。

    ContextSwitch

    任务调度切换次数。

    HwiNum

    中断触发次数。

    Avg Pri

    切入任务不为idle任务的任务优先级平均值。

    HiTask(%)

    高优先级任务运行时长所占百分比,定义优先级小于16为高优先级。

    HiTask SwiNum

    切入新任务为高优先级的切换次数,定义优先级小于16为高优先级。

    HiTask P(ms)

    高优先级任务运行的平均时长,定义优先级小于16为高优先级。

    MP Hwi

    核间中断触发次数,仅用于多核。

    MGR

    核间迁移次数,仅用于多核。

  • 调用schedstatinfo后系统输出如下图所示:

    参数描述如下表所示:

    参数

    描述

    Passed Time

    调度统计功能运行时长。

    Task

    任务名称。

    TID

    任务ID。

    Total Time

    所有CPU的任务运行时长。

    Total CST

    所有CPU任务上下文切换次数。

    Total MGR

    所有CPU的核间迁移次数,仅用于多核。

    CPU

    任务运行的CPU ID。

    Time

    任务在某个CPU上的运行时长。

    CST

    任务在某个CPU上的上下文切换次数。

    MGR

    任务在某个CPU上的核间迁移次数,仅用于多核。

内存调测方法

概述

LiteOS提供内存维测功能,在菜单项中使能Debug ---> Enable a Debug Version ---> Enable MEM Debug,可以开启内存维测。

开启内存维测后,在LOS_MemFree和LOS_MemRealloc接口中会根据链表关系对目标节点及前后节点进行合法性校验,若检测到异常则立即挂起系统并抛出异常。除此之外,还提供了内存维测子功能,各功能可以独立使能,详见后续章节。

多内存池机制

使用场景

系统中使用多个动态内存池时,需对各内存池进行管理和使用情况统计。

功能说明

系统内存机制中通过链表实现对多个内存池的管理。内存池需回收时可调用对应接口进行去初始化。

通过多内存池机制,可以获取系统各个内存池的信息和使用情况,也可以检测内存池空间分配交叉情况,当系统两个内存池空间交叉时,第二个内存池会初始化失败,并给出空间交叉的提示信息。

功能分类

接口名

描述

初始化内存池

LOS_MemInit

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

删除内存池

LOS_MemDeInit

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

显示系统内存池

LOS_MemPoolList

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

使用方法

  1. 打开菜单开启多内存池机制。

    功能依赖于LOSCFG_MEM_MUL_POOL,使用时在菜单项中开启“Enable Memory multi-pool control”:

    Debug  ---> Enable a Debug Version---> Enable MEM Debug---> Enable Memory multi-pool control
    
  2. 调用LOS_MemInit接口进行内存池初始化,内存池回收时调用LOS_MemDeInit接口进行去初始化。

  3. 调用LOS_MemInfoGet获取指定内存池的信息和使用情况。

  4. 调用LOS_MemPoolList获取系统所有内存池信息和使用情况。

注意事项

  • 初始化内存池时,需保证各内存池空间无交叉,若交叉则会导致初始化失败。

  • malloc/free系列接口默认从OS系统内存池申请和释放内存,其它内存池的操作必须调用LiteOS内存接口(LOS_MemAlloc等),不能调用malloc/free系列接口及其相关封装接口。

  • 内存池回收必须调用LOS_MemDeInit接口去初始化(回收前需确保池中内存块均已释放),否则二次初始化该内存池空间会失败,导致该内存池不能被重新使用。

  • 内存池大小需根据业务实际情况合理分配。

编程实例

void test(void)
{
    UINT32 ret = 0;
    UINT32 size = 0x100000;

    VOID *poolAddr1 = LOS_MemAlloc(OS_SYS_MEM_ADDR, size);
    ret = LOS_MemInit(poolAddr1, size);
    if (ret != 0) {
        PRINTK("LOS_MemInit failed\n");
        return;
    }

    VOID *poolAddr2 = LOS_MemAlloc(OS_SYS_MEM_ADDR, size);
    ret = LOS_MemInit(poolAddr2, size);
    if (ret != 0) {
        PRINTK("LOS_MemInit failed\n");
        return;
    }

    PRINTK("\n********step1 list the mem poll\n");
    LOS_MemPoolList();

    LOS_MemDeInit(poolAddr1);
    if (ret != 0) {
        PRINTK("LOS_MemDeInit failed\n");
        return;
    }

    PRINTK("\n********step2 list the mem poll\n");
    LOS_MemPoolList();

    LOS_MemDeInit(poolAddr2);
    if (ret != 0) {
        PRINTK("LOS_MemDeInit failed\n");
        return;
    } 

    PRINTK("\n********step3 list the mem poll\n");
    LOS_MemPoolList(); 
}

log:

********step1 list the mem poll
pool0 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8017b2c0         0x100000     0x2e1fc       0xd1d20      0xd1d20              0x2b               0x1            
pool1 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8027b2c0         0x7d84d40    0x7070c8      0x767db94    0x767db94            0x1026             0x1            
pool2 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8078244c         0x100000     0x10          0xfff0c      0xfff0c              0x1                0x1            
pool3 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8088245c         0x100000     0x10          0xfff0c      0xfff0c              0x1                0x1            

********step2 list the mem poll
pool0 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8017b2c0         0x100000     0x2e1fc       0xd1d20      0xd1d20              0x2b               0x1            
pool1 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8027b2c0         0x7d84d40    0x7070c8      0x767db94    0x767db94            0x1026             0x1            
pool2 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8088245c         0x100000     0x10          0xfff0c      0xfff0c              0x1                0x1            

********step3 list the mem poll
pool0 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8017b2c0         0x100000     0x2e1fc       0xd1d20      0xd1d20              0x2b               0x1            
pool1 :
pool addr          pool size    used size     free size    max free node size   used node num     free node num
---------------    --------     -------       --------     --------------       -------------      ------------
0x8027b2c0         0x7d84d40    0x7070c8      0x767db94    0x767db94            0x1026             0x1   

内存合法性检查

使用场景

业务发生踩内存导致内存节点控制头被踩,长时间后才触发业务异常,业务逻辑复杂,难以定位发生踩内存的位置。

功能说明

开启该功能后,在动态内存申请接口中增加内存合法性检查,对动态内存池中所有节点控制头的合法性进行检查,若已发生动态内存节点被踩,及时触发异常,输出error信息,缩小问题定位范围。

使用方法

  1. 打开菜单开启内存合法性检查。

    功能依赖LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK,使用时在菜单项中开启“Enable integrity check or not”:

    Debug  ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable integrity check or not
    
  2. 发生踩内存后,下一次内存申请操作即触发异常,并给出被踩节点和前一节点信息,可初步分析定位是否是前一节点越界踩内存。踩内存发生范围为上一次内存申请和此次内存申请之间。检测到节点被踩会主动Panic并打印异常信息。

注意事项

该功能开启时,系统内存申请操作的性能下降明显,建议仅在定位问题时开启,默认关闭。

踩内存问题定位实例

通过构造超出内存长度的memset操作,构造越界踩内存,造成内存节点损坏,构造代码如下:

VOID SampleFunc(VOID *p)
{
    memset(p, 0, 0x110); // 超出长度的memset,设置踩内存场景
}

UINT32 Test(UINT32 argc, CHAR **args)
{
    void *p1, *p2;

    p1 = LOS_MemAlloc((void*)OS_SYS_MEM_ADDR, 0x100);
    p2 = LOS_MemAlloc((void*)OS_SYS_MEM_ADDR, 0x100);
    dprintf("p1 = %p, p2 = %p \n", p1, p2);
    SampleFunc(p1); // 因为p1和p2相邻,当memset的长度超过p1的内存大小,就会越界踩到p2内存

    LOS_MemFree(OS_SYS_MEM_ADDR, (void *)p1);
    LOS_MemFree(OS_SYS_MEM_ADDR, (void *)p2);
    return 0;
}

执行上述代码后,执行Shell命令“memcheck”,其输出内容如下:

从上图可以看到打印了错误信息。

  • 标记2所指“cur node:0x11b1ac”表示该节点内存被踩,“pre node:0x11b09c”表示被踩节点前面的节点。标记3所示“pre node was allocated by task:app_Task”表示在app_Task任务中发生了踩内存。

  • 标记1打印的是p1和p2内存的起始地址,“p2 = 0x11b1bc”,减去控制头大小0x10,即p2-0x10=0x11b1ac,就是cur node打印出的地址,即p2内存被踩。从代码可以看到p1和p2是两个相邻的节点(这也可以从打印的p1和p2地址看出来,即p1+p1的size+控制头大小=p2,0x11b0ac+0x100+0x10=0x11b1bc),所以“pre node:0x11b09c”应该就是p1的地址,从标记1获取p1地址为“p1 = 0x11b0ac”,即pre node加上控制头大小0x10(0x11b09c+0x10=0x11b0ac)。

内存size检查

使用场景

memset和memcpy操作动态内存,发生越界踩内存问题。

功能说明

对于memset和memcpy操作,当入参为动态内存节点时,增加对内存节点实际大小与入参指定大小的检查,若指定大小大于节点实际大小时,输出error信息,并且取消该次memset或memcpy操作,所以能够防止操作越界。动态内存越界场景下,可开启该功能定位问题。

接口名

描述

LOS_MemCheckLevelSet

设置内存检查级别。

LOS_MemCheckLevelGet

获取内存检查级别。

LOS_MemNodeSizeCheck

获取指定内存块的总大小和可用大小。

错误码

序号

定义

实际数值

描述

参考解决方案

1

LOS_ERRNO_MEMCHECK_PARA_NULL

0x02000101

LOS_MemNodeSizeCheck的入参中存在空指针。

传入有效指针。

2

LOS_ERRNO_MEMCHECK_OUTSIDE

0x02000102

内存地址不在合法范围内。

输入内存地址本身不在内存管理范围之内,不做处理。

3

LOS_ERRNO_MEMCHECK_NO_HEAD

0x02000103

内存地址已经被释放或者是野指针。

检查传入的内存地址,保证该地址是有效地址。

4

LOS_ERRNO_MEMCHECK_WRONG_LEVEL

0x02000104

内存检测等级不合法。

通过LOS_MemCheckLevelGet检查等级,并通过LOS_MemCheckLevelSet来配置合法的等级。

5

LOS_ERRNO_MEMCHECK_DISABLED

0x02000105

内存检测被关闭。

通过LOS_MemCheckLevelSet使能内存检测。

须知: 错误码定义见“错误码简介”。8~15位代表的所属模块为动态内存模块,值为0x01。

使用方法

打开菜单开启内存size检查:

  • 目前只有bestfit内存管理算法支持该功能,所以需要使能LOSCFG_KERNEL_MEM_BESTFIT。

    Kernel ---> Memory Management ---> Dynamic Memory Management Algorithm ---> bestfit
    
  • 同时该功能依赖于LOSCFG_BASE_MEM_NODE_SIZE_CHECK,该宏开关可以通过在菜单项中开启“Enable size check or not”使能。

    Debug  ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable size check or not
    

注意事项

开启该功能后,memset和memcpy性能下降,建议仅在需要定位越界问题时开启,默认关闭。

内存越界问题定位实例

通过构造超出内存长度的memset和memcpy操作,构造越界踩内存问题,构造代码如下:

VOID test(VOID)
{
    UINT32 size = 0x100;

    VOID *poolAddr1 = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, size);
    if (poolAddr1 == NULL) {
        PRINTK("malloc poolAddr1 failed\n");
        return;
    } else {
        PRINTK("malloc poolAddr1 %p successed\n", poolAddr1);
    }

    VOID *poolAddr2 = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, size);
    if (poolAddr2 == NULL) {
        PRINTK("malloc poolAddr2 failed\n");
        return;
    } else {
        PRINTK("malloc poolAddr2 %p successed\n", poolAddr2);
    }

    LOS_MemCheckLevelSet(LOS_MEM_CHECK_LEVEL_LOW);      // 开启对memset和memcpy的长度检测

    PRINTK("memset poolAddr1 overflow\n"); 
    memset(poolAddr1, 0x10, size * 2);                  // 超出长度的memset
    PRINTK("memset poolAddr1\n"); 
    memset(poolAddr1, 0x10, size);                      // 合理长度的memset

    PRINTK("memcpy poolAddr2 overflow\n"); 
    memcpy(poolAddr2, poolAddr1, size * 2);            // 超出长度的memcpy

    PRINTK("memcpy poolAddr2\n"); 
    memcpy(poolAddr2, poolAddr1, size);                // 合理长度的memcpy

    LOS_MemCheckLevelSet(LOS_MEM_CHECK_LEVEL_DISABLE); // 关闭对memset和memcpy的长度检测

    LOS_MemFree((VOID *)OS_SYS_MEM_ADDR, (VOID *)poolAddr1);
    LOS_MemFree((VOID *)OS_SYS_MEM_ADDR, (VOID *)poolAddr2);

    return 0;
}

log:

malloc poolAddr1 0x80349514 successed
malloc poolAddr2 0x80349624 successed
LOS_MemCheckLevelSet: LOS_MEM_CHECK_LEVEL_LOW
memset poolAddr1 overflow
[ERR] ---------------------------------------------
memset: dst inode availSize is not enough availSize = 0x100, memcpy length = 0x200
runTask->taskName = osMain
runTask->taskId = 64
*******backtrace begin*******
traceback 0 -- lr = 0x80209798    fp = 0x802c6930
traceback 1 -- lr = 0x80210fc4    fp = 0x802c6954
traceback 2 -- lr = 0x8020194c    fp = 0x802c6994
traceback 3 -- lr = 0x80201448    fp = 0x802c699c
traceback 4 -- lr = 0x802012fc    fp = 0x0
[ERR] ---------------------------------------------
memset poolAddr1
memcpy poolAddr2 overflow
[ERR] ---------------------------------------------
memcpy: dst inode availSize is not enough availSize = 0x100, memcpy length = 0x200
runTask->taskName = osMain
runTask->taskId = 64
*******backtrace begin*******
traceback 0 -- lr = 0x80209798    fp = 0x802c6930
traceback 1 -- lr = 0x8020dbc4    fp = 0x802c6954
traceback 2 -- lr = 0x8020194c    fp = 0x802c6994
traceback 3 -- lr = 0x80201448    fp = 0x802c699c
traceback 4 -- lr = 0x802012fc    fp = 0x0
[ERR] ---------------------------------------------
memcpy poolAddr2

由于开启size检测,非法的memset和memcpy操作被取消,输出error信息。“runTask->taskName = osMain”显示了该非法操作发生在osMain函数中,并打印寄存器lr和fp的值。此时可以打开编译后生成的 .asm反汇编文件,默认生成在“RTOS_Lite/out/<platform>”目录下,其中的platform为具体的平台名,通过对比“寄存器lr”的值,查看函数的嵌套调用。

内存泄露检测

使用场景

业务运行中发生内存泄露,业务逻辑复杂或者长时间运行才出现。

功能说明

申请内存和释放内存时,在内存节点控制头中记录函数调用栈,发生内存泄露时,通过分析used节点信息,可定位疑似内存泄露的位置。

使用方法

多层栈回溯:

  1. 打开菜单开启内存泄漏检测。

    • 目前只有bestfit内存管理算法支持该功能,需要使能LOSCFG_KERNEL_MEM_BESTFIT。

      Kernel ---> Memory Management ---> Dynamic Memory Management Algorithm ---> bestfit
      
    • 同时该功能依赖于LOSCFG_MEM_LEAKCHECK,可以在菜单项中选择“Enable Function call stack of Mem operation recorded”开启该宏开关:

      Debug  ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable Function call stack of Mem operation recorded
      
  2. 配置调用栈回溯信息菜单配置路径:

    Debug  ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable Function call stack of Mem operation recorded
    
    • LOSCFG_MEM_OMIT_LR_CNT:调用栈回溯忽略层级,默认配置为2。

    • LOSCFG_MEM_RECORD_LR_CNT:调用栈回溯记录个数,默认配置为3。

    默认配置下,获取0~2层LR信息,忽略第0和第1两层(调用封装接口的节点0层和1层LR信息相同),记录第2层。

  3. 使用Shell命令“memused”获取used节点数据。

    系统稳定运行后,若used节点个数随时间一直增加,极大可能存在内存泄露,对数据进行对比分析,重点关注LR重复的节点是否存在内存泄露,泄漏点可通过LR信息进行回溯查找。

    打印log信息如下,memused命令说明详见“memused”:

    LiteOS # memused
    node         LR[0]       LR[1]       LR[2]
    0x802d7b34:  0x8006d86c  0x8011c604  0x8011c758
    0x802dab6c:  0x8006d16c  0x8006d8a0  0x8011c604
    

基于任务的内存节点统计:

  1. 打开菜单开启LOSCFG_MEM_DFX_SHOW_CALLER_RA宏。

    Kernel ---> Memory Management ---> Enable Memory Task Usage Statistics ---> Enable Show where MemAlloc function is called
    

    目前bestfit和bestfit_little内存管理算法都支持该功能。

  2. 使用Shell命令“task_mem”获取used节点数据。

    打印log信息如下:

    HuaWei LiteOS # task_mem
    ========== pool info ==========
    pool addr          pool size    used size     free size    max free node size   used node num     free node num      UsageWaterLine
    ---------------    --------     -------       --------     --------------       -------------      ------------      ------------
    0x114770           0x7eb890     0x5c68        0x7e5ae8     0x7e5ae8             0x9                0x1                0x5fd0         
    ========== [all task heap] pool start: 0x114770 ==========
    Task:Swt_Task(taskId=00) malloc details:
    Current total pool alloc size=0 Bytes
    Current total slab alloc size=400 Bytes
    Current total node cost size=0 Bytes
     
    Task:IdleCore000(taskId=01) malloc details:
    callerRA=0x00101d50 : totalSize=00018528 Bytes, blockCount= 3
    callerRA=0x00106e36 : totalSize=00000604 Bytes, blockCount= 1
    callerRA=0x00108172 : totalSize=00002068 Bytes, blockCount= 1
    callerRA=0x001031a2 : totalSize=00002456 Bytes, blockCount= 4
    Current total pool alloc size=23656 Bytes
    Current total slab alloc size=0 Bytes
    Current total node cost size=180 Bytes
    
  3. 分析定位

    callerRA: 内存申请调用者。

    totalSize: 调用者申请的总size大小。

    blockCount:调用者申请的内存块个数。

    定位思路:多次执行taskmem命令,重点排查size和count一直在递增的callerRA;

    如果callerRA指向用户自己封装的user_defined_malloc接口,则在user_defined_malloc前后加上callerRA统计打点,示例如下:

    #include "los_memory.h"
     
    void *user_defined_malloc(size)
    {
        LOS_SaveCallerRa((uintptr_t)__builtin_return_address(0));
    /* 函数主体
         ......
    */
        LOS_ResetCallerRa();
    }
    

    再次编译运行后,可以通过命令查看调用user_defined_malloc函数的上层函数callerRA,由此找到内存泄漏点。

注意事项

上述功能开启时会增加系统内存占用,影响内存操作性能,建议仅在定位问题时开启,默认关闭。

多层栈回溯内存占用size = 内存节点个数 * (LOSCFG_MEM_RECORD_LR_CNT - LOSCFG_MEM_OMIT_LR_CNT)* sizeof(指针)。

基于任务的内存节点统计内存占用size = 内存节点个数 * sizeof(unsigned int)。

内存重复释放检测

使用场景

业务运行中出现内存重复释放,业务逻辑复杂或者长时间运行才出现。

功能说明

释放内存时,在内存节点控制头中记录函数调用栈,发生内存泄露时,通过分析节点信息调用栈信息,可定位内存重复释放的位置。

使用方法

打开LOSCFG_MEM_DFX_DOUBLE_FREE_CHECK宏

Kernel ---> Memory Management ---> Enable check mem double free check by record function called address

目前bestfit和bestfit_little内存管理算法都支持该功能。

当发生内存重复释放时,会打印log信息:(当开启LOSCFG_MEM_DEBUG时,系统会因为重复释放挂死)

Memory double free detected!
Current running taskId = 3, taskName = app_Task.
Memory first free function addr = 0x0x100860.
Memory double free function addr = 0x0x101710.

任务间通信调测方法

队列调测方法

功能说明

队列是一种生产者消费者模型,生产者生产消息放入队列,等待被消费者使用。如果队列已满,生产者被挂起,如果队列已空,消费者被挂起。LiteOS中使用队列传递消息时,可以设置超时时间,队列的主要作用是实现任务间的异步通信。通过Shell命令“queue”或者使用OsShellCmdQueueInfoGe函数可以查看队列的使用情况。

使用方法

“queue”命令和OsShellCmdQueueInfoGe函数依赖LOSCFG_DEBUG_QUEUE,使用时需要在menuconfig中开启“Enable Queue Debugging”。

Debug ---> Enable a Debug Version ---> Enable MMC Debug ---> Enable mmc dma descriptors dump

使用实例

在Shell窗口中执行命令“queue”或者直接在任务中使用OsShellCmdQueueInfoGet函数,打印系统中的队列信息如下:

其输出项的含义见“队列”,调试过程中主要使用上图中的标识项“TaskEntry of creator”,即创建队列的接口函数地址(0x100830)。在.asm反汇编文件(make编译默认在“LiteOS/Huawei_LiteOS/out/<platform>”目录下,CMake编译默认在产品对应的目录下, 其中的platform为具体的平台名)中找到该地址,可以看到创建队列的函数名,比如这里的app_init(0x100830),见下图。

死锁检测机制

功能说明

在许多应用中进程需要以独占的方式访问资源,当操作系统允许多个进程并发执行时可能会出现进程永远被阻塞现象,如两个进程分别等待对方所占的资源,于是两者都不能执行而处于永远等待状态,此现象称为死锁。死锁检测模块支持自旋锁、互斥锁、 二值信号量和线程锁(pthread mutex)四种类型锁的死锁,以及重复上锁、错误释放和锁的深度溢出的检测。

使用方法

打开菜单,进入Debug ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Lockdep Debugging菜单, 完成死锁检测模块的配置。

配置项

含义

取值范围

默认值

依赖

LOSCFG_KERNEL_LOCKDEP

使能死锁检测功能。

YES/NO

NO

LOSCFG_KERNEL_MEM_ALLOC

LOSCFG_KERNEL_SPINDEP

使能自旋锁死锁检测。

YES/NO

NO

LOSCFG_KERNEL_SMP

LOSCFG_KERNEL_MUXDEP

使能互斥锁死锁检测。

YES/NO

NO

LOSCFG_BASE_IPC_MUX

LOSCFG_KERNEL_SEMDEP

使能二值信号量死锁检测。

YES/NO

NO

LOSCFG_BASE_IPC_SEM

LOSCFG_PTHREAD_MUXDEP

使能线程锁死锁检测。

YES/NO

NO

LOSCFG_COMPAT_POSIX

自旋锁定位实例

  1. 打开自旋锁检测后,检测到死锁时会打印死锁信息,死锁检测的打印信息示例如图1所示。

    图 1 死锁模块打印信息

  2. 复制request addr的值(本例中为0x471d2),在系统镜像的.asm反汇编文件(make编译默认在“LiteOS/Huawei_LiteOS/out/<platform>”目录下,CMake编译默认在产品对应的目录下, 其中的platform为具体的平台名)中找到相应的地址,如图2所示,即可定位到调用的位置及调用函数(本例中为Task_B)。

    图 2 反汇编文件中找到对应地址

  3. 根据图1中第二个蓝框的自旋锁,通过代码逻辑找到另一个任务持有该锁的情况,再结合代码,调整spinlock调用的时序,从而解决死锁问题。

须知: 自旋锁死锁一般发生在某个CPU卡住、任务不再发生调度时。若不开启自旋锁死锁检查,可以使用JLink等调试工具halt住CPU或者触发看门狗异常,查看PC值是否为自旋锁代码,以此确定是否发生了自旋锁死锁。

互斥锁死锁定位实例

构造ABBA互斥锁死锁场景,具体如下:

有两个任务,分别为Task_A和Task_B,同时系统中还存在其他系统默认初始任务。在任务Task_A中依次申请Mutex1和Mutex0,Task_B任务依次申请Mutex1和Mutex0, 构成两个任务ABBA死锁场景。

代码如下:

#include "los_mux.h"

static UINT32 mutexTest[2];
UINT32 g_TestTaskID01, g_TestTaskID02;

static VOID Task_A(VOID)
{
    UINT32  ret;
    ret = LOS_MuxPend(mutexTest[0], LOS_WAIT_FOREVER);
    if (ret == LOS_OK) {
        dprintf("%s pend muxid %d\n", __func__, mutexTest[0]);
    }

    LOS_TaskDelay(0x2);

    ret = LOS_MuxPend(mutexTest[1], 6000); // 定时阻塞模式 6000tick
    if (ret == LOS_ERRNO_MUX_TIMEOUT) {
        dprintf("%s pend muxid %d\n", __func__, mutexTest[1]);
    }

    return;
}

static VOID Task_B(VOID)
{
    UINT32  ret;
    ret = LOS_MuxPend(mutexTest[1], LOS_WAIT_FOREVER);
    if (ret == LOS_OK) {
        dprintf("%s pend muxid %d\n", __func__, mutexTest[1]);
    }
    LOS_TaskDelay(0x2);
    ret = LOS_MuxPend(mutexTest[0], 6000); // 定时阻塞模式 6000tick
    if (ret == LOS_ERRNO_MUX_TIMEOUT) {
        dprintf("%s pend muxid %d\n", __func__, mutexTest[0]);
    }
    return;
}

UINT32 mutexDeadlock(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S stTask1 = {0};
    stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Task_A;
    stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE;
    stTask1.pcName       = "Task_A";
    stTask1.usTaskPrio   = 9;

    TSK_INIT_PARAM_S stTask2 = {0};
    stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Task_B;
    stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE;
    stTask2.pcName       = "Task_B";
    stTask2.usTaskPrio   = 9;

    ret = LOS_MuxCreate(&mutexTest[0]);
    ret |= LOS_MuxCreate(&mutexTest[1]);
    if (ret != LOS_OK) {
        PRINT_ERR("create mutex error %u\n", ret);
    }

    ret = LOS_TaskCreate(&g_TestTaskID01, &stTask1);
    ret |= LOS_TaskCreate(&g_TestTaskID02, &stTask2);
        if (ret != LOS_OK) {
        PRINT_ERR("create task error %u\n", ret);
    }

    LOS_TaskDelay(3000); // 任务休眠3000Ticks 

    ret = LOS_MuxDelete(mutexTest[0]);
    ret = LOS_MuxDelete(mutexTest[1]);

    LOS_TaskDelete(g_TestTaskID01);
    LOS_TaskDelete(g_TestTaskID02);
    return LOS_OK;
}

针对以上场景出现的问题,根据日志分析即可,具体操作步骤如下:

注:详细流程可参考自旋锁定位实例。

  1. 根据dump信息,可以看出任务Task_A与Task_B发生死锁,。

  2. 在.asm反汇编文件中,找到相应的任务函数,定位到互斥锁的pend位置及调用的接口。

  3. 根据上下文确认死锁原因,并调整申请锁的时序。

异常交互

使用场景

操作系统对运行期间发生异常时,用户可以使用Shell命令进一步定位异常原因,查看系统状态。

功能说明

系统发生异常时,保留Shell基础交互功能。

配置项

配置项

含义

取值范围

默认值

依赖

LOSCFG_EXC_INTERACTION

异常交互功能的裁剪开关

YES/NO

NO

LOSCFG_SHELL && LOSCFG_KERNEL_CONSOLE

使用方法

  1. 打开菜单开启异常交互功能。

    该功能需要配置LOSCFG_EXC_INTERACTION=y,默认处于关闭状态。若关闭该选项,则异常交互失效。

    开启该宏开关的菜单项为:

    Debug ---> Enable a Debug Version ---> Enable Shell ---> Enable exc interaction
    
  2. 输入“help”,查看当前支持的Shell命令。

注意事项

  • 系统异常时,异常交互功能只会使能串口中断、其他中断都会被关闭;用户创建的任务也会被删除,只保留异常交互所使用的基本任务。

  • 异常交互属于调测功能,默认配置为关闭,正式商用产品中禁止使用该功能。

  • 免责声明:华为不承担在正式商用产品中使用该功能的任何风险。

低功耗调测方法

低功耗调测使能

LiteOS提供低功耗维测功能,在菜单项中使能Debug ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource--->Enable Lowpower Sleep Debugging,可以开启低功耗维测。使能低功耗维测先使能低功耗框架需要先打开menuconfig菜单使能Shell,详见“配置项”;同时启用低功耗框架,详见“低功耗框架”。该功能仅供用户开发阶段使用,正式版本商用前请关闭此功能。

系统状态信息查询

功能说明

系统状态信息查询命令会打印出中断、定时器和任务的详细信息。阻止任务深睡事件查询、系统休眠唤醒打点命令配合该命令使用,方便用户定位。系统状态信息查询命令效果可参考系统命令hwi、swtmr、task,详见“系统命令参考”。

使用方法

输入命令“pm sysinfo”。

注意事项

打印中断与定时器信息,应先支持系统命令hwi、swtmr。详见“系统命令参考”。

低功耗动态开关

功能说明

低功耗动态开关支持手动打开、关闭、状态查询功能,默认处于打开状态。

使用方法

  1. 打开低功耗动态开关:lpm enable。

  2. 关闭低功耗动态开关:lpm disable。

  3. 查询低功耗动态开关的状态:lpm status。

注意事项

无。

阻止系统深睡事件查询

功能说明

阻止系统深睡事件是指用户钩子getSleepMode返回深睡模式,但是系统经判断无法进入深睡的事件。阻止系统深睡的原因可能是距离定时器或任务到期剩余的时间达不到深睡要求的最低时间阈值、任务投票阻止等。

使用方法

  1. 开机以来阻止系统深睡事件查询命令:lpm blockinfo cpuid。cpuid指的是具体的CPU号。

  2. 阻止系统深睡事件统计信息清空命令:lpm resetblockinfo cpuid。

示例:

从下图中可以得到以下信息:

  • 0核task id为3的任务阻止系统深睡2次,任务地址为0x8008ced。

  • 0核swtmr id为2的定时任务阻止系统深睡1次,定时器任务地址为0x800fc91。

  • Normal,3)、(Light,3)、(Deep,3)三个投票各阻碍一次深睡。

注意事项

  • 当前只支持单核,因此cpuid当前只能为0,即“lpm blockinfo 0”或者“lpm resetblockinfo 0”。

  • 统计信息根据定时器id或者任务id记录,任务或定时器销毁与创建可能存在id相同的情况,会被统计到一起,用户应避免此场景。如:某a时刻任务A id为8,执行完后销毁,阻止深睡3次;经过一段时间,任务B被创建,id同样为8,阻止深睡2次;那么统计信息中type为task,id为8的阻碍次数为3次,handler为任务B的handler。

  • 被阻碍一次深睡会统计此刻所有的不满足深睡条件的因素。

系统休眠唤醒打点

功能说明

系统休眠唤醒打点功能会对Normal、Light、Deep模式下的睡眠时戳信息进行统计。单项信息依次为cpu号、睡眠模式、睡眠开始时刻、睡眠结束时刻,实际睡眠时间,被唤醒的中断。默认记录条数为10条,深睡时唤醒中断为固定值4294967295。

使用方法

  1. 系统休眠唤醒打点查询命令:lpm timestamp cpuid。cpuid指的是具体的CPU号。

  2. 修改最大记录条数可通过配置LOSCFG_LOWPOWER_RECORD_COUNT实现,最大值为1000。

示例:

注意事项

  • 当前只支持单核,因此cpuid当前只能为0,即“lpm timestamp 0”。

  • 系统休眠唤醒打点统计的是实际睡眠时戳信息。如上图中用户配置浅睡时间为100,实际浅睡时间为99。

魔法键使用方法

使用场景

系统未输出挂死相关信息,但是无响应时,可以通过魔法键查看中断是否有响应。在中断有响应的情况下,可以通过魔法键查看task信息中 的CPUP(CPU占用率),找到是哪个任务长时间占用CPU导致系统其他任务无响应(一般为比较高优先级任务一直抢占CPU,导致低优先级任务无响应)。

功能说明

在UART中断和USB转虚拟串口中断中,嵌入魔法键检查功能,对特殊按键进行识别,输出相关信息。

使用方法

  1. 打开菜单开启魔法键功能。

    该功能需要配置LOSCFG_ENABLE_MAGICKEY=y,默认处于关闭状态,若关闭该选项,则魔法键失效,开启该宏开关的菜单项为:

    Debug ---> Enable MAGIC KEY
    

    说明: 可以在menuconfig中光标移动到该选项上,输入“?”,查看帮助信息:

    Answer Y to enable LiteOS Magic key.
     ctrl + r : Magic key check switch;
     ctrl + z : Show all magic op key;
     ctrl + t : Show task information;
     ctrl + p : System panic;
     ctrl + e : Check system memory pool.
    
  2. 输入“Ctrl + r ” 键,打开或者关闭魔法键检测功能。

    在连接UART或者USB转虚拟串口的情况下,输入“Ctrl + r ” 键,打开魔法键检测功能,输出“Magic key on”;再输入一次后,则关闭魔法键检测功能,输出“Magic key off”。魔法键功能如下:

    • Ctrl + z:帮助键,输出相关魔法键简单介绍。

    • Ctrl + t:输出任务相关信息。

    • Ctrl + p:系统主动进入panic,输出panic相关信息后,系统会挂住。

    • Ctrl + e:系统进行简单的内存池完整性检查,检查发现错误则输出相关错误信息,检查正常则输出“system memcheck over, all passed!”。

注意事项

魔法键检测功能打开情况下,如果需要通过UART或者USB转虚拟串口输入特殊字符需避免与魔法键值重复,否则魔法键会被误触发,而原有设计功能可能出现错误。

栈估算工具使用方法

使用场景

对于内存资源受限的嵌入式设备,可以使用栈估算工具分析任务栈,达到精确使用栈空间的目的。

  • 创建任务为任务设置任务栈大小时,可以使用栈估算工具分析任务栈,达到精确设置任务栈大小的目的。

  • 系统划分内存设置中断栈大小时,可以使用栈估算工具分析中断栈,达到精确设置中断栈大小的目的。

  • 可以使用栈估算工具分析任务栈溢出问题。

功能说明

栈估算工具是基于python语言开发的函数栈静态分析工具,是一款离线工具。通过解析反汇编文件,计算得到任务运行中栈空间使用的最大值,为设置任务栈大小、栈溢出分析、栈内存优化等场景,提供静态分析的数据参考。同时栈估算工具还提供递归函数告警和函数调用关系树展示功能。

工具运作机制

栈估算工具逐行解析反汇编文件,将跳转指令转化为函数调用关系,开栈指令解析为函数栈局部空间开销,最终使用深度优先搜索,遍历任务入口函数及其调用的所有子函数,得到任务栈内存使用的峰值。

使用方法

栈估算工具名为“stackusage.py”,使用工具前,可以通过“python3 stackusage.py –h”命令查看帮助信息。栈估算工具支持python3.6及以上版本。如果没有安装python3.6及以上版本,可以参见官网python安装文档完成安装。

  1. 生成反汇编文件(可选)。

    如果没有反汇编文件,可以通过elf文件生成,例如有“Huawei_LiteOS.elf”文件,可以参考如下命令生成名为“Huawei_LiteOS.asm”的反汇编文件:

    arm-none-eabi-objdump -d Huawei_LiteOS.elf > Huawei_LiteOS.asm
    
  2. 修改配置文件(可选)。

    统计错误分支中的异常处理方法的栈开销,有时是无意义的。因此栈估算工具支持可选地忽略部分断言函数、异常分支处理函数、打印函数等,在统计过程中会将他们的栈开销设置为0。

    在修改“cfg/config.ini”文件时,将统计时需要忽略的函数,添加至“Ignore_function”行末尾,如有多个函数可以以空格隔开。工具执行时将自动解析配置文件。

  3. 运行栈估算工具。

    例如反汇编文件为“Huawei_LiteOS.asm”,想要使用工具估算OsIdleTask任务的任务栈大小,可以参考如下命令运行工具:

    python3 stackusage.py -sp -t OsIdleTask -out Huawei_LiteOS.asm 
    

    执行上面的命令后,其输出如下图所示:

    工具输出结果分为四部分:函数调用关系配置文件提示、递归函数告警、计算结果输出、调用关系树状图。调用关系树状图中,显示了任务的调用关系,以及每一层调用函数栈的最大开销和局部栈开销,并以“*Rec”标记对递归调用的函数进行提示,以“*Ignore”标记对忽略统计的函数进行提示。

  4. 标注函数指针(可选)。

    工具将无法自动解析的函数调用关系,保存至“cfg/config.ini”配置文件中。以下图为例,表示在LOS_LkPrint方法和OsIdleTask任务入口函数中,存在无法自动解析的函数调用关系,需要用户手动将方法中调用的函数名添加到“Remarks_here”行末尾,如果有多个函数则以空格隔开,如果没有需要添加的函数则标注为NULL。

    完成函数调用关系配置文件的编辑后,需要按步骤2重新运行工具,以获取更新后的计算结果,此时配置文件中添加的函数调用关系已添加进调用关系树中。

  5. 计算任务栈大小。

    从工具输出结果中可以看到估算的任务栈大小,比如3显示的OsIdleTask任务栈为476Byte,由于设定任务栈时需要增加任务栈初始化开销和任务切换寄存器压栈开销(建议增加300Byte的开销),所以OsIdleTask的最终任务栈大小计算为776Byte。

  6. 估算中断栈(可选)。

    栈估算工具无法自动识别或解析中断入口函数和中断回调函数,用户需要在“cfg/config.ini”配置文件中手动配置,配置格式如下,Interrupt_entry表示中断入口,Interrupt_isr表示中断回调函数,冒号后面需要增加中断号、中断回调函数入口。

    执行下面的命令,可以进行中断栈估算:

    python3 stackusage.py -irq -out Huawei_LiteOS.asm 
    

    估算结果如下:

    中断入口栈开销72Byte,中断号58的回调函数在所有配置的中断号里栈开销最大,为372Byte,所以总共的中断栈开销是444Byte。

工具命令

命令

功能

-h, --help

查看帮助信息。

-m N

统计模式,可配置的模式N有以下三种:

0:仅分析汇编代码;

1:分析源代码行号;

2:分析反汇编文件中的源代码。

-sp

分析指定的任务条目,如果不标明-sp,将会统计所有函数。

-t taskEntry

指定任务入口taskEntry,默认值为OsIdleTASK,如果没有指定-sp, 本命令将会被忽略。

-out

将估算结果输出到界面。

-j

将估算结果输出到JSON文件中。

-r N

指定递归函数的递归层数N,默认为1。

-irq

统计中断栈。如果设置了本命令,将只统计中断栈。

注意事项

  • 由于基于静态的反汇编文件解析的函数调用关系,无法准确获取到动态赋值的函数指针取值,因此缺失函数调用关系,可能导致计算偏差。使用时请补全函数指针配置文件中的指针取值,以得到更准确的计算结果。

  • 由于静态解析时无法确定递归函数的递归深度,工具默认以调用深度为1进行统计。如果需要设置递归深度值,可在使用工具时增加“-r”入参设置。

  • 解析结果需要增加任务栈初始化开销和任务切换时寄存器压栈开销。这些开销一般与系统可使用的寄存器数量相关,建议增加300Byte的开销。

  • 栈估算工具支持解析ARM指令集和RISC-V指令集的反汇编文件,不支持其他指令集。

  • 由于中断栈随着中断嵌套的深度增加而增加,而中断嵌套又与设置的中断优先级、系统运行的实际情况有关,因此栈估算工具无法静态地估算中断嵌套情景的栈开销。栈估算工具支持用户配置多个中断回调函数入口,统计结束后会给出一个最大的中断栈开销,用户可根据实际业务可能的嵌套情况,参考最大中断栈开销去设置中断栈大小。

调试案例

踩内存定位方法

异常信息定位踩内存方法

通过异常信息定位问题,参见异常接管的“编程实例”。

内存合法性检查定位踩内存方法

通过内存合法性检查定位问题,参见“内存合法性检查”。

内存size检查定位踩内存方法

通过内存size检查定位问题,参见“内存size检查”。

全局变量踩内存定位方法

调试过程中,发现一个全局变量只在一处赋值为0,但使用时打印发现变成一个非零异常值,大概率是该全局变量被踩。

如果已知一个全局变量被踩内存,可在“LiteOS/Huawei_LiteOS/out/<platform>/.map”文件中找到该全局变量所在的地址。注意该地址前面最近被使用的变量,排查是否前面变量操作不当引发踩内存,比如对该前面变量进行memcpy,memset操作时越界,溢出覆盖了当前全局变量。

这里列举一个测试的例子,在文件中定义了两全局变量,并且初始化。

UINT32 g_uwEraseMap[16] = {0};
UINT32 g_uwEraseCount = 0;

在.map文件中可以找到这些全局变量在bss段对应的位置。

若g_uwEraseMap被踩,在“.map”文件中找到其地址,再查找该地址前面的变量,即g_uwEraseCount。特别注意分析g_uwEraseCount变量的使用情况,观察是否存在某处,对变量g_uwEraseCount进行了越界操作。

task状态判断是否踩内存

Shell命令“task”,可以查看当前系统所有任务的状态。命令输出的stackSize、WaterLine、StackPoint、Top0fStack信息,可以作为判断任务栈是否踩内存的指标。

这里举例说明如何通过task命令判断是否踩内存,如下图所示,有一任务名为shellTask。

StackSize = 0x3000(创建该任务时分配的栈大小)

WaterLine = 0x2810(水线,目前为止该任务栈已经被使用的内存大小)

StackPoint = 0x80d10084 (任务栈指针, 指向该任务当前的地址)

Top0fStack = 0x80d0d768(栈顶)

MaxStackPoint = Top0fStack + StackSize = 0x80d10768(得到该任务栈最大的可访问地址)

  • 若WaterLine > StackSize,则说明该任务踩内存。

  • 若StackPoint > MaxStackPoint 或 StackPoint < Top0fStack,则说明该任务踩内存。

Trigger定位踩内存方法

Trigger方法主要用于定位固定地址踩踏的问题,如全局变量,已知被踩地址,可以快速找出被谁踩。

功能分类

接口名

描述

根据提供的地址及范围配置保护

ArchProtectionSetAddrRo

参数说明:

addr:被保护范围的起始地址,当size为4时,addr需要4字节对齐;

size:被保护范围的大小。

当调用ArchProtectionSetAddrRo(0x100000, 4)时,被保护的地址范围为 [0x100000, 0x100003] 共4Byte。

取消指定索引的trigger的保护

ArchProtectionUnSetAddrRo

参数说明:

index:trigger索引,取值范围为[0, 3],当index == 0xffffffff时,标识取消所有trigger保护。

使用方法

  1. 打开菜单LOSCFG_TRIGGER_ENABLE宏。

    Targets ---> Enable Access Memory trigger.
    
  2. 使用API来保护被踩地址。

    UINT32 ret = ArchProtectionSetAddrRo(0x1405ca4, 4);
    

    对地址写后会触发如下打印:

    其中:

    • task_name:写被保护地址的任务;

    • mepc:写被保护地址的指令地址,可从反汇编文件中找到对应代码所在行和函数;

    • caller:最近一层函数调用栈;

    • diff_addr:被踩的地址;

    • new_val:被踩地址被写入的新值;

    • old_val:被踩地址的旧值。

注意事项

当前方案基于LiteOS内核设计,仅针对RISCV核且LiteOS内核没有开启LOSCFG_ARCH_RISCV_INT_AUTO_STACK特性。方案支持的平台为LINX131、LINX132、LINX170以及LINX-1XX_TEE。

LiteOS Kernel启动流程

图 1 LiteOS Kernel启动流程

图1为系统启动流程,部分模块的初始化会在模块宏开启时才生效,用户可以选择是否创建app任务(app_init),菜单配置路径为:

Debug ---> Enable TestSuite or AppInit ---> Choose TestSuite or AppInit 菜单。

LOSCFG_APPINIT_TESTSUIT开启后可以选择创建app或testsuite任务,如果不开启,则认为app和testsuite任务都不创建。

平台兼容性适配修改要点

差异对比

在32位与64位模式下,涉及的主要差异点在于各个不同类型的数据位宽,开发过程中需要关注,详细如下表所示:

Programming type

Size in A32

Size in A64-LP64

char

8bit

8bit

short

16bit

16bit

int

32bit

32bit

int *(pointer)

32bit

64bit

long

32bit

64bit

long long

64bit

64bit

float

32bit

32bit

double

64bit

64bit

size_t

32bit

64bit

LiteOS的64位编译器使用的是LP64指令模式,从上表可以看出,指针、long和size_t类型都升级为64bit。

在64位模式下,相比于32位模式,由于位宽的差异会导致一些使用上的差异,请参考如下7个修改要点,对应用代码做相关差异性的检查。

修改要点

  1. 指针的处理。

    指针不建议强转成非指针类型,如long、int等非指针数据类型。指针可以强转成其他类型的指针和void*,但64位的编译器会报出告警,需要消除掉。

    须知: 也不建议指针用于数学计算时临时强转成unsigned long或int,可以转换成char* 然后再加或减常数。

  2. long都要修改为int。

    除非涉及到内核公共模块,long都要修改为int,不要使用long以及相关强转。

    须知: 在某些64bit编程规范中,严格禁止使用long类型。否则很容易出错。

  3. 仔细检查一切有关int、long、指针的赋值和比较语句。

    虽然原则上不允许使用long,但存在引用的数据类型是Kernel内部原生定义为long的情况。这样在不能修改内部原生数据定义的情况下,需要注意检查引用处。

    在新的编译器中,数据类型会按照如下规则自动转换:

    int + long = long

    unsigned + signed = unsigned

    在这种原则下,下列代码:

    long long a;
    int b;
    unsigned int c;
    b = -2;
    c = 1;
    a = b + c;
    

    a的值是0x00000000FFFFFFFF, 而不是-1。原因是b+c是unsigned+signed,结果是个无符号数,再转成long long,只能在前面填充0。而如下代码:

    long long a;
    int b;
    unsigned int c;
    b = -2;
    c = 1;
    a = (long long) b + c;
    

    a的结果就是0xFFFFFFFFFFFFFFFF,因为b转为long后就是0xFFFFFFFFFFFFFFFE,与unsigned int c相加,就是上述结果。

  4. 要在常量表达式中指定数据类型。

    如果想将一个常量作为64位来使用,必须清楚地在数值后跟上ll或者LL,否则,编译器有可能把它当作int来对待。例如下面这个函数:

    long long SetBitN(long long value, unsigned bitNum)
    { 
        long long mask; 
        mask = 1 << bitNum; 
        return value | mask; 
    }
    

    在64位下乍一看以为没问题,但其实1被当成int型处理了, 如果bitNum为超过32的数,实际无效,超过的部分会被截断,结果就是0。必须这样写:

    long long SetBitN(long long value, unsigned bitNum)
    { 
        long long mask; 
        mask = 1LL << bitNum; 
        return value | mask; 
    }
    

    另外再举例,以下两种操作会导致指针的高32比特被清0。

    (void*)((long long)p & ~(16 - 1))
    (void*)((long long)p & ~(alain - 1)) /* alain为u32 */
    

    可以修改成:

    (void*)((long long)p & ~((long long)16 - 1))
    (void*)((long long)p & ~((long long)alain - 1)) /* alain为u32 */
    

    也可以采用如下定义规避:

    1LL // (long long) 
    1ULL // (unsigned long long)
    
  5. 格式化输入输出问题及解决办法。

    与32位系统相比,64位系统会有如下问题:

    • 32位系统可以用%x打印指针,但在64位系统会有截断问题。

      解决办法:指针的输入输出需要用%p。

    • 32位系统上,使用%d来打印int或long类型的值,但是在64位平台上会截断。

      解决办法:long的输入输出需要用%ld。

    关于size_t类型:在32bit平台上,它的原形是unsigned int,而在64bit平台上,它的原形是unsigned long。这导致使用printf等函数时,无论使用%u或者%llu打印都会报warning。目前可采用%lu打印方式,规避该问题。

    输入的时候,注意buffer的长度,在64位下发生变化了,确保buffer的长度足够:

    char * buf[9];
    int a = 0;
    Sprint(buf, "%p", &a);
    

    上述代码在32bit模式下是正确的,一个指针的值是0xC0001A38,输入后是8个字符。在64bit模式下,长度是16个字符。

  6. 最大数、无效数的宏定义。

    特别注意代码中有无引用相关最大数,无效数的宏定义,在64bit下可能已经发生变化,如:

    #define BYTES_IN_WORD 4
    #define INVALID_DMA_ADDRESS 0xffffffff
    

    这些是否在64bit下还是无效或最大,需要注意,如:

    size_t count = BIG_NUMBER; 
    for (unsigned int index = 0; index != count; index++)
    ………
    

    size_t是64位的,而index是32位的,所以上边的循环会一直循环下去。

  7. 检查union中成员变量大小。

    union中成员变量大小要一致,32、64位差异可能会导致原union中的某些成员变量变大,各成员变量平衡被打破。检查各联合体,确保联合体内各成员变量大小一致。

    如下示例是一个常见的开源代码包,可在ILP32却不可在LP64环境下运行。代码假定长度为2的unsigned short数组,占用了与long同样的空间,可这在LP64平台上却不正确。

    union{
        unsigned long bytes;
        unsigned short len[2];
    } size;
    

    正确的方法是检查是否对结构体有特殊的应用,如果有,那么需要在所有代码中仔细检查联合体,以确认所有的数据成员在LP64中都为同等长度。

优化建议

  1. 数据类型方面。

    • 如果确定涉及到64bit计算和存储则定义为long long类型。如果32bit够用的话,数据类型建议使用int或unsigned int,以减少内存。不允许定义long类型。

    • 类型定义使用体系结构无关的类型定义:u32,u16,u64,s16,s32,s64。

  2. 结构体定义:将最常用的数据字段都定义在一起相邻位置,这样便于一次取到Cache中,提升运行性能和效率。

  3. 由于对齐方式变为8字节了,结构体的排放方式需要注意新的对齐方式。

    struct MyStruct
    {
        bool m_bool;
        char *m_pointer;
        int m_int;
    };
    

    如上所示,结构体bool和char*之间会填充4个字节,故需要修改结构体排布方式,以节约内存使用,如下:

    struct MyStructOpt
    {
        char *m_pointer;
        int m_int;
        bool m_bool;
    };
    

其他建议

  1. 关于非对齐操作

    不同芯片平台对不同介质的非对齐操作支持程度不一致。在使用非对齐操作前,请确认目标平台是否支持以非对齐方式访问。

  2. 关于联合和位域的使用

    C语言标准中未定义联合和位域的比特序。在编译时,联合和位域的比特序由编译器实现决定。建议在程序开发中减少联合和位域的使用。