鸿蒙内核源码分析(物理内存篇) | 怎么管理物理内存

news2025/1/11 13:00:16

如何初始化物理内存?

鸿蒙内核物理内存采用了段页式管理,先看两个主要结构体.结构体的每个成员变量的含义都已经注解出来,请结合源码理解.

#define VM_LIST_ORDER_MAX    9    //伙伴算法分组数量,从 2^0,2^1,...,2^8 (256*4K)=1M 
#define VM_PHYS_SEG_MAX    32    //最大支持32个段

typedef struct VmPhysSeg {//物理段描述符
    PADDR_T start;            /* The start of physical memory area */ //物理内存段的开始地址
    size_t size;              /* The size of physical memory area */ //物理内存段的大小
    LosVmPage *pageBase;      /* The first page address of this area */ //本段首个物理页框地址
    SPIN_LOCK_S freeListLock; /* The buddy list spinlock */    //伙伴算法自旋锁,用于操作freeList上锁
    struct VmFreeList freeList[VM_LIST_ORDER_MAX];  /* The free pages in the buddy list */ //伙伴算法的分组,默认分成10组 2^0,2^1,...,2^VM_LIST_ORDER_MAX
    SPIN_LOCK_S lruLock;  //用于置换的自旋锁,用于操作lruList
    size_t lruSize[VM_NR_LRU_LISTS];  //5个双循环链表大小,如此方便得到size
    LOS_DL_LIST lruList[VM_NR_LRU_LISTS]; //页面置换算法,5个双循环链表头,它们分别描述五中不同类型的链表
} LosVmPhysSeg;

//注意: vmPage 中并没有虚拟地址,只有物理地址
typedef struct VmPage { //物理页框描述符
    LOS_DL_LIST         node;        /**< vm object dl list */ //虚拟内存节点,通过它挂/摘到全局g_vmPhysSeg[segID]->freeList[order]物理页框链表上
    UINT32              index;       /**< vm page index to vm object */ //索引位置
    PADDR_T             physAddr;    /**< vm page physical addr */  //物理页框起始物理地址,只能用于计算,不会用于操作(读/写数据==)
    Atomic              refCounts;   /**< vm page ref count */   //被引用次数,共享内存会被多次引用
    UINT32              flags;       /**< vm page flags */    //页标签,同时可以有多个标签(共享/引用/活动/被锁==)
    UINT8               order;       /**< vm page in which order list */ //被安置在伙伴算法的几号序列(              2^0,2^1,2^2,...,2^order)
    UINT8               segID;       /**< the segment id of vm page */ //所属段ID
    UINT16              nPages;      /**< the vm page is used for kernel heap */ //分配页数,标识从本页开始连续的几页将一块被分配
} LosVmPage;//注意:关于nPages和order的关系说明,当请求分配为5页时,order是等于3的,因为只有2^3才能满足5页的请求

理解它们是理解物理内存管理的关键,尤其是 **LosVmPage ,**鸿蒙内存模块代码通篇都能看到它的影子.内核默认最大允许管理32个段.

段页式管理简单说就是先将物理内存切成一段段,每段再切成单位为 4K 的物理页框, 页是在内核层的操作单元, 物理内存的分配,置换,缺页,内存共享,文件高速缓存的读写,都是以页为单位的,所以LosVmPage 很重要,很重要!

结构体的每个变量代表了一个个的功能点, 结构体中频繁了出现LOS_DL_LIST的身影,双向链表是鸿蒙内核最重要的结构体,在系列篇开篇就专门讲过它的重要性.

再比如 LosVmPage.refCounts 页被引用的次数,可理解被进程拥有的次数,当refCounts大于1时,被多个进程所拥有,说明这页就是共享页.当等于0时,说明没有进程在使用了,这时就可以被释放了.

看到这里熟悉JAVA的同学是不是似曾相识,这像是Java的内存回收机制.在内核层面,引用的概念不仅仅适用于内存模块,也适用于其他模块,比如文件/设备模块,同样都存在共享的场景.这些模块不在这里展开说,后续有专门的章节细讲.

段一开始是怎么划分的 ? 需要方案提供商手动配置,存在静态的全局变量中,鸿蒙默认只配置了一段.

struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX];//物理段数组,最大32段
INT32 g_vmPhysSegNum = 0; //总段数
LosVmPage *g_vmPageArray = NULL;//物理页框数组
size_t g_vmPageArraySize;//总物理页框数

/* Physical memory area array */
STATIC struct VmPhysArea g_physArea[] = {//这里只有一个区域,即只生成一个段
    {
        .start = SYS_MEM_BASE, //整个物理内存基地址,#define SYS_MEM_BASE            DDR_MEM_ADDR ,  0x80000000
        .size = SYS_MEM_SIZE_DEFAULT,//整个物理内存总大小 0x07f00000
    },
};

有了段和这些全局变量,就可以对内存初始化了. OsVmPageStartup 是对物理内存的初始化, 它被整个系统内存初始化 OsSysMemInit所调用.  直接上代码.

/******************************************************************************
 完成对物理内存整体初始化,本函数一定运行在实模式下
 1.申请大块内存g_vmPageArray存放LosVmPage,按4K一页划分物理内存存放在数组中.
******************************************************************************/
VOID OsVmPageStartup(VOID)
{
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;
    paddr_t pa;
    UINT32 nPage;
    INT32 segID;

    OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));//校正 g_physArea size

    nPage = OsVmPhysPageNumGet();//得到 g_physArea 总页数
    g_vmPageArraySize = nPage * sizeof(LosVmPage);//页表总大小
    g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//实模式下申请内存,此时还没有初始化MMU

    OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));//

    OsVmPhysSegAdd();// 完成对段的初始化
    OsVmPhysInit();// 加入空闲链表和设置置换算法,LRU(最近最久未使用)算法

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {//遍历物理段,将段切成一页一页
        seg = &g_vmPhysSeg[segID];
        nPage = seg->size >> PAGE_SHIFT;//本段总页数
        for (page = seg->pageBase, pa = seg->start; page <= seg->pageBase + nPage;//遍历,算出每个页框的物理地址
             page++, pa += PAGE_SIZE) {
            OsVmPageInit(page, pa, segID);//对物理页框进行初始化,注意每页的物理地址都不一样
        }
        OsVmPageOrderListInit(seg->pageBase, nPage);//伙伴算法初始化,将所有页加入空闲链表供分配
    }
}

结合中文注释,代码很好理解, 此番操作之后全局变量里的值就都各就各位了,可以开始工作了.

如何分配/回收物理内存? 答案是伙伴算法

伙伴算法系列篇中有说过好几篇,这里再看图理解下什么伙伴算法,伙伴算法注重物理内存的连续性,注意是连续性!

结合图比如,要分配4(2^2)页(16k)的内存空间,算法会先从free_area2中查看free链表是否为空,如果有空闲块,则从中分配,如果没有空闲块,就从它的上一级free_area3(每块32K)中分配出16K,并将多余的内存(16K)加入到free_area2中去。如果free_area3也没有空闲,则从更上一级申请空间,依次递推,直到free_area max_order,如果顶级都没有空间,那么就报告分配失败。

释放是申请的逆过程,当释放一个内存块时,先在其对于的free_area链表中查找是否有伙伴存在,如果没有伙伴块,直接将释放的块插入链表头。如果有或板块的存在,则将其从链表摘下,合并成一个大块,然后继续查找合并后的块在更大一级链表中是否有伙伴的存在,直至不能合并或者已经合并至最大块2^max_order为止。


看过系列篇文章的可能都发现了,笔者喜欢用讲故事和打比方来说明内核运作机制, 为了更好的理解,同样打个比方, 笔者认为伙伴算法很像是卖标准猪肉块的算法.

物理内存是一整头猪,已经切成了1斤1斤的了,但是还都连在一起,每一斤上都贴了个标号, 而且老板只按 1斤(2^0), 2斤(2^1), 4斤(22),…256斤(28)的方式来卖.售货柜上分成了9组

张三来了要7斤猪肉,怎么办? **给8斤,注意是给8斤啊 ,因为它要严格按它的标准来卖.**张三如果归还了,查看现有8斤组里有没有序号能连在一块的,有的话2个8斤合成16斤,放到16斤组里去. 如果没有这8斤猪肉将挂到上图中第2组(2^3)再卖.

大家脑海中有画面了吗? 那么问题来了,它为什么要这么卖猪肉,好处是什么? 简单啊:至少两个好处:

第一:卖肉速度快,效率高,标准化的东西最好卖了.

第二:可防止碎肉太多,后面的人想买大块的猪肉买不到了. 请仔细想想是不是这样的?如果每次客户来了要多少就割多少出去,运行一段时候后你还能买到10斤连在一块的猪肉吗? 很可能给是一包碎肉,里面甚至还有一两一两的边角肉,碎肉的结果必然是管理麻烦,效率低啊.如果按伙伴算法的结果是运行一段时候后,图中0,1,2各组中都有可卖的猪肉啊,张三哥归还了那8斤(其实他指向要7斤)猪肉,王五兄弟来了要6斤,直接把张三哥归还的给王五就行了.效率极高.

那么问题又来了,凡事总有两面性,它的坏处是什么? 也简单啊 :至少两个坏处:

第一:浪费了!,白给的三斤对王五没用啊,浪费的问题有其他办法解决,但不是在这个层面去解决,而是由 slab分配器解决,这里不重点说后续会专门讲slab分配器是如何解决这个问题的.

第二:合并要求太严格了,一定得是伙伴(连续)才能合并成更大的块.这样也会导致时间久了很难有大块的连续性的猪肉块.

比方打完了,鸿蒙内核是如何实现卖肉算法的呢? 请看代码

LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages)
{
    struct VmFreeList *list = NULL;
    LosVmPage *page = NULL;
    UINT32 order;
    UINT32 newOrder;

    if ((seg == NULL) || (nPages == 0)) {
        return NULL;
    }
 //因为伙伴算法分配单元是 1,2,4,8 页,比如nPages = 3时,就需要从 4号空闲链表中分,剩余的1页需要劈开放到1号空闲链表中
    order = OsVmPagesToOrder(nPages);//根据页数计算出用哪个块组
    if (order < VM_LIST_ORDER_MAX) {//order不能大于9 即:256*4K = 1M 可理解为向内核堆申请内存一次不能超过1M
        for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {//没有就找更大块
            list = &seg->freeList[newOrder];//从最合适的块处开始找
            if (LOS_ListEmpty(&list->node)) {//理想情况链表为空,说明没找到
                continue;//继续找更大块的
            }
            page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);//找第一个节点就行,因为链表上挂的都是同样大小物理页框
            goto DONE;
        }
    }
    return NULL;
DONE:
    OsVmPhysFreeListDelUnsafe(page);//将物理页框从链表上摘出来
    OsVmPhysPagesSpiltUnsafe(page, order, newOrder);//将物理页框劈开,把用不了的页再挂到对应的空闲链表上
    return page;
}

/******************************************************************************
 本函数很像卖猪肉的,拿一大块肉剁,先把多余的放回到小块肉堆里去.
 oldOrder:原本要买 2^2肉
 newOrder:却找到个 2^8肉块
******************************************************************************/
STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder)
{
    UINT32 order;
    LosVmPage *buddyPage = NULL;

    for (order = newOrder; order > oldOrder;) {//把肉剁碎的过程,把多余的肉块切成2^7,2^6...标准块,
        order--;//越切越小,逐一挂到对应的空闲链表上
        buddyPage = &page[VM_ORDER_TO_PAGES(order)];//@note_good 先把多余的肉割出来,这句代码很赞!因为LosVmPage本身是在一个大数组上,page[nPages]可直接定位
        LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX);//没挂到伙伴算法对应组块空闲链表上的物理页框的order必须是VM_LIST_ORDER_MAX
        OsVmPhysFreeListAddUnsafe(buddyPage, order);//将劈开的节点挂到对应序号的链表上,buddyPage->order = order
    }
}

为了方便理解代码细节, 这里说一种情况: 比如三哥要买3斤的,发现4斤,8斤的都没有了,只有16斤的怎么办? 注意不会给16斤,只会给4斤.这时需要把肉劈开,劈成 8,4,4,其中4斤给张三哥,将剩下的8斤,4斤挂到对应链表上. OsVmPhysPagesSpiltUnsafe 干的就是劈猪肉的活.

伙伴算法的链表是怎么初始化的,再看段代码

//初始化空闲链表,分配物理页框使用伙伴算法
STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg)
{
    int i;
    UINT32 intSave;
    struct VmFreeList *list = NULL;

    LOS_SpinInit(&seg->freeListLock);//初始化用于分配的自旋锁

    LOS_SpinLockSave(&seg->freeListLock, &intSave);
    for (i = 0; i < VM_LIST_ORDER_MAX; i++) {//遍历伙伴算法空闲块组链表
        list = &seg->freeList[i]; //一个个来
        LOS_ListInit(&list->node); //LosVmPage.node将挂到list->node上
        list->listCnt = 0;   //链表上的数量默认0
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}

鸿蒙是面向未来设计的系统,高瞻远瞩,格局远大,设计精良, 海量知识点, 对内核源码加上中文注解已有三个多月,越深入精读内核源码,越能感受到设计者的精巧用心,创新突破, 向开发者致敬. 可以毫不夸张的说鸿蒙内核源码可作为大学C语言,数据结构,操作系统,汇编语言 四门课程的教学项目.如此宝库,不深入研究实在是暴殄天物,于心不忍.

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

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

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

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

相关文章

【JavaSec】反序列化初探(配合URLDNS)

JavaSec反序列化初探&#xff08;配合URLDNS&#xff09; 文章目录 JavaSec反序列化初探&#xff08;配合URLDNS&#xff09;基本demoMap入口类Java反射 基本demo 构建一个demo 实体类&#xff1a; package bli_seri;import java.io.Serializable;public class Person implem…

Crawlab 分布式部署指南:从 Scrapy 项目到单文件的全流程详解

crawlab分布式部署 远程服务器环境搭建 同之前gerapy分布式部署一样 添加服务器防火墙端口 redis&#xff1a;6379mysql&#xff1a;3306mogodb&#xff1a;27017scrapyd&#xff1a;6800crawlab&#xff1a;8080 访问crawlab服务&#xff1a;47.93.10.129 连接远程数据库 …

Unity Dots学习 (一)

先学习怎么使用&#xff0c;再研究底层代码。Dots大家都有所耳闻。一直没时间研究&#xff0c;最近研究一下 看上图可知&#xff0c;哪怕是CPU的第三级缓存也比内存要快2-5倍。 资料&#xff1a; 《DOTS之路》第零节——前导课(1)——DOTS的5W1H问题_哔哩哔哩_bilibili 《DOT…

javaweb的新能源充电系统pf

TOC springboot339javaweb的新能源充电系统pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…

SVG中的paint-order属性实现文字描边

过去只支持 SVG 元素 paint-order&#xff0c;表示绘制的顺序。 对于一个图形的绘制&#xff0c;顺序还是非常重要的。例如用SVG来绘制一个带边框的矩形 <style>rect{fill: #FFE8A3;stroke: #9747FF;stroke-width: 4;} </style><svg viewBox"0 0 300 30…

XSS-DOM

文章目录 源码SVG标签Dom-Clobbringtostring 源码 <script>const data decodeURIComponent(location.hash.substr(1));;const root document.createElement(div);root.innerHTML data;// 这里模拟了XSS过滤的过程&#xff0c;方法是移除所有属性&#xff0c;sanitize…

[数据集][图像分类]波色绝缘子缺失分类数据集1440张2类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;1440 分类类别数&#xff1a;2 类别名称:["missing","norma…

轻量高效的ControlNet开源 | ControlNetXt:支持主流生成架构,可与LoRA无缝集成!

当前的可控生成方法如ControlNet、Adapaters和ReferenceNet等通常需要大量额外的计算资源&#xff0c;尤其是对于视频生成&#xff0c;并且在训练中面临挑战或表现出较弱的控制力。 对此&#xff0c;港中文提出了一种轻量级可控模块&#xff1a;ControlNeXt&#xff0c;这是一…

PCIe Linux MRRS和MPS参数设置策略

1.概述 MPS&#xff08;Max Payload Size&#xff09;和MRRS&#xff08;Max Read Request Size&#xff09;共同影响PCIe总线的传输效率。如果MPS和MRRS设置的过小&#xff0c;传输相同长度的数据&#xff0c;需要更多的TLP报文&#xff0c;导致PCIe总线传输效率降低&#xf…

PHP多项目多场景排队叫号系统源码

&#x1f514;&#x1f4c8;多项目多场景排队叫号系统&#xff0c;让等待也高效有序&#xff01; 一、告别无序等待&#xff0c;智能排队新风尚 你是否曾在医院、银行或政务大厅等地方&#xff0c;面对冗长的队伍感到无奈&#xff1f;多项目多场景排队叫号系统&#xff0c;正…

Mybatis的分页,延迟加载和缓存

目录 分页&#xff1a; 方式一&#xff1a;利用 limit 实现物理分页 利用limit的关键字分页 方式二&#xff1a;RowBounds集合逻辑分页 方式三&#xff1a;插件分页 延迟加载和立即加载&#xff1a; 什么是立即加载&#xff1a; 什么是延迟加载 延迟加载的配置 缓存&a…

XSS漏洞洞讲解

目录 一、XSS漏洞的定义 1.什么是XSS漏洞&#xff1f; 二、XSS漏洞的类型 1.反射型 XSS 2.DOM型 XSS 3.存储型 XSS 三、实战案例演练 第1关 Ma Spaghet 第2关 Jefff 第3关 Ugandan Knuckles 第4关 Ricardo Milos 第5关 Ah Thats Hawt 第6关 Ligma ​第7关 Mafia …

c++ 使用Tesseract5.0 识别图片文字示例

Tesseract5.0相对于旧版本的程序&#xff0c;识别精准度会提升不少&#xff0c;如下&#xff1a; 1、示例1&#xff1a; 图片&#xff1a; 结果&#xff1a; 2、示例2&#xff1a; 图片&#xff1a; 结果&#xff1a; c代码如下&#xff1a; #include <iostream> #in…

C++ 设计模式——建造者模式

建造者模式 建造者模式组成部分建造者模式使用步骤1. 定义产品类2. 创建具体产品类3. 创建建造者接口4. 实现具体建造者5. 创建指挥者类6. 客户端代码 建造者模式 UML 图建造者模式 UML 图解析建造者模式的优缺点建造者模式的适用场景完整代码 建造者模式 建造者模式&#xff…

Hogan 阻抗控制的理解

机器人阻抗控制是一种基于力的控制方法,它通过调节机器人在受到外部力作用时所表现出的抵抗能力(即阻抗),来实现与环境的良好交互。以下是对机器人阻抗控制的详细理解: 一、阻抗控制的基本原理 阻抗控制的核心思想是通过模拟物体的力学特性(如刚度、阻尼和质量),使机…

定时器处理按键抖动

一、按键的抖动 1.按键的定义和原因 按键抖动是由于‌机械按键在闭合和断开时&#xff0c;由于触点的弹性作用&#xff0c;会产生一系列的抖动现象。这种抖动对于人类来说几乎感觉不到&#xff0c;但对‌单片机来说&#xff0c;却是一个可以感应到的过程&#xff0c;且处理时…

SQL - 设计数据库

数据建模 数据建模就是为要存储在数据库中的数据创建模型的过程 步骤 1.理解和分析业务需求 (收集信息) 收集需求&#xff0c;明确业务流程&#xff0c;定义数据需求&#xff0c;分析业务规则 2.构建业务的概念模型 (识别和表示业务中实体、事务或概念以及它们之间的关系) 识别…

APP支付宝授权获取code uniapp

1.点击使用plus.runtime跳转打开支付宝 //打开支付宝授权&#xff0c;在支付宝APP中授权后会在支付宝中跳转到你填写的h5地址//urls是授权地址可以后端拼接也可以前端写死 //以下是一个拼接示例&#xff0c;需修改app_id的值和redirect_uri的值即可 //app_id是商户的APPID&…

WorkPlus-为用户提供IM即时通讯和实时音视频通信本地化服务

WorkPlus作为一家领先的企业级通讯解决方案提供商&#xff0c;为用户提供了本地化服务&#xff0c;以满足IM即时通讯和实时音视频通信的需求。本文将深入探讨WorkPlus本地化服务的重要性以及其为用户提供的IM即时通讯和实时音视频通信的解决方案。 一、本地化服务的意义 低延迟…

【动态规划、dp】[CSP-J 2022] 上升点列 题解

题目描述 在一个二维平面内&#xff0c;给定 n n n 个整数点 ( x i , y i ) (x_i, y_i) (xi​,yi​)&#xff0c;此外你还可以自由添加 k k k 个整数点。 你在自由添加 k k k 个点后&#xff0c;还需要从 n k n k nk 个点中选出若干个整数点并组成一个序列&#xff0c…