OpenHarmony智能开发套件[内核编程·上] 原创 精华

stackor已停更
发布于 2023-5-13 21:39
浏览
5收藏

OpenHarmony智能开发套件[内核编程·上]

前言

本篇具体介绍OpenHarmony在智能开发套件Hi3861上的内核编程学习。

编程入门[Hello,OpenHarmony]

在正式开始之前,对于刚接触OpenHarmony的伙伴们,面对大篇幅的源码可能无从下手,不知道怎么去编码写程序,下面用一个简单的例子带伙伴们入门。

任务

编写程序,让开发板在串口调试工具中输出”Hello,OpenHarmony“。

操作

在源码的根目录中有名为”applications“的文件,他存放着应用程序样例,下面是他的目录结构:

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

我们要编写的程序样例就在源码根目录下的:applications/sample/wifi-iot/app/

下面将具体演示如何编写程序样例。

  1. 新建样例目录

    applications/sample/wifi-iot/app/hello_demo

  2. 新建源文件和gn文件

    applications/sample/wifi-iot/app/hello_demo/hello.c

    applications/sample/wifi-iot/app/hello_demo/BUILD.gn

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

  1. 编写源文件

​ hello.c

#include <stdio.h>
#include "ohos_init.h"

void hello(void){
    printf("Hello,OpenHarmony!");
}

SYS_RUN(hello);

​ 第一次操作的伙伴们可能会在引入”ohos_init.h“库时报错,面对这个问题我们只需要修改我们的include path即可,一般我们直接在目录下的 .vscode/c_cpp_properties.json文件中直接修改includePath

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 笔者的代码版本是OpenHarmony3.2Release版,不同版本的源码可能库所存放的路径不同,那么怎么去找到对应的库呢,对于不熟悉源码结构的伙伴们学习起来很不友好。

​ 对于在纯Windows环境开发的伙伴们,笔者推荐使用everything这款工具,它可以快速查找主机中的文件,比在资源管理器的搜索快上不少。

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

everything官网

​ everything似乎不能找到我WSL中的Ubuntu中的文件,因此对于Windows + Linux环境下的伙伴们,这款工具又不那么适用。那就可以根据Linux的查询指令来定位文件所在目录,下面提供查询案例防止有不熟悉Linux的伙伴们。我们使用locate指令来查找文件。

​ 首先安装locate

sudo apt install mlocate

​ 更新mlocate.db

sudo updatedb

​ 查询文件目录

locate ohos_init.h

​ 找到我们源码根目录下 include路径下的ohos_init.h文件

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

  1. 编写gn文件
static_library("sayHello"){
    sources = [
        "hello.c"
    ]
    include_dirs = [
        "//commonlibrary/utils_lite/include"
    ]
}

​ static_library表示我们编写的静态模块,名为"sayHello", sources表示我们要编译的源码,include_dirs表示我们引入的库,这里的双斜杠就代表我们的源码根目录,”/commonlibrary/utils_lite/include“就是我们ohos_init.h的所在目录

  1. 编写app下的gn文件

在app的目录下也有一个gn文件,我们只需要去修改他即可

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 这表示我们的程序将会执行hello_demo样例中的sayHello模块

  1. 编译,烧录,串口调试

这一步就属于基础操作了,不做过多赘述,不会的伙伴们可以看我之前发布的[环境搭建篇],里面也详细介绍了操作流程。

环境搭建篇

  1. 观察控制台的输出

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 至此编码完成了编码入门,下面就具体介绍OpenHarmony的内核编程。

内核

内核介绍

​ 什么是内核?或者说内核在一个操作系统中起到一个什么样的作用?相信初次接触这个词的伙伴们也会有同样的疑问。不过不用担心,笔者会尽可能地通俗地介绍内核的相关知识,以便大家能够更好地去体会内核编程。

​ 我们先来看一张图,这是OpenHarmony官网发布的技术架构图

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 我们可以看到最底层叫做内核层,有Linux,LiteOS等。内核在整个架构,或者操作系统中起到一个核心作用,他负责管理计算机系统内的资源和硬件设备,提供给顶层的应用层一个统一规范的接口,从而使得整个系统能够完成应用与硬件的交互。

​ 具体点来说,内核可以做以下相关的工作:

  1. 进程管理
  2. 内存管理
  3. 文件资源管理
  4. 网络通信管理
  5. 设备驱动管理

​ 当然不局限于这些,这里只是给出具体的例子供伙伴们理解,如果实在难以理解,那么笔者再举一个例子,进程。可能你没听过进程,但你一定打开过任务管理器。

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 这些都是进程,一个进程又由多个线程组成。那么CPU,内存,硬盘,网络这些硬件层面资源是怎么合理分配到我们软件的各个进程中呢?这就是内核帮助我们完成的事情,我们并不关心我们设备上的应用在哪里执行,如何分配资源,内核会完成这些事情。我们日常与软件交互,而内核会帮助我们完成软件和硬件的交互。

OpenHarmony内核

​ 明白了什么是内核后,我们来看看OpenHarmony的内核是怎么样设计的吧。

​ OpenHarmony采用的是多内核设计 有基于Linux内核的标准系统,有基于LiteOS-A的小型系统,也有基于LiteOS-M的轻量系统。他们分别适配不同的设备,比如说智能手表就是轻量级别的,智能汽车就是标准级别的等等。本篇并不介绍标准系统和小型系统,轻量系统更加适合初学者。

LiteOS-M内核

​ 下面是一张LiteOS-M的架构图

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 下面重点介绍KAL抽象层 和 基础内核的操作

KAL抽象层

​ 相信大家还是会有疑惑,什么是KAL抽象层?

​ Kernel Abstraction Layer

​ 在刚刚的内核中我们提到了,内核主要完成的是软件与硬件的交互,他会给应用层提供统一的规范接口,而KAL抽象层正是内核对应用层提供的接口集合。应用程序可以通过KAL抽象层完成对硬件的控制交互。

​ 抽象层是因为他隐藏了与硬件接口具体的交互逻辑,开发人员只需要关心如何操作硬件,而无需关心硬件底层的细节,大大提高了可移植性和维护性。

​ 以笔者的角度去看,KAL简单来说就是一堆接口,帮助你去操控硬件。CMSIS与POSIX就是具有统一规范的一些接口。通过他们我们就可以去控制一些基础的内核,线程,软件定时器,互斥锁,信号量等等。概念就先简单介绍这么多,感兴趣的伙伴们可以上官网查看更多的关于OpenHarmony内核的信息。下面笔者会带着大家编码操作,从实际去体会内核编程。

内核编程

线程管理

​ 在管理线程前,我们需要了解线程,线程是调度的基本单位,具有独立的栈空间和寄存器上下文,相比与进程,他是轻量的。举一个实际的例子,动物园卖票。

​ 对于动物园卖票这件事本身而言是一个进程,而每一个买票的人可以看作一个线程,在多个售票口处,我们并发执行,并行计算,共同消费动物园的门票,像享受共同的内存资源空间一样。为什么要线程管理呢?你我都希望买到票,但是票有限,我们都不希望看到售票厅一篇混乱,因此对线程进行管理是非常重要的一件事情。

任务

​ 创建一个线程,每间隔0.1秒,输出“Hello,OpenHarmony”,1秒后终止线程。

操作

​ 回忆第一个hello.c的例子

​ 我们要编写的程序样例就在源码根目录下的:applications/sample/wifi-iot/app/

​ 下面将具体演示如何编写程序样例。

  1. 新建样例目录

    applications/sample/wifi-iot/app/thread_demo

  2. 新建源文件和gn文件

    applications/sample/wifi-iot/app/thread_demo/singleThread.c

    applications/sample/wifi-iot/app/thread_demo/BUILD.gn

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

  1. 编写源码

注意:我们需要使用到cmsis_os2.h这个库,请伙伴们按照笔者介绍的方法把includePath修改好。

问题一:怎么创建线程?

typedef struct {
  /** Thread name */
  const char                   *name;
  /** Thread attribute bits */
  uint32_t                 attr_bits;
  /** Memory for the thread control block */
  void                      *cb_mem;
  /** Size of the memory for the thread control block */
  uint32_t                   cb_size;
  /** Memory for the thread stack */
  void                   *stack_mem;
  /** Size of the thread stack */
  uint32_t                stack_size;
  /** Thread priority */
  osPriority_t              priority;
  /** TrustZone module of the thread */
  TZ_ModuleId_t            tz_module;
  /** Reserved */
  uint32_t                  reserved;
} osThreadAttr_t;

这是线程的结构体,它具有以下属性:

  • name:线程的名称。
  • attr_bits:线程属性位。
  • cb_mem:线程控制块的内存地址。
  • cb_size:线程控制块的内存大小。
  • stack_mem:线程栈的内存地址。
  • stack_size:线程栈的大小。
  • priority:线程的优先级。
  • tz_module:线程所属的TrustZone模块。
  • reserved:保留字段。

问题二:怎么把线程启动起来呢?

osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr);

这是创建线程的接口函数,他有三个参数,一个返回值,我们来逐个解析

func: 是线程的回调函数,你创建的这个线程会执行这段函数的内容。

arguments:线程回调函数的参数。

attr:线程的属性,也就是我们之前创建的线程

返回值:线程的id 如果id不为空则说明成功。

问题三:怎么终止线程呢?

osStatus_t osThreadTerminate (osThreadId_t thread_id);

显然我们只要传入线程的id就会让该线程终止,返回值是一个状态码,下面给出全部的状态码

typedef enum {
  /** Operation completed successfully */
  osOK                      =  0,
  /** Unspecified error */
  osError                   = -1,
  /** Timeout */
  osErrorTimeout            = -2,
  /** Resource error */
  osErrorResource           = -3,
  /** Incorrect parameter */
  osErrorParameter          = -4,
  /** Insufficient memory */
  osErrorNoMemory           = -5,
  /** Service interruption */
  osErrorISR                = -6,
  /** Reserved. It is used to prevent the compiler from optimizing enumerations. */
  osStatusReserved          = 0x7FFFFFFF
} osStatus_t;

​ 回调函数怎么写?当然是结合我们的任务,每间隔0.1秒,输出“Hello,OpenHarmony”,1秒后终止。讲到这里,代码的整体逻辑是不是就清晰了很多,直接上完整代码。

#include <stdio.h>
#include "ohos_init.h"
// CMSIS
#include "cmsis_os2.h"
// POSIX
#include <unistd.h>

// 线程回调函数
void printThread(void *args){
    (void)args;
    while(1){
        printf("Hello,OpenHarmony!\r\n");
        // 休眠0.1秒
        osDelay(10);
    }
}

void threadTest(void){
    // 创建线程
    osThreadAttr_t attr;
    attr.name = "mainThread";
    // 线程
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 1024;
    attr.priority = osPriorityNormal;

    // 将线程启动
    osThreadId_t tid = osThreadNew((osThreadFunc_t)printThread, NULL, &attr);
    if(tid == NULL){
        printf("[Thread Test] Failed to create printThread!\r\n");
    }

    // 休眠5秒
    osDelay(500);
    // 终止线程
    osStatus_t status = osThreadTerminate(tid);
    printf("[Thread Test] printThread stop, status = %d.\r\n", status);

}

APP_FEATURE_INIT(threadTest);
  1. 编写gn文件
static_library("thread_demo"){
    sources = [
        "singleThread.c"
    ]
    include_dirs = [
        "//commonlibrary/utils_lite/include",
        "//device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
    ]
}
  1. 编写app下的gn文件

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

​ 注意的是,这次的写法与上次不同,是因为笔者的样例文件名和静态模块的名字是一样的就可以简写。

执行效果

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

多线程的封装

在处理业务的时候,我们一般是多线程的背景,下面笔者将创建线程函数封装起来,方便大家创建多线程

osThreadId_t newThread(char *name, osThreadFunc_t func, void *arg){
    // 定义线程和属性
    osThreadAttr_t attr = {
        name, 0, NULL, 0, NULL, 1024, osPriorityNormal, 0, 0
    };
    // 创建线程
    osThreadId_t tid = osThreadNew(func, arg, &attr);
    if(tid == NULL){
        printf("[newThread] osThreadNew(%s) failed.\r\n", name);
    }
    return tid;
}

​ 线程部分先体会到这里,想要探索更过线程相关的API,笔者这里提供了API网站,供大家参考学习。

CMSIS_OS2 Thread API

软件定时器

​ 下面我们介绍软件定时器,老样子我们先来介绍以下软件定时器。软件定时器是一种在软件层面上实现的计时器机制,用于在特定的时间间隔内执行特定的任务或触发特定的事件。它不依赖于硬件定时器,而是通过软件编程的方式实现。举一个例子,手机应用。

​ 当你使用手机上的某个应用时,你可能会注意到,如果你在一段时间内没有进行任何操作,应用程序会自动断开连接并要求你重新登录。这是为了保护你的账号安全并释放服务器资源。类似的设定都是有软件定时器实现的,下面进行实际操作,让大家体会一下软件定时器。

任务

​ 创建一个软件定时器,用来模拟上述手机应用的例子。为了方便理解,假设从此刻开始,我们不对手机做任何操作,也就是说,我们的回调函数只需要单纯的计算应用不被操作的时常即可。

操作

  1. 新建样例目录

    applications/sample/wifi-iot/app/thread_demo

  2. 新建源文件和gn文件

    applications/sample/wifi-iot/app/thread_demo/singleThread.c

    applications/sample/wifi-iot/app/thread_demo/BUILD.gn

  3. 编写源码

创建软件定时器

osTimerId_t osTimerNew (osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr_t *attr);

func: 软件定时器的回调函数

type:软件定时器的种类

argument:软件定时器回调函数的参数

attr:软件定时器的属性

返回值:返回软件定时器的id, id为空则说明软件定时器失败

typedef enum {
  /** One-shot timer */
  osTimerOnce               = 0,
  /** Repeating timer */
  osTimerPeriodic           = 1
} osTimerType_t;

软件定时器的种类有两个,分为一次性定时器和周期性定时器,一次性在执行完回调函数后就会停止计数,而周期性定时器会重复触发,每次触发重新计时。根据不同的需求我们可以选择使用不同的软件定时器。

启动软件定时器

osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks);

timer_id:软件定时器的参数,指定要启动哪个软件定时器

ticks:等待多少个ticks执行回调函数,在Hi3861中 100个ticks为1秒

返回值:软件定时器的状态码,在线程部分已经展示给大家了全部的状态码

停止定时器

osStatus_t osTimerStop (osTimerId_t timer_id);

这个函数很简单,只需要传软件定时器的id,即可停止软件计时器,并且返回他的状态码

删除定时器

osStatus_t osTimerDelete (osTimerId_t timer_id);

删除和停止类似,就不多说明了。

下面是源代码

#include <stdio.h>
#include "ohos_init.h"
// CMSIS
#include "cmsis_os2.h"
// POSIX
#include <unistd.h>

// 为操作软件的时间
static int times = 0;

// 软件定时器回调函数
void timerFunction(void){
    times++;
    printf("[Timer Test] Timer is Running, times = %d.\r\n", times);
}

// 主函数
void timerMain(void){
    // 创建软件定时器
    osTimerId_t tid = osTimerNew(timerFunction, osTimerPeriodic, NULL, NULL);
    if(tid == NULL){
        printf("[Timer Test] Failed to create a timer!\r\n");
        return;
    } else {
        printf("[Timer Test] Create a timer success!\r\n");
    }
    // 启动软件定时器,每1秒执行一次回调函数
    osStatus_t status = osTimerStart(tid, 100);

    // 当超过三个周期位操作软件时,关闭软件
    while(times <= 3){
        osDelay(100);
    }
    // 停止软件定时器
    status = osTimerStop(tid);
    // 删除软件定时器
    status = osTimerDelete(tid);
    printf("[Timer Test] Time Out!\r\n");
}

void TimerTest(void){
    // 创建测试线程
    osThreadAttr_t attr;
    attr.name = "timerMain";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 0U;
    attr.priority = osPriorityNormal;
    
    // 启动测试线程
    osThreadId_t tid = osThreadNew((osThreadFunc_t)timerMain, NULL, &attr);
    if(tid == NULL){
        printf("[Timer Test] Failed to created timerMain!\r\n");
    }
}

APP_FEATURE_INIT(TimerTest);
  1. 编写gn文件
static_library("timer_demo"){
    sources = [
        "timer.c"
    ]
    include_dirs = [
        "//commonlibrary/utils_lite/include",
        "//device/soc/hisilicon/hi3861v100/hi3861_adapter/kal/cmsis"
    ]
}
  1. 编写app下的gn文件

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

执行效果

OpenHarmony智能开发套件[内核编程·上]-鸿蒙开发者社区

软件定时器的API相对较少,这里还是提供所有的软件定时器API

CMSIS_OS2 Timer API

结束语

​ 本篇主要介绍了一些基础内核编程相关的内容,希望能够帮助到学习OpenHarmony的伙伴们,考虑到篇幅问题,剩余的基础内核编程将在OpenHarmony智能开发套件[内核编程·上]中介绍,如有建议或问题的伙伴请在评论区留言。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-5-13 21:39:25修改
4
收藏 5
回复
举报
3条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

everything官网的链接好像点不开,大佬可以补一下吗

回复
2023-5-15 10:56:09
stackor已停更
stackor已停更 回复了 红叶亦知秋
everything官网的链接好像点不开,大佬可以补一下吗
2
回复
2023-5-15 12:49:42
红叶亦知秋
红叶亦知秋 回复了 stackor已停更
https://www.voidtools.com/zh-cn/

感谢回复

1
回复
2023-5-15 14:19:27
回复
    相关推荐