理论
OpenHarmony源码体系
OpenHarmony的源码架构基于模块化设计,为了方便系统的功能的增加和裁剪,设计了基于GN构建的模块系统。整个模块可从大到小划分为产品(product)、领域/子系统集(domain)、子系统(sub system)、部件(component)、模块/组件(module)、特性(feature)几个部分,这种模块化的树状编译框架,非常方便根据目标产品硬件资源的大小进行灵活的裁剪,从而实现统一OS,弹性部署的目标。
OpenHarmony系统架构
一个产品可以包含1~n个子系统(subsystem),一个子系统可以包含1~n个部件(component),一个部件可以包含1~n个模块(module),不同产品的中的相同部件可以编译不同的特性(feature),子系统集(domain)在源代码一级根目录有体现。
由于以上的特性,OpenHarmony只需要通过gn的配置实现无需删减代码达到裁剪系统的目的,提高系统整体稳定性。
产品(product)
产品解决方案为基于开发板的完整产品,主要包含产品对OS的适配、部件拼装配置、启动配置和文件系统配置等。源码路径为:/vender/{产品解决方案厂商}/{产品名称},使用命令tree -L 级数,如果tree -L 4
vender
└──── company # 产品解决方案厂商,如:hihope
├──── product # 产品名称,如:rk3568
│ ├──── init_configs
│ │ ├──── etc # init启动进程配置(可选,仅linux内核需要)
│ │ └──── init.cfg # 系统服务启动配置
│ ├──── hals # 产品解决方案OS适配
│ ├──── BUILD.gn # 产品编译脚本
│ ├──── config.json # 产品配置文件
│ └──── fs.yml # 文件系统打包配置
├──── ...
/vender/{产品解决方案厂商}/{产品名称}/init+configs/etc文件夹中包含rcS脚本、Sxxx脚本和fstab脚本。init进程在启动系统服务前执行这些脚本,执行流程为rcS—>fstab—>Sxxx,Sxxx脚本中的内容与开发板和产品需求有关,主要包含
产品配置规则:/vender/{产品解决方案厂商}/{产品名称}/config.json文件中可以配置产品所需系统的子系统,其中的inherit字段可以继承事先定义好的样板模板,模板位于productdefine/common目录下
领域/子系统集(domain)
OpenHarmony技术架构中有四大子系统集:“系统基本能力子系统集”、“基础软件服务子系统集”、“增强软件服务子系统集”、“硬件服务子系统集”。四大子系统不会直接出现在编译选项或者参数中,而是有对应的一级源代码文件夹:
“系统基本能力子系统集”对应源码foundation文件夹;“基础软件服务子系统集”和“硬件服务子系统集”对应源码base文件夹;“增强软件服务子系统集”对应源码domains文件夹。
├──applications # 应用程序
├──arkcompiler # ark编译器
├──base # “基础软件服务子系统集”和“硬件服务子系统集”
├──build # 编译目录
├──build.py -> build/lite/build.py # 软链接
├──build.sh -> build/build_scripts/build.sh # 软链接,标准系统编译入口
├──commonlibrary # 通用库
├──developtools # 开发工具
├──device # 芯片相关
├──docs # 文档md文件目录
├──domains # 增强软件服务子系统集
├──drivers # 驱动文件
├──foundation # “系统基本能力子系统集”
├──ide # ide
├──interface # 接口
├──kernel # 内核,liteos-m,liteos-a,linux,uniproton
├──napi_generator # 代码生成工具
├──prebuilts # 编译工具路径
├──productdefine # 产品定义
├──qemu-run -> vendor/ohemu/common/qemu-run # qemu模拟器运行脚本
├──test # 测试用例
├──third_party # 三方库
└──vendor # 产品源码
子系统(SubSystem)
子系统是一个逻辑概念,它具体由对应的部件构成。在多设备部署场景下,支持根据实际需求裁剪某些非必要的子系统或部件。在build/subsystem_config.json中定义。
部件(component)
部件是对子系统的进一步拆分,可复用的软件单元,每一个部件单独存放一个文件夹,它包含源码、配置文件、资源文件和编译脚本;能独立构建,以二进制方式集成,具备独立验证能力的二进制单元。部件由对应源码文件夹下的bundle.json文件进行定义。
{
"name": "@ohos/sensor_lite", # HPM部件英文名称,格式"@组织/部件名称"
"description": "Obtaining sensor data", # 部件功能一句话描述
"version": "3.1", # 版本号,版本号与OpenHarmony版本号一致
"license": "Apache License 2.0",
"publishAs": "code-segment", # HPM包的发布方式,当前默认都为code-segment
"segment": {
"destPath": "base/sensors/sensor_lite" # 发布类型为code-segment时为必填项,定义发布类型code-segment的源码路径
},
"dirs": {
"base/sensors/sensor_lite" # HPM包的目录结构,字段必填内容可以留空
},
"scripts":{}, # HPM包定义需要执行的脚本,字段必填,值非必填
"licensePath": "COPYING",
"readmePath": {
"en": "README.rst"
},
"component": {
"name": "sensor_lite", # 部件名称
"subsystem": "sensors", # 部件所属子系统
"syscap": [], # 部件为应用提供的系统能力
"features": [], # 部件对外的可配置特性列表,一般与build中sub_component对应,可供产品配置
"adapted_system_type": [ "mini", small, standard], # 轻量(mini) 小型(small)和标准(standard),可以是多个
"rom": "92KB", # 部件ROM估值
"ram": "~200KB", # 部件RAM估值
"deps": {
"components": [ # 部件依赖的其他部件
"hilog_lite",
"ipc",
"samgr_lite",
],
"third_party": [ "bounds_checking_function" ] # 部件依赖的三方开源软件
"hisysevent_config": [] # 部件HiSysEvent打点配置文件编译入口
},
"build": { # 编译相关配置
"sub_component": [
"//base/sensors/sensor_lite/services:sensor_service",
"//base/sensors/sensor_lite/frameworks:sensor_lite"
], # 部件编译入口配置
"inner_kits": [], # 部件对外暴露的接口,用于其它部件或者模块进行引用
"test": [
"//base/sensors/sensor_lite/interfaces/kits/native:unittest"
] # 部件测试用例编译入口
}
}
}
路径规则为:{领域/子系统集}/{子系统}/{部件},部件目录树规则如下:
component
├── interfaces
│ │
│ ├── innerkits # 系统内接口,部件间使用
│ │
│ └── kits # 应用接口,应用开发者使用
├── frameworks # framework实现
├── services # service实现
└── BUILD.gn # 部件编译脚本
模块(module)
模块就是编译子系统的一个编译目标,部件也可以是编译目标。模块属于哪个部件,在gn文件中由part_name指定。
ohos_shared_library("ace_napi") { # ace_napi为模块名,同时也是编译目标
deps = [ ":ace_napi_static" ] # 模块的依赖,被依赖的对象即使没有被subsystem显式包含,也会被编译
public_configs = [ ":ace_napi_config" ] # 模块配置参数,比如cflag
if(!is_cross_platform_build) {
public_deps = [ "//third_party/libuv:uv" ]
}
subsystem_name = "arkui" # 模块所属部件所属子系统名称
part_name = "napi" # 模块所属部件名称,一个模块只能属于一个部件
}
特性(feature)
特性是部件用于体现不同产品之间的差异。通常不同特性可以定义不同编译宏或者代码,从而影响到源代码中define的特性。
如vender/hihope/rk3568_mini_system/config.json中配置了dsoftbus
{
"subsystem": "communication",
"components": [
{ "component": "dsoftbus", "features":["dsoftbus_get_devicename=false"] }
]
}
在/foundation/communication/dsoftbus/bundle.json中features下配置有dsoftbus_get_devicename
"features": [
"dsoftbus_feature_conn_p2p",
"dsoftbus_feature_conn_legacy",
"dsoftbus_feature_disc_ble",
"dsoftbus_feature_conn_br",
"dsoftbus_feature_conn_ble",
"dsoftbus_feature_lnn_net",
"dsoftbus_feature_trans_udp_stream",
"dsoftbus_feature_trans_udp_file",
"dsoftbus_get_devicename",
"dsoftbus_feature_product_config_path",
"dsoftbus_feature_lnn_wifiservice_dependence",
"dsoftbus_feature_protocol_newip",
"dsoftbus_feature_ex_kits",
"dsoftbus_feature_wifi_notify"
],
在foundation/communication/dsoftbus/core/adapter/core_adapter.gni中通过判断dsoftbus_get_devicename属性从而走向不同的逻辑
...
if (!dsoftbus_get_devicename) { # 通过判断走不同的逻辑
bus_center_core_adapter_src += [
"$dsoftbus_root_path/core/adapter/bus_center/src/lnn_settingdata_event_monitor_virtual.cpp"
]
bus_center_core_adapter_inc +=
[ "$dsoftbus_root_path/core/adapter/bus_center/include" ]
} else {
bus_center_core_adapter_src += [ "$dsoftbus_root_path/core/adapter/bus_center/src/lnn_settingdata_event_monitor.cpp" ]
bus_center_core_adapter_inc += [
"$dsoftbus_root_path/adapter/common/bus_center/include",
"$dsoftbus_root_path/core/adapter/bus_center/include",
]
...
实践
本节以添加一个自定义的部件为例,描述如何编译部件,编译库、编译可执行文件等。
单一任务部件
实例部件compdemo1由模块demo1model_bin模块组成,该模块的编译目标为一个可执行程序。
示例部件compdemo1完整目录结构如下
my_test
└── componentdemo1
├── BUILD.gn
├── bundle.json
├── include
│ └── model1.h
└── src
└── model1.c
1.编写gn脚本/my_test/componentdemo1/BUILD.gn
import("//build/ohos.gni") # 导入编译模板,编译环境等依赖
ohos_executable("demo1model1_bin"){ # 可执行模块
sources = [ # 模块源码
"src/model1.c"
]
include_dirs = [ # 模块依赖头文件
"include"
]
cflags = []
cflags_c = []
cflags_cc = []
ldflags = []
configs = []
deps = [] # 部件内部依赖
external_deps = [] # 跨部件的依赖,格式为"部件名:模块名"
part_name = "compdemo1" # 模块所属部件名称
install_enable = true # 是否安装(缺省默认不安装)
}
注:复制代码时需要删除#及注释,否则无法编译通过
2.对应src下创建model1.c文件,include下创建model1.h文件,文件内容如下,其实现了一个简单的打印
.model1.h
#ifndef MODEL_1_H
#define MODEL_1_H
#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif
void HelloPrint();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif
model1.c
#include <stdio.h>
#include "model.h"
int main(int argc, char **argv)
{
HelloPrint();
return 0;
}
void HelloPrint()
{
printf("This component dir is /my_test/componentdemo1, mycomptest->compdemo1->demo1model1_bin\n");
}
3.编写/my_test/componentdemo1/bundle.json
{
"name": "@ohos/compdemo1",
"description": "compdemo1 services",
"version": "3.1",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "my_test/componentdemo1"
},
"dirs": {},
"scripts": {},
"component": {
"name": "compdemo1",
"subsystem": "mycomptest",
"syscap": [],
"features": [],
"adapted_system_type": [ "mini", "small", "standard" ],
"rom": "10KB",
"ram": "10KB",
"deps": {
"components": [],
"third_party": [],
"hisysevent_config": []
},
"build": {
"sub_component": [
"//my_test/componentdemo1:demo1model1_bin"
],
"inner_kits": [],
"test": []
}
}
}
注:此处bundle.json中的"name": "@ohos/compdemo1"和"component": {中的"name": "compdemo1"对应BUILD.gn文件部件名称"part_name"对应的值;"sub_component": ["//my_test/componentdemo1:demo1model1_bin"]对应BUILD.gn中ohos_executable("demo1model1_bin")的值。
4.在/build/subsystem_config.json文件最后增加如下内容
,
"mycomptest": {
"path": "my_test",
"name": "mycomptest"
}
...
}
# 注:以上...}代表内容放在文件最后一级}下
注:subsystem_config.json文件此处的"subsystem": "mycomptest"为bundle.json中的"subsystem": "mycomptest"。
5. 在/vender/{产品解决方案厂商}/{产品名称}/config.json文件最后增加如下内容,如rk3568芯片则路径为:/vendor/hihope/rk3568/config.json
,
{
"subsystem":"mycomptest",
"components": [
{
"component": "compdemo1",
"features": []
}
]
}
...
]
}
注:以上...]}代表以上内容放在文件中最后一级数组中
注:config.json文件中此处的"subsystem":"mycomptest"为subsystem_config.json文件中的"name": "mycomptest";"component": "compdemo1"为BUILD.gn中的part_name = "compdemo1"。
6.编译
可以使用整编命令
./build.sh --product-name rk3568 --no-prebuilt-sdk
也可以使用“–build-target 模块名"单独编译,编译命令如下:
./build.sh --product-name rk3568 --build-target demo1model1_bin --ccache
还可以编译模块所在的部件:
./build.sh --product-name rk3568 --build-target compdemo1 --ccache
7.烧录到设备后,输入模块命令即可打印
demo1model1_bin
组合任务部件
示例部件compdemo2由demo2model1_lib模块、demo2model2_bin模块和demo2model3_config模块组成,demo2model1_lib模块的编译目标为一个动态库,demo2model2_bin模块的目标为一个可执行程序,demo2model2_conf模块的目标为一个etc配置文件。
示例部件compdemo2的完整目录结构如下
my_test/componentdemo2
├── a
│ ├── BUILD.gn
│ ├── include
│ │ └── model1.h
│ └── src
│ └── model1.cpp
├── b
│ ├── BUILD.gn
│ ├── include
│ │ └── model2.h
│ └── src
│ └── model2.cpp
├── c
│ ├── BUILD.gn
│ └── src
│ └── model3.conf
├── BUILD.gn
└── bundle.json
demo2model1_lib模块编写
1.编写/my_test/componentdemo2/a/BUILD.gn
import("//build/ohos.gni")
config("demo2model1_lib_config"){
include_dirs = [ "include" ]
}
ohos_shared_library("demo2model1_lib"){
sources = [
"include/model1.h",
"src/model1.cpp"
]
public_configs = [ ":demo2model1_lib_config" ]
deps = []
external_deps = []
part_name = "compdemo2"
}
2.对应src下创建model1.c文件,include下创建model1.h文件,文件内容如下
model1.h
#ifndef MODEL_1_H
#define MODEL_1_H
#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif
void HelloPrintModel1();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif
model1.cpp
#include <stdio.h>
#include "model1.h"
int main(int argc, char **argv)
{
HelloPrintModel1();
return 0;
}
void HelloPrintModel1()
{
printf("This component dir is /my_test/componentdemo2/a mycomptest->compdemo1->demo2model1_lib\n");
}
3.编写/my_test/componentdemo2/b/BUILD.gn
import("//build/ohos.gni")
ohos_executable("demo2model2_bin"){
sources = [
"src/model2.cpp"
]
include_dirs = [
"include"
]
deps = [ "../a:demo2model1_lib" ]
external_deps = []
part_name = "compdemo2"
install_enable = true
}
4.对应src下创建model2.c文件,include下创建model2.h文件,文件内容如下
model2.h
#ifndef MODEL_2_H
#define MODEL_2_H
#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif
void HelloPrintModel2();
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif
#endif
model2.cpp
#include <stdio.h>
#include "model2.h"
#include "model1.h"
int main(int argc, char **argv)
{
HelloPrintModel2();
return 0;
}
void HelloPrintModel2()
{
printf("This component dir is /my_test/componentdemo2/b mycomptest->compdemo1->demo2model2_bin begin\n");
HelloPrintModel1();
printf("This component dir is /my_test/componentdemo2/b mycomptest->compdemo1->demo2model2_bin end\n");
}
5.编写/my_test/componentdemo2/c/BUILD.gn
import("//build/ohos.gni")
ohos_prebuilt_etc("demo2model3_conf"){
source = "src/model3.conf"
relative_install_dir = "init" # 模块安装相对路径,相对于system/etc;此时路径为system/etc/init下,如果有module_install_dir配置时,该配置不生效。
part_name = "compdemo2"
}
注:复制代码时#备注需要全部删除,否则无法编译通过
6.对应src下创建model3.conf文件,文件内容示例如下
var_a=1
var_b=2
7.编写/my_test/componentdemo2/BUILD.gn
import("//build/ohos.gni")
group("compdemo2_group"){
deps = [
"a:demo2model1_lib",
"b:demo2model2_bin",
"c:demo2model3_conf",
]
}
注:dps数组中的添加格式:目录名:模块名
8.编写/my_test/componentdemo2/bundle.json
{
"name": "@ohos/compdemo2",
"description": "compdemo2 services",
"version": "3.1",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "my_test/componentdemo2"
},
"dirs": {},
"scripts": {},
"component": {
"name": "compdemo2",
"subsystem": "mycomptest",
"syscap": [],
"features": [],
"adapted_system_type": [ "mini", "small", "standard" ],
"rom": "10KB",
"ram": "10KB",
"deps": {
"components": [],
"third_party": [],
"hisysevent_config": []
},
"build": {
"sub_component": [
"//my_test/componentdemo2:compdemo2_group"
],
"inner_kits": [],
"test": []
}
}
}
注:此处"sub_component": ["//my_test/componentdemo2:compdemo2_group"]对应BUILD.gn中group("compdemo2_group")的值。
9..在/build/subsystem_config.json文件最后增加如下内容
,
"mycomptest": {
"path": "my_test",
"name": "mycomptest"
}
...
}
# 注:以上...}代表内容放在文件最后一级}下
注:subsystem_config.json文件此处的"subsystem": "mycomptest"为bundle.json中的"subsystem": "mycomptest"。
10. 在/vender/{产品解决方案厂商}/{产品名称}/config.json文件最后增加如下内容,如rk3568芯片则路径为:/vendor/hihope/rk3568/config.json
,
{
"subsystem":"mycomptest",
"components": [
{
"component": "compdemo2",
"features": []
}
]
}
...
]
}
注:以上...]}代表以上内容放在文件中最后一级数组中
注:config.json文件中此处的"subsystem":"mycomptest"为subsystem_config.json文件中的"name": "mycomptest";"component": "compdemo2"为BUILD.gn中的part_name = "compdemo2"。
11.编译
可以使用整编命令
./build.sh --product-name rk3568 --no-prebuilt-sdk
也可以使用“–build-target 模块名"单独编译,编译命令如下:
./build.sh --product-name rk3568 --build-target demo2model1_bin --ccache
还可以编译模块所在的部件:
./build.sh --product-name rk3568 --build-target compdemo2 --ccache
7.烧录到设备后,输入模块命令即可打印
后记:以上模块烧录后生成文件所在目录为:
模块类型 | 生成文件目录 |
ohos_shared_library | /system/lib |
ohos_executable | /system/bin |
ohos_prebuilt_etc | /system/etc/ |