Niobe开发板中基于OpenHarmony操作系统进行多线程(多任务)开发
niobe开发套件详情介绍:Niobe行业物联网开发板及套件详解
线程的基本概念
从系统角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。
OpenHarmony LiteOS可以给用户提供多个线程,实现线程间的切换,帮助用户管理业务程序流程。具有如下特性:
- 支持多线程。
- 一个线程代表一个任务。
- 抢占式调度机制,高优先级的线程可打断低优先级线程,低优先级线程必须在高优先级线程阻塞或结束后才能得到调度。
- 相同优先级线程支持时间片轮转调度方式。
- 共有32个优先级[0-31],最高优先级为0,最低优先级为31。用户进程可配置的优先级有22个 (10~31)。
1、线程的状态
线程有多种运行状态。系统初始化完成后,创建的线程就可以在系统中竞争一定的资源,由内核进行调度。
线程状态通常分为以下四种:
- 就绪(Ready):该线程在就绪队列中,只等待CPU。
- 运行(Running):该线程正在执行。
- 阻塞(Blocked):该线程不在就绪队列中。包含线程被挂起(suspend状态)、线程被延时(delay状态)、线程正在等待信号量、读写队列或者等待事件等。
- 退出态(Dead):该线程运行结束,等待系统回收资源。
2、 线程状态迁移
就绪态→运行态: 任务创建后进入就绪态,发生任务切换时,就绪队列中最高优先级的任务被执行,从而进入运行态,同时该任务从就绪队列中移出。
运行态→阻塞态 :正在运行的任务发生阻塞(挂起、延时、读信号量等)时,将该任务插入到对应的阻塞队列中,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪队列中最高优先级任务。
阻塞态→就绪态(阻塞态→运行态):阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪队列,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,该任务由就绪态变成运行态。
就绪态→阻塞态 : 任务也有可能在就绪态时被阻塞(挂起),此时任务状态由就绪态变为阻塞态,该任务从就绪队列中删除,不会参与任务调度,直到该任务被恢复。
运行态→就绪态 : 有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪队列中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪队列中。
运行态→退出态 : 运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出状态以及Invalid状态。例如,任务运行结束但是没有自删除,对外呈现的就是Invalid状态,即退出态。
阻塞态→退出态 : 阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。
3、线程管理
对于多线程的场景,HarmonyOS内核管理线程靠任务池和就绪队列,执行靠调度算法。
调度算法:HarmonyOS内核中的线程采用抢占式调度机制,同时支持SCHED_RR和SCHED_FIFO调度策略
RR策略能基本保证我们每个任务都能够得到有效的执行,不会有一些任务进行长时间等待
FIFO策略优点在于任务的切换比较简单,而且对于一些时间片不好把握的任务来说,FIFO能偶更有效的利用我们的cpu。
线程相关API
此处介绍cmsis2.0的线程接口,头文件:”//third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h”
创建线程
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr);
函数osThreadNew通过将线程添加到活动线程列表并将其设置为就绪状态来启动线程函数。线程函数的参数使用参数指针*argument传递。当创建的thread函数的优先级高于当前运行的线程时,创建的thread函数立即启动并成为新的运行线程。线程属性是用参数指针attr定义的。属性包括线程优先级、堆栈大小或内存分配的设置。可以在RTOS启动(调用 osKernelStart)之前安全地调用该函数,但不能在内核初始化 (调用 osKernelInitialize)之前调用该函数。
开发实例
1、 确定目录结构
先在路径./applications/app下新建一个目录,用于存放业务源码文件。其中“.”表示OpenHarmony源码的根目录。
例如:在app下新增业务NIOBE_OS_helloworld,其中hello_world.c为业务代码,BUILD.gn为编译脚本,其目录结构如下:
.
└── applications
└── app
│── TW002_OS_thread
│ │── os_thread_example.c
│ └── BUILD.gn
└── BUILD.gn
2、 编写业务代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
/*****任务一*****/
void thread_entry1(void)
{
int sum = 0;
while (1)
{
printf("This is Niobe Thread1----%d\r\n", sum++);
usleep(500000);
}
}
/*****任务二*****/
void thread_entry2(void)
{
int sum = 0;
while (1)
{
printf("This is Niobe Thread2----%d\r\n", sum++);
usleep(500000);
}
}
/*****任务创建*****/
static void OS_Thread_example(void)
{
osThreadAttr_t attr;
attr.name = "thread1";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 4;
attr.priority = 25;
if (osThreadNew((osThreadFunc_t)thread_entry1, NULL, &attr) == NULL)
{
printf("Falied to create thread1!\n");
}
attr.name = "thread2";
if (osThreadNew((osThreadFunc_t)thread_entry2, NULL, &attr) == NULL)
{
printf("Falied to create thread2!\n");
}
}
3、 编写将业务构建成静态库的BUILD.gn
static_library("os_thread_example"){
sources = [
"os_thread_example.c"
]
include_dirs = [
"//third_party/cmsis/CMSIS/RTOS2/Include"
]
}
4、编写模块BUILD.gn文件
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
#"NIOBE_OS_helloworld:helloworld",
"TW002_OS_thread:os_thread_example"
]
}
编译
用docker编译,进入OpenHarmony代码根目录,运行命令进入docker镜像,在镜像中用hb编译:
sudo docker run -it -v $(pwd):/home/openharmony swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:0.0.5
hb set
.
//继续回车选择niobe_wifi_iot
hb build -b release -f
等待编译成功。
烧录
编译成功后,bin文件会保存在out/niobe/niobe_wifi_iot目录下:
用HiBurn.exe将Hi3861_wifiiot_app_allinone.bin文件烧录到niobe核心板上:
首先用typeC线连接电脑和Niobe核心板,可通过设备管理确定Niobe连接的端口号,该端口号后续HiBurn和sscom都需要。
再通过HiBurn.exe工具将固件烧录到Niobe上,HiBurn工具的获取和操作可参考烧录指导
调试
采用串口调试工具sscom查看串口打印信息,先对sscom进行配置,设置端口号、波特率等:
点击打开串口,按下Niobe核心板上的复位按键,可通过sscom看到串口打印日志如下:
This is Niobe Thread1----2
This is Niobe Thread2----5
This is Niobe Thread1----3
This is Niobe Thread2----6
可以看到线程thread_entry1和线程thread_entry2交替运行。