鸿蒙内核源码分析 (内存池管理) | 如何高效切割合并内存块

动态分配

系列篇将动态分配分成上下两篇,本篇为下篇,阅读之前建议翻看上篇。

  • 鸿蒙内核源码分析(TLFS算法) 结合图表从理论视角说清楚 TLFS 算法
  • 鸿蒙内核源码分析(内存池管理) 结合源码说清楚鸿蒙内核动态内存池实现过程,个人认为这部分代码很精彩,简洁高效,尤其对空闲节点和已使用节点的实现令人称奇。

为了便于理解源码,站长画了以下图,图中列出主要结构体,位图,分配和释放信息,逐一说明。

  • 请将内存池想成一条画好了网格虚线的大白纸,会有两种角色往白纸上画东西,一个是内核画管理数据,一个外部程序画业务数据,内核先画,外部程序想画需申请大小,申请成功内核会提供个地址给外部使用,例如申请20个格子,成功后内核返回一个(5,8)坐标,表示从第五行第八列开始往后的连续20个格子你可以使用。用完了释放只需要告诉内核一个坐标(5,8)而不需要大小,内核就知道回收多少格子。但内核凭什么知道要释放多少个格子呢 ? 一定有个格子给记录下来了对不对,实际中存大小的格子坐标就是(5,7)。其值是在申请的时候或更早的时候填进去的。而且不一定是20,但一定不小于20。如果您能完全理解以上这段话,那可能已经理解了内存池的管理的方式,不用往下看了。

内存池 | OsMemPoolHead

/// 内存池头信息
struct OsMemPoolHead {struct OsMemPoolInfo info; ///< 记录内存池的信息UINT32 freeListBitmap[OS_MEM_BITMAP_WORDS]; ///< 空闲位图 int[7] = 32 * 7 = 224struct OsMemFreeNodeHead *freeList[OS_MEM_FREE_LIST_COUNT];///< 空闲节点链表 32 + 24 * 8 = 224  SPIN_LOCK_S spinlock;	///< 操作本池的自旋锁,涉及CPU多核竞争,所以必须得是自旋锁
#ifdef LOSCFG_MEM_MUL_POOLVOID *nextPool;	///< 指向下一个内存池 OsMemPoolHead 类型
#endif
};
/// 内存池信息
struct OsMemPoolInfo {VOID *pool;			///< 指向内存块基地址,仅做记录而已,真正的分配内存跟它没啥关系UINT32 totalSize;	///< 总大小,确定了内存池的边界UINT32 attr;		///< 属性 default attr: lock, not expand.
#ifdef LOSCFG_MEM_WATERLINEUINT32 waterLine;   /* Maximum usage size in a memory pool | 内存吃水线*/UINT32 curUsedSize; /* Current usage size in a memory pool | 当前已使用大小*/
#endif
}; 

解读

  • OsMemPoolInfo.pool 是整个内存池的第一个格子,里面放的是一个内存池起始虚拟地址。

  • OsMemPoolInfo.totalSize 表示这张纸有多少个格子。

  • OsMemPoolInfo.attr 表示池子还能不能再变大。

  • OsMemPoolInfo.waterLine 池子水位警戒线,跟咱三峡大坝发洪水时的警戒线 175米 类似,告知上限,水一旦漫过此线就有重大风险,waterLine一词很形象,内核很多思想真来源于生活。

  • OsMemPoolInfo.curUsedSize 所有已分配内存大小的叠加。

  • freeListBitmap 空闲位图,这是tlfs算法的一二级表示,是个长度为7的整型数组

    #define OS_MEM_BITMAP_WORDS     ((OS_MEM_FREE_LIST_COUNT >> 5) + 1) 
    #define OS_MEM_FREE_LIST_COUNT  (OS_MEM_SMALL_BUCKET_COUNT + (OS_MEM_LARGE_BUCKET_COUNT << OS_MEM_SLI)) 
    #define OS_MEM_LARGE_START_BUCKET       7 /// 大桶的开始下标
    #define OS_MEM_SMALL_BUCKET_COUNT       31 ///< 小桶的偏移单位 从 4 ~ 124 ,共32级
    #define OS_MEM_SLI                      3 ///< 二级小区间级数,

    这一坨坨的宏看着有点绕,简单说就是鸿蒙对申请大小分成两种情况

    • 第一种:小桶申请** 当小于128个字节大小的需求平均分成了([0-4],[4-8],...,[124-128])32个等级,而freeListBitmap[0]为一个UINT32,共32位刚好表示这32个等级是否有空闲块。例如: 当freeListBitmap[0] = 0b...101时,如果此时malloc(3)到来,因101对应的是12,8,4等级,而且124位图位为1,说明在 4的等级上有空闲内存块可以满足malloc(3),需要注意的是虽然malloc(3)但因为4等级上只有一种单位4所以malloc(3)最后实际得到的是4,而如果 malloc(7)到来时,正常需要8等级来满足,但8等级位图位为0表示没有空闲内存块,就需要向上找位图为112等级来申请,于是12将被分成84两块,8提供给malloc(7),剩下的4挂入等级为4的空闲链表上。
    • 第二种:大桶申请** 将占用freeListBitmap的剩余6UINT32整型变量,共可以表示32 * 6 = 192位 ,同时 192 = 24 * 8,鸿蒙将大于128个字节的申请按2次幂分成24大等级,每个等级又分成8个小等级 即 TLFS 算法
      24级对应的范围为([2^7-2^8-1],[2^8-2^9-1],...,[2^30-2^31-1])
      而每大级被平均分成8小级,
      例如最小的[2^7-2^8-1]将被分成每份递增 2^4 = 16 大小的八份
      ([2^7-2^7+2^4],[2^7+2^4-2^7+2^4*2],...,[2^7+2^4*7-2^8-1])
      而最大的[2^30-2^31-1]将被分成每份递增 2^27 = 134 217 728大小的八份,请记住2^27这个数,后面还会说它。
      ([2^30-2^30+2^27],[2^30+2^4-2^30+2^27*2],...,[2^30+2^4*7-2^31-1])
  • OsMemFreeNodeHead freeList[..] 是空闲链表数组,大小 224个,即每个freeListBitmap等级都对应了一个链表

    /// 内存池空闲节点
    struct OsMemFreeNodeHead {struct OsMemNodeHead header;	///< 内存池节点struct OsMemFreeNodeHead *prev;	///< 前一个空闲前驱节点struct OsMemFreeNodeHead *next;	///< 后一个空闲后继节点
    };

    prevnext,指向同级前后节点,
    节点的内容在OsMemNodeHead中,这是一个关键结构体,需单独讲。

内存池节点 | OsMemNodeHead

/// 内存池节点
struct OsMemNodeHead {UINT32 magic;	///< 魔法数字 0xABCDDCBAunion {//注意这里的前后指向的是连续的地址节点,用于分割和合并struct OsMemNodeHead *prev; /* The prev is used for current node points to the previous node | prev 用于当前节点指向前一个节点*/struct OsMemNodeHead *next; /* The next is used for last node points to the expand node | next 用于最后一个节点指向展开节点*/} ptr;
#ifdef LOSCFG_MEM_LEAKCHECK //内存泄漏检测UINTPTR linkReg[LOS_RECORD_LR_CNT];///< 存放左右节点地址,用于检测
#endifUINT32 sizeAndFlag;	///< 数据域大小
};
/// 已使用内存池节点
struct OsMemUsedNodeHead {struct OsMemNodeHead header;///< 已被使用节点
#if OS_MEM_FREE_BY_TASKIDUINT32 taskID; ///< 使用节点的任务ID
#endif
};

解读

  • magic 魔法数字多次提高,内核很多模块都用到了它,比如 栈顶 ,存在的意义是防止越界,栈溢出栈顶元素就一定会被修改。同理使用了大于申请的内存会导致紧挨着的内存块魔法数字被修改,从而判定为内存溢出。

  • 出现一个联合体,其中的prev,是指向前节点的 虚拟地址 或者叫 线性地址 也可以叫 逻辑地址, 这些地址是 连续 的,注意 连续性 很重要,它是内存块合并和分割的前提,回到图中的0x12450x12A50x1305来看,三个内存块节点的地址是逻辑地址相连的,内存块节点由头体两部分组成,头部放的是该节点的信息,体是 malloc(…) 的返回地址,所以当释放 free(0xXXX) 某块内存时很容易知道本节点的起始地址是多少,但向前合并就得知道前节点prev的地址,而后节点next的地址可通过0xXXX + sizeAndFlag - 头部 = next计算得到。既然不需要next那联合体出现在的next有什么意思呢? 这个next是指该块内存的尾节点的意思,当内存池允许扩展大小时,新旧两块内存之间就会产生一个连接处,它们的线性地址是不可能连续的,所以不存在合并的问题,prev于它而言没有意义,需要记录下一个内存块的地址,这个工作就交给了联合体中的next

  • 一个内存池可以由多个内存块组成,每个内存块都有独立的尾节点,指向下一块内存的开始地址,最后一个内存块的尾节点也称为哨兵节点,它像个哨兵一样为整个内存池站岗,风餐露宿,固守边疆。当扩大版图之后它又跑到下一站,一个内存池只有一个哨兵,它是最可爱的人,此处应有掌声。

  • linkReg 用于检测内存泄漏,这部分内容在 鸿蒙内核源码分析(模块监控) 已有详细说明,此处不再赘述。

  • UINT32 sizeAndFlag,表示总大小 包括(头部和体部)和 标签 ,上面已经让大家记住2^27这个数,这是动态内存能分配的最大的尺寸。 UINT32 中留28位给它足以,剩下的高4位就留给Flag。每位又分别表示以下含义

    #define OS_MEM_NODE_USED_FLAG      0x80000000U ///< 已使用标签
    #define OS_MEM_NODE_ALIGNED_FLAG   0x40000000U ///< 对齐标签
    #define OS_MEM_NODE_LAST_FLAG      0x20000000U  /* Sentinel Node | 哨兵节点标签,最后一个节点*/
    #define OS_MEM_NODE_ALIGNED_AND_USED_FLAG (OS_MEM_NODE_USED_FLAG | OS_MEM_NODE_ALIGNED_FLAG | OS_MEM_NODE_LAST_FLAG)
  • 从联合体和sizeAndFlag可以看出鸿蒙的设计思想,充分利用空间,准确区分概念,一张卫生纸擦完嘴还要接着擦地,节俭之家必有余粮啊,这是非常有必要的,因为内存资源太稀缺了。在实际运行过程中,分配节点常数以万计,每个能省一个UINT32,就是一万个UINT32,约等于39KB,非常可观。 这也是为什么站长始终觉得鸿蒙是个大宝藏的原因。

  • OsMemUsedNodeHead.taskID已使用节点比空闲节点头部多了一个使用该节点任务的标记,由开关宏OS_MEM_FREE_BY_TASKID控制,默认是关闭的。

代码实现

有了这么长的铺垫,再来看鸿蒙内核动态内存管理的代码简直就是易如反掌,此处拆解 节点切割节点合并内存池扩展 三段代码。

节点切割 | OsMemSplitNode
/// 切割节点
STATIC INLINE VOID OsMemSplitNode(VOID *pool, struct OsMemNodeHead *allocNode, UINT32 allocSize)
{struct OsMemFreeNodeHead *newFreeNode = NULL;struct OsMemNodeHead *nextNode = NULL;newFreeNode = (struct OsMemFreeNodeHead *)(VOID *)((UINT8 *)allocNode + allocSize);//切割后出现的新空闲节点,在分配节点的右侧newFreeNode->header.ptr.prev = allocNode;//新节点指向前节点,说明是从左到右切割newFreeNode->header.sizeAndFlag = allocNode->sizeAndFlag - allocSize;//新空闲节点大小allocNode->sizeAndFlag = allocSize;//分配节点大小nextNode = OS_MEM_NEXT_NODE(&newFreeNode->header);//获取新节点的下一个节点if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) {//如果下一个节点不是哨兵节点(末尾节点)nextNode->ptr.prev = &newFreeNode->header;//下一个节点的前节点为新空闲节点if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag)) {//如果下一个节点也是空闲的OsMemFreeNodeDelete(pool, (struct OsMemFreeNodeHead *)nextNode);//删除下一个节点信息OsMemMergeNode(nextNode);//下一个节点和新空闲节点 合并成一个新节点}}OsMemFreeNodeAdd(pool, newFreeNode);//挂入空闲链表
}
节点合并 | OsMemMergeNode
/// 合并节点,和前面的节点合并 node 消失
STATIC INLINE VOID OsMemMergeNode(struct OsMemNodeHead *node)
{struct OsMemNodeHead *nextNode = NULL;node->ptr.prev->sizeAndFlag += node->sizeAndFlag; //前节点长度变长nextNode = (struct OsMemNodeHead *)((UINTPTR)node + node->sizeAndFlag); // 下一个节点位置if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) {//不是哨兵节点nextNode->ptr.prev = node->ptr.prev;//后一个节点的前节点变成前前节点}
}

内存池扩展

/// 内存池扩展实现
STATIC INLINE INT32 OsMemPoolExpandSub(VOID *pool, UINT32 size, UINT32 intSave)
{UINT32 tryCount = MAX_SHRINK_PAGECACHE_TRY;struct OsMemPoolHead *poolInfo = (struct OsMemPoolHead *)pool;struct OsMemNodeHead *newNode = NULL;struct OsMemNodeHead *endNode = NULL;size = ROUNDUP(size + OS_MEM_NODE_HEAD_SIZE, PAGE_SIZE);//圆整endNode = OS_MEM_END_NODE(pool, poolInfo->info.totalSize);//获取哨兵节点
RETRY:newNode = (struct OsMemNodeHead *)LOS_PhysPagesAllocContiguous(size >> PAGE_SHIFT);//申请新的内存池 | 物理内存if (newNode == NULL) return -1;newNode->sizeAndFlag = (size - OS_MEM_NODE_HEAD_SIZE);//设置新节点大小newNode->ptr.prev = OS_MEM_END_NODE(newNode, size);//新节点的前节点指向新节点的哨兵节点OsMemSentinelNodeSet(endNode, newNode, size);//设置老内存池的哨兵节点信息,其实就是指向新内存块OsMemFreeNodeAdd(pool, (struct OsMemFreeNodeHead *)newNode);//将新节点加入空闲链表endNode = OS_MEM_END_NODE(newNode, size);//获取新节点的哨兵节点(VOID)memset(endNode, 0, sizeof(*endNode));//清空内存endNode->ptr.next = NULL;//新哨兵节点没有后续指向,因为它已成为最后endNode->magic = OS_MEM_NODE_MAGIC;//设置新哨兵节的魔法数字OsMemSentinelNodeSet(endNode, NULL, 0); //设置新哨兵节点内容OsMemWaterUsedRecord(poolInfo, OS_MEM_NODE_HEAD_SIZE);//更新内存池警戒线return 0;
}

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

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

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

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

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

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

相关文章

羊大师分析,羊奶助力共筑健康中国新生活

羊大师分析&#xff0c;羊奶助力共筑健康中国新生活 在健康中国行动的大背景下&#xff0c;我们越来越注重生活方式的健康与营养。羊大师发现&#xff0c;羊奶作为一种营养丰富、易于吸收的天然食品&#xff0c;正逐渐成为我们追求健康生活的得力助手。 羊奶富含优质蛋白质、矿…

vue3自定义指令​(通过指令钩子获得dom和钩子参数)

实现文本框自动获得焦点 Index.vue: <script setup> import { ref, onMounted } from vue import ./index.cssconst vFocus {mounted: (el, binding) > {el.focus()console.log(binding)} }onMounted(() > {}) </script><template><div class&qu…

设计说明-行为型-状态模式-State

状态接口 public interface State {//状态接口void insertQuarter();//投币void ejectQuarter();//退币void turnCrank();//按下“出纸巾”按钮void dispense();//出纸巾 } 有纸巾类 public class HasQuarterState implements State {private TissueMachine tissueMachine;O…

Python中tkinter编程入门4

在Python中tkinter编程入门3-CSDN博客中创建了Button控件&#xff0c;点击该控件就会产生一个点击事件&#xff0c;在创建Button控件时指定该点击事件的处理程序后&#xff0c;按键控件就会对用户的点击事件产生响应。 1 定义事件处理器 定义事件处理器就是一个自定义的函数。…

前端连续发送同一个请求时,终止上一次请求

场景&#xff1a;几个tab页之间快速的切换&#xff08;tab页只是参数不同&#xff0c;下边的数据渲染给同一个data&#xff09;就会导致如果我在1,2,3&#xff0c;tab页按照顺序快速点击&#xff0c;发送三个请求&#xff0c;我想要展示的是3但是如果1或者2请求响应的时间比3长…

Python tensor向量维度转换,不同维度的向量转化为相同的维度,经过全连接层MLP的维度转换,代码实战

问题&#xff1a;在机器学习特征工程中&#xff0c;假如每类特征需要转化为相同的维度进行拼接&#xff0c;那该怎么办呢&#xff1f;接一个全连接层MLP就可以了。 例子&#xff1a;将&#xff08;128,64&#xff09; 维度的向量转化为&#xff08;128,32&#xff09;维。 impo…

Stm32串口搭配DMA实现自定义printf、scanf

前言:本文仅供学习参考使用&#xff0c;主要目的是让大家快速使用串口调试&#xff0c;文章所提及的GCC适用于Clion&#xff0c;Vscode等第三方编辑器的用户。作者有时间会继续更新^_^ 一、GCC环境 1、标准库 (1)、使用方法 在主函数while(1)初始化中&#xff0c;添加Seria…

柯桥法语学习-5大法语听写网站:全力助攻你的dictée!

提到法语dicte&#xff0c;绝对可是法语学生们的老大难&#xff0c;简直就是心痛得不能自已啊&#xff01;所以今天&#xff0c;法语君整理了5个听写网站助攻大家的dicte哦&#xff01; Projet Voltaire 01 一个很容易让你对dicte上瘾的APP 写邮件、实习报告或者动机信时&…

Nat Plants | 植物抽核单细胞!多组学探究大豆根瘤成熟过程

发表时间&#xff1a;2023-04 发表期刊&#xff1a;Nature Plants 影响因子&#xff1a;17.352 DOI&#xff1a;10.1038/s41477-023-01387-z 研究背景 根瘤菌是亲和互作寄主植物&#xff0c;感染宿主并在根部形成共生器官根瘤&#xff0c;具有固氮…

jmeter中HttpClient4发送失败,java方法请求成功

jmeter中HttpClient4请求失败 上传文件时&#xff1a;Httpclient4: 请求体 请求头 响应结果 ,后端服务都总是提示存在非法标签。 jmeter中使用java请求成功 修改使用java方式&#xff0c;访问正常&#xff1b; 根据分析可能因为HC4对一些特殊字符会进行转义&#xff0c;转义后…

idea配置MySQL提示

点击sql语句&#xff0c;然后再选择show context actions 然后再选择Inject language or reference 然后再选择MySQL 然后我们会发现sql语句变颜色了 如果表是红色 那么需要我们连接mysql的对于的数据库

做私域不止是积累流量,生态也很重要!

如今&#xff0c;私域流水占比已经逼近整个零售市场的30%&#xff0c;达到4万亿规模&#xff0c;百度、阿里、腾讯等头部玩家也都在加速布局&#xff0c;私域运营&#xff0c;已不再是一个单一的商业模式或者运营手段&#xff0c;而是逐渐构成一种可持续的行业生态。 一、什么…

全球量子计算已开始商业化!应用最多的行业你一定想不到

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨卉可 排版丨沛贤 深度好文&#xff1a;2600字丨5 分钟阅读 01 量子计算是一场高风险的游戏 近日&#xff0c;PsiQuantum从澳大利亚联邦政府和地方政府获得了10亿澳元资金&#xff0c;这…

大屏UI:建筑可视化应用越来越广泛,根本挡不住。

建筑可视化在可视化大屏中有许多应用场景&#xff0c;以下是其中一些常见的应用场景&#xff1a; 建筑项目展示&#xff1a;可以使用建筑可视化技术展示正在进行或已完成的建筑项目。通过可视化大屏&#xff0c;可以展示建筑的外观、内部布局、材料选择等信息&#xff0c;帮助…

鬼畜作品创作必备素材,鬼畜自学语音包合集

一、素材描述 鬼畜是什么&#xff1f;鬼畜是一种网络流行语&#xff0c;也是网络文化的一种表现形式。它指的是将原本无关的两个或多个视频、音频、图片或文字进行剪辑、混合、重组等处理后&#xff0c;形成一种新的有趣、诙谐或恶搞的作品。鬼畜的制作过程通常需要一定的技术…

图像归一化处理

归一化 归一化是一种简化计算的方式&#xff0c;即将有量纲的表达式&#xff0c;经过变换&#xff0c;化为无量纲的表达式&#xff0c;成为标量。 在多种计算中都经常用到这种方法。 简单介绍 归一化是一种无量纲处理手段&#xff0c;使物理系统数值的绝对值变成某种相对值关…

C++哈希(个人笔记)

C哈希 1.unordered_mapd1.1unordered_map的构造函数1.2unorder_map的容量1.3unordered_map的迭代器1.4unordered_map的元素访问1.5unorder_map的查找1.6unordered_map的修改操作1.7unordered_map的桶操作 2.unordered_set3.unordered_set和unordered_set的笔试题4.哈希4.1哈希概…

应急救灾北斗终端手机应用方案

在应对自然灾害和紧急救援的严峻挑战中&#xff0c;技术的力量从未如此重要。为了保障救援人员能够迅速、准确地响应灾情&#xff0c;提供及时有效的救助&#xff0c;顶坚应急救灾北斗终端手机应用应运而生。这款应用依托北斗卫星导航系统的高精度定位与通信功能&#xff0c;不…

图扑智慧农业——生态鱼塘数字孪生监控

智慧农业园作为新型农业经营模式&#xff0c;正在以其高效、环保、可持续的特点受到广泛关注。智慧鱼塘作为智慧农业中一项关键技术&#xff0c;结合物联网、人工智能、云计算等技术&#xff0c;实现对新型养殖模式的实时监控、优化与管理。 效果展示 图扑软件应用自研 HT for…

巩固学习7

正则表达式 就是用来找到符合模式的字符串&#xff0c;这些模式包括&#xff1a;是什么字符&#xff0c;重复多少次&#xff0c;在什么位置&#xff0c;有哪些额外的约束 找某个字符串 import re text身高:178 体重:168 学号:123456 密码:9527 #在Python中&#xff0c;r前缀用…