移植案例与原理 - startup子系统之bootstrap_lite服务启动引导部件(1)

news2024/11/27 11:52:29

bootstrap_lite服务启动引导组件提供了各服务和功能的启动入口标识。在 SAMGR(System ability manager,系统服务管理) 启动时,会调用bootstrap_lite标识的入口函数,并启动系统服务。本文介绍下移植开发板时如何适配服务启动引导部件bootstrap_lite,并介绍下相关的运行机制原理。bootstrap_lite启动引导部件定义在build\lite\components\startup.json。bootstrap_lite启动引导部件源代码目录如下:

base/startup/bootstrap_lite/    # 启动引导组件
└── services
    └── source                  # 启动引导组件源文件目录

1、bootstrap_lite服务启动引导部件适配示例

1.1 产品解决方案配置启用部件

移植开发板适配startup子系统之bootstrap_lite服务启动引导部件时,需要在产品解决方案的config.json增加下述配置项,可以参考vendor\bestechnic\display_demo\config.json中的配置示例:

    {
      "subsystem": "startup",
      "components": [
        {
          "component": "bootstrap_lite"
        },
        ......
      ]
    },

1.2 使用bootstrap服务启动部件提供的初始化宏函数

然后就可以使用bootstrap服务启动部件提供的初始化宏函数SYS_SERVICE_INIT、APP_SERVICE_INIT等来自动初始化服务,示例代码可以参考device\board\fnlink\v200zr\liteos_m\at\at_wifi.c中的用法,片段如下,可以看到调用了宏函数来初始化RegisterCustomATCmd函数实现的服务。device\board\bearpi\bearpi_hm_nano\app\目录下有更多的使用示例。下文分析实现机制原理。

static void RegisterCustomATCmd()
{
    cmd_tbl_t cmd_list[] = {
        {"AT+IFCFG", 8, at_lwip_ifconfig, "AT+IFCFG - ifconfig\n"},
        {"AT+STARTAP", 7, at_start_softap, "AT+STARTAP - start wifi softap\n"},
        {"AT+STOPAP", 1, at_stop_softap, "AT+STOPAP - stop wifi softap\n"},
        {"AT+STARTSTA", 1, at_start_wifista, "AT+STARTSTA - start wifi sta\n"},
        {"AT+STOPSTA", 1, at_stop_wifista, "AT+STOPSTA - stop wifi sta\n"},
        {"AT+DHCP", 3, at_setup_dhcp, "AT+DHCP - dhcp\n"},
        {"AT+DHCPS", 3, at_setup_dhcps, "AT+DHCPS - dhcps\n"},
    };
    for (int i = 0; i < sizeof(cmd_list) / sizeof(cmd_tbl_t); i++) {
        console_cmd_add(&cmd_list[i]);
    }
}
SYS_SERVICE_INIT(RegisterCustomATCmd);

1.3 链接脚本中增加zInit代码段

适配bootstrap_lite部件时,还需要在链接脚本文件中手动新增如下段,链接脚本示例可以参考//device/soc/bestechnic/bes2600/liteos_m/sdk/bsp/out/best2600w_liteos/_best2001.lds,还可以参考device\soc\hisilicon\hi3861v100\sdk_liteos\build\link\link.ld.S。从链接脚本片段中可以看出,有.zinitcall.bsp、.zinitcall.device、.zinitcall.core、.zinitcall.sys.service、.zinitcall.sys.feature、.zinitcall.run、.zinitcall.app.service、.zinitcall.app.feature、.zinitcall.test和.zinitcall.exit等几种类型的段。

/* zInit code and data - will be freed after init */
 .zInit (.) :
 {
  __zinitcall_bsp_start = .;
  KEEP (*(.zinitcall.bsp0.init))
  KEEP (*(.zinitcall.bsp1.init))
  KEEP (*(.zinitcall.bsp2.init))
  KEEP (*(.zinitcall.bsp3.init))
  KEEP (*(.zinitcall.bsp4.init))
  __zinitcall_bsp_end = .;
  . = ALIGN(4);
  __zinitcall_device_start = .;
  KEEP (*(.zinitcall.device0.init))
  KEEP (*(.zinitcall.device1.init))
  KEEP (*(.zinitcall.device2.init))
  KEEP (*(.zinitcall.device3.init))
  KEEP (*(.zinitcall.device4.init))
  __zinitcall_device_end = .;
  . = ALIGN(4);
  __zinitcall_core_start = .;
  KEEP (*(.zinitcall.core0.init))
  KEEP (*(.zinitcall.core1.init))
  KEEP (*(.zinitcall.core2.init))
  KEEP (*(.zinitcall.core3.init))
  KEEP (*(.zinitcall.core4.init))
  __zinitcall_core_end = .;
  . = ALIGN(4);
  __zinitcall_sys_service_start = .;
  KEEP (*(.zinitcall.sys.service0.init))
  KEEP (*(.zinitcall.sys.service1.init))
  KEEP (*(.zinitcall.sys.service2.init))
  KEEP (*(.zinitcall.sys.service3.init))
  KEEP (*(.zinitcall.sys.service4.init))
  __zinitcall_sys_service_end = .;
  . = ALIGN(4);
  __zinitcall_sys_feature_start = .;
  KEEP (*(.zinitcall.sys.feature0.init))
  KEEP (*(.zinitcall.sys.feature1.init))
  KEEP (*(.zinitcall.sys.feature2.init))
  KEEP (*(.zinitcall.sys.feature3.init))
  KEEP (*(.zinitcall.sys.feature4.init))
  __zinitcall_sys_feature_end = .;
  . = ALIGN(4);
  __zinitcall_run_start = .;
  KEEP (*(.zinitcall.run0.init))
  KEEP (*(.zinitcall.run1.init))
  KEEP (*(.zinitcall.run2.init))
  KEEP (*(.zinitcall.run3.init))
  KEEP (*(.zinitcall.run4.init))
  __zinitcall_run_end = .;
  . = ALIGN(4);
  __zinitcall_app_service_start = .;
  KEEP (*(.zinitcall.app.service0.init))
  KEEP (*(.zinitcall.app.service1.init))
  KEEP (*(.zinitcall.app.service2.init))
  KEEP (*(.zinitcall.app.service3.init))
  KEEP (*(.zinitcall.app.service4.init))
  __zinitcall_app_service_end = .;
  . = ALIGN(4);
  __zinitcall_app_feature_start = .;
  KEEP (*(.zinitcall.app.feature0.init))
  KEEP (*(.zinitcall.app.feature1.init))
  KEEP (*(.zinitcall.app.feature2.init))
  KEEP (*(.zinitcall.app.feature3.init))
         KEEP (*(.zinitcall.app.feature4.init))
  __zinitcall_app_feature_end = .;
  . = ALIGN(4);
  __zinitcall_test_start = .;
  KEEP (*(.zinitcall.test0.init))
  KEEP (*(.zinitcall.test1.init))
  KEEP (*(.zinitcall.test2.init))
  KEEP (*(.zinitcall.test3.init))
  KEEP (*(.zinitcall.test4.init))
  __zinitcall_test_end = .;
  . = ALIGN(4);
  __zinitcall_exit_start = .;
  KEEP (*(.zinitcall.exit0.init))
  KEEP (*(.zinitcall.exit1.init))
  KEEP (*(.zinitcall.exit2.init))
  KEEP (*(.zinitcall.exit3.init))
  KEEP (*(.zinitcall.exit4.init))
  __zinitcall_exit_end = .;
  . = ALIGN(4);
 } > FLASH

1.4 配置编译时链接bootstrap库

另外,bootstrap_lite部件会编译//base/startup/bootstrap_lite/services/source/bootstrap_service.c,该文件中,通过SYS_SERVICE_INIT将Init函数符号指定到__zinitcall_sys_service_start和__zinitcall_sys_service_end段中,由于没有显式调用Init函数,所以需要将它强制链接到最终的镜像。可以参考device\board\goodix\gr5515_sk\liteos_m\config.gni文件中的链接选项。恒玄的开发板适配时,是配置到vendor\bestechnic\display_demo\config.json文件中的自己定义的配置项force_link_libs里,该配置项在device\soc\bestechnic\bes2600\BUILD.gn中被解析、链接。

board_ld_flags = [
....
  "-lbootstrap",

1.5 调用OHOS_SystemInit接口

函数void OHOS_SystemInit(void)定义在文件base\startup\bootstrap_lite\services\source\system_init.c中,在移植适配时,需要调用该接口。可以参考文件device\soc\bestechnic\bes2600\liteos_m\sdk\bsp\rtos\liteos\liteos_m\board.c中的使用示例。

int main(void);
extern void OHOS_SystemInit(void);
    ......
        OHOS_SystemInit();
    ......
    while (1) {
        osDelay(1000);
        TRACE(0, "main idle");
    }
}

2、bootstrap_lite服务启动引导部件实现原理之system_init

bootstrap_lite服务启动部件实现了服务的自动初始化,即服务的初始化函数无需显式调用,它是使用宏定义的方式申明,在系统启动时自动被执行。实现原理是将服务启动的函数通过宏申明之后,放在预定义好的zInit代码段中,系统启动的时候调用OHOS_SystemInit接口,遍历该代码段并调用其中的函数。因此在适配移植时,需要在device/soc/下面具体的芯片的链接脚本中添加zInit段,并且在main函数里调用OHOS_SystemInit接口。

2.1 bootstrap_lite服务启动引导部件的初始化宏

bootstrap_lite服务启动引导部件的初始化宏定义在文件utils\native\lite\include\ohos_init.h,片段如下。初始化函数宏SYS_SERVICE_INIT(func)用于标识核心系统服务的初始化入口,该宏识别的函数在启动过程中核心系统服务优先级2阶段被调用;初始化宏SYS_SERVICE_INIT_PRI(func, priority)可以指定优先级数值,优先级的取值范围为[0,5),调用顺序为0, 1, 2, 3, 4。

  /**
  * @brief Identifies the entry for initializing and starting a core system service by the
  * priority 2.
  *
  * This macro is used to identify the entry called at the priority 2 in the core system
  * service phase of the startup process. \n
  *
  * @param func Indicates the entry function for initializing and starting a core system service.
  * The type is void (*)(void).
  */
  #define SYS_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, sys_service, "sys.service")
  /**
  * @brief Identifies the entry for initializing and starting a core system service by the
  * specified priority.
  *
  * This macro is used to identify the entry called at the specified priority in the core system
  * service phase of the startup process. \n
  *
  * @param func Indicates the entry function for initializing and starting a core system service.
  * The type is void (*)(void).
  * @param priority Indicates the calling priority when starting the core system service in the
  * startup phase. The value range is [0,5), and the calling sequence is 0, 1, 2, 3, and 4.
  */
  #define SYS_SERVICE_INIT_PRI(func, priority) LAYER_INITCALL(func, sys_service, "sys.service", priority)

更多的初始化宏见下文的列表,处理这些初始化宏,还有可以指定优先级的版本XXX_PRI。

初始化宏名称描述
CORE_INIT(func)标识核心阶段的初始化启动入口;Samgr初始化启动时调用该宏。
SYS_SERVICE_INIT(func)标识核心系统服务的初始化启动入口;启动过程中的核心系统服务阶段调用该宏标识的函数。
SYS_FEATURE_INIT(func)标识核心系统功能的初始化启动入口;启动过程中的核心系统功能阶段调用该宏标识的函数。
SYS_RUN(func)标识系统启动阶段的初始化启动入口;启动过程中的系统启动能阶段调用该宏标识的函数。
SYSEX_SERVICE_INIT(func)标识系统服务的初始化启动入口;启动过程中的系统服务阶段调用该宏标识的函数。
SYSEX_FEATURE_INIT(func)标识系统功能的初始化启动入口;启动过程中的系统功能阶段调用该宏标识的函数。
APP_SERVICE_INIT(func)标识应用层服务的初始化启动入口;启动过程中的应用层服务阶段调用该宏标识的函数。
APP_FEATURE_INIT(func)标识应用层功能的初始化启动入口;启动过程中的应用层功能阶段调用该宏标识的函数。

2.2 LAYER_INITCALL宏定义

从上文已知,bootstrap_lite服务启动引导部件的初始化宏会调用LAYER_INITCALL_DEF和LAYER_INITCALL宏。这些宏的定义在文件utils\native\lite\include\ohos_init.h,代码片段如下。⑴处声明函数类型,无参无返回值。⑵处处理定义分层初始化共享库宏LAYER_INIT_SHARED_LIB的情况,如果没有定义该宏,则执行⑹。在文件foundation/distributedschedule/samgr_lite/samgr/BUILD.gn中定义了该宏,在移植适配芯片开发板时,没有定义这个宏。⑶处定义5个分层初始化级别,⑷处定义7个构建值(constructor value,简称CTOR Value)。⑸处是宏LAYER_INITCALL的定义,该宏需要4个参数,分别是初始化服务或功能函数func;layer是分层名称,支持的取值为device、core、sys_service、sys_feature、app_service、app_feature和run,拼装为CTOR_VALUE_XXX;clayer参数在定义宏LAYER_INIT_SHARED_LIB时未使用;priority是优先级参数。attribute((constructor))表示这段代码将在main函数前调用。当传入参数为(myFunc, sys_feature, “sys.feature”, 2)时,函数宏替换为:static __attribute__((constructor(130 + 2))) void BOOT_sys_featurer2myFunc {myFunc();}。等于定义个一个新的启动引导函数BOOT_sys_featurer2myFunc()

当没有定义LAYER_INIT_SHARED_LIB宏时,执行⑹,当传入参数为(myFunc, sys_feature, “sys.feature”, 2)时,函数宏替换为:static const InitCall __attribute__((used)) __zinitcall_sys_feature_myFunc __attribute__((section(".zinitcall.sys.feature2.init"))) = myFunc,除了__attribute__部分,等于声明一个函数类型InitCall的变量__zinitcall_sys_feature_myFunc。该函数变量放入section段".zinitcall.sys.feature2.init"内,所以移植适配时,需要在芯片开发板的链接脚本里添加zInit代码段。

⑴  typedef void (*InitCall)(void);

    #define USED_ATTR __attribute__((used))

⑵  #ifdef LAYER_INIT_SHARED_LIB
⑶  #define LAYER_INIT_LEVEL_0 0
    #define LAYER_INIT_LEVEL_1 1
    #define LAYER_INIT_LEVEL_2 2
    #define LAYER_INIT_LEVEL_3 3
    #define LAYER_INIT_LEVEL_4 4
⑷  #define CTOR_VALUE_device 100
    #define CTOR_VALUE_core 110
    #define CTOR_VALUE_sys_service 120
    #define CTOR_VALUE_sys_feature 130
    #define CTOR_VALUE_app_service 140
    #define CTOR_VALUE_app_feature 150
    #define CTOR_VALUE_run  700
⑸  #define LAYER_INITCALL(func, layer, clayer, priority)                                     \
        static __attribute__((constructor(CTOR_VALUE_##layer + LAYER_INIT_LEVEL_##priority))) \
            void BOOT_##layer##priority##func() {func();}
    #else
⑹  #define LAYER_INITCALL(func, layer, clayer, priority)            \
        static const InitCall USED_ATTR __zinitcall_##layer##_##func \
            __attribute__((section(".zinitcall." clayer #priority ".init"))) = func
    #endif
    // Default priority is 2, priority range is [0, 4]
    #define LAYER_INITCALL_DEF(func, layer, clayer) \
        LAYER_INITCALL(func, layer, clayer, 2)

如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:https://qr21.cn/FV7h05

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1834794.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

硬件电路基础【5.二极管】

二极管 前言一、基本原理1.1 介绍1.2 结构组成1.3 符号1.4 正负极判断 二、特性参数开关电路注意的参数极限特性电气特性特性曲线 三、应用场景稳压二极管原理故障特点连接方式参数最大额定参数电气特性特性曲线 应用典型的串联型稳压电路过压保护稳压二极管的应用与选择 肖特基…

MySql进阶:深入理解MySQL语句执行逻辑

深入理解MySQL语句执行逻辑 一、前言 本文源自微博客(www.microblog.store),且以获得授权 一直是想知道一条SQL语句是怎么被执行的&#xff0c;它执行的顺序是怎样的&#xff0c;然后查看总结各方资料&#xff0c;就有了下面这一篇博文了。   本文将从MySQL总体架构—&…

电商淘宝京东,精准关键词搜索API接口

当使用电商淘宝京东的精准关键词搜索API接口时&#xff0c;以下是清晰的步骤指南&#xff1a; 一、注册与申请API密钥 注册账号&#xff1a;在淘宝开放平台或京东开放平台注册成为开发者&#xff0c;并创建账号。 创建应用&#xff1a;登录后&#xff0c;在开放平台创建一个应…

运维技术栈总结

文章目录 Linux CommandBasecd/lschmod/chown/chgrpvi/vimscptarsudf Installrpmyumdeb/apt Filtertailgrepawkfindnetstatechotelnetwhereistouch/mkdirgzip/rar/tar Statistics Linux MonitorCPUtophtopsar Memoryfreevmstat I/Oiostatpidstatiotop Networknetstatiftoptcpdu…

网络编程3----TCP简单客户端服务器的实现

服务器的代码&#xff1a; package network;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.security.spec.RSAOtherPrimeInfo; im…

前端可观测性系统建设

一. 背景 随着前端业务的日趋庞大&#xff0c;及时发现和解决业务中的问题、优化用户体验、实时监控业务健康度变得愈发重要。在业务层面&#xff0c;我们希望能够监控每次发布版本后&#xff0c;核心功能是否有显著提升或至少没有负面影响&#xff0c;核心接口是否正常运作&a…

禁用/屏蔽 Chrome 默认快捷键

Chrome 有一些内置的快捷键&#xff0c;但是它并没有像其他软件一样提供管理快捷键的界面。在某些时候&#xff0c;当我们因为个人需求希望禁用 Chrome 某些快捷键时&#xff0c;又无从下手。 好在有开发者开发了 Chrome 插件&#xff0c;可以禁用 Chrome 快捷键的插件&#x…

振动分析-4-振动传感器的安装部署

参考(电机、减速机、风机)振动传感器部署指南 设备状态监测的测点通常选在设备轴承或靠近轴承的位置&#xff0c;通过在轴向、垂直方向、水平方向部署振动传感器来实现设备振动信号的采集。但在实际工作中&#xff0c;考虑安装空间和硬件成本&#xff0c;部署过程通常被两个问题…

使用Apache Flink实现实时数据同步与清洗:MySQL和Oracle到目标MySQL的ETL流程

使用Apache Flink实现实时数据同步与清洗&#xff1a;MySQL和Oracle到目标MySQL的ETL流程 实现数据同步的ETL&#xff08;抽取、转换、加载&#xff09;过程通常涉及从源系统&#xff08;如数据库、消息队列或文件&#xff09;中抽取数据&#xff0c;进行必要的转换&#xff0c…

基于VTK9.3.0+Visual Studio2017 c++实现DICOM影像MPR多平面重建

开源库&#xff1a;VTK9.3.0 开发工具&#xff1a;Visual Studio2017 开发语言&#xff1a;C 实现过程&#xff1a; void initImageActor(double* Matrix, double* center, vtkSmartPointer<vtkImageCast> pImageCast,vtkSmartPointer<vtkImageReslice> imageRe…

Python 库PySpark,一个超级强大的数据处理引擎

目录 01初识 PySpark 为什么选择 PySpark? 安装 PySpark 配置 PySpark 02基本操作 创建 RDD 基本 RDD 操作 03DataFrame 和 Spark SQL 创建 DataFrame 基本 DataFrame 操作 使用 Spark SQL 04机器学习与流处理 …

MacOS - 3 招快速去除桌面上的图标文件

在平时用 Mac 电脑的时候&#xff0c;会产生许多我们不用的或废弃的图标、文件&#xff0c;在 Mac 桌面上显得很乱&#xff0c;不仅影响美观也直接影响了我们工作的心情。下面我们分享 3 招快速去除桌面上的图标或文件的方法&#xff0c;有需要的朋友可以试一试。 1. 右键删除&…

QPushButton、QCheckBox、QRadioPutton、QLineEdit用法

实现LineEdit 文本的 居左、居中、居右设置 实现LineEdit 文本的粗体、斜体、下划线设置 实现LineEdit 控件的 ReadOnly、Enable、ClearButtonEnable的设置 创建资源文件&#xff0c;引入button需要的icon 总体布局 窗体使用垂直布局&#xff0c;每个组合控件内部是水平布局 2个…

游泳耳机品牌排行榜,10大实力超群的游泳耳机分享!

在当今快节奏的生活中&#xff0c;运动已成为许多人不可或缺的一部分&#xff0c;不仅为了健康&#xff0c;也是释放压力、提升生活品质的有效方式。而随着科技与健身的深度融合&#xff0c;智能穿戴设备尤其是专为运动设计的耳机&#xff0c;正逐渐成为运动爱好者的新宠。对于…

nodejs爬虫小红书评论区

发现好像还是爬虫的知识热度比较高&#xff0c;最近一直在加强JS这块。这两天脚本模拟爬BOSS的时候也想着怎么用nodejs&#xff0c;昨天都没更新文章&#xff0c;Q-Q&#xff0c;因为一直failed没啥成果。 使用模块 这边可以看到使用的模块其实也挺多&#xff0c;但主要还是ht…

vue大作业-实现学校官网

vue大作业-实现学校官网 基于vue2实现的学校官网 项目展示 学校官网介绍 欢迎访问我们学校的官方网站&#xff0c;这里为您提供了全面的信息和资源&#xff0c;帮助您更好地了解我们的教育理念、教学资源和学术活动。 首页 首页是您了解我们学校的起点。这里展示了学校的最…

单元测试的思考与实践

1. 什么是单元测试 通常来说单元测试&#xff0c;是一种自动化测试&#xff0c;同时包含一下特性&#xff1a; 验证很小的一段代码&#xff08;业务意义 或者 代码逻辑 上不可再分割的单元&#xff09;&#xff0c;能够更准确的定位到问题代码的位置 能够快速运行&#xff08;…

初始化一个Android项目时,Android Studio会自动生成一些文件和目录结构,以帮助你快速上手开发

当你初始化一个Android项目时&#xff0c;Android Studio会自动生成一些文件和目录结构&#xff0c;以帮助你快速上手开发。这些文件和目录各自有其特定的功能和用途。下面我为你解释一下这些自动生成的内容&#xff1a; 1. app 目录 这是你的应用模块的根目录&#xff0c;包…

C++之模板(三)

1、缺省模板参数 可以将数据结构类型传递进来&#xff0c;比如vectop<T>&#xff08;如果没传就是默认&#xff09; 把vector当作类型参数来传递&#xff0c;从而使用它的接口然后适配出新的接口。实际上这个Stack称为适配器。有时候可能需要vector&#xff0c;但是又需…

深入解析知识付费平台的核心功能模块:满足个性化学习需求的数字化教育新星

在数字化学习的大潮中&#xff0c;知识付费平台已成为教育行业的一颗新星&#xff0c;它以满足用户需求为核心&#xff0c;提供便捷高效的学习渠道。该平台汇聚了各类专业知识&#xff0c;覆盖职业技能、生活兴趣和人文社科等多个领域&#xff0c;满足不同用户的学习需求。同时…