1 关键字


启动、Init、产品配置、启动配置

2 简要描述


本文档主要以XX开发版为例分析OpenHarmony系统启动过程、产品配置、启动配置,并举例说明如何配置。
-  内核加载Init进程,一般在bootloader启动内核时通过设置内核的cmdline来指定init的位置
-  init进程启动后,会挂载tmpfs,procfs,创建基本的dev设备节点,提供最基本的跟文件系统
-  init也会启动ueventd监听内核热插拔设备事件,为这些设备创建dev设备节点。包括block设备各个分区设备都是通过此事件创建。
- init进程挂载block设备各个分区(system,vendor)后,开始扫描各个系统服务的init启动脚本,并拉起各个SA服务。
- samgr是各个SA的服务注册中心,每个SA启动时,都需要向samgr注册,每个SA会分配一个ID,应用可以通过该ID访问SA。
- foundation是一个特殊的SA服务进程,提供了用户程序管理框架及基础服务。由该进程负责应用的生命周期管理。
- 由于应用都需要加载JS的运行环境,涉及大量准备工作,因此appspawn作为应用的孵化器,在接收到foundation里的应用启动请求时,可以直接孵化出应用进程,减少应用启动时间。

3 init启动引导组件简要说明


init启动引导组件对应的进程为init进程,是内核完成初始化后启动的第一个用户态进程。init进程启动之后,读取init.cfg配置文件,根据解析结果,执行相应命令并依次启动各关键系统服务进程,在启动系统服务进程的同时设置其对应权限。
- 每个系统服务启动时都需要编写各自的启动脚本文件init.cfg,定义各自的服务名、可执行文件路径、权限和其他信息
- 每个系统服务各自安装其启动脚本到/system/etc/init 目录下,init进程统一扫描执行

4 init相关的移植适配、产品配置


4.1 新芯片平台移植


新芯片平台移植时,平台相关的初始化配置需要增加平台相关的初始化配置文件/device/board/[companyName]/{hardware}/cfg/init.{hardware}.cfg (/vendow/etc/init.{hardware}.cfg);该文件完成平台相关的初始化设置,如安装ko驱动,设置平台相关的/proc节点信息

4.1.1 适配文件系统


- 复制/device/board/hihope/rk3568/cfg/init.rk3568.cfg创建/device/board/[companyName]/{hardware}/cfg/fstab.{hardware}文件
- 根据{hardware}查找到dts文件:/kernel/linux/linux-5.10/arch/arm/boot/dts/{hardware}*.dts,获取到启动参数bootargs
- 根据bootargs的值,获取required分区,如(此处用的rk3568的举例):ohos.required_mount.system=/dev/block/platform/fe310000.sdhci/by-name/xxx@/usr@ext4@ro,barrier=1@wait,required
- 修改fstab.{hardware}文件,删除不需要的分区,修改bootDevice(rk3568的值是fe310000.sdhci,其他平台根据实际值修改)如:

# fstab file.
#<src>                                                  <mnt_point> <type>    <mnt_flags and options>                              <fs_mgr_flags>
/dev/block/platform/fe310000.sdhci/by-name/system               /usr       ext4     ro,barrier=1  wait,required
/dev/block/platform/fe310000.sdhci/by-name/vendor              /vendor        ext4     ro,barrier=1  wait,required
/dev/block/platform/fe310000.sdhci/by-name/userdata               /data       f2fs     discard,noatime,nosuid,nodev,fscrypt=2:aes-256-cts:aes-256-xts,usrquota  wait,check,fileencryption=software,quota
/dev/block/platform/fe310000.sdhci/by-name/misc           /misc none none wait,required

 

4.1.2 usb适配


复制/device/board/hihope/rk3568/cfg/init.rk3568.usb.cfg创建/device/board/[companyName]/{hardware}/cfg/init.{hardware}.usb.cfg文件,修改其中sys.usb.controller的值,此值和具体开发板usb硬件有关
- 进入开发板执行"ls /sys/class/udc",获取值如(此值为rk3568平台的,其他根据实际值替换):fcc00000.dwc3
- 修改对应cmd,以rk3568为例:"setparam sys.usb.controller fcc00000.dwc3"

4.1.3 内核等适配


- 复制/device/board/hihope/rk3568/cfg/init.rk3568.cfg创建/device/board/[companyName]/{hardware}/cfg/init.{hardware}.cfg配置文件
- 根据内核新增模块及适配获取新增的ko列表
- 根据镜像分区配置文件/build/ohos/images/mkimage/socko_image_conf.txt获取分区:/mnt/socko
- 根据上述信息,创建目录,挂载分区,载入内核模块,如:

{
    "name" : "fs",
    "cmds" : [
        "mkdir /mnt/socko",
        "mount ext4 /dev/block/platform/{bootDevice}/by-name/socko /mnt/socko wait rdonly barrier=1",
        "insmod /mnt/socko/gpu-***.ko",
        ... ...
    ]
}


- 其他设备/模块等适配,根据开发版预定义或功能要求来,如rk3568的blue_host,该串口设备在系统中的名字在开发板中预定义为"/dev/ttyS8",也可以在代码中找到:
/vendor/hihope/rk3568/bluetooth/include/bt_vendor_brcm.h

#define BLUETOOTH_UART_DEVICE_PORT "/dev/ttyS8" /* maguro */


在cfg文件对应的cmd根据设备名修改:

"chown blue_host blue_host /dev/ttyS8",

4.1.4 修改build.gn


复制/device/board/hihope/rk3568/cfg/BUILD.gn创建/device/board/[companyName]/{hardware}/cfg/BUILD.gn,根据{hardware}修改对应配置

4.2 产品配置


- 产品配置路径:不同设备路径不同,一般规则:/vendor/[companyName]/{hardware}/config.json
- 产品配置说明举例
config.json为编译构建的主入口,包含了开发板、OS组件和内核等配置信息,其中各组件及特性需要根据设备需求配置,新增的子系统或模块组件可以配置到此文件中。如果配置到rich.json等将要继承的配置文件也可以起作用,但放到此文件/对应的配置文件更规范。如果不配置新增的子系统或组件,新增的这些就无效。哪些是必须要修改的是根据设备和业务需求来的,比如L1设备,startup子系统的组件就需要选择init_lite,再比如某些厂商提供了适配于开发板的第三方内核,此时就可以以新增子系统或组件的形式移植进Openharmony,然后修改config.json配置文件相关子系统和组件。以rk3568为例,配置如下:

{
  "product_name": "rk3568",     // 产品名称,支持自定义
  "device_company": "rockchip", // 芯片解决方案厂商名称,一般与device的二级目录名称一致
  "device_build_path": "device/board/hihope/rk3568",
  "target_cpu": "arm",
  "type": "standard",        // 设备类型--标准设备
  "version": "3.0",            // OpenHarmony版本号,应与实际下载的版本移植
  "board": "rk3568",        // 开发版名称,与device的三级目录名称一致{hardware}
  "api_version": 8,
  "enable_ramdisk": true,
  "enable_absystem": false,
  "build_selinux": true,
  "build_seccomp": true,
  "inherit": [ "productdefine/common/inherit/rich.json", "productdefine/common/inherit/chipset_common.json" ], //继承其他组件配置信息,和本配置文件合并,如果配置重复,高优先级取代更新低优先级配置,对于服务的cfg配置文件优先级是/system/etc < /system/etc/init < /chipset/etc
  "subsystems": [ // 非通用子系统,应为OS支持的子系统
    {
      "subsystem": "security",
      "components": [ // 产品选择的某个子系统下的组件,应为某个子系统支持的组件
        {
          "component": "selinux_adapter",
          "features": []  // 产品配置的某个组件的特性
        }
      ]
    },
    ... ...
    {
      "subsystem": "hdf",
      "components": [
        {
          "component": "drivers_interface_ril",
          "features": []
        },
        {
          "component": "drivers_peripheral_ril",
          "features":[]
        }
      ]
    },
    ... ...
    {
      "subsystem": "startup", // 配置启动子系统
      "components": [
        {
          "component": "init", // init组件,如果是L1设备选择init_lite
          "features": [
            "enable_ohos_startup_init_feature_ab_partition = true",
            "enable_ohos_startup_init_feature_loader = true"
          ]
        }
      ]
    },
    {
    "subsystem": "product_**", // 如果新增子系统或组件,适配子系统时最好依次添加,确保一个没问题后再加其余的
    "components": [
        {
          "component": "product_a8s",
          "features": []
        },
        ... ...
      ]
    }
  ]
}

 

5 启动子系统init

5.1 init.cfg适配


- init配置脚本是init.cfg或init.without_two_stages.cfg,由ramdisk适配时DISABLE_INIT_TWO_STAGES宏定义控制,enable_ramdisk为false时会定义DISABLE_INIT_TWO_STAGES
- 使用import导入芯片解决方案和产品解决方案中添加的*.cfg配置文件(具体可参考下面的配置文件示例解析)
- 新增的各个子系统或组件的对应cfg文件不用导入,init进程会统一到/system/etc/init目录下读取(新增子系统的配置可参考下面的配置文件示例解析和其他子系统的配置)
- init.cfg或init.without_two_stages.cfg通常无需改动
- 如果要在init.cfg需要添加自定义启动阶段等,再根据自定义内容修改对应内容

5.2 init启动


当Linux内核kernel启动后初始化各种软硬件环境,加载驱动程序,挂载根文件系统就可以加载init进程了。在init进程中,init启动引导组件会收集每个系统服务的启动脚本文件cfg,该脚本文件定义了各种的服务名、可执行文件路径、权限和其他信息。每个系统的启动脚本安装到/system/etc/init目录下,由init进程统一扫描执行。
/base/startup/init/services/init/main.c 

#include <signal.h>
#include "init.h"
#include "init_log.h"

static const pid_t INIT_PROCESS_PID = 1;

int main(int argc, char * const argv[])
{
    int isSecondStage = 0;
    (void)signal(SIGPIPE, SIG_IGN);
    // Number of command line parameters is 2
    if (argc == 2 && (strcmp(argv[1], "--second-stage") == 0)) {
        isSecondStage = 1;
    }
    if (getpid() != INIT_PROCESS_PID) {
        INIT_LOGE("Process id error %d!", getpid());
        return 0;
    }
    EnableInitLog(INIT_INFO);
    if (isSecondStage == 0) {
        SystemPrepare();
    } else {
        LogInit();
    }
    SystemInit();
    SystemExecuteRcs();
    SystemConfig(); // 读取处理cfg配置文件
    SystemRun();
    return 0;
}

init进程pid为1,init进程首先启动日志,然后依次执行系统初始化SystemInit()、执行rcs进程SystemExecuteRcs()、系统配置SystemConfig()、系统运行SystemRun()

- SystemInit() 执行init进程的初始化,启动uevent监听热插拔事件,用于创建块设备文件,挂载required分区
- SystemExecuteRcs() 执行Linux的初始化
- SystemConfig() 通过初始化参数文件:
    1. 读取cfg配置文件内容(根据是否重启计重启原因加载不同的cfg配置),扫描各个系统的启动脚本,解析jobs的pre-init,init,post-init合并到一起存储在/etc/init.cfg中(其他自命名的job默认在post-init阶段执行)
    
    2. 解析services配置,获取要初始化服务的path、uid、gid等信息(/foundation,/appspawn等服务)
    
    3. 最后通过trigger依次执行这些操作,执行如创建文件夹,文件授权等cmd操作和start service的操作
- SystemRun() 创建一个LoopEvent处理事件,通过epoll实现,运行启动service服务

5.3 启动SA


init会优先启动samgr的系统服务进程(在init阶段就会启动),因为samgr是各个SystemAbility的服务进程中心,每个SA的启动都需要向samgr注册,然后分配到一个ID,通过ID才能访问到该SA。

在post-init阶段,SA的启动是并行的,并同时提供多种方式保证SA的时序:
1. 通过在init.cfg配置start-mode添加分组策略,利润samgr的服务就是通过"start-mode" : "boot"来保证在init阶段运行(具体见下面示例)。

2. 可以在进程初始化过程中通过等待对应启动事件,来执行代码级别的操作。例如下面的start ueventd:

{
    "name" : "pre-init",
    "cmds" : [
        "write /proc/sys/kernel/sysrq 0",
        "start ueventd",
        "start watchdog_service",
        ... ...
    ]
}

3. 通过订阅事件(BootEvent)参数,供启动配置文件、Native服务以及应用程序来等待和订阅事件。
3.1 启动事件归属不同的子系统,由归属子系统设置,供其他子系统get、wait和watch。
例如在配置文件中设置:
/base/useriam/pin_auth/sa_profile/pinauth_sa_profile.cfg

{
    "jobs" : [{
            "name" : "param:bootevent.useriam.fwkready=true",
            "condition" : "bootevent.useriam.fwkready=true",
            "cmds" : [
                "start pinauth"
            ]
        }
    ],
    "services" : [{
            "name" : "pinauth",
            "path" : ["/system/bin/sa_main", "/system/profile/pinauth.json"],
            "uid" : "useriam",
            "gid" : ["useriam", "shell"],
            "apl" : "system_basic",
            "permission" : ["ohos.permission.ACCESS_AUTH_RESPOOL"],
            "permission_acls" : ["ohos.permission.ACCESS_AUTH_RESPOOL"],
            "secon" : "u:r:pinauth:s0",
            "start-mode" : "condition"
        }
    ]
}


只有当满足condition条件(bootevent.useriam.fwkready=true)后才能执行start service操作
3.2 SA注册事件订阅机制
SystemAbility类接口中包含OnAddSystemAbility()和OnRemoveSystemAbility()方法监听SA注册事件,只需要再AddSystemAbilityListener中添加监听的SA ID即可。

class MyAbility : public SystemAbility {
    ...
proteced:
    void OnAddSystemAbility();
    void OnRemoveSystemAbility();
}


非SA进程则需要继承SystemAbilityStatusChangeStub类实现OnAddSystemAbility()和OnRemoveSystemAbility()方法,或通过SystemAbilityManagerClient单例类来获取到samgr的句柄类。

class MyAbility : public SystemAbilityStatusChangeStub {
    ...
proteced:
    void OnAddSystemAbility();
    void OnRemoveSystemAbility();
}
sptr<ISystemAbilityManager> samgrProxy = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
if (samgrProxy == nullptr) {
    HILOG_ERROR();
    return;
}

int32_t ret = samgrProxy->SubscriSystemAbility(systemAbilityId, statusChangerListenner_);
if (ret != ERR_OK) {
    HILOG_ERROR();
    return;
}


在合适的地方设置系统参数,如:
basee/useriam/user_auth_framework/services/ipc/src/co_auth_service.cpp

void CoAuthService::Init()
{
    auto hdi = HdiWrapper::GetHdiRemoteObjInstance();
    if (hdi) {
        hdi->AddDeathRecipient(new (std::nothrow) IpcCommon::PeerDeathRecipient([]() {
            ResourceNodePool::Instance().DeleteAll();
            RelativeTimer::GetInstance().Register(Init, DEFER_TIME);
            IAM_LOGI("delete all executors for hdi dead");
            UserIam::UserAuth::ReportSystemFault(Common::GetNowTimeString(), "user_auth_hdi host");
        }));
        IAM_LOGI("set fwk ready parameter");
        SetParameter("bootevent.useriam.fwkready", "false");
        SetParameter("bootevent.useriam.fwkready", "true"); // 设置 bootevent.useriam.fwkready=true
    } else {
        RelativeTimer::GetInstance().Register(Init, DEFER_TIME);
    }
}

foundation是一个特殊的SA服务进程,提供用户程序管理框架及基础服务,该进程负责应用的生命周期管理,ready可进行startAbility/connectAbility。当多个服务都完成对应的启动事件后,由bootevents投票,在init进程中设置bootevent.boot.completed事件为true,表示系统启动完成。
base/startup/init/services/modules/bootevent/bootevent.c

static void BootEventParaFireByName(const char *paramName)
{
    ... ...
    // All parameters are fired, set boot completed now ...
    INIT_LOGI("All boot events are fired, boot complete now ...");
    SystemWriteParam(BOOT_EVENT_BOOT_COMPLETED, "true");
    ... ...
}

在foundation监控到此事件再以CES事件的方式广播出去,让应用能够检测到。foundation服务进程想应用孵化器appspawn发送应用启动请求。appspawn接收到应用启动请求,直接孵化出应用进程。当foundation启动Openharmony应用Home即我们看到的桌面程序,至此从开机到应用启动所有过程就完成了。

5.4 启动配置文件说明


    1. cfg文件包含import、jobs、services三部分
    2. 在services中可以定义多个service,service必选参数:name和path。path为一个数组[],第一个是服务路径,后面可以跟服务参数,参数中不能有空格。
    3. once:配置0为常驻进程,进程被kill后自动重启;配置1则不重启。
    4. jobs 可以配置4096个job,支持多个命令;start 启动服务,mkdir 创建文件夹,export 导出环境变量;详细可参考文档:https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-boot-init-jobs.md
    5. jobs 数组支持多个job,job包含两个参数,name和cmds,预制name有3个:pre-init,init,post-init;
        pre-init:第一阶段,其他服务依赖的文件夹和依赖的服务在此阶段创建
        init:主要阶段,除了job和start service外,boot 类型的service也会在这个阶段启动
        post-init:除了job中start service外,normal类型的service也会在这个阶段启动
        还可以定义其他阶段,通过trigger触发,可参考下面示例注释


5.5 启动配置文件示例解析


如/base/startup/init/services/etc/init.without_two_stages.cfg,为了说明配置情况,其中有些配置有添加删改

{
    "import" : [  // 导入其他配置文件,解析时各配置文件合并,各子系统的配置文件不用导入,会直接到/system/etc/init下读取
            "/etc/init.usb.cfg",
            "/etc/init.usb.configfs.cfg",
            "/vendor/etc/init.${ohos.boot.hardware}.cfg" // 如果时rk3568平台,${ohos.boot.hardware}的值就是rk3568
    ],
    "jobs" : [{ // 配置等待执行命令集合,name,cmds必不可少
            "name" : "pre-init", // init前置阶段,其他服务依赖的关键服务,data分区挂载会在这一阶段启动
            "cmds" : [             // 等待执行的命令集合
                "write /proc/sys/kernel/sysrq 0",
                "start ueventd",    // 配置start-mode: condition条件启动,此处比start-mode: boot更早
                "start watchdog_service",
                "mkdir /data",
                "mount_fstab /vendor/etc/fstab.${ohos.boot.hardware}", // 按照fstab挂载分区
                ...
            ]
        }, {
            "name" : "init", // 主要阶段,除大量命令执行外也是init分组并行启动boot组(第一组)服务的启动阶段(start-mode: boot)
            "cmds" : [
                "copy /proc/cmdline /dev/urandom",  // cmdline: 用于设置内核启动参数,启动内核时会读取相关配置
                "copy /system/etc/prop.default /dev/urandom",
                "symlink /proc/self/fd/0 /dev/stdin",
                ...
            ]
        },  {
            "name" : "post-init", // 这一阶段主要通过trigger命令触发其他阶段执行,如init.cfg中的name为post-fs等阶段;它还是init分组并行启动normal组(第二组)服务的启动阶段(start-mode:normal)
            "cmds" : [
                "trigger early-fs", // 使用trigger触发对应jobs,early-fs、fs等是本系统定义的几个阶段;也可通过trigger触发自定义的jobs
                "trigger fs",
                "trigger post-fs",
                "trigger late-fs",
                ...
            ]
        }, {
            "name" : "post-fs",
            "cmds" : [
                "mount rootfs rootfs / remount bind ro nodev",
                ...
            ]
        }, {
            "name" : "late-fs",
            "cmds" : [
                "chmod 0755 /sys/kernel/debug/tracing"
            ]
        }, {
            "name" : "post-fs-data",
            "cmds" : [
                "init_global_key /data",
                ...
            ]
        }, {
            "name" : "boot",
            "cmds" : [
                "write /proc/sys/net/core/xfrm_acq_expires 3600",
                ...
            ]
        }, { //条件job
            "name" : "param:sys.sysctl.extra_free_kbytes=*", // 名称一般命名为:param:[条件]
            "condition" : "sys.sysctl.extra_free_kbytes=*",  // 系统参数sys.sysctl.extra_free_kbytes只要被赋值,就执行次job(中的cmds)
            "cmds" : [
                "write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}"
            ]
        }, {
            "name" : "param:sys.sysctl.tcp_def_init_rwnd=*",
            "condition" : "sys.sysctl.tcp_def_init_rwnd=*",
            "cmds" : [
                "write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}"
            ]
        }, { // 条件job
            "name" : "param:security.perf_harden=0",
            "condition" : "security.perf_harden=0", // 系统参数security.perf_harden被赋值为0时执行此job(中的cmds)
            "cmds" : [
                "write /proc/sys/kernel/perf_event_paranoid 1",
                "write /proc/sys/kernel/perf_event_max_sample_rate ${debug.perf_event_max_sample_rate:-100000}",
                "write /proc/sys/kernel/perf_cpu_time_max_percent ${debug.perf_cpu_time_max_percent:-25}",
                "write /proc/sys/kernel/perf_event_mlock_kb ${debug.perf_event_mlock_kb:-516}"
            ]
        }, 
        ...
    ],
    "services" : [{ // 用于配置系统支持的Native服务
            "name" : "ueventd",  //进程名称
            "path" : ["/system/bin/ueventd"], // 路径,如果数组中有多个字符串的,后面的是参数
            "socket" : [{
                "name" : "ueventd",
                "family" : "AF_NETLINK",
                "type" : "SOCK_DGRAM",
                "protocol" : "NETLINK_KOBJECT_UEVENT",
                "permissions" : "0660",
                "uid" : "system",  // uid和gid 从/etc/passwd中获取
                "gid" : "system",
                "option" : [
                    "SOCKET_OPTION_PASSCRED",
                    "SOCKET_OPTION_RCVBUFFORCE",
                    "SOCK_CLOEXEC",
                    "SOCK_NONBLOCK"
                ]
            }],
            "critical" : [ 0, 15, 5], // "critical" : [M, N, T],其中M:使能标志位(0:不使能,1:使能),N:频繁拉取服务次数,N大于0, T:时间(单位:秒)
            "ondemand" : true, // 按需启动,ondemand属性默认是按需启动的(不是常驻进程,比如用户点击某个应用时才启动的服务),ondemand和critical属性互斥,两者同时配置,服务不能被正确解析。critical是常驻进程配置,配置ondemand属性后,不需要再配置critical,或者配置critical属性不使能。如果同时配置start-mode会使start-mode无效(当前服务两个都配了,不影响按条件启动)
            "sandbox" : 0, // 沙盒开关
            "start-mode" : "condition" // 条件启动,通过job触发,如"start ueventd"; 还有boot,normal两个属性:normal默认服务的启动属性对应普通服务,在post-init启动;boot在init阶段启动;可用于并行控制
        }, {
            "name" : "appspawn",
            "path" : ["/system/bin/appspawn",
                      "--process-name com.ohos.appspawn.startup --start-flags daemon --type standard",
                      "***",...], // 数组中第一条数据是路径,后面的是参数
            "importance" : -20, //取值范围:[-20~19],值越小优先级越高,超出范围无效,不配做时默认0
            ...
            "cpucore" : [0,1], // 此进程绑定的执行CPU,这种配置是绑定序号为0,1的CPU;默认全部CPU都可执行此进程
            "start-mode" : "boot", //init阶段并行启动
            "secon" : "u:r:appspawn:s0" // selinux 标签
            "start-mode" : "condition"
        },{
            "name" : "samgr",
            "path" : ["/system/bin/samgr"],
            ...
            "start-mode" : "boot",
            "jobs" : { // 可以把服务相关的命令放到服务自己的进程中执行
                "on-start" : "services:samgr" // jobs中的配置有:on-start 服务启动阶段执行,在fork的子进程中执行;on-boot(当前没有cfg文件在使用,服务启动前在init进程中执行);on-restart 服务重启时执行对应的job; on-stop 在服务停止时执行,回到init进程中执行
            }
        }, 
        ...
    ]
}

 

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐