场景
关于驱动的开发我们主要目的是实现驱动代码的编写,但是驱动开发过程中需要服务管理、消息机制管理,才能使驱动在代码编译过程中进行加载。以下开发步骤中介绍了驱动开发、驱动消息机制管理开发、驱动服务管理开发的步骤。
驱动开发实例
基于HDF框架的驱动开发主要分为三个部分:驱动实现、驱动编译脚本编写和驱动配置。详细开发流程如下所示:
驱动实现
驱动实现包含驱动业务代码实现和驱动入口注册,具体写法如下:
-
驱动业务代码
#include "hdf_device_desc.h" // HDF框架对驱动开发相关能力接口的头文件 #include "hdf_log.h" // HDF框架提供的日志接口头文件#define HDF_LOG_TAG sample_driver // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签。// 将驱动对外提供的服务能力接口绑定到HDF框架。 int32_t HdfSampleDriverBind(struct HdfDeviceObject *deviceObject) {HDF_LOGD("Sample driver bind success");return HDF_SUCCESS; }// 驱动自身业务初始化的接口 int32_t HdfSampleDriverInit(struct HdfDeviceObject *deviceObject) {HDF_LOGD("Sample driver Init success");return HDF_SUCCESS; }// 驱动资源释放的接口 void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject) {HDF_LOGD("Sample driver release success");return; }
-
驱动入口注册到HDF框架
// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量。 struct HdfDriverEntry g_sampleDriverEntry = {.moduleVersion = 1,.moduleName = "sample_driver",.Bind = HdfSampleDriverBind,.Init = HdfSampleDriverInit,.Release = HdfSampleDriverRelease, };
点击领取→纯血鸿蒙Next全套最新学习资料(安全链接,放心点击)希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源~
// 调用HDF_INIT将驱动入口注册到HDF框架中。在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动;当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 HDF_INIT(g_sampleDriverEntry);
驱动编译脚本编写
-
LiteOS
涉及Makefile和BUILD.gn修改:
-
Makefile部分:
驱动代码的编译必须要使用HDF框架提供的Makefile模板进行编译。
include $(LITEOSTOPDIR)/../../drivers/hdf_core/adapter/khdf/liteos/lite.mk # 【必需】导入hdf预定义内容 MODULE_NAME := #生成的结果文件 LOCAL_INCLUDE := #本驱动的头文件目录 LOCAL_SRCS := #本驱动的源代码文件 LOCAL_CFLAGS := #自定义的编译选项 include $(HDF_DRIVER) #导入Makefile模板完成编译
编译结果文件链接到内核镜像,添加到drivers/hdf_core/adapter/khdf/liteos目录下的hdf_lite.mk里面,示例如下:
LITEOS_BASELIB += -lxxx #链接生成的静态库 LIB_SUBDIRS += #驱动代码Makefile的目录
-
BUILD.gn部分:
添加模块BUILD.gn,可参考如下示例:
import("//build/lite/config/component/lite_component.gni") import("//drivers/hdf_core/adapter/khdf/liteos/hdf.gni") module_switch = defined(LOSCFG_DRIVERS_HDF_xxx) module_name = "xxx" hdf_driver(module_name) {sources = ["xxx/xxx/xxx.c", #模块要编译的源码文件]public_configs = [ ":public" ] #使用依赖的头文件配置 } config("public") { #定义依赖的头文件配置include_dirs = ["xxx/xxx/xxx", #依赖的头文件目录] }
把新增模块的BUILD.gn所在的目录添加到**/drivers/hdf_core/adapter/khdf/liteos/BUILD.gn**里面:
group("liteos") {public_deps = [ ":$module_name" ]deps = ["xxx/xxx",#新增模块BUILD.gn所在的目录,/drivers/hdf_core/adapter/khdf/liteos] }
-
-
Linux
如果需要定义模块控制宏,需要在模块目录xxx里面添加Kconfig文件,并把Kconfig文件路径添加到drivers/hdf_core/adapter/khdf/linux/Kconfig里面:
source "drivers/hdf/khdf/xxx/Kconfig" #目录为hdf模块软链接到kernel里面的目录
添加模块目录到drivers/hdf_core/adapter/khdf/linux/Makefile:
obj-$(CONFIG_DRIVERS_HDF) += xxx/
在模块目录xxx里面添加Makefile文件,在Makefile文件里面添加模块代码编译规则:
obj-y += xxx.o
驱动配置
HDF使用HCS作为配置描述源码
驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息,具体写法如下:
-
驱动设备描述(必选)
HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述,因此基于HDF框架开发的驱动必须要在HDF框架定义的device_info.hcs配置文件中添加对应的设备描述。驱动的设备描述填写如下所示:
root {device_info {match_attr = "hdf_manager";template host { // host模板,继承该模板的节点(如下sample_host)如果使用模板中的默认值,则节点字段可以缺省。hostName = "";priority = 100;uid = ""; // 用户态进程uid,缺省为空,会被配置为hostName的定义值,即普通用户。gid = ""; // 用户态进程gid,缺省为空,会被配置为hostName的定义值,即普通用户组。caps = [""]; // 用户态进程Linux capabilities配置,缺省为空,需要业务模块按照业务需要进行配置。template device {template deviceNode {policy = 0;priority = 100;preload = 0;permission = 0664;moduleName = "";serviceName = "";deviceMatchAttr = "";}}}sample_host :: host{hostName = "host0"; // host名称,host节点是用来存放某一类驱动的容器。priority = 100; // host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序。caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; // 用户态进程Linux capabilities配置。device_sample :: device { // sample设备节点device0 :: deviceNode { // sample驱动的DeviceNode节点policy = 1; // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍。priority = 100; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序。preload = 0; // 驱动按需加载字段。permission = 0664; // 驱动创建设备节点权限moduleName = "sample_driver"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。serviceName = "sample_service"; // 驱动对外发布服务的名称,必须唯一。deviceMatchAttr = "sample_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等。}}}} }
说明:
- uid、gid、caps等配置项是用户态驱动的启动配置,内核态不用配置。
- 根据进程权限最小化设计原则,业务模块uid、gid不用配置,如上面的sample_host,使用普通用户权限,即uid和gid被定义为hostName的定义值。
- 如果普通用户权限不能满足业务要求,需要把uid、gid定义为system或者root权限时,请找安全专家进行评审。
- 进程的uid在文件base/startup/init/services/etc/passwd中配置,进程的gid在文件base/startup/init/services/etc/group中配置
- caps值:格式为caps = ["xxx"],如果要配置CAP_DAC_OVERRIDE,此处需要填写caps = ["DAC_OVERRIDE"],不能填写为caps = ["CAP_DAC_OVERRIDE"]。
- preload:驱动按需加载字段。
-
驱动私有配置信息(可选)
如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息。HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject中的property里面,通过Bind和Init(参考驱动实现驱动实现驱动实现)传递给驱动。驱动的配置信息示例如下:
root {SampleDriverConfig {sample_version = 1;sample_bus = "I2C_0";match_attr = "sample_config"; // 该字段的值必须和device_info.hcs中的deviceMatchAttr值一致} }
配置信息定义之后,需要将该配置文件添加到板级配置入口文件hdf.hcs,示例如下:
#include "device_info/device_info.hcs" #include "sample/sample_config.hcs"
驱动消息机制管理开发
-
将驱动配置信息中服务策略policy字段设置为2(SERVICE_POLICY_CAPACITY,policy定义policy定义policy定义)。
device_sample :: Device {policy = 2;... }
-
配置驱动信息中的服务设备节点权限(permission字段)是框架给驱动创建设备节点的权限,默认是0666,驱动开发者根据驱动的实际使用场景配置驱动设备节点的权限。
-
在服务实现过程中,实现服务基类成员IDeviceIoService中的Dispatch方法。
// Dispatch是用来处理用户态发下来的消息 int32_t SampleDriverDispatch(struct HdfDeviceIoClient *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) {HDF_LOGI("sample driver lite A dispatch");return HDF_SUCCESS; } int32_t SampleDriverBind(struct HdfDeviceObject *device) {HDF_LOGI("test for lite os sample driver A Open!");if (device == NULL) {HDF_LOGE("test for lite os sample driver A Open failed!");return HDF_FAILURE;}static struct ISampleDriverService sampleDriverA = {.ioService.Dispatch = SampleDriverDispatch,.ServiceA = SampleDriverServiceA,.ServiceB = SampleDriverServiceB,};device->service = (struct IDeviceIoService *)(&sampleDriverA);return HDF_SUCCESS; }
-
驱动定义消息处理函数中的cmd类型。
#define SAMPLE_WRITE_READ 1 // 读写操作码1
-
用户态获取服务接口并发送消息到驱动。
int SendMsg(const char *testMsg) {if (testMsg == NULL) {HDF_LOGE("test msg is null");return HDF_FAILURE;}struct HdfIoService *serv = HdfIoServiceBind("sample_driver");if (serv == NULL) {HDF_LOGE("fail to get service");return HDF_FAILURE;}struct HdfSBuf *data = HdfSbufObtainDefaultSize();if (data == NULL) {HDF_LOGE("fail to obtain sbuf data");return HDF_FAILURE;}struct HdfSBuf *reply = HdfSbufObtainDefaultSize();if (reply == NULL) {HDF_LOGE("fail to obtain sbuf reply");ret = HDF_DEV_ERR_NO_MEMORY;goto out;}if (!HdfSbufWriteString(data, testMsg)) {HDF_LOGE("fail to write sbuf");ret = HDF_FAILURE;goto out;}int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);if (ret != HDF_SUCCESS) {HDF_LOGE("fail to send service call");goto out;} out:HdfSbufRecycle(data);HdfSbbufRecycle(reply);HdfIoServiceRecycle(serv);return ret; }
-
用户态接收该驱动上报的消息。
-
用户态编写驱动上报消息的处理函数。
static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data) {OsalTimespec time;OsalGetTime(&time);HDF_LOGI("%{public}s received event at %{public}llu.%{public}llu", (char *)priv, time.sec, time.usec);const char *string = HdfSbufReadString(data);if (string == NULL) {HDF_LOGE("fail to read string in event data");return HDF_FAILURE;}HDF_LOGI("%{public}s: dev event received: %{public}d %{public}s", (char *)priv, id, string);return HDF_SUCCESS; }
-
用户态注册接收驱动上报消息的操作方法。
int RegisterListen() {struct HdfIoService *serv = HdfIoServiceBind("sample_driver");if (serv == NULL) {HDF_LOGE("fail to get service");return HDF_FAILURE;}static struct HdfDevEventlistener listener = {.callBack = OnDevEventReceived,.priv ="Service0"};if (HdfDeviceRegisterEventListener(serv, &listener) != 0) {HDF_LOGE("fail to register event listener");return HDF_FAILURE;}......HdfDeviceUnregisterEventListener(serv, &listener);HdfIoServiceRecycle(serv);return HDF_SUCCESS; }
-
驱动上报事件。
int32_t SampleDriverDispatch(HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) {... // process api call herereturn HdfDeviceSendEvent(client->device, cmdCode, data); }
-
驱动服务管理开发
驱动服务管理的开发包括驱动服务的编写、绑定、获取或者订阅,详细步骤如下。
驱动服务编写
// 驱动服务结构的定义
struct ISampleDriverService {struct IDeviceIoService ioService; // 服务结构的首个成员必须是IDeviceIoService类型的成员。int32_t (*ServiceA)(void); // 驱动的第一个服务接口。int32_t (*ServiceB)(uint32_t inputCode); // 驱动的第二个服务接口,有多个可以依次往下累加。
};// 驱动服务接口的实现
int32_t SampleDriverServiceA(void)
{// 驱动开发者实现业务逻辑return HDF_SUCCESS;
}int32_t SampleDriverServiceB(uint32_t inputCode)
{// 驱动开发者实现业务逻辑return HDF_SUCCESS;
}
驱动服务绑定
开发者实现HdfDriverEntry中的Bind指针函数,如下的SampleDriverBind,把驱动服务绑定到HDF框架中。
int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{// deviceObject为HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。if (deviceObject == NULL) {HDF_LOGE("Sample device object is null!");return HDF_FAILURE;}static struct ISampleDriverService sampleDriverA = {.ServiceA = SampleDriverServiceA,.ServiceB = SampleDriverServiceB,};deviceObject->service = &sampleDriverA.ioService;return HDF_SUCCESS;
}
驱动服务获取
应用程序开发者获取驱动服务有两种方式:通过HDF接口直接获取和通过HDF提供的订阅机制获取。
通过HDF接口直接获取
当驱动服务获取者明确驱动已经加载完成时,获取该驱动的服务可以通过HDF框架提供的能力接口直接获取,如下所示:
const struct ISampleDriverService *sampleService =(const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");
if (sampleService == NULL) {return HDF_FAILURE;
}
sampleService->ServiceA();
sampleService->ServiceB(5);
通过HDF提供的订阅机制获取
当内核态驱动服务获取者对驱动(同一个host)加载的时机不感知时,可以通过HDF框架提供的订阅机制来订阅该驱动服务。当该驱动加载完成时,HDF框架会将被订阅的驱动服务发布给订阅者(驱动服务获取者),实现方式如下所示:
// 订阅回调函数的编写,当被订阅的驱动加载完成后,HDF框架会将被订阅驱动的服务发布给订阅者,通过这个回调函数给订阅者使用。
// object为订阅者的私有数据,service为被订阅的服务对象。
int32_t TestDriverSubCallBack(struct HdfDeviceObject *deviceObject, const struct HdfObject *service)
{const struct ISampleDriverService *sampleService =(const struct ISampleDriverService *)service;if (sampleService == NULL) {return HDF_FAILURE;}sampleService->ServiceA();sampleService->ServiceB(5);
}
// 订阅过程的实现
int32_t TestDriverInit(struct HdfDeviceObject *deviceObject)
{if (deviceObject == NULL) {HDF_LOGE("Test driver init failed, deviceObject is null!");return HDF_FAILURE;}struct SubscriberCallback callBack;callBack.deviceObject = deviceObject;callBack.OnServiceConnected = TestDriverSubCallBack;int32_t ret = HdfDeviceSubscribeService(deviceObject, "sample_driver", callBack);if (ret != HDF_SUCCESS) {HDF_LOGE("Test driver subscribe sample driver failed!");}return ret;
}
HDF开发实例
下面基于HDF框架,提供一个完整的样例,包含配置文件的添加,驱动代码的实现以及用户态程序和驱动交互的流程。
添加配置
在HDF框架的配置文件(例如vendor/hisilicon/xxx/hdf_config/device_info)中添加该驱动的配置信息,如下所示:
root {device_info {match_attr = "hdf_manager";template host {hostName = "";priority = 100;template device {template deviceNode {policy = 0;priority = 100;preload = 0;permission = 0664;moduleName = "";serviceName = "";deviceMatchAttr = "";}}}sample_host :: host {hostName = "sample_host";sample_device :: device {device0 :: deviceNode {policy = 2;priority = 100;preload = 1;permission = 0664;moduleName = "sample_driver";serviceName = "sample_service";}}}}
}
编写驱动代码
基于HDF框架编写的sample驱动代码如下:
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "hdf_log.h"
#include "hdf_base.h"
#include "hdf_device_desc.h"#define HDF_LOG_TAG sample_driver#define SAMPLE_WRITE_READ 123static int32_t HdfSampleDriverDispatch(struct HdfDeviceIoClient *client, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
{HDF_LOGI("%{public}s: received cmd %{public}d", __func__, id);if (id == SAMPLE_WRITE_READ) {const char *readData = HdfSbufReadString(data);if (readData != NULL) {HDF_LOGE("%{public}s: read data is: %{public}s", __func__, readData);}if (!HdfSbufWriteInt32(reply, INT32_MAX)) {HDF_LOGE("%{public}s: reply int32 fail", __func__);}return HdfDeviceSendEvent(client->device, id, data);}return HDF_FAILURE;
}static void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{// release resources herereturn;
}static int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{if (deviceObject == NULL) {return HDF_FAILURE;}static struct IDeviceIoService testService = {.Dispatch = HdfSampleDriverDispatch,};deviceObject->service = &testService;return HDF_SUCCESS;
}static int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{if (deviceObject == NULL) {HDF_LOGE("%{public}s::ptr is null!", __func__);return HDF_FAILURE;}HDF_LOGI("Sample driver Init success");return HDF_SUCCESS;
}static struct HdfDriverEntry g_sampleDriverEntry = {.moduleVersion = 1,.moduleName = "sample_driver",.Bind = HdfSampleDriverBind,.Init = HdfSampleDriverInit,.Release = HdfSampleDriverRelease,
};HDF_INIT(g_sampleDriverEntry);
编写用户程序和驱动交互代码
基于HDF框架编写的用户态程序和驱动交互的代码如下(代码可以放在目录drivers/hdf_core/adapter/uhdf下面编译,BUILD.gn可以参考drivers/hdf_core/framework/sample/platform/uart/dev/BUILD.gn):
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "hdf_log.h"
#include "hdf_sbuf.h"
#include "hdf_io_service_if.h"#define HDF_LOG_TAG sample_test
#define SAMPLE_SERVICE_NAME "sample_service"#define SAMPLE_WRITE_READ 123int g_replyFlag = 0;static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
{const char *string = HdfSbufReadString(data);if (string == NULL) {HDF_LOGE("fail to read string in event data");g_replyFlag = 1;return HDF_FAILURE;}HDF_LOGI("%{public}s: dev event received: %{public}u %{public}s", (char *)priv, id, string);g_replyFlag = 1;return HDF_SUCCESS;
}static int SendEvent(struct HdfIoService *serv, char *eventData)
{int ret = 0;struct HdfSBuf *data = HdfSbufObtainDefaultSize();if (data == NULL) {HDF_LOGE("fail to obtain sbuf data");return 1;}struct HdfSBuf *reply = HdfSbufObtainDefaultSize();if (reply == NULL) {HDF_LOGE("fail to obtain sbuf reply");ret = HDF_DEV_ERR_NO_MEMORY;goto out;}if (!HdfSbufWriteString(data, eventData)) {HDF_LOGE("fail to write sbuf");ret = HDF_FAILURE;goto out;}ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);if (ret != HDF_SUCCESS) {HDF_LOGE("fail to send service call");goto out;}int replyData = 0;if (!HdfSbufReadInt32(reply, &replyData)) {HDF_LOGE("fail to get service call reply");ret = HDF_ERR_INVALID_OBJECT;goto out;}HDF_LOGI("Get reply is: %{public}d", replyData);
out:HdfSbufRecycle(data);HdfSbufRecycle(reply);return ret;
}int main()
{char *sendData = "default event info";struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME);if (serv == NULL) {HDF_LOGE("fail to get service %s", SAMPLE_SERVICE_NAME);return HDF_FAILURE;}static struct HdfDevEventlistener listener = {.callBack = OnDevEventReceived,.priv ="Service0"};if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) {HDF_LOGE("fail to register event listener");return HDF_FAILURE;}if (SendEvent(serv, sendData)) {HDF_LOGE("fail to send event");return HDF_FAILURE;}while (g_replyFlag == 0) {sleep(1);}if (HdfDeviceUnregisterEventListener(serv, &listener)) {HDF_LOGE("fail to unregister listener");return HDF_FAILURE;}HdfIoServiceRecycle(serv);return HDF_SUCCESS;
}
说明: 用户态应用程序使用了HDF框架中的消息发送接口,因此在编译用户态程序的过程中需要依赖HDF框架对外提供的hdf_core和osal的动态库,在gn编译文件中添加如下依赖项:
deps = [
"//drivers/hdf_core/adapter/uhdf/manager:hdf_core",
"//drivers/hdf_core/adapter/uhdf/posix:hdf_posix_osal",
]
最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?但是又不知道从哪里下手,而且学习时频繁踩坑,最终浪费大量时间。所以本人整理了一些比较合适的鸿蒙(HarmonyOS NEXT)学习路径和一些资料的整理供小伙伴学习
点击领取→纯血鸿蒙Next全套最新学习资料(安全链接,放心点击)
希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
一、鸿蒙(HarmonyOS NEXT)最新学习路线
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)…等技术知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
二、HarmonyOS Next 最新全套视频教程
三、《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》
- Stage模型入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙进阶实战》
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
四、大厂面试必问面试题
五、鸿蒙南向开发技术
六、鸿蒙APP开发必备
七、鸿蒙生态应用开发白皮书V2.0PDF
完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。