LiteOS基础学习

1 IDE环境安装

目的:安装LiteOS IDE,并且是使用仿真方式运行。

1.1 IDE安装

HUAWEI LiteOS Studio安装 (gitee.io)

image-20221219142304029

1.2 中文安装

HUAWEI LiteOS Studio扩展介绍 (gitee.io)

  • 选择通过VSIX文件安装:

image-20221219142621153

  • 然后选择设置为中文:

HUAWEI LiteOS Studio安装 (gitee.io)

步骤 1 安装完成后,通过点击菜单栏中的View->Command Palette调出命令输入界面

步骤 2 在命令输入框中输入Configure Display Language,回车,选择需要切换的语言(enzh-cn等),弹出重启IDE完成配置的提示窗口,点击重启,即可完成语言切换

image-20221219143112753

1.3 常用工具安装

HUAWEI LiteOS Studio安装 (gitee.io)

1.3.1 交叉编译工具链

kenneth/GNU-Arm-Embedded-Toolchain - 码云 - 开源中国 (gitee.com)

直接将压缩包解压缩,然后放到IDE的安装目录下:

image-20221219144220704

1.3.2 Make工具

https://gitee.com/rtos_yuan/x-pack-windows-build-tools.git

直接将压缩包解压缩,然后放到IDE的安装目录下:

image-20221219144242138

1.3.3 QEMU仿真器

QEMU for Windows – Installers (64 bit) (weilnetz.de)

直接双击安装包,然后一直Next即可。

1.4 编译运行

1.4.1 编译

首先配置目标板子为QEMU:

image-20221219145707413

然后点击编译按钮:

image-20221219144815004

编译完成状态,会输出一个Bin文件:

image-20221219145521329

1.4.2 运行

STM32工程示例 (gitee.io)

以仿真方式运行,需要先配置烧录器为Simulator:

image-20221219145852929

然后点击烧录:

image-20221219150006355

2 基础学习

1、Windows下搭建LiteOS Studio IDE的仿真环境;

2、了解基本的编译系统;

3、添加用户自定义命令。

代码本地路径:D:\LiteOS\SVN\Code

LiteOS API: Thread (huawei.com)

2.1 编译系统

配置&编译框架简介_LiteOS_编译和开发工具_华为云 (huaweicloud.com)

Huawei LiteOS使用Kconfig文件配置系统,基于GCC/Makefile实现组件化编译。

不论是Linux下使用make menuconfig命令配置系统,还是Windows下使用Huawei LiteOS Studio进行图形化配置,Huawei LiteOS都会同时解析、展示根目录下的.config文件和tools/menuconfig/config.in文件(该文件包含了各个模块的Kconfig文件),同时在开发板的include文件夹下生成menuconfig.hconfig.in文件由Kconfig语言(一种菜单配置语言)编写而成。config.in文件决定了要展示的配置项,.config文件决定了各个配置项的默认值。

Huawei LiteOS通过在根目录下执行make命令完成自动化编译整个工程。对于根目录下的Makefile文件,其中包含了config.mkconfig.mk又包含了los_config.mk,而los_config.mk则包含了各个模块的Makefile.config文件,从而定义了对整个工程的编译链接规则。

img

Huawei LiteOS目前支持Windows及Linux平台的配置&编译。

  • 对于Windows平台,提供了Huawei LiteOS Studio图形化IDE,用户可直接在 Studio上完成配置&编译。
  • 对于Linux平台,通过menuconfig进行组件化配置及裁剪后,执行make命令完成编译。

2.1.1 menuconfig系统

简介:Huawei LiteOS使用Kconfig文件配置系统。所用的Kconfig语言是一种菜单配置语言,config.in和Kconfig都由该语言编写而成。Huawei LiteOS使用python kconfiglib来解析、展示Kconfig文件。解析Kconfig文件后,会在根目录下生成/更新.config文件,同时在开发板的include文件夹下生成menuconfig.h。使用menuconfig前,需要先安装python和kconfiglib代码。

代码结构:

  • 顶层Makefile中包括Makefile.kconfig文件。
include tools/menuconfig/Makefile.kconfig
  • Makefile.kconfig文件会根据不同的make目标,执行python脚本带不同的参数
.PHONY += menuconfig savemenuconfig
.PHONY += defconfig allyesconfig allnoconfig

menuconfig: # 打开menuconfig配置菜单
	python  $(LITEOSTOPDIR)/tools/menuconfig/usr_config.py
	-mv -f $(LITEOS_MENUCONFIG_H) $(LITEOS_PLATFORM_MENUCONFIG_H)
	sh $(LITEOSTOPDIR)/components/download.sh

savemenuconfig: # 保存当前菜单配置
	python  $(LITEOSTOPDIR)/tools/menuconfig/usr_config.py savemenuconfig
	-mv -f $(LITEOS_MENUCONFIG_H) $(LITEOS_PLATFORM_MENUCONFIG_H)

defconfig: # 根据kconfig文件,保存默认配置
	python  $(LITEOSTOPDIR)/tools/menuconfig/usr_config.py defconfig
	-mv -f $(LITEOS_MENUCONFIG_H) $(LITEOS_PLATFORM_MENUCONFIG_H)

allyesconfig: # 尽可能将多的配置项设置为'n'
	python  $(LITEOSTOPDIR)/tools/menuconfig/usr_config.py allyesconfig
	-mv -f $(LITEOS_MENUCONFIG_H) $(LITEOS_PLATFORM_MENUCONFIG_H)

allnoconfig: # 尽可能将多的配置项设置为'y'
	python  $(LITEOSTOPDIR)/tools/menuconfig/usr_config.py allnoconfig
	-mv -f $(LITEOS_MENUCONFIG_H) $(LITEOS_PLATFORM_MENUCONFIG_H)

因此最终执行的是:tools\menuconfig\usr_config.py脚本,这个是根据python的kconfiglib库进行编写的,可以参考官方的说明:GitHub - ulfalizer/Kconfiglib: A flexible Python 2/3 Kconfig implementation and library

关于kconfig的语法可以参考:Kconfig Language — The Linux Kernel documentation

执行后的输出文件:menuconfig.h(配置头文件)、.config(Makefile使用的配置项选择文件)。

另外,关于python Kconfiglib库和kconfig语法可以参考我的博客:Python Kconfiglib初次学习 - zhengcixi - 博客园 (cnblogs.com)

  • 相关的文件

主要在tools\menuconfig目录以及公共模块目录下的kconfig文件

config.in:顶层kconfig配置文件,里面包含很多source配置项,用来包含其它模块的kconfig文件
Makefile.kconfig:makefile文件
usr_config.py:python kconfiglib库调用脚本

2.1.2 顶层Makefile

Makefile基础知识学习可参考:

https://www.gnu.org/software/make/manual/make.html (GNU make官方文档)

https://seisman.github.io/how-to-write-makefile/overview.html (跟我一起写Makefile 陈皓)

LiteOS_Lab Makefile分析-云社区-华为云 (huaweicloud.com)

编译输出文件目录out\realview-pbx-a9,由于QEMU使用的是realview-pbx-a9开发板进行仿真。

编译依赖部分文件清单

文件 作用
Makefile 顶层Makefile
config.mk 包含los_config.mk
build\mk\los_config.mk 包含了各个模块的Makefile.config文件,定义了对整个工程的编译链接规则
HIDE := @ 控制了编译打印是否输出
build\mk\liteos_tables_ldflags.mk 重要)添加系统命令或者用户自定义命令的命令项参数
build\mk\module.mk 编译各个模块具体的Makefile文件
build\mk\module_lib.mk 编译库的Makefile文件
targets\realview-pbx-a9\Makefile 以编译realview-pbx-a9目标板为例进行说明,这是配置编译该目录下的一些编译选项,具体的编译执行是module.mk文件。

一些配置说明:

build\mk\los_config.mk

# HIDE变量:用于进行输出抑制,如果用户想查看编译过程,可以进行打开;
HIDE    := @  # 默认输出抑制,不打印编译过程中的字符串
HIDE    :=    # 输出不抑制,打印编译过程的字符串

# MODULE变量:编译模块的Makefile文件路径
MODULE = $(MK_PATH)/module.mk

# MODULE_LIB变量:编译输出库文件的Makefile路径
MODULE_LIB = $(MK_PATH)/module_lib.mk

# OUT变量:编译输出文件路径
# BUILD变量:编译输出目标文件路径
OUT  = $(LITEOSTOPDIR)/out/$(LITEOS_PLATFORM)
BUILD  = $(OUT)/obj
MK_PATH  = $(LITEOSTOPDIR)/build/mk

config.mk

# $(TOP_LD_PATH) = d:/LiteOS/SVN/Code 源码顶层路径
# $(LITEOS_MK_PATH) = d:/LiteOS/SVN/Code/build/mk 编译Makefile路径
# $(LITEOS_SUBDIRS) = arch/arm/cortex_a_r targets/bsp targets/realview-pbx-a9 kernel compat lib osdepends components demos shell 编译模块清单
# $(LIB_BIGODIR) = d:/LiteOS/SVN/Code/out/realview-pbx-a9/lib/obj 编译输出目标文件路径

TOP_LD_PATH      = $(LITEOSTOPDIR)
LITEOS_MK_PATH   = $(MK_PATH)
LITEOS_SUBDIRS   = $(LIB_SUBDIRS)
LIB_BIGODIR      = $(LITEOS_LIB_BIGODIR)

顶层Makefile执行的大致流程:

Makefile

可以看出,需要编译以下的模块:

arch/arm/cortex_a_r targets/bsp targets/realview-pbx-a9 kernel compat lib osdepends components demos shell

2.1.3 模块Makefile

targets\realview-pbx-a9\Makefile为例进行说明:

# 编译的本地源文件目录,包括C文件和S文件
LOCAL_SRCS += $(ASSRCS)

# 编译的本地头文件目录
LOCAL_INCLUDE += \
    -I $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM)/include \
    -I $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM)/include/hisoc \
    -I $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM)/include/asm \
    -I $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM)/include/pm \
    -I $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM)/uart \
    -I $(LITEOSTOPDIR)/targets/$(LITEOS_PLATFORM) \
    -I $(LITEOSTOPDIR)/include

BOARD_DEF += $(C_DEFS)

LOCAL_FLAGS := $(BOARD_DEF) $(LOCAL_INCLUDE) $(LITEOS_GCOV_OPTS)

# $(MK_PATH) = d:/ESC225/LiteOS/SVN/Code/build/mk
# MODULE = $(MK_PATH)/module.mk
include $(MODULE)

最终就是使用build\mk\module.mk进行模块的编译。

因此,如果想增加编译的C文件,仅需要在变量LOCAL_SRCS变量中进行添加,想添加头文件,就在LOCAL_INCLUDE变量中添加,比如新增编译bsp/cmds目录下的文件:

ifeq ($(LOSCFG_LSW_SHELL), y)
LOCAL_SRCS += $(wildcard bsp/cmds/*.c)
LOCAL_INCLUDE += -I $(LITEOSTOPDIR)/targets/bsp/cmds
endif

2.2 启动流程

内核启动流程_LiteOS_内核_华为云 (huaweicloud.com)

img

启动流程

2.3 Shell命令添加方法

概述_LiteOS_维测指南_Shell_华为云 (huaweicloud.com)

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

功能分类 接口名 描述
注册命令 SHELLCMD_ENTRY 静态注册命令
osCmdReg 动态注册命令

静态注册命令方式一般用于注册系统常用命令,动态注册命令方式一般用于注册用户命令。

静态注册命令有5个入参,动态注册命令有4个入参。下面除去第一个入参是静态注册独有的,剩余的四个入参两个注册命令是一致的。

/* 静态注册命令宏 */
#define SHELLCMD_ENTRY(l, cmdType, cmdKey, paraNum, cmdHook)    \
    CmdItem l LOS_HAL_TABLE_ENTRY(shellcmd) = {                 \
        cmdType,                                                \
        cmdKey,                                                 \
        paraNum,                                                \
        cmdHook                                                 \
    }

/* 动态注册命令函数 */
extern UINT32 osCmdReg(CmdType cmdType, CHAR *cmdKey, UINT32 paraNum, CmdCallBackFunc cmdProc);
  • 第一个入参:这个入参是静态注册独有的,动态注册中没有这个入参。命令变量名,用于设置链接选项(build/mk/liteos_tables_ldflags.mkLITEOS_TABLES_LDFLAGS变量)。

    例如变量名为ls_shellcmd,链接选项就应该设置为:LITEOS_TABLES_LDFLAGS += -uls_shellcmd

  • 第二个入参:命令类型,目前支持两种命令类型。

    • CMD_TYPE_EX:不支持标准命令参数输入,会把用户填写的命令关键字屏蔽掉。例如:输入ls /ramfs,传入给命令处理函数的参数只有/ramfs,对应于命令处理函数中的argv[0],而ls命令关键字并不会被传入。
    • CMD_TYPE_STD:支持的标准命令参数输入,所有输入的字符都会通过命令解析后被传入。例如:输入ls /ramfsls/ramfs都会被传入命令处理函数,分别对应于命令处理函数中的argv[0]argv[1]
  • 第三个入参:命令关键字,是命令处理函数在Shell中对应的名称。命令关键字必须唯一,即两个不同的命令项不能有相同的命令关键字,否则只会执行其中一个。Shell在执行用户命令时,如果存在多个命令关键字相同的命令,只会执行在help命令中排在最前面的那个。

  • 第四个入参:命令处理函数的入参最大个数。

    • 静态注册命令暂不支持设置。
    • 动态注册命令支持设置不超过32的入参最大个数,或者设置为XARGS(其在代码中被定义为0xffffffff)表示不限制参数个数。
  • 第五个入参:命令处理函数名,即在Shell中执行命令时被调用的函数。

2.3.1 新增命令开发流程

(1)定义Shell命令处理函数

Shell命令处理函数用于处理注册的命令。例如定义一个命令处理函数osShellCmdLs,处理ls命令,并在头文件中声明命令处理函数原型。

int osShellCmdLs(int argc, const char **argv);

命令处理函数的参数与C语言中main函数的参数类似,包括两个入参:

  • argc:Shell命令的参数个数。个数中是否包括命令关键字,和注册命令时的命令类型有关。
  • argv:为指针数组,每个元素指向一个字符串,该字符串就是执行shell命令时传入命令处理函数的参数。参数中是否包括命令关键字,和注册命令时的命令类型有关。

(2)注册命令:有静态注册命令和系统运行时动态注册命令两种注册方式。

(3)对于静态注册命令方式,在build/mk/liteos_tables_ldflags.mk中设置链接选项(LITEOS_TABLES_LDFLAGS变量)。

(4)通过make menuconfig使能Shell,详见配置项

(5)编译烧录系统后,可以执行新增的Shell命令。

2.3.2 静态注册编程实例

本实例演示如何使用静态注册命令方式新增一个名为test的Shell命令,步骤如下:

  1. 定义一个新增命令所要调用的命令处理函数cmd_test
  2. 使用SHELLCMD_ENTRY函数添加新增命令项。
  3. liteos_tables_ldflags.mk中添加链接该新增命令项参数。
  4. 通过make menuconfig使能Shell
  5. 重新编译代码后运行。

1、定义命令所要调用的命令处理函数cmd_test

#include "shell.h"
#include "shcmd.h"

int cmd_test(int argc, const char **argv)
{
    int i = 0;

    printf("hello everybody!\n");
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++) {
        printf("args[%u] = %s\n", i, argv[i]);
    }

    return 0;
}

2、添加新增命令项

SHELLCMD_ENTRY(test_shellcmd, CMD_TYPE_EX, "test", 0, (CMD_CBK_FUNC)cmd_test);

3、在链接选项中添加链接该新增命令项参数,这里新增加了一个变量LITEOS_TABLES_LSW_LDFLAGS

build/mk/liteos_tables_ldflags.mkLITEOS_TABLES_LDFLAGS项下添加-utest_shellcmd

LITEOS_TABLES_LSW_LDFLAGS := \
    -utest_shellcmd

LITEOS_TABLES_LDFLAGS := \
	... ... \
    $(LITEOS_TABLES_LSW_LDFLAGS)

4、通过make menuconfig使能Shell,即设置LOSCFG_SHELL=y

5、重新编译代码,烧录执行,可以看出第一个字符串test没有当作参数。

Huawei LiteOS # test 0 1 
hello everybody!
argc = 2
args[0] = 0
args[1] = 1

2.3.3 动态注册编程实例

本实例演示如何使用动态注册命令方式新增一个名为test的Shell命令。

  1. 定义一个新增命令所要调用的命令处理函数cmd_test
  2. 使用osCmdReg函数添加新增命令项。
  3. 通过make menuconfig使能Shell。
  4. 重新编译代码后运行。

1、定义命令所要调用的命令处理函数cmd_test

#include "shell.h"
#include "shcmd.h"

int cmd_test(int argc, const char **argv)
{
    int i = 0;

    printf("hello everybody!\n");
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++) {
        printf("args[%u] = %s\n", i, argv[i]);
    }

    return 0;
}

2、app_init函数中调用osCmdReg函数动态注册命令,必须放在DemoEntry函数之后:

__attribute__((weak)) VOID app_init(VOID)
{
    printf("app init!!!!\n");
    (VOID)DemoEntry();
    
    osCmdReg(CMD_TYPE_EX, "test", 0, (CMD_CBK_FUNC)cmd_test);

}

3、通过make menuconfig使能Shell,即设置LOSCFG_SHELL=y

4、重新编译代码、烧录、执行:

Huawei LiteOS # test 1 2 3 4 5
hello everybody!
argc = 5
args[0] = 1
args[1] = 2
args[2] = 3
args[3] = 4
args[4] = 5

2.3.4 系统命令

LiteOS自带了一些系统命令,可以参考:

使能系统命令_LiteOS_维测指南_Shell_系统命令参考_华为云 (huaweicloud.com)

一些常用的命令:

  • help命令:用于显示当前操作系统内所有的Shell命令。
  • date命令:用于查询及设置系统时间。
  • uname命令:用于显示操作系统的名称,系统编译时间,版本信息等。
  • task命令:用于查询系统任务信息。
  • free命令:可显示系统内存的使用情况,同时显示系统的text段、data段、rodata段、bss段大小。
  • cpup命令:用于查询系统CPU的占用率,并以百分比显示占用率。

2.4 任务

2.4.1 简介

概述_LiteOS_内核_任务_华为云 (huaweicloud.com)

Huawei LiteOS的任务模块具有如下特性:

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

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

功能分类 描述 接口名
创建和删除任务 创建任务,并使该任务进入suspend状态,不对该任务进行调度。如果需要调度,可以调用LOS_TaskResume使该任务进入ready状态 LOS_TaskCreateOnly
创建任务,并使该任务进入ready状态,如果就绪队列中没有更高优先级的任务,则运行该任务 LOS_TaskCreate
创建任务,任务栈由用户传入,并使该任务进入suspend状态,不对该任务进行调度。如果需要调度,可以调用LOS_TaskResume使该任务进入ready状态 LOS_TaskCreateOnlyStatic
创建任务,任务栈由用户传入,并使该任务进入ready状态,如果就绪队列中没有更高优先级的任务,则运行该任务 LOS_TaskCreateStatic
删除指定的任务 LOS_TaskDelete
控制任务状态 恢复挂起的任务,使该任务进入ready状态 LOS_TaskResume
挂起指定的任务,然后切换任务 LOS_TaskSuspend
任务延时等待,释放CPU,等待时间到期后该任务会重新进入ready状态 LOS_TaskDelay
当前任务释放CPU,并将其移到具有相同优先级的就绪任务队列的末尾 LOS_TaskYield
控制任务调度 锁任务调度,但任务仍可被中断打断 LOS_TaskLock
解锁任务调度 LOS_TaskUnlock
控制任务优先级 设置当前任务的优先级 LOS_CurTaskPriSet
设置指定任务的优先级 LOS_TaskPriSet
获取指定任务的优先级 LOS_TaskPriGet
设置任务亲和性 设置指定任务的运行cpu集合(该函数仅在SMP模式下支持) LOS_TaskCpuAffiSet
回收任务栈资源 回收所有待回收的任务栈资源 LOS_TaskResRecycle
获取任务信息 获取当前任务的ID LOS_CurTaskIDGet
获取指定任务的信息,包括任务状态、优先级、任务栈大小、栈顶指针SP、任务入口函数、已使用的任务栈大小等 LOS_TaskInfoGet
获取指定任务的运行cpu集合(该函数仅在SMP模式下支持) LOS_TaskCpuAffiGet
任务信息维测 注册任务上下文切换的钩子函数。只有开启LOSCFG_BASE_CORE_TSK_MONITOR宏开关后,这个钩子函数才会在任务发生上下文切换时被调用 LOS_TaskSwitchHookReg
任务空闲处理回调 注册空闲任务钩子函数,当系统空闲时调用 LOS_IdleHandlerHookReg

2.4.2 编程示例

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

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

UINT32 Example_TaskHi(VOID)
{
    UINT32 ret;

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

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

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

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

    return ret;
}

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

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

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

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

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

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

    return ret;
}

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

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

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

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

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

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

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

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

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

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

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

    return LOS_OK;
}

运行:

Huawei LiteOS # test-cmd
LOS_TaskLock() Success!
Example_TaskHi create Success!
Enter TaskHi Handler.
Example_TaskLo create Success!
Enter TaskLo Handler.

Huawei LiteOS # TaskHi LOS_TaskDelay Done.
TaskHi LOS_TaskSuspend Success.
TaskHi LOS_TaskResume Success.
TaskHi LOS_TaskDelete Success.

2.5 互斥锁

2.5.1 简介

概述_LiteOS_内核_互斥锁_华为云 (huaweicloud.com)

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

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

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

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

功能分类 接口名 描述
创建/删除互斥锁 LOS_MuxCreate 创建互斥锁
LOS_MuxDelete 删除指定互斥锁
申请/释放互斥锁 LOS_MuxPend 申请指定互斥锁
LOS_MuxPost 释放指定互斥锁

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

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

释放互斥锁:

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

2.5.2 编程示例

本实例实现如下流程:

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

代码实现:

#include "los_typedef.h"
#include "los_mux.h"

/* 互斥锁句柄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   = 15;
    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;
}

运行测试:

Huawei LiteOS # test
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.

Huawei LiteOS # test
task1 try to get mutex, wait 10 ticks.
task2 try to get mutex, wait forever.
task1 get mutex g_testMux.
task2 get mutex g_testMux and suspend 100 ticks.
task2 resumed and post the g_testMux

可以看出,任务1和任务2谁先运行是随机的,不是官网说的那样。

posted @ 2022-12-23 11:31  zhengcixi  阅读(1694)  评论(0编辑  收藏  举报
回到顶部