Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解 原创 精华

发布于 2021-6-4 09:50
浏览
7收藏

Hi3861的SAMGR--系统服务框架子系统-2

关键结构体分解

liangkz 2021.06.04 v1.5

 

接前文《Hi3861的SAMGR--系统服务框架子系统-1

 

4 结构体的分解

4.1 先上samgr的展开图【附件有原图】

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区4.2  Samgr:SamgrLiteImpl g_samgrImpl 

        typedef struct SamgrLiteImpl SamgrLiteImpl;
        struct SamgrLiteImpl  {
A:          SamgrLite vtbl;       //SAMGR_GetInstance() return this SamgrLite Instance
B:          MutexId mutex;
C:          BootStatus status;    //see BootStatus
D:          Vector services;      //a vector to record all services
E:          TaskPool *sharedPool[MAX_POOL_NUM];
        };

注册第一个服务的时候,首先要通过SAMGR_GetInstance()从全局变量g_samgrImpl获取一个samgr的实例对象,g_samgrImpl已经初始化过了的话,可以直接返回samgr对象的引用,g_samgrImpl没有初始化的话,就需要通过Init函数进行初始化相关的配置之后,才能返回samgr对象的引用。

以后的各种service/feature的操作,基本是都是需要通过g_samgrImpl进行管理的。下面把这个全局变量展开来看一下:

 A:  SamgrLite vtbl; 

vtbl 就是SamgrLite 的实例,SAMGR_GetInstance() 时,返回的就是指向它的指针。SamgrLite结构体(或者直接说类)定义了四组十个函数指针,如下图,同一组用同一个颜色框起来:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

在g_samgrImpl的初始化(init)时与对应的实现函数对应关联起来,之后就可以通过这四组函数来对service/ feature进行注册、注销、获取API等操作了。

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

而具体的service、feature在各自INIT时,会通过g_samgrImpl的samgr实例调用这里的Register接口,向g_samgrImpl注册自己和API,把自己纳入samgr的管理体系中,然后就是samgr为service、feature创建queue/taskpool/task等运行环境了。

 

 B: MutexId mutex;

互斥锁。关键代码段的操作要加锁,保证多线程下对共享数据的操作的完整性。

在g_samgrImpl的初始化(init)时就会通过MUTEX_InitValue() 生成互斥锁,MUTEX_InitValue()声明在thread_adapter.h,它的实现则取决于平台使用的内核,M核平台用cmsis接口来实现,A核/Linux平台,则使用posix接口来实现。

类似的还有内存、队列、线程、timer的相关操作,代码见:

Hi3861/foundation/distributedschedule/samgr_lite/samgr/adapter/

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

理论上,全局对象g_samgrImpl初始化(init)时就会生成一个mutex,且只要g_samgrImpl没有重新初始化,这个mutex就应该是不变的,Hi3861平台上我看到的也确实如此。

但是Hi3516平台上我看到了一个奇怪的地方,见前文《鸿蒙系统框架层的启动细节》的附件log,搜索关键字“SAMGR_GetInstance|StartServiceByName|Invoke: msg”可以看到:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

有若干处mutex是为NULL的,需要重新初始化g_samgrImpl,这样岂不是会把之前注册的service Vector/TaskPool等清除掉吗?先存疑,待进一步仔细阅读Hi3516的代码后再确认。

 

 C: BootStatus status;

系统的启动阶段状态标记。

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

Hi3861平台上电启动到 system_init.c 的

void HOS_SystemInit(void)
{
    ......
    SYS_INIT(service);
    SYS_INIT(feature);
    SAMGR_Bootstrap();
}

这一步时,通过SYS_INIT() 启动的都是用SYS_SERVICE_INIT()和SYS_FEATURE_INIT() 标记的service和feature,而通过SYSEX_SERVICE_INIT/APP_SERVICE_INIT/ SYSEX_FEATURE_INIT/APP_FEATURE_INIT 标记的service和feature,则会在系统启动到 BOOT_APP 这一步时,由Bootstrap service来启动,见 bootstrap_service.c 的MessageHandle()函数内的 INIT_APP_CALL() 的调用:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

系统启动状态标记到 BOOT_DYNAMIC 这一步时,意味着系统所有的应用服务也启动完毕了,进入了一个比较稳定的状态,至于BOOT_DYNAMIC状态还会做其他什么事情,我暂时还没有更多的了解。

为了理解例程代码如何工作,我在这里加了 BOOT_DEBUG和BOOT_DEBUG_WAIT两个状态,用来调用例程里的INIT_TEST_CALL(),跑相关的测试流程,通过相关的log来验证自己的分析,详见附件的log。

 

 D: Vector services;

g_samgrImpl 初始化时:

g_samgrImpl.services = VECTOR_Make((VECTOR_Key)GetServiceName, (VECTOR_Compare)strcmp);

创建了一个Vector:{0, 0, 0, NULL, key, compare},Vector的定义如下:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

其中:

  • max:表示下面的**data指针数组的大小,也就是data[max]所能存储的最大指针数目;
  • top: 当前使用到了的指针数组的最高位置data[top],当top==max,同时free为0时,表示data[max]已经装满了,需要扩容,一次扩容增加4个element位置,变成data[max+4],详情见VECTOR_Add()函数的实现。
  • free:当前0~top之间,释放了的位置的数量。当已注册在册的service unregister时,对应记录这个service的data[i]会被清空,free+1;下次有新的service注册时,会优先使用data[i]来记录新service的Impl对象指针,free-1。详情见VECTOR_Swap()函数的实现
  • **data:指针数组data[max],每一个data[i]记录了一个注册进来的service对应的ServiceImpl 对象的指针,初始化为NULL,通过VECTOR_Add()函数内的操作扩容。
  • key:是samgr_lite.c定义的GetServiceName(ServiceImpl*)函数指针,它可以通过参数来获取对应的service Name。
  • compare:是strcmp函数指针,也就是string标准库提供的字符串比较函数。

 

Samgr通过这个Vector来管理所有注册进来的service(实际上管理的是serviceImpl对象,通过serviceImpl来关联具体的service和service对应的feature)。

每个service可以有0个/1个或多个feature,每个service(serviceImpl对象)也是通过自己的Vector来管理feature。

对Vector的操作,全部定义在:

Hi3861/foundation/distributedschedule/services/samgr_lite/samgr/source/common.c

里面了,需要仔细阅读分析,去理解相关操作。

 

下面是几处要点:

  • VECTOR_Make() 会创建一个{0, 0, 0, NULL, key, compare} Vector,当需要往Vector中添加element时,会在VECTOR_Add()中扩容。
  • VECTOR_Add(Vector *vector, void *element) 的时候,发现Vector的data[max] 空间用尽了,就会重新申请一块增大了4个element的内存空间Newdata[max+4],把旧的data[x]全部拷贝进去(还有4个新的空余的element位置),g_samgrImpl.services.data重新指向Newdata,再把旧的data[x]占用的空间释放掉,这样,新的element (ServiceImpl 对象的指针)就可以记录进来了。
  • VECTOR_Swap()用来删除一个已经记录在案的element(也就是unregister一个service),把它对应的 data[x] 置为NULL,free+1,空出的位置会给未来新注册的service 优先使用。
  • VECTOR_Find()/VECTOR_FindByKey() 可以查找element(serviceImpl),并返回它的index。

特别是VECTOR_FindByKey(Vector *vector, const void *key),第二个参数“void *key”实际上是一个service的名字字符串,如“Broadcast”。

FindByKey会循环获取data[0~top]的ServiceImpl 对象的指针,并将其作为key函数指针(GetServiceName())的参数来获取service的Name,将Name其与上面的第二个参数的service Name,用compare函数指针(strcmp)进行对比,匹配则返回对应的data[i]的 index i。

 

 E: TaskPool *sharedPool[MAX_POOL_NUM];  // MAX_POOL_NUM 是8

Hi3861平台启动到 system_init.c 的最后一步时:

void HOS_SystemInit(void)
{
    ......
    SAMGR_Bootstrap();  
}

调用SAMGR_Bootstrap()去启动已经注册了的service,会为service创建queue和taskPool资源,每个service有一个GetTaskConfig()接口,可以返回这个service运行起来的task配置参数,如 bootstrap service的TaskConfig为:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

请自行查看 TaskConfig的定义。

 

这里需要关注的是 SHARED_TASK 这个标记,在samgr的 AddTaskPool()这一步时:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

bootstrap service的TaskConfig配置会被修改成默认的DEFAULT_TASK_CFG,意味着在bootstrap_service.c 中定义的TaskConfig 参数(stackSize和queueSize)其实并没有起作用,想要修改SHARED_TASK的stackSize和queueSize,要去修改DEFAULT_TASK_CFG的配置。

而上面case SHARED_TASK的操作以及g_samgrImpl.sharedPool[]的存在,可以为若干个共同标记了SHARED_TASK的service共享一个Queue和taskPool资源(这样可以节约好多资源),TaskEntry在Queue中收到msg时,可以通过Exchange 消息里面的Identity字段解析出Sid/Fid/Qid信息,以此来确认到底是哪个service/feature需要处理这个消息。

TaskConfig里的priority字段,则确定了service用的是哪个sharePool[x],优先级越高,x越大。

 

我在代码里全局搜索了一下“SHARED_TASK”关键字,得到如下结果:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

主要是在samgr例程里,不同优先级别的service,共享着不同x的sharedPool[x]资源。

 

不是SHARED_TASK的service则有自己独立的Queue和taskPool,不会直接记录在g_samgrImpl.sharedPool[]里,而是记录在各自service的serviceImpl对象的taskPool* 里。

 

4.3  ServiceImpl 类

    struct ServiceImpl {
A:		Service*      service;
B:		IUnknown* defaultApi;
C:		TaskPool*   taskPool;
D:		Vector    features;
E:		int16      serviceId;
F:		uint8      inited;
G:		Operations ops;
    };

g_samgrImpl 全局变量的services Vector里,只直接记录ServiceImpl对象的指针,并不直接记录和管理service、feature对象本身。service在向samgr注册自己时,samgr会首先生成一个ServiceImpl对象,将service对象的指针记录在ServiceImpl的Service* service里,而ServiceImpl对象本身的指针,则记录在g_samgrImpl.services.data[i] 中,这样就建立了g_samgrImpl 到具体service的联系,见本文上面的展开图。

 

A:  Service* service;   

指向当前ServiceImpl对象所对应的具体的service对象,这些具体的service对象都是Service类的子类对象。

 

B: IUnknown* defaultApi;

继承了IUnknown接口(INHERIT_IUNKNOWN)的service或者feature,都会有IUnknown的一组三个默认的接口,这组接口主要是记录service/feature对象的引用数量,还可以通过其中的QueryInterface接口实现父类(IUnknown)指针到具体的service/feature子类对象指针的类型转换,以获取子类提供的功能。

详情见下面对IUnknown的分析。

我在《鸿蒙的DFX子系统 》一文中,有对hiview service做过一个展开,可以去那里了解一下。

 

C:  TaskPool* taskPool;

这就是上一小节中,samgr为service创建的taskPool的指针(同时创建的还有消息队列,queueId同时保存在taskPool里面)。

如果service task是SHARED_TASK类型的,那就会有多个service共享一个taskPool和queue,这里的taskPool指针就会指向同一块内存空间,同时,g_samgrImpl的对应优先级别的sharedPool[x]也会记录着这个taskPool指针。

如果service task不是SHARED_TASK类型的,那就只会在这里记录service的taskPool(包括queue),而不会在g_samgrImpl中做记录。

 

D: Vector    features;

feature需要依赖于对应的service才能注册和运行,一个service可以有0个、1个或多个feature。service本身不记录它对应的feature的信息,而是由这个ServiceImpl.features来记录。ServiceImpl 的这个Vector features类似于g_samgrImpl用于记录serviceImpl的Vector services。

一个service没有feature时,features Vector就保持初始化的样子,对应的features.data 是NULL。

一个service有若干个feature时,就会在feature注册时,由samgr生成对应FeatureImpl类对象,将此对象与具体的feature关联起来,再将此对象的指针保存在Vector features.data[x]里,并将这个 x 作为对应feature的ID,另做保存。以后samgr就可以通过它自己的vector找到serviceImpl,再进一步通过这个vector找到对应的featureImpl,从而找到最终的feature。

 

E: int16     serviceId;

当前的ServiceImpl 对象指针保存在g_samgrImpl.services vector内的data[x]上的这个 x 序号,就作为当前service的ID,保存在这里。这个serviceId也可能同时保存在具体的service对象的Identity 结构体里。

 

F:  uint8     inited;

标记当前ServiceImpl 对应的service的状态,service没有init起来是不能注册feature的,service在处理消息事件的时候,状态也要对应置为 BUSY,处理完消息又要将状态写回IDLE。具体可自行查阅代码。

 

G: Operations ops;

主要记录了service处理消息事件的时间戳、msg数量(编号?)、步骤和是否存在异常等信息,估计与跨设备的服务/消息处理的同步有关,对此暂未做深入理解,待作进一步的理解。

 

4.4  FeatureImpl类

    typedef struct FeatureImpl FeatureImpl;
    struct FeatureImpl {
A:	    Feature  *feature;
B:	    IUnknown *iUnknown;
    };

FeatureImpl 类看起来相对简单,直接是一个Feature指针(A)指向对应的具体的feature对象。

有些feature除了继承自Feature类之外,还继承了某些interface,为feature提供额外的功能,这些interface 都是继承了最原始的IUnknown接口类(INHERIT_IUNKNOWN)。

这里的IUnknown *iUnknown与上面的ServiceImpl 的IUnknown* defaultApi; 其实是同一个东西,它们都指 向了一个feature或者service所继承/实现的接口中,IUnknown接口所在的位置,见上面ServiceImpl对IUnknown* defaultApi;的说明。

更多详情见下面对IUnknown的分析。

 

4.5  Service类及其子类

struct Service {
    const char *(*GetName)(Service *service);					//获取service名称
    BOOL (*Initialize)(Service *service, Identity identity);    //service的初始化
    BOOL (*MessageHandle)(Service *service, Request *request); 	//service的消息处理函数
    TaskConfig (*GetTaskConfig)(Service *service);				//获取service 任务运行的配置
}

Service类是所有service类的父类,它声明了四个函数指针,这是每一个服务都必须要实现的生命周期函数,见上面的注释。

每一个具体的服务类都继承自这个Service类,然后可以扩展自己独特的功能。

下面分别看一下Hi3861默认的三个具体的服务。

A: Bootstrap service

typedef struct Bootstrap {
    INHERIT_SERVICE;  //继承上面的Sevice类
    Identity identity;   //bootstrap service对象的id信息
    uint8 flag;
} Bootstrap;

Bootstrap 除了继承Service之外,还增加了一个Identity identity 和 uint8 flag。

  • Identity identity

     这是Bootstrap service具体对象的身份信息,里面包括了: serviceId/featureId/queueId三个信息。

     serviceId:Bootstrap service 对应的 serviceImpl对象,在g_samgrImpl.services这个Vector.data[]中存放位置 的index,这里值为0.

     featureId:Bootstrap service不带feature,所以值为 -1。

     queueId: Bootstrap service启动时,samgr会为其创建消息队列,这就是消息队列的ID,是一串数字。

  • uint8 flag

     一个标记,主要是LOAD_FLAG 0x01这一个位,用来标记非系统service/feature是否已经加载和注册,见 bootstrap_service.c的MessageHandle()内对flag的使用。

 

B. Broadcast service

typedef struct BroadcastService BroadcastService;
struct BroadcastService {
    INHERIT_SERVICE;
};

Broadcast service仅仅直接继承了Service类,确保它的service对象的生命周期的完整,因为它还会有feature,会在具体的feature对象中保存id信息和其它扩展信息,详见下面的PubSubFeature类的解析。

 

C. Hiview service

typedef struct {
    INHERIT_IUNKNOWN;    
    void (*Output)(IUnknown *iUnknown, int16 msgId, uint16 type);
} HiviewInterface;

typedef struct {
    INHERIT_SERVICE;
    INHERIT_IUNKNOWNENTRY(HiviewInterface);
    Identity identity;
} HiviewService; 

Hiview service除了继承自Service类实现service的生命周期函数之外,还继承了HiviewInterface,这个HiviewInterface又继承了最原始的IUnknown接口类(INHERIT_IUNKNOWN)。通过这种多继承机制,既实现了服务所需的生命周期,又具备了类似feature所提供的部分接口功能(Hiview service实际上又不带feature)。

详情见下面对IUnknown类的分析。

identity则是Hiview service对象的身份信息,同样包括了: serviceId/featureId/queueId三个信息。

 

4.6   Feature类及其子类

struct Feature {
    const char *(*GetName)(Feature *feature);                         //获取feature的名字
    void (*OnInitialize)(Feature *feature, Service *parent, Identity identity);  //feature 的初始化
    void (*OnStop)(Feature *feature, Identity identity);                   //停止对外提供feature功能
    BOOL (*OnMessage)(Feature *feature, Request *request);             //对本feature的消息处理
};

Feature类是所有feature类的父类,也是声明了四个函数指针,这是每一个feature都必须要实现的生命周期函数,见上面的注释。

每一个具体的feature类都继承自这个Feature类,然后扩展自己独特的功能。

 

下面是Hi3861的Broadcast service 提供的feature类及Impl类的定义:

A: PubSubFeature  g_broadcastFeature

typedef struct PubSubFeature PubSubFeature;
struct PubSubFeature {
    INHERIT_FEATURE;   //继承 Feature 类
    Relation *(*GetRelation)(PubSubFeature *feature, const Topic *topic);
    MutexId mutex;
    Relation relations;
    Identity identity;
};

PubSubFeature 本尊,它被FeatureImpl 对象以及下面的PubSubImplement 对象引用。

FeatureImpl 对象又会被记录在 ServiceImpl 的Features Vector向量里,获得一个Fid,连同Sid/Qid一起记录在PubSubFeature 的identity里。

PubSubFeature 还提供一个双向链表结构的Relation,以及基于这个双向链表结构的查找节点的函数GetRelation(),这个结构及其作用,我后面再另写文章详细分析。

 

B: PubSubImplement  g_pubSubImplement

typedef struct PubSubInterface PubSubInterface;
struct PubSubInterface {
    INHERIT_IUNKNOWN;
    Subscriber subscriber;
    Provider provider;
};

typedef struct PubSubImplement {
    INHERIT_IUNKNOWNENTRY(PubSubInterface);
    PubSubFeature *feature;
} PubSubImplement;

PubSubImplement 对象会引用上面的PubSubFeature对象,记录在这里的PubSubFeature *feature上。

PubSubImplement 还继承了PubSubInterface,实现了Subscriber 和Provider的功能。

它们的关系,见本文最上面的展开图。

 

这个PubSubFeature 和PubSubImplement 深究下去就有点复杂了,它们是SOA(面向服务的架构)的具体实现:

  • Provider:服务的提供者,为系统提供能力(对外接口)。
  • Consumer:服务的消费者,调用服务提供的功能(对外接口)。
  • Samgr:作为中介者,管理Provider提供的能力,同时帮助Consumer发现Provider的能力。

如下是官方readme上画的架构图。

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

这里我就先不做进一步详细的分析了,后面会结合broadcast_example.c示例程序来做分析和验证,再单独写一篇文章来做总结。

 

4.7  IUnknown 接口类及其相关定义

首先需要理解,C语言的struct本质上与C++中的class是一样的,都是一块存储区域,里面有数据和对数据的操作。C++通过“:”关键字来标记继承关系,如Aa继承自A表示为“class Aa : public A”,而C语言直接是struct Aa内嵌套struct A来标记“继承关系”:

struct A {
    dataA;
    funcPtr* funcA;
}
struct Aa {
    struct A;
    dataAa;
    funcPtr* funcAa;
}

更具体的一些细节,可以自行在网上搜索和学习。

 

接下来,我们仔细对比一下HivieService 和PubSubImplement,它们都分别通过INHERIT_IUNKNOWNENTRY() 这个关键字来分别继承HiviewInterface和PubSubInterface,而这HiviewInterface和PubSubInterface又都通过INHERIT_IUNKNOWN来继承IUnknown接口类。

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

下面我们就跟着Hi3861/foundation/distributedschedule/interfaces/innerkits/samgr_lite/samgr/iunknown.h中的定义去展开和理解一下这两个宏(类)。

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

INHERIT_IUNKNOWN 宏定义是为了方便用C语言的形式“继承”IUnknown接口类。

INHERIT_IUNKNOWNENTRY() 宏定义是为了方便用C语言的形式“继承”【实现了IUnknown接口的】 IUnknownEntry接口类。

这两个宏之间的关系,就是上面struct A和struct Aa之间的父类子类关系,换回struct 的形式,就是:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

IUnknown 是父类,IUnknownEntry 是子类,子类是父类的一个implement。

 

按上面的定义把HiviewService类(或结构体)的定义彻底展开,就是如下的样子:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

把HiviewService的全局对象 g_hiviewService的初始化也按上面的形式展开,也会如下:

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

这样一来,.iUnknown的地址、HiviewService类型、g_hiviewService对象的地址(引用/指针)之间的关系,就可以通过计算和类型转换来互相获取了,这就是下面三个宏的作用:

a.  GET_IUNKNOWN(T)

定义在://foundation/distributedschedule/interfaces/innerkits/samgr_lite/samgr/iunknown.h

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

简单理解为:从g_hiviewService对象中获取其内部的.iUnknown对象的地址。

 

b.  GET_OFFSIZE(T, member)

c.  GET_OBJECT(Ptr, T, member)

这两个定义在://foundation/distributedschedule/interfaces/innerkits/samgr_lite/samgr/common.h

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

简单理解为:从.iUnknown父类对象下转换得到子类HiviewInterface对象或者g_hiviewService对象的地址。

 

    如本文开头的第一张展开图所示,BroadcastImpl和HiviewImpl都有自己的 IUnknown* defaultApi,分别是通过GET_IUNKNOWN(g_pubSubImplement) 和GET_IUNKNOWN(g_hiviewService)来得到的,得到了.iUnknown的地址,也就是得到了(*QueryInterface)/(*AddRef)/(*Release)这三个defaultApi的地址,就可以使用它们了,实际只直接使用(*QueryInterface)这个API,它的作用是“Queries the subclass object of the IUnknown interface of a specified version”

查询/获取指定的版本的IUnknown接口的子类对象(同时增加对该对象的引用次数),调用者通过这个子类对象就可以调用子类中定义的其它接口了。

    对于上面的g_hiviewService例子来说,调用QueryInterface接口的例子在hiview_service.c 的HiviewSendMessage() 里,它返回的实际上就是g_hiviewService 这个service对象内部的一个区域的起始地址,这个区域就是HiviewInterface这个类的对象,同时增加了对这个对象的引用次数,之后,就可以通过这个对象来调用HiviewInterface所定义的全部API了(这里主要是Output函数)。

Hi3861的SAMGR--系统服务框架子系统-2-关键结构体分解-开源基础软件社区

4.8  其它类/结构体

samgr子系统中还有其他的一些很重要的类或结构体,比如Request/Response/Exchange 等等,这里就先不进一步展开了,以后应该还会继续补充完整的,或者在接下来的流程分析中按需进行分解,也请各位自行做一下理解。

 

 

 

接下来的一篇文章,将会对部分流程进行详细的分析,正在码字中,敬请期待......

 

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
samgr 展开图.rar 900.09K 133次下载
Hi3861平台开机部分log.rar 3.77K 92次下载
已于2021-7-3 11:13:10修改
5
收藏 7
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐