OpenHarmony(鸿蒙南向开发)——轻量系统内核(LiteOS-M)【内存管理】

news2024/10/8 15:47:24

往期知识点记录:

  • 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
  • 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
  • 持续更新中……

基本概念

内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。

在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。

OpenHarmony LiteOS-M的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。

  • 动态内存:在动态内存池中分配用户指定大小的内存块。

    • 优点:按需分配。
    • 缺点:内存池中可能出现碎片。
  • 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。

    • 优点:分配和释放效率高,静态内存池中无碎片。
    • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

静态内存

运行机制

静态内存实质上是一个静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。

静态内存池由一个控制块LOS_MEMBOX_INFO和若干相同大小的内存块LOS_MEMBOX_NODE构成。控制块位于内存池头部,用于内存块管理,包含内存块大小uwBlkSize,内存块数量uwBlkNum,已分配使用的内存块数量uwBlkCnt和空闲内存块链表stFreeList。内存块的申请和释放以块大小为粒度,每个内存块包含指向下一个内存块的指针pstNext。

图1 静态内存示意图

开发指导

使用场景

当用户需要使用固定长度的内存时,可以通过静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。

接口说明

OpenHarmony LiteOS-M的静态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。

表1 静态内存模块接口

功能分类接口名
初始化静态内存池LOS_MemboxInit:初始化一个静态内存池,根据入参设定其起始地址、总大小及每个内存块大小。
清除静态内存块内容LOS_MemboxClr:清零从静态内存池中申请的静态内存块的内容。
申请、释放静态内存LOS_MemboxAlloc:从指定的静态内存池中申请一块静态内存块。
LOS_MemboxFree:释放从静态内存池中申请的一块静态内存块。
获取、打印静态内存池信息LOS_MemboxStatisticsGet:获取指定静态内存池的信息,包括内存池中总内存块数量、已经分配出去的内存块数量、每个内存块的大小。
LOS_ShowBox:打印指定静态内存池所有节点信息,打印等级是LOG_INFO_LEVEL(当前打印等级配置是PRINT_LEVEL),包括内存池起始地址、内存块大小、总内存块数量、每个空闲内存块的起始地址、所有内存块的起始地址。

说明: 初始化后的内存池的内存块数量,不等于总大小除于内存块大小,因为内存池的控制块和每个内存块的控制头,都存在内存开销,设置总大小时,需要将这些因素考虑进去。

开发流程

本节介绍使用静态内存的典型场景开发流程。

  1. 规划一片内存区域作为静态内存池。

  2. 调用LOS_MemboxInit初始化静态内存池。 初始化会将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处放置控制头。

  3. 调用LOS_MemboxAlloc接口分配静态内存。 系统将会从空闲链表中获取第一个空闲块,并返回该内存块的起始地址。

  4. 调用LOS_MemboxClr接口。 将入参地址对应的内存块清零。

  5. 调用LOS_MemboxFree接口。 将该内存块加入空闲链表。

编程实例

本实例执行以下步骤:

  1. 初始化一个静态内存池。

  2. 从静态内存池中申请一块静态内存。

  3. 在内存块存放一个数据。

  4. 打印出内存块中的数据。

  5. 清除内存块中的数据。

  6. 释放该内存块。 示例代码如下:

    本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleStaticMem。

#include "los_membox.h"

#define MEMBOX_POOL_SIZE    100
#define MEMBOX_BLOCK_SZIE   10
#define MEMBOX_WR_TEST_NUM  828
VOID ExampleStaticMem(VOID)
{
    UINT32 *mem = NULL;
    UINT32 blkSize = MEMBOX_BLOCK_SZIE;
    UINT32 poolSize = MEMBOX_POOL_SIZE;
    UINT32 boxMem[MEMBOX_POOL_SIZE];
    UINT32 ret;

    /* 内存池初始化 */
    ret = LOS_MemboxInit(&boxMem[0], poolSize, blkSize);
    if(ret != LOS_OK) {
        printf("Membox init failed!\n");
        return;
    } else {
        printf("Membox init success!\n");
    }

    /* 申请内存块 */
    mem = (UINT32 *)LOS_MemboxAlloc(boxMem);
    if (mem == NULL) {
        printf("Mem alloc failed!\n");
        return;
    }
    printf("Mem alloc success!\n");

    /* 内存地址读写验证 */
    *mem = MEMBOX_WR_TEST_NUM;
    printf("*mem = %d\n", *mem);

    /* 清除内存内容 */
    LOS_MemboxClr(boxMem, mem);
    printf("Mem clear success \n*mem = %d\n", *mem);

    /* 释放内存 */
    ret = LOS_MemboxFree(boxMem, mem);
    if (LOS_OK == ret) {
        printf("Mem free success!\n");
    } else {
        printf("Mem free failed!\n");
    }

    return;
}

结果验证

输出结果如下:

Membox init success!
Mem alloc success!
*mem = 828
Mem clear success   
*mem = 0
Mem free success!

动态内存

运行机制

动态内存管理,即在内存资源充足的情况下,根据用户需求,从系统配置的一块比较大的连续内存(内存池,也是堆内存)中分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。

OpenHarmony LiteOS-M动态内存在TLSF算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如下:

图1 轻量系统动态内存核心算法

根据空闲内存块的大小,使用多个空闲链表来管理。根据内存空闲块大小分为两个部分:[4, 127]和[27, 231],如上图size class所示:

  1. 对[4,127]区间的内存进行等分,如上图下半部分所示,分为31个小区间,每个小区间对应内存块大小为4字节的倍数。每个小区间对应一个空闲内存链表和用于标记对应空闲内存链表是否为空的一个比特位,值为1时,空闲链表非空。[4,127]区间的31个小区间内存对应31个比特位进行标记链表是否为空。

  2. 大于127字节的空闲内存块,按照2的次幂区间大小进行空闲链表管理。总共分为24个小区间,每个小区间又等分为8个二级小区间,见上图上半部分的Size Class和Size SubClass部分。每个二级小区间对应一个空闲链表和用于标记对应空闲内存链表是否为空的一个比特位。总共24*8=192个二级小区间,对应192个空闲链表和192个比特位进行标记链表是否为空。

例如,当有40字节的空闲内存需要插入空闲链表时,对应小区间[40,43],第10个空闲链表,位图标记的第10比特位。把40字节的空闲内存挂载第10个空闲链表上,并判断是否需要更新位图标记。当需要申请40字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。当有580字节的空闲内存需要插入空闲链表时,对应二级小区间[29,29+2^6],第31+2*8=47个空闲链表,并使用位图的第47个比特位来标记链表是否为空。把580字节的空闲内存挂载第47个空闲链表上,并判断是否需要更新位图标记。当需要申请580字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。如果对应的空闲链表为空,则向更大的内存区间去查询是否有满足条件的空闲链表,实际计算时,会一次性查找到满足申请大小的空闲链表。

内存管理结构如下图所示:

图2 轻量系统动态内存管理结构图

  • 内存池池头部分 内存池池头部分包含内存池信息、位图标记数组和空闲链表数组。内存池信息包含内存池起始地址及堆区域总大小,内存池属性。位图标记数组有7个32位无符号整数组成,每个比特位标记对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包含223个空闲内存头节点信息,每个空闲内存头节点信息维护内存节点头和空闲链表中的前驱、后继空闲内存节点。

  • 内存池节点部分 包含3种类型节点:未使用空闲内存节点,已使用内存节点和尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,还维护内存节点的大小和使用标记。空闲内存节点和已使用内存节点后面的内存区域是数据域,尾节点没有数据域。

一些芯片片内RAM大小无法满足要求,需要使用片外物理内存进行扩充。对于这样的多段非连续性内存, LiteOS-M内核支持把多个非连续性内存逻辑上合一,用户不感知底层的多段非连续性内存区域。 LiteOS-M内核内存模块把不连续的内存区域作为空闲内存结点插入到空闲内存节点链表,把不同内存区域间的不连续部分标记为虚拟的已使用内存节点,从逻辑上把多个非连续性内存区域实现为一个统一的内存池。下面通过示意图说明下多段非连续性内存的运行机制:

图3 非连续性内存合一示意图

结合上述示意图,非连续性内存合并为一个统一的内存池的步骤如下:

  1. 把多段非连续性内存区域的第一块内存区域通过调用LOS_MemInit接口进行初始化。

  2. 获取下一个内存区域的开始地址和长度,计算该内存区域和上一块内存区域的间隔大小gapSize。

  3. 把内存区域间隔部分视为虚拟的已使用节点,使用上一个内存区域的尾节点,设置其大小为gapSize + OS_MEM_NODE_HEAD_SIZE(即sizeof(struct OsMemUsedNodeHead))。

  4. 把当前内存区域划分为一个空闲内存节点和一个尾节点,把空闲内存节点插入到空闲链表,并设置各个节点的前后链接关系。

  5. 如果有更多的非连续内存区域,重复上述步骤2-4。

开发指导

使用场景

动态内存管理的主要工作是动态分配并管理用户申请到的内存区间。动态内存管理主要用于用户需要使用大小不等的内存块的场景,当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。

接口说明

OpenHarmony LiteOS-M的动态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。

表1 动态内存模块接口

功能分类接口描述
初始化和删除内存池LOS_MemInit:初始化一块指定的动态内存池,大小为size。
LOS_MemDeInit:删除指定内存池,仅打开编译控制开关LOSCFG_MEM_MUL_POOL时有效。
申请、释放动态内存LOS_MemAlloc:从指定动态内存池中申请size长度的内存。
LOS_MemFree:释放从指定动态内存中申请的内存。
LOS_MemRealloc:释放从指定动态内存中申请的内存。
获取内存池信息LOS_MemPoolSizeGet:获取指定动态内存池的总大小。
LOS_MemTotalUsedGet:获取指定动态内存池的总使用量大小。
LOS_MemInfoGet:获取指定内存池的内存结构信息,包括空闲内存大小、已使用内存大小、空闲内存块数量、已使用的内存块数量、最大的空闲内存块大小。
LOS_MemPoolList:打印系统中已初始化的所有内存池,包括内存池的起始地址、内存池大小、空闲内存总大小、已使用内存总大小、最大的空闲内存块大小、空闲内存块数量、已使用的内存块数量。仅打开编译控制开关LOSCFG_MEM_MUL_POOL时有效。
获取内存块信息LOS_MemFreeNodeShow:打印指定内存池的空闲内存块的大小及数量。
LOS_MemUsedNodeShow:打印指定内存池的已使用内存块的大小及数量。
检查指定内存池的完整性LOS_MemIntegrityCheck:对指定内存池做完整性检查,仅打开编译控制开关LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK时有效。
增加非连续性内存区域LOS_MemRegionsAdd:支持多段非连续性内存区域,把非连续性内存区域逻辑上整合为一个统一的内存池。仅打开LOSCFG_MEM_MUL_REGIONS时有效。如果内存池指针参数pool为空,则使用多段内存的第一个初始化为内存池,其他内存区域,作为空闲节点插入;如果内存池指针参数pool不为空,则把多段内存作为空闲节点,插入到指定的内存池。

说明:

  • 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外消耗内存,故实际用户可使用内存总量小于配置项OS_SYS_MEM_SIZE的大小。

  • 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign因为要进行地址对齐,可能会额外消耗部分内存,故存在一些遗失内存,当系统释放该对齐内存时,同时回收由于对齐导致的遗失内存。

  • 非连续性内存区域接口LOS_MemRegionsAdd的LosMemRegion数组参数传入的非连续性内存区域需要按各个内存区域的内存开始地址升序,且内存区域不能重叠。

开发流程

本节介绍使用动态内存的典型场景开发流程。

  1. 初始化LOS_MemInit。 初始一个内存池后生成一个内存池控制头、尾节点EndNode,剩余的内存被标记为FreeNode内存节点。注:EndNode作为内存池末尾的节点,size为0。

  2. 申请任意大小的动态内存LOS_MemAlloc。 判断动态内存池中是否存在大于申请量大小的空闲内存块空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。如果空闲内存块大于申请量,需要对内存块进行分割,剩余的部分作为空闲内存块挂载到空闲内存链表上。

  3. 释放动态内存LOS_MemFree。 回收内存块,供下一次使用。调用LOS_MemFree释放内存块,则会回收内存块,并且将其标记为FreeNode。在回收内存块时,相邻的FreeNode会自动合并。

编程实例

本实例执行以下步骤:

  1. 初始化一个动态内存池。

  2. 从动态内存池中申请一个内存块。

  3. 在内存块中存放一个数据。

  4. 打印出内存块中的数据。

  5. 释放该内存块。

示例代码如下:

本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleDynMem。

#include "los_memory.h"

#define TEST_POOL_SIZE (2*1024)
#define MEMBOX_WR_TEST_NUM  828

__attribute__((aligned(4))) UINT8 g_testDynPool[TEST_POOL_SIZE];

VOID ExampleDynMem(VOID)
{
    UINT32 *mem = NULL;
    UINT32 ret;

    /* 初始化内存池 */
    ret = LOS_MemInit(g_testDynPool, TEST_POOL_SIZE);
    if (LOS_OK  == ret) {
        printf("Mem init success!\n");
    } else {
        printf("Mem init failed!\n");
        return;
    }

    /* 申请内存块 */
    mem = (UINT32 *)LOS_MemAlloc(g_testDynPool, 4);
    if (mem == NULL) {
        printf("Mem alloc failed!\n");
        return;
    }
    printf("Mem alloc success!\n");

    /* 内存地址读写验证 */
    *mem = MEMBOX_WR_TEST_NUM;
    printf("*mem = %d\n", *mem);

    /* 释放内存 */
    ret = LOS_MemFree(g_testDynPool, mem);
    if (LOS_OK == ret) {
        printf("Mem free success!\n");
    } else {
        printf("Mem free failed!\n");
    }

    return;
}

结果验证

输出结果如下:

Mem init success!
Mem alloc success!
*mem = 828
Mem free success!

最后

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

鸿蒙开发面试真题(含参考答案):

在这里插入图片描述

《OpenHarmony源码解析》:

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片
在这里插入图片描述

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

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

相关文章

LeetCode 刷题基础 -- 模板原型Ⅰ

模板原型 - 基础篇 学习网站一、进制转换二、二分查找① 查找指定元素② 查找第一个大于等于 x 值的序列下标③ 查找第一个大于 x 值的序列下标④ 单峰序列 三、双指针① 两数之和② 序列合并③ 集合求交④ 集合求并 四、其他高效技巧与算法① 区间和② 01 对③ 左小数 五、数学…

【每日刷题】Day134

【每日刷题】Day134 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 1218. 最长定差子序列 - 力扣(LeetCode) 2. LCR 116. 省份数量 - 力扣&…

掌握这17个Python自动化操作,简化你的日常工作流程,提升工作效率!

Python是一种流行的编程语言,以其简单性和可读性而闻名。因其能够提供大量的库和模块,它成为了自动化各种任务的绝佳选择。让我们进入自动化的世界,探索17个可以简化工作并节省时间精力的Python脚本。 目录(上篇) 1.自…

小型数控铣床助力职业教育教学模式

小型数控铣床是在普通铣床上集成了数字控制系统,可以在程序代码的控制下较精确地进行铣削加工的机床。与普通小型铣床相比,小型数控铣床通过数字控制系统实现了自动化加工,提高了加工精度和效率。 小型数控铣床的引入推动了教育装备的现代化进…

基于单片机的书库环境监测

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机,采用DHT11湿度传感器检测湿度,DS18B20温度传感器检测温度, 采用滑动变阻器连接数模转换器模拟二氧化碳和氧气浓度检测,各项数值通过lc…

Trickle流量限速工具使用示例

简介:trickle 是一个轻量级的流量限速工具,允许用户限制应用程序的网络带宽使用,以便更好地管理网络资源和优化网络性能。本文将介绍 trickle 的安装和使用方法,并通过 Python 封装示例展示如何使用 trickle 控制网络带宽。 历史…

关于PPT生成的开源大模型总结

目前需要开源的PPT生成模型,在这里对github上的一些模型进行筛选 搜索关键词:ppt generate(more starts) williamfzc/chat-gpt-ppt: 支持直接生成PPT支持中英文需要调用ChatGPT(Add your token (official openai api k…

使用正则表达式删除文本的奇数行或者偶数行

用智谱清言和kimi搜出来的结果都没法在notepad生效,后面在overflow上找到的答案比较靠谱。 查找:^[^\n]*\n([^\n]*) 替换:\1 删除偶数行 查找:^([^\n]*)\n[^\n]* 替换:\1 代码解释 ^:这个符号代表字符…

Excel日期导入数据库变为数字怎么办

在Excel导入到数据库的过程中,经常会碰到Excel里面的日期数据,导进去过后变成了数字。 如下图: 使用navicate等数据库编辑器导入数据库后: 原因分析:这是因为日期和时间在excel中都是以数字形式存储的,这个…

重学SpringBoot3-集成Redis(二)之注解驱动

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(二)之注解驱动 1. 为什么选择 Redis 作为缓存?2. 如何在 Spring Boot 中启用 Redis 缓存?2.1 …

多模态大模型调研BLIP、BLIP2、InstructBLIP

ITC:图像向量与文本向量对齐在同一特征空间 ITM:二分类任务。负样本构建:前方ITC分错的地方,在对比学习的基础上,更细粒度的对其特征。 LM:GPT的生成任务,将文本重新进行预测。 BLIP另一个贡献在于弱监督图文数据清洗方面,通过训…

学习threejs,模拟窗户光源

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言二、🍀绘制任意字体模型…

棒球运动物体检测系统源码分享

棒球运动物体检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

应用界面编写(十四)

一. 介绍QT 接下来我们会在Qt Creater来进行界面的编写,并且在荔枝派中运行。那么我们有必要了解一下Qt到底是什么呢?它又为什么可以在荔枝派中运行呢? QT是一个跨平台的应用程序和用户界面框架,用于开发具有图形界面的软件。而…

【最新版】Stable Diffusion4.9(AI绘画)下载及安装教程(附软件安装包)!

随着技术的迭代,目前 Stable Diffusion 已经能够生成非常艺术化的图片了,完全有赶超人类的架势,已经有不少工作被这类服务替代,比如制作一个 logo 图片,画一张虚拟老婆照片,画质堪比相机。 最新 Stable Di…

日常物品实例分割系统源码&数据集分享

日常物品实例分割系统源码&数据集分享 [yolov8-seg-dyhead-DCNV3&yolov8-seg-SPPF-LSKA等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Globa…

Maven - 依赖管理

依赖配置 在pom.xml的project标签内添加dependencies标签&#xff0c;之后添加依赖配置。 <dependencies><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.4.5</version>…

Acwing 记忆化搜索

Acwing 901.滑雪 输入样例&#xff1a; 5 5 1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9 输出样例&#xff1a; 25 实现思路&#xff1a; 状态表示f[i][j]&#xff0c;表示从点(i,j)出发的最长路径长度集合划分可分为四种情况&#xff1a;从点(i,j)出…

Mybatis框架梳理——更新中

Mybatis框架梳理 前言1.ORM2.模块划分2.1 ORM的实现2.2 SQL的映射2.3 插件机制2.4 缓存机制2.5 其他2.5.1 用到的设计模式 3. 愿景 前言 如果让我聊一聊mybatis&#xff0c;我该怎么说呢&#xff1f;开发中时时刻刻都在用它&#xff0c;此时此刻&#xff0c;脑海中却只浮现ORM框…