前言
本人是一名大一学生,有幸被选拔进了深圳技术大学第一届开源鸿蒙菁英班,并在暑期培训进行线上分享,故将讲解的内容也制作成帖子发上来作为学习笔记。在准备分享的过程中,我基于学长们的先前成果,结合开源鸿蒙源码的最新版本进行了相应的调整和优化,帮助大家更好地理解和应用开源鸿蒙技术。
环境
- OpenHarmony-4.0 源码
- 九联 unionpi_whale 开发板
一、OpenHarmony源码的模块划分
1.OpenHarmony内核分类
系统分为三种不同体量的系统,本文主要讲解基于Linux的标准系统构建。
2.系统代码版本
Openharmony有主干代码与发行版代码两种,代码的获取请参考官方文档。
3.OpenHarmony源码体系
OpenHarmony的源码架构基于模块化设计,为了方便系统的功能的增加和裁剪,设计了基于GN构建的模块系统。整个模块可从大到小划分为产品(product)、领域/子系统集(domain)、子系统(sub_system)、部件(component)、模块/组件(module)、特性(feature)几个部分,这种模块化的树状编译框架,非常方便根据目标产品硬件资源的大小进行灵活的裁剪,从而实现**“统一0S,弹性部署”**的目标。
4.各部分的关系
一个产品(product)可以包含1-n个子系统(subsystem),一个子系统可以包含1-~n个部件(component),一个部件可以包含1-n个模块(modue),不同产品的中的相同部件可以编译不同的特性(feature),**子系统集(domain)**在源代码一级根目录有体现。
不同产品相同模块可以通过特性feature进行差异化定义。
5.南向开发的概念
开源鸿蒙系统的南向开发,主要是针对设备或者终端的软件开发,例如各种智能家居设备、穿戴设备等。由于这些设备种类繁多,硬件配置和操作系统都各不相同,因此需要进行适配和优化,以确保设备能够顺利地运行开源鸿蒙系统。
二、OpenHarmony源码的产品、子系统、部件、组件配置详解
1.产品
产品解决方案为基于开发板的完整产品,主要包含产品对OS的适配、部件拼装配置、启动配置和文件系统配置等。产品解决方案的源码路径规则为:vendor/{产品解决方案厂商}/{产品名称}。
vendor/company/product/config.json config.json为编译构建的主入口,包含了开发板、OS部件和内核等配置信息。
2.领域
OpenHarmony技术架构中有四大子系统集:“系统基本能力子系统集”、“基础软件服务子系统集"、“增强软件服务子系统集"、“硬件服务子系统集”。四大子系统不会直接出现在编译选项或者参数中,而是有对应的一级源代码文件夹:“系统基本能力子系统集”对应源码foundation文件夹;“基础软件服务子系统集”和“硬件服务子系统集”对应源码base文件夹;“增强软件服务子系统集"对应源码domains文件夹。
- vendor仓:存放厂商驱动代码,配置文件
- device仓:存放硬件设备代码,板级配置和驱动代码
3.子系统
子系统是一个逻辑概念,它具体由对应的组件构成。配置规则如下:
{
"arkui": {
"path": "foundation/arkui", # 路径
"name": "arkui" # 子系统名
},
"ai": {
"path": "foundation/ai",
"name": "ai"
},
"account": {
"path": "base/account",
"name": "account"
},
"distributeddatamgr": {
"path": "foundation/distributeddatamgr",
"name": "distributeddatamgr"
},
"security": {
"path": "base/security",
"name": "security"
},
...
}
子系统的配置规则主要是在build/subsystem_config.json中指定子系统的路径和子系统名称。
4.部件(组件)
对子系统的进一步拆分,可复用的软件单元,它包含源码、配置文件、资源文件和编译脚本;能独立构建,以二进制方式集成,具备独立验证能力的二进制单元。
部件的bundle.json放在部件源码的根目录下。配置规则如下:
{
"name": "@ohos/sensor_lite", # HPM部件英文名称,格式"@组织/部件名称"
"description": "Sensor services", # 部件功能一句话描述
"version": "3.1", # 版本号,版本号与OpenHarmony版本号一致
"license": "MIT", # 部件License
"publishAs": "code-segment", # HPM包的发布方式
"segment": {
"destPath": ""
}, # 发布类型为code-segment时为必填项,定义发布类型code-segment的代码还原路径(源码路径)
"dirs": {"base/sensors/sensor_lite"}, # HPM包的目录结构,字段必填内容可以留空
"scripts": {}, # HPM包定义需要执行的脚本,字段必填,值非必填
"licensePath": "COPYING",
"readmePath": {
"en": "README.rst"
},
"component": { # 部件属性
"name": "sensor_lite", # 部件名称
"subsystem": "", # 部件所属子系统
"syscap": [], # 部件为应用提供的系统能力
"features": [], # 部件对外的可配置特性列表,一般与build中的sub_component对应,可供产品配置
"adapted_system_type": [], # 轻量(mini)小型(small)和标准(standard),可以是多个
"rom": "92KB", # 部件ROM值
"ram": "~200KB", # 部件RAM估值
"deps": {
"components": [ # 部件依赖的其他部件
"samgr_lite",
"ipc_lite"
],
"third_party": [ # 部件依赖的三方开源软件
"bounds_checking_function"
],
"hisysevent_config": [] # 部件HiSysEvent打点配置文件编译入口
}
"build": { # 编译相关配置
"sub_component": [
""//base/sensors/sensor_lite/services:sensor_service"", # 部件编译入口
], # 部件编译入口,模块在此处配置
"inner_kits": [], # 部件间接口
"test": [] # 部件测试用例编译入口
}
}
}
5.模块
编译子系统通过模块、部件和产品三层配置来实现编译和打包。模块就是编译子系统的一个目标,包括(动态库、静态库、配置文件、预编译模块等)。模块要定义属于哪个部件,一个模块只能归属于一个部件。这里列出了初学者常用的几个模版及其配置规则:
# C/C++模板
ohos_shared_library # 动态库gn脚本
ohos_static_library # 静态库gn脚本
ohos_executable # 可执行文件gn脚本
# 配置文件
ohos_prebuilt_etc # etc模块gn脚本
配置中只有sources和part_name是必选,其他都是可选的。
- ohos_shared_library示例
import("//build/ohos.gni")
ohos_shared_library("helloworld") {
sources = ["file"]
include_dirs = [] # 如有重复头文件定义,优先使用前面路径头文件。
cflags = [] # 如重复冲突定义,后面的参数优先生效,也就是该配置项中优先生效。
cflags_c = []
cflags_cc = []
ldflags = [] # 如重复冲突定义,前面参数优先生效,也就是ohos_template中预制参数优先生效。
configs = []
deps = [] # 部件内模块依赖
external_deps = [ # 跨部件模块依赖定义
"part_name:module_name", # 定义格式为 "部件名:模块名称"。
] # 这里依赖的模块必须是依赖的部件声明在inner_kits中的模块。
output_name = [string] # 模块输出名
output_extension = [] # 模块名后缀
module_install_dir = "" # 模块安装路径,缺省在/system/lib64或/system/lib下; 模块安装路径从system/,vendor/后开始指定。
relative_install_dir = "" # 模块安装相对路径,相对于/system/lib64或/system/lib;如果有module_install_dir配置时,该配置不生效。
part_name = "" # 必选,所属部件名称
output_dir
# Sanitizer配置,每项都是可选的,默认为false/空。
sanitize = {
# 各个Sanitizer开关
cfi = [boolean] # 控制流完整性检测
cfi_cross_dso = [boolean] # 开启跨so调用的控制流完整性检测
integer_overflow = [boolean] # 整数溢出检测
boundary_sanitize = [boolean] # 边界检测
ubsan = [boolean] # 部分ubsan选项
all_ubsan = [boolean] # 全量ubsan选项
...
debug = [boolean] # 调测模式
blocklist = [string] # 屏蔽名单路径
}
testonly = [boolean]
license_as_sources = []
license_file = [] # 后缀名是.txt的文件
remove_configs = []
no_default_deps = []
install_images = []
install_enable = [boolean]
symlink_target_name = []
version_script = []
use_exceptions = []
}
- ohos_static_library示例
import("//build/ohos.gni")
ohos_static_library("helloworld") {
sources = ["file"] # 后缀名是.c的相关文件
include_dirs = ["dir"] # 包含目录
configs = [] # 配置
deps = [] # 部件内模块依赖
part_name = "" # 部件名称
subsystem_name = "" # 子系统名称
cflags = []
external_deps = [ # 跨部件模块依赖定义,
"part_name:module_name", # 定义格式为 "部件名:模块名称"
] # 这里依赖的模块必须是依赖的部件声明在inner_kits中的模块。
lib_dirs = []
public_configs = []
# Sanitizer配置,每项都是可选的,默认为false/空
sanitize = {
# 各个Sanitizer开关
cfi = [boolean] # 控制流完整性检测
cfi_cross_dso = [boolean] # 开启跨so调用的控制流完整性检测
integer_overflow = [boolean] # 整数溢出检测
boundary_sanitize = [boolean] # 边界检测
ubsan = [boolean] # 部分ubsan选项
all_ubsan = [boolean] # 全量ubsan选项
...
debug = [boolean] # 调测模式
blocklist = [string] # 屏蔽名单路径
}
remove_configs = []
no_default_deps = []
license_file = [] # 后缀名是.txt的文件
license_as_sources = []
use_exceptions = []
}
- ohos_executable示例
import("//build/ohos.gni")
ohos_executable("helloworld") {
configs = [] # 配置
part_name = "" # 部件名称
subsystem_name = "" # 子系统名称
deps = [] # 部件内模块依赖
external_deps = [ # 跨部件模块依赖定义,
"part_name:module_name", # 定义格式为 "部件名:模块名称"
] # 这里依赖的模块必须是依赖的部件声明在inner_kits中的模块。
ohos_test = []
test_output_dir = []
# Sanitizer配置,每项都是可选的,默认为false/空
sanitize = {
# 各个Sanitizer开关
cfi = [boolean] # 控制流完整性检测
cfi_cross_dso = [boolean] # 开启跨so调用的控制流完整性检测
integer_overflow = [boolean] # 整数溢出检测
boundary_sanitize = [boolean] # 边界检测
ubsan = [boolean] # 部分ubsan选项
all_ubsan = [boolean] # 全量ubsan选项
...
debug = [boolean] # 调测模式
blocklist = [string] # 屏蔽名单路径
}
testonly = [boolean]
license_as_sources = []
license_file = [] # 后缀名是.txt的文件
remove_configs = []
static_link = []
install_images = []
module_install_dir = "" # 模块安装路径,从system/,vendor/后开始指定
relative_install_dir = ""
symlink_target_name = []
output_dir = [directory] # 存放输出文件的目录
install_enable = [boolean]
version_script = []
use_exceptions = []
}
- ohos_prebuilt_etc示例
import("//build/ohos.gni")
ohos_prebuilt_etc("helloworld") {
# ohos_prebuilt_etc模板最常用属性:
source = "file" # 指定单个原文件
module_install_dir = "" # 模块安装路径,从system/,vendor/后开始指定
subsystem_name = "" # 子系统名
part_name = "" # 必选,所属部件名称
install_images = []
relative_install_dir = "" # 模块安装相对路径,相对于system/etc;如果有module_install_dir配置时,该配置不生效。
# ohos_prebuilt_etc模板不常用属性:
deps = [] # 部件内模块依赖
testonly = [boolean]
visibility = []
public_configs = []
symlink_target_name = [string]
license_file = [string]
license_as_sources = []
}
6.特性
特性是部件用于体现不同产品之间的差异,通常不同特性可以定义不同编译宏或者代码。
三、子系统样例源码实战
1.单模块示例
本示例目标是编写一个helloworld程序。在OpenHarmony源码目录下创建子系统文件夹Mysample,其下创建部件文件夹Myhello,下设include和src文件夹分别用于存放头文件和源码。创建如下文件:
(忽略图片中我自己的其他部件)
Mysample/Myhello/include/helloworld.h
#ifndef HELLOWORLD_H //条件编译指令
#define HELLOWORLD_H
#ifdef __cplusplus //C++ 编译器
#if __cplusplus
extern "C" { //表示下面的代码将以 C 语言的方式进行编译
#endif
#endif
void HelloPrint();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif // HELLOWORLD_H
Mysample/Myhello/src/helloworld.c
#include <stdio.h>
#include "helloworld.h"
// #include "../include/helloworld.h"
int main(int argc, char **argv)
{
HelloPrint();
return 0;
}
void HelloPrint()
{
printf("\n\n");
printf("\n\t\tHello World!\n");
printf("\n\n");
}
在部件文件夹Myhello下创建配置文件:
Mysample/Myhello/BUILD.gn
import("//build/ohos.gni") # 导入编译模板
ohos_executable("helloworld") { # 可执行模块,target组件、模块
sources = [ # 模块源码
"src/helloworld.c"
]
include_dirs = [ # 模块依赖头文件目录
"include"
]
cflags = []
cflags_c = []
cflags_cc = []
ldflags = []
configs = []
deps =[] # 部件内部依赖
part_name = "Myhello" # 所属部件名称,必选
install_enable = true # 是否默认安装(缺省默认不安装),可选
}
Mysample/Myhello/bundle.json
{
"name": "@ohos/Myhello",
"description": "Hello world example.",
"version": "3.1",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "Mysample/Myhello"
},
"dirs": {},
"scripts": {},
"component": {
"name": "Myhello",
"subsystem": "Mysample",
"syscap": [],
"features": [],
"adapted_system_type": [ "mini", "small", "standard" ],
"rom": "10KB",
"ram": "10KB",
"deps": {
"components": [],
"third_party": []
},
"build": {
"sub_component": [
"//Mysample/Myhello:helloworld"
],
"inner_kits": [],
"test": []
}
}
}
修改模块配置文件:
vendor/unionman/unionpi_whale/config.json
{
"subsystem": "Mysample",
"components": [
{
"component": "Myhello",
"features": []
}
]
}
修改子系统配置文件:
build/subsystem_config.json
"Mysample": {
"path": "Mysample",
"name": "Mysample"
},
2.多模块示例
本示例目标是带大家使用几个编译模板。在OpenHarmony源码目录下创建子系统文件夹Mysample,其下创建部件文件夹Mycomponent,下设module1、module2和’module3`文件夹分别用于3个模块。创建如下文件:
- 需要注意的是:每个模块有自己的配置文件BUILD.gn,组件还需要一个group模板的BUILD.gn。
Mysample/Mycomponent/module1/include/hello1.h
#ifndef HELLO1_H //条件编译指令
#define HELLO1_H
#ifdef __cplusplus //C++ 编译器
#if __cplusplus
extern "C" { //表示下面的代码将以 C 语言的方式进行编译
#endif
#endif
void HelloPrint1();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif // HELLO1_H
Mysample/Mycomponent/module1/src/hello1.cpp
#include <iostream>
#include "hello1.h"
void HelloPrint1()
{
printf("\n\n");
printf("\n\t\tHello1!\n");
printf("\n\n");
}
Mysample/Mycomponent/module1/BUILD.gn
import("//build/ohos.gni")
config("hello_lib_config") {
include_dirs = [ "include" ]
}
ohos_shared_library("hello_lib") {
sources = [
"include/hello1.h",
"src/hello1.cpp",
]
public_configs = [ ":hello_lib_config" ]
part_name = "Mycomponent"
}
Mysample/Mycomponent/module2/include/hello2.h
#ifndef HELLO2_H //条件编译指令
#define HELLO2_H
#ifdef __cplusplus //C++ 编译器
#if __cplusplus
extern "C" { //表示下面的代码将以 C 语言的方式进行编译
#endif
#endif
void HelloPrint2();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif // HELLO2_H
Mysample/Mycomponent/module2/src/hello2.cpp
#include <iostream>
#include "hello1.h"
#include "hello2.h"
int main(int argc, char **argv)
{
HelloPrint2();
return 0;
}
void HelloPrint2()
{
HelloPrint1();
printf("\n\n");
printf("\n\t\tHello2!\n");
printf("\n\n");
}
Mysample/Mycomponent/module2/BUILD.gn
import("//build/ohos.gni")
ohos_executable("hello_bin") {
sources = [
"src/hello2.cpp"
]
include_dirs = [ "include" ]
deps = [ # 依赖部件内模块
"../module1:hello_lib"
]
install_enable = true # 可执行程序缺省不安装,需要安装时需要指定
part_name = "Mycomponent"
}
Mysample/Mycomponent/module3/src/config.conf
var_a = 10
var_b = 20
var_c = 30
Mysample/Mycomponent/module3/BUILD.gn
import("//build/ohos.gni")
ohos_prebuilt_etc("hello_etc") {
source = "src/config.conf"
relative_install_dir = "init" #可选,模块安装相对路径,相对于默认安装路径;默认在/system/etc目录
part_name = "Mycomponent"
}
在部件文件夹Mycomponent下创建配置文件:
Mysample/Mycomponent/BUILD.gn
import("//build/ohos.gni")
group("mycomponent"){
deps = [
"module1:hello_lib",
"module2:hello_bin",
"module3:hello_etc"
]
}
Mysample/Mycomponent/bundle.json
{
"name": "@ohos/Mycomponent",
"description": "Hello world example.",
"version": "3.1",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "Mysample/Mycomponent"
},
"dirs": {},
"scripts": {},
"component": {
"name": "Mycomponent",
"subsystem": "Mysample",
"syscap": [],
"features": [],
"adapted_system_type": [ "mini", "small", "standard" ],
"rom": "10KB",
"ram": "10KB",
"deps": {
"components": [],
"third_party": []
},
"build": {
"sub_component": [
"//Mysample/Mycomponent:mycomponent"
],
"inner_kits": [],
"test": []
}
}
}
修改模块配置文件:
vendor/unionman/unionpi_whale/config.json
{
"subsystem": "Mysample",
"components": [
{
"component": "Myhello",
"features": []
},
{
"component": "Mycomponent",
"features": []
}
]
},
3.编译
- 命令行方式
./build.sh --product-name {product_name} #全量编译
./build.sh --product-name {product_name} --build-target {target_name} #单独编译部件
./build.sh --product-name {product_name} --build-target {target_name} --fast-rebuild #快速重建
- hb方式
hb set #设置编译参数
hb build #全量编译
hb build -T {target_name} #单独编译部件
hb build -T {target_name} --fast-rebuild #快速重建
- 我们这里使用hb方式来进行编译。在终端输入命令hb set,选择standard和unionpi_whale,在终端输入命令hb build -T Myhello。耗时1:26,编译成功日志如下:
- 由于编译Myhello部件的已经检查了一遍编译关系,而我们又没有改动编译构建文件,故可以使用快速重建命令hb build -T Mycomponent --fast-rebuild编译Mycomponent部件。编译成功日志如下:
- 可见所用时间大大减少。
- 编译产物在out/board/product目录下。
4.烧录
- 全量烧录: 适合更新版本或者代码大变动
打包镜像->RKDevTool烧录。 - HDC工具:适合代码更新时单独发送所需文件(以helloworld为例)
- 找到可执行文件helloworld,并将其放置到电脑hdc.exe同级目录下。
- 连接设备:将开发板上电,并连接电脑。
- 从hdc文件夹下进入终端,输入hdc list targets检查是否连接好,检测到设备后输-入hdc smode授予进程root权限,再输入hdc shell mount -o rw,remount /挂载分区,并且赋予可写权限。
- 输入hdc shell进入开发板终端,mkdir sample创建文件夹,exit退出终端。
- hdc file send ./helloworld /sample/传输文件。(将当前目录下的hello文件传输到开发板的sample目录下)
- hdc shell再次进入开发板终端,cd sample进入文件夹,chmod 777 *给程序赋予可执行权限。
5.烧录测试并执行
- 烧录:
- whale开发板烧录口为蓝色USB口上层口,使用USBtoUSB线烧录。
- 需要注意:可执行文件可以发送到任意目录,而动态库文件libhello_lib.z.so需要发送到/system/lib/目录下。
- 使用命令./可执行程序名执行程序。在终端查看输出结果。
- 测试成功
总结
在本人看来,入门开源鸿蒙南向设备学习,有很大可能会被这么多的配置选项和新概念劝退,所以在暑期培训我选择分享这部分的内容,希望对大家有帮助。