鸿蒙系统的启动流程v2.0 原创 精华
鸿蒙系统的启动流程
liangkz 2021.04.15 Ver2.0
目录
1. 第一阶段:U-Boot启动..................................................................................... 3
2. 第二阶段:汇编代码引导LiteOS-a内核........................................................ 3
3. 第三阶段:内核LiteOS-a的C语言启动阶段................................................ 4
4. 第四阶段:鸿蒙系统框架层的启动.............................................................. 14
5. 第四阶段:鸿蒙系统框架层的启动细节...................................................... 17
6. 鸿蒙应用(APP)的启动...................................................................................... 20
附录A. #task命令查看进程/线程信息(简表).................................................... 21
声明:
升级文档到v2.0后可以声明是原创的了。这是上了朱有鹏老师的免费课《想读懂鸿蒙2.0源码,也许你需要先懂这些》(link:https://edu.51cto.com/course/27341.html?hm)之后,做的一些总结。
课程时间一个半小时,内容也很多,学习过程中我发现朱老师的ppt上部分代码/文件,在我本地的鸿蒙系统代码上找不到,或者路径不相同,所以我就做了一些整理。
这里仅摘取课程中的鸿蒙系统在HI3516DV300平台上的启动流程部分(从30:00开始讲解启动过程)进行汇总和整理,如有错误,请朱老师和各位同学指正。后继在学习过程中会继续对本文当作修正升级。附件升级了的v2.0文档对阅读更友好,欢迎下载收藏。
更新记录:
2021.04.11 | v1.0 |
初始版本,有不少理解不到位或错误的地方,详见第一版文档《鸿蒙系统的启动流程 v1.0》的附件pdf 文档。(Link: https://harmonyos.51cto.com/posts/3893) |
2021.04.15 | v2.0 | 重新整理了第四阶段:鸿蒙系统框架层的启动:增加了新的理解内容,修改错误。增加了框架层的启动细节部分概述。 |
我的本地代码是基于最新发布的OpenHarmony 1.1.0 LTS(2021-04-01)版本抓取的,代码根目录OHOS1_1_0LTS:
$repo init -u https://gitee.com/openharmony/manifest.git -b refs/tags/OpenHarmony_release_v1.1.0 --no-repo-verify
$repo sync
在根目录下执行
OHOS1_1_0LTS$ hb set
[OHOS INFO] Input code path: .
OHOS Which product do you need?
->ipcamera_hispark_taurus@hisilicon
OHOS1_1_0LTS$ hb build
即可开始编译 hi3516dv300 平台代码。
输出的过程文件和最终bin,在以下路径内:
out\hispark_taurus\ipcamera_hispark_taurus
因为本人还没有开发板,无法烧录、抓取log分析以及做相关的操作去验证。
1. 第一阶段:U-Boot启动
System startup
Uncompress Ok!
U-Boot 2016.11 (......) hi3516dv300
............
............(省略)
Hit any key to stop autoboot: 0
MMC read: dev #0, block # 2048, count 16384 ... 16384 blocks read: OK
## Starting application at 0x80000000...
到此为止属于U-Boot的启动。
Uboot不属于鸿蒙系统,这里不做进一步分析,代码在目录
device\hisilicon\third_party\uboot\u-boot-2020.01
2. 第二阶段:汇编代码引导LiteOS-a内核
Uboot引导liteos-a内核启动起来,需要有一个入口,在:
kernel\liteos_a\tools\build\liteos.ld
打开这个文件,可见:
ENTRY(reset_vector)
INCLUDE board.ld
SECTIONS
{
......
}
reset_vector 就是整个鸿蒙内核启动的入口点,这是一个符号,定义在:
kernel\liteos_a\arch\arm\arm\src\startup\reset_vector_mp.S
同目录下还有一个reset_vector_up.S文件,因为HI3516是ARM Cortex A7双核处理器,所以需要看mp(多核)这个文件,up这个是单核的。
打开reset_vector_mp.S文件,找到“reset_vector:”符号,从这里开始跑汇编代码,引导liteos-a内核的启动,一直到:
“
bl main
_start_hang:
b _start_hang
”
这里调用一个 main 函数,然后执行 _start_hang 进入死循环,至此汇编代码阶段就结束了。
通过main函数进入内核LiteOS-a启动的C语言阶段。
3. 第三阶段:内核LiteOS-a的C语言启动阶段
上面汇编阶段调用的main函数,位于:
kernel\liteos_a\platform\main.c
main函数通过OsSystemInfo();函数里打印下面这些信息
******************Welcome******************
Processor : Cortex-A7*2
Run Mode : SMP
GIC Rev : GICv2
build time : ......
Kernel : Huawei LiteOS 2.0.0.xxx
********************************************
main core booting up...
...
...
从这一步的main开始读liteos-a的C语言源码,可以直接在鸿蒙代码的
kernel\liteos_a\platform\main.c
进行阅读理解。
不过,推荐从下面仓库拉代码下来读,
鸿蒙内核源码注解分析:
https://gitee.com/weharmony/kernel_liteos_a_note.git
这是在鸿蒙官方开源项目 kernel_liteos_a 基础上,给源代码加上了给常详细的中文注解,有利于加快理解。
需要注意的是,这个注解的内核代码可能版本稍旧,而上面repo下来的内核部分代码可能做了部分重构和修改,会有少量的不同。
main函数截图如下:
Line173行调用的OsMain()函数,位于:
kenerl\liteos_a\kernel\common\los_config.c
主要做了:
其中的:
- OsTickInit(......); // tick初始化,包含注册中断事件
硬件时钟初始化,启动节拍,注册硬中断
- OsKernelInitProcess(); // 完成内核进程的初始化
【鸿蒙内核源码注解分析上的API与OpenHarmony 1.1.0 LTS(2021-04-01)上的API,有少量差异,但功能基本上是一样的。】
代码都在:kernel\liteos_a\kernel\base\core\los_process.c 文件内。
下面分别来看一下:
-------------------------------------------------------------------------------------------------
鸿蒙内核源码注解分析:OsKernelInitProcess()
-------------------------------------------------------------------------------------------------
OsCreateResourceFreeTask()函数,下面的调用也是一样的。
-------------------------------------------------------------------------------------------------
OpenHarmony 1.1.0 LTS(2021-04-01):OsSystemProcessCreate( )
-------------------------------------------------------------------------------------------------
由上面的代码截图可以看出:
- 创建2号进程 KProcess,最高优先级0,这是一个内核态进程。
- 创建KProcess的子线程ResourceTask,优先级别5,用于资源回收
- 由KProcess复制(fork)出一个名叫 KIdle 的内核态进程,最低优先级31
- KIdle进程创建子线程Idle,优先级别31,给CPU空闲的时候使用.
注意,此时1号进程还没有创建,它是用户态根进程,要到稍微后面才创建。
可以在shell内执行task命令查看进程和线程信息,表格见附录。
- OsSwtmrInit(); //软时钟模块初始化
创建Swt_Task(software timmer)线程,父进程是2号进程KProcess,
- OsSystemInit(); //系统初始化
系统软硬件的初始化,由2号进程 KProcess创建“system_wq”“SystemInit”“memshow_Task”等线程。
“SystemInit”线程:
跳出内核代码,到外部提供的SystemInit()做系统初始化,在:
device\hisilicon\hispark_taurus\sdk_liteos\mpp\module_init\src\system_init.c
里的SystemInit()函数:
其中的:
- ProcFsInit()
创建和挂载/proc文件系统,代码见:
kernel\liteos_a\fs\proc\os_adept\proc_init.c
- SDK_init() //calling SDK_init form HISI_SDK
初始化3516DV300特有的SDK,用内部的DSP硬件来做视频编解码,只提供相关库文件,不开源。
代码在:device\hisilicon\hispark_taurus\sdk_liteos\mpp\module_init\src\sdk_init.c
- OsMountRootfs()
挂载根文件系统:
out\hispark_taurus\ipcamera_hispark_taurus\rootfs.tar
可以通过tar -tf rootfs.tar 命令查看里面都有些什么内容,下面显示一部分目录:
- OsUserInitProcess()
kernel\liteos_a\kernel\base\core\los_process.c
回到内核代码,鸿蒙内核源码注解分析:OsUserInitProcess()【最新1.1.0 LTS代码有重构,但意思一样】
这时候才创建1号进程Init,这是用户态根进程,优先级别为28,
__user_init_entry 就是第一个用户态根进程的地址,它通过宏LITE_USER_SEC_ENTRY进行定义:
代码在:
kernel\liteos_a\kernel\user\src\los_user_init.c
LiteOS_a操作系统起来后,到挂载根文件系统,都处于内核态(内核空间),创建第一个用户态根进程Init时,
通过第一个用户态程序的地址 __user_init_entry / LITE_USER_SEC_ENTRY [ 进入OsUserInit() 并执行系统调用
"sys_call3" 切换到用户态(用户空间) ] 去运行g_initPath = "/bin/init" 程序。
简单来说第一个用户态根进程,实际上运行的就是/bin/init的代码。
========================================================
1. /kernel/liteos_a/apps目录下,是liteoa_a自带的内核应用,也就是内核的用户态进程,
目前有 init、shell、tftp三个内核应用,编译后会在下面路径下生成同名的可执行文件:
out\hispark_taurus\ipcamera_hispark_taurus\obj\kernel\liteos_a\bin
其中init.c 做的事情很简单,就是fork一个新的用户态进程并运行"/bin/shell".
========================================================
2. /base/startup/init_lite目录下的init_lite组件(init_lite module)的详细说明,见该目录下的官方文档【README_zh.md】。
查看BUILD.gn可看到它也会被编译成 init 可执行程序
在out\hispark_taurus\ipcamera_hispark_taurus\bin目录下生成,同目录下还有foundation、appspawn等可执行程序。
========================================================
3. 【疑惑、解疑】
写本文档的v1.0版本时,我就一直存在一个很大的疑惑,理论上LiteOS_a内核的启动,从内核态切换到用户态时,
系统调用["sys_call3" ]去运行的 g_initPath = "/bin/init" ,应该是上面1编译出来的init才对,可是为什么会去运行上面2编译出来的init呢?
因为不懂直接分析build.py和gen_rootfs.py等编译脚本,我只好用了最笨的分析方法,步骤如下:
3.1 可以确认的是两个init,大小不一样,生成位置也不一样,init的生成时间1要比2早大约2分钟。
3.2 怀疑是gen_rootfs.py在生成rootfs.tar时,只拷贝了2的init而没有拷贝1的init到rootfs/bin目录下。
但马上推翻了,因为1生成的shell、tftp内核应用同时也拷贝到了rootfs/bin目录下,不应该特殊处理init:
3.3 那就有可能是gen_rootfs.py在生成rootfs.tar时,先拷贝1的init/shell/tftp到/rootfs/bin,
接着再拷贝2的init/foundation/appspawn……到/rootfs/bin时,把1的init给替换掉了。
马上验证一下。
重新启动编译,等2的init一生成,马上将其改名为init5,等编译完成后,重新打开rootfs.tar查看,
果不其然,rootfs/bin里面出现了init和init5,文件大小基本对应得上,所以这就是真相。
3.4 有清楚gen_rootfs.py工作过程细节的同学麻烦从脚本分析入手确认一下。
3.5 另,关于shell的疑问,也请懂的同学回复一下,这里就不分析和验证了:
kernel\liteos_a\apps\shell 会编译成shell,据朱有鹏老师说这是精简版的shell,
kernel\liteos_a\shell 这个也会编译成shell,朱有鹏老师说是全功能版的shell,
这两个版本的shell,哪个会在最终的gen_rootfs.py 时拷贝到 /bin 中?
========================================================
通过上面的验证,确认了LiteOS_a内核的启动,从内核态切换到用户态时,
系统调用["sys_call3" ]去运行的 g_initPath = "/bin/init" ,确实是base\startup\init_lite 组件
编译生成的init,也就是鸿蒙系统的第一个用户态根进程就是init_lite组件,这就进入到了
鸿蒙系统的框架层framework的启动。
4. 第四阶段:鸿蒙系统框架层的启动
这个应用层实际上就是鸿蒙的framework,启动init入口在:
base\startup\init_lite\services\src\main.c
- ReadFileToBuf()
这一步读取的 /etc/init.cfg文件,在上面OsMountRootfs()挂载根文件系统的时候就挂载上了,它是
vendor\hisilicon\hispark_taurus\init_configs\init_liteos_a_3516dv300.cfg
的副本,这个文件就包含了“pre-init”“init”“post-init”的相关操作,分别是设置挂载一些设备、设置好路径,启动服务等工作。
而后面的"services"则包含一组服务的定义,它们是系统里的关键进程。
- DoJob("init")
由“1号进程init”,在应用层通过start指令创建和启动:shell/apphilogcat/.../ai_server等3~9号进程,它们都是用户态进程,父进程都是“1号进程init”。
init将根据上面cfg配置的job和services来做对应的操作和启动对应的服务程序,并设置它们的uid、gid、进程优先级和权限等。
可以在shell内执行task命令查看进程和线程信息,表格见文末。
【见官方文档:base\startup\init_lite\README_zh.md】
这个init组件(即base\startup\init_lite)负责处理从内核加载第一个用户态进程(1号进程init)开始,到第一个应用程序启动之间的系统服务进程启动过程。
init将系统启动分为三个阶段:
- “pre-init”阶段:启动系统服务之前需要先执行的操作,例如挂载文件系统、创建文件夹、修改权限等
- “init”阶段:系统服务启动阶段
- “post-init”阶段:系统服务启动完后还需要执行的操作
上述每个阶段在配置文件init.cfg中都用一个job表示,每个job都对应一个命令集合,init通过依次执行每个job中的命令来完成系统初始化。job执行顺序:先执行“pre-init”,再执行“init”,最后执行“post-init”,所有job都集中放在init.cfg的jobs数组中。
除上述jobs数组之外,init.cfg中还有一个services数组,用于存放所有需要由init进程启动的系统关键服务的服务名、可执行文件路径、权限和其他属性信息。
配置文件init.cfg位于代码仓库/vendor/hisilicon/hispark_aries/init_configs/目录,部署在/etc/下,采用json格式,文件大小目前限制在100KB以内。
init组件会编译成out\hispark_taurus\ipcamera_hispark_taurus目录下的bin/init,同时打包在根文件系统rootfs.tar内,上面挂载根文件系统时,会挂载成/bin/init,由第三阶段的最后一步OsUserInit()调用和执行。
【见官方文档:base\startup\init_lite\README_zh.md】
总结:
Init_lite组件会编译成out\hispark_taurus\ipcamera_hispark_taurus目录下的bin/init,
然后在gen_rootfs.py生成rootfs.tar时替换掉系统自带的内核应用init,成为系统第一个
用户态根进程的启动程序。并由它开启系统里的其他关键服务进程。
5. 第四阶段:鸿蒙系统框架层的启动细节
上面第四阶段简单解释了鸿蒙系统的关键系统进程和相关服务的启动。
这一小节就要看看shell/apphilogcat/.../ai_server等3~9号进程的具体启动过程和调用的相关代码入口。
DoJob(“init”)会根据cmds的顺序启动相关服务:
"name" : "init",
"cmds" : [
"start shell",
"start apphilogcat",
"start foundation",
"start bundle_daemon",
"start appspawn",
"start media_server",
"start wms_server",
"start hiview",
"start sensor_service",
"start ai_server"
]
这些服务程序都对应在根目录的/bin下存在(除了hiview?需平台确认)
1.1 start shell
启动shell服务,进程ID是3。精简版和全功能版的shell,代码分别在:
kernel\liteos_a\apps\shell
kernel\liteos_a\shell\full
具体跑哪个,目前存疑,待验证。
1.2 start apphilogcat
启动apphilogcat服务,进程ID是4。
base\hiviewdfx\hilog_lite
hilog_lite组件,提供DFX(Design For X,面向X的设计)子系统在轻量系统和小型系统的流水日志功能。
详情见目录下的【README_zh.md】
base\hiviewdfx\hilog_lite\services\apphilogcat\hiview_applogcat.c
base\hiviewdfx\hilog_lite\services\hilogcat\hiview_logcat.c
main() 里面都是log文件的相关操作
1.3 start foundation
启动foundation服务,进程ID是5。
foundation\distributedschedule\safwk_lite
safwk_lite 是foundation进程的实现,负责提供基础服务运行的空进程。
safwk_lite 属于分布式任务调度子系统的一个模块。
详情见目录下的【README_zh.md】 saf: system ability (SA) framework
foundation\distributedschedule\safwk_lite\src\main.c
main()里有调用: OHOS_SystemInit();
同文件有定义,不过是弱引用:
void __attribute__((weak)) OHOS_SystemInit(void)
{
SAMGR_Bootstrap();
}
在base\startup\bootstrap_lite\services\source\system_init.c 有另一个定义:
void OHOS_SystemInit(void)
{
MODULE_INIT(bsp);
MODULE_INIT(device);
MODULE_INIT(core);
SYS_INIT(service);
SYS_INIT(feature);
MODULE_INIT(run); //SYS_RUN(HelloWorld); 从这里开始启动,待验证。
//系统服务框架子系统启动 [system ability (SA) framework]
//foundation\distributedschedule\samgr_lite
SAMGR_Bootstrap();
}
1.4 start bundle_daemon
启动bundle_daemon服务,进程ID是6。
foundation\appexecfwk\appexecfwk_lite
包管理组件服务,用户程序框架子系统。
详情见目录下的【README_zh.md】
foundation\appexecfwk\appexecfwk_lite\services\bundlemgr_lite\bundle_daemon\src\main.cpp
main()里有调用: HOS_SystemInit();
同文件有定义,同样是弱引用,不过暂未发现其他地方有定义:
void __attribute__((weak)) HOS_SystemInit(void)
{
SAMGR_Bootstrap();
}
1.5 start appspawn
启动appspawn服务,进程ID是7。
base\startup\appspawn_lite
appspawn_lite 应用孵化器组件,负责接受应用程序框架的命令孵化应用进程,设置其对应权限,并调用应用程序框架的入口。
详情见目录下的【README_zh.md】
base\startup\appspawn_lite\services\src\main.c
main() 跑的流程基本上与bundle_daemon的一样。
1.6 start media_server
启动media_server服务,进程ID是8。
foundation\multimedia\media_lite
media_lite媒体子系统组件(C++实现),提供播放、录制、解析、解码等接口能力,并提供媒体播放录制引擎服务化能力。详情见目录下的【README_zh.md】
foundation\multimedia\media_lite\services\media_main.cpp
1.7 start wms_server
启动wms_server服务,进程ID是9。
oundation\graphic\wms
图形服务采用C/S架构,内部分为窗口管理(WMS: Window Manager Service)和输入事件管理(IMS: Input Manger Service)两个子服务。APP调用客户端接口完成窗口状态获取、事件处理等操作,服务端与硬件交互实现送显、输入事件分发等。
详情见目录下的【README_zh.md】
foundation\graphic\wms\services\wms\wms.cpp
1.8 start hiview
启动hiviewr服务,进程ID是10。
base\hiviewdfx\hiview_lite
hiview_lite组件,提供DFX子系统整体的初始化功能,控制各组件按需启动
static void Init(void)
{
}
SYS_SERVICE_INIT(Init);
1.9 start sensor_service
启动sensor_server服务,进程ID是11。
base\sensors\sensor_lite
泛Sensor服务子系统提供了轻量级sensor服务基础框架。
详情见目录下的【README_zh.md】
1.10 start ai_server
启动ai_server服务,进程ID是12。
foundation\ai\engine
AI业务子系统是OpenHarmony提供原生的分布式AI能力的子系统。
详情见目录下的【README_zh.md】
5.11 HMOS的Service
HMOS的init进程启动后,接下来会启动上面一系列的服务(应该还会有其他更多的),对于这些服务,鸿蒙OS有一个统一管理,所有的服务都被注册到如下的动态数组当中。
base\startup\init_lite\services\src\init_service_manager.c
static Service* g_services = NULL;
static int g_servicesCnt = 0;
具体情况需阅读代码进行理解。
6. 鸿蒙应用(APP)的启动
在hi3516dv300平台(带屏幕)的桌面(也就是launcher进程)上点击camera应用图标,这时候会启动camera应用程序,实际上会通过“7号进程appspawn”创建子进程“com.huawei.camera”,这是一个应用程序进程,其父进程并不是launcher进程,而是appspawn进程。
实际上所有的应用程序的父进程都是appspawn进程。
鸿蒙应用开发的第一个示例程序“helloworld”的启动也应该类似。
#include <stdio.h>
#include "ohos_init.h"
#include "ohos_types.h"
void HelloWorld(void)
{
printf("[Init] Hello World!\n");
}
SYS_RUN(HelloWorld);
关于SYS_RUN()如何运作,以便让HelloWorld运行起来,其他老师有非常详细的解释,这里不再复述。
附录A. #task命令查看进程/线程信息(简表)
赞,楼主讲的很详细。
👍👍👍
楼主讲的很详细。
好文章
好文章,学习了
文档更新中:
重新整理了第四阶段:鸿蒙系统框架层的启动:增加了新的理解内容,修改错误。
增加了框架层的启动细节部分概述。
整理确认OK后会尽快发布。
不断更新,不断进步。
学习者的视角,很赞。学习~
文档更新中:
v1.0/2.0版本都是基于Hi3516平台+LiteOS_A内核的启动流程分析。
v3.0增加了Part II,基于Hi3861平台+LiteOS_M内核的启动流程分析。
整理确认OK后会尽快发布。
去看《鸿蒙系统的启动流程v3.0》这个,
https://harmonyos.51cto.com/posts/4013
本文《第四阶段》有更新和补充,详见新发文章:鸿蒙系统框架层的启动细节