LiteOS基础学习
1 IDE环境安装
目的:安装LiteOS IDE,并且是使用仿真方式运行。
1.1 IDE安装
HUAWEI LiteOS Studio安装 (gitee.io)
1.2 中文安装
HUAWEI LiteOS Studio扩展介绍 (gitee.io)
- 选择通过VSIX文件安装:
- 然后选择设置为中文:
HUAWEI LiteOS Studio安装 (gitee.io)
步骤 1 安装完成后,通过点击菜单栏中的View
->Command Palette
调出命令输入界面
步骤 2 在命令输入框中输入Configure Display Language
,回车,选择需要切换的语言(en
、zh-cn
等),弹出重启IDE完成配置的提示窗口,点击重启,即可完成语言切换
1.3 常用工具安装
HUAWEI LiteOS Studio安装 (gitee.io)
1.3.1 交叉编译工具链
kenneth/GNU-Arm-Embedded-Toolchain - 码云 - 开源中国 (gitee.com)
直接将压缩包解压缩,然后放到IDE的安装目录下:
1.3.2 Make工具
https://gitee.com/rtos_yuan/x-pack-windows-build-tools.git
直接将压缩包解压缩,然后放到IDE的安装目录下:
1.3.3 QEMU仿真器
QEMU for Windows – Installers (64 bit) (weilnetz.de)
直接双击安装包,然后一直Next即可。
1.4 编译运行
1.4.1 编译
首先配置目标板子为QEMU:
然后点击编译按钮:
编译完成状态,会输出一个Bin文件:
1.4.2 运行
以仿真方式运行,需要先配置烧录器为Simulator:
然后点击烧录:
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.h
。config.in
文件由Kconfig
语言(一种菜单配置语言)编写而成。config.in
文件决定了要展示的配置项,.config
文件决定了各个配置项的默认值。
Huawei LiteOS
通过在根目录下执行make命令完成自动化编译整个工程。对于根目录下的Makefile文件,其中包含了config.mk
,config.mk
又包含了los_config.mk
,而los_config.mk
则包含了各个模块的Makefile
和.config
文件,从而定义了对整个工程的编译链接规则。
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执行的大致流程:
可以看出,需要编译以下的模块:
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)
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.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中执行命令时被调用的函数。
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命令,步骤如下:
- 定义一个新增命令所要调用的命令处理函数
cmd_test
。 - 使用
SHELLCMD_ENTRY
函数添加新增命令项。 - 在
liteos_tables_ldflags.mk
中添加链接该新增命令项参数。 - 通过
make menuconfig
使能Shell
。 - 重新编译代码后运行。
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.mk
中LITEOS_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命令。
- 定义一个新增命令所要调用的命令处理函数
cmd_test
。 - 使用
osCmdReg
函数添加新增命令项。 - 通过
make menuconfig
使能Shell。 - 重新编译代码后运行。
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 编程示例
本实例实现如下流程:
- 任务
Example_TaskEntry
创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1
、Example_MutexTask2
。Example_MutexTask2
优先级高于Example_MutexTask1
,解锁任务调度,然后Example_TaskEntry
任务休眠300Tick
。 Example_MutexTask2
被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick
,Example_MutexTask2
挂起,Example_MutexTask1
被唤醒。Example_MutexTask1
以定时阻塞模式申请互斥锁,等待时间为10Tick
,因互斥锁仍被Example_MutexTask2
持有,Example_MutexTask1
挂起。10Tick
超时时间到达后,Example_MutexTask1
被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2
持有,Example_MutexTask1
挂起。100Tick
休眠时间到达后,Example_MutexTask2
被唤醒,释放互斥锁,唤醒Example_MutexTask1
。Example_MutexTask1
成功获取到互斥锁后,释放锁。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谁先运行是随机的,不是官网说的那样。