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

如何初始化物理内存?

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

#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 斤 (2^2),…256 斤 (2^8) 的方式来卖。售货柜上分成了 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);
}

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

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

开发基础知识:https://qr21.cn/FV7h05

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

基于ArkTS 开发:https://qr21.cn/FV7h05

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

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我的小程序接口被刷爆了

自然流量的惊喜 书接上文&#xff0c;凭着短视频的好奇&#xff0c;搭了个小程序&#xff0c;做了文案提取&#xff0c;配音等功能&#xff0c;也顺带写了两篇口水文章&#xff0c;不曾想居然收获历史最高的点赞与收藏。有兴趣的朋友可以点这里一看究尽&#xff1a;《短视频配音…

ZL-WK-4五孔注意力测试系统

简单介绍&#xff1a; 五孔注意力测试系统基于视觉上的辨别力&#xff0c;通过运行5-CSRTT任务(5-choice serial reaction time task)&#xff0c; 测试动物的注意力、冲动性(impulsivity)等一系列行为学指标&#xff0c;主要用于注意力缺失/多动综合症(attention deficit/hype…

若依框架判断是否关闭了某个页面,或者在关闭某个页面进行操作

src\plugins\tab.js // 关闭指定tab页签closePage(obj) {console.log(obj,obj)if (obj undefined) {return store.dispatch(tagsView/delView, router.currentRoute).then(({ lastPath }) > {return router.push(lastPath || /);});}if(obj.fullPath "/plugin/workfl…

RabbitMQ消息模型之Fanout消息模型

Fanout消息模型 * 广播模型&#xff1a;* 一个交换机绑定多个队列* 每个队列都有一个消费者* 每个消费者消费自己队列中的消息&#xff0c;每个队列的信息是一样的生产者 package com.example.demo02.mq.fanout;import com.example.demo02.mq.util.ConnectionUtils; impor…

2023年金融贷款骗局套路之一

源地址&#xff1a;2023年金融贷款骗局套路之一_预防网贷套路_计算机技术网 随着无卡消费的日夜流行&#xff0c;三年疫情出现&#xff0c;钱难寻&#xff0c;难找的尴尬境地&#xff0c;贷款骗局也出现不少。今天我们讲讲最近很流行的贷款骗局之一中的一种贷款骗局。 在平常…

C++设计模式|0.前言

1.什么是设计模式&#xff1f; 简答来说&#xff0c;设计模式就是一套好用的代码经验总结&#xff0c;也就是怎么写好代码的方法论。使用设计模式是为了可重用代码、让代码更容易被他人理解、提高代码的可靠性。 2.设计模式的分类 设计模式可以分为三类&#xff1a;创建型、…

【Unity渲染】渲染管线原理

整理自B站UPKerry佬的视频【【教程】技术美术入门&#xff1a;渲染管线概述】 https://www.bilibili.com/video/BV1Q54y1G7v3/?share_sourcecopy_web&vd_source7e6249c05fba6efe32e8867373f75917 应用阶段 、几何阶段顶点处理、光栅化、片元处理、输出合并 应用阶段是CUP…

WebSocket一篇讲清楚

文章目录 WebSocket简介WebSocket与HTTP的区别WebSocket的工作原理WebSocket的应用场景WebSocket的使用WebSocket 属性WebSocket 事件WebSocket 方法 WebSocket的心跳机制WebSocket 的安全性和跨域问题如何处理&#xff1f;有哪些好用的客户端WebSocket第三方库总结 WebSocket简…

2024年 Mathorcup高校数学建模竞赛(B题)| 甲骨文识别 | 特征提取,图像分割解析,小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮200人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;通过神经网络解决甲骨文识别问题。结合特征提取&#xff0c;图像分割等多元算法&…

如何在Windows通过固定tcp公网地址ssh远程访问本地Kali Linux

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 本文主要介绍如何在Kali系统编辑SSH配置文件并结合cpolar内网穿透软件&#xff0c;实现公网环境ssh远程连接本地kali系统。 1. 启…

科技云报道:大模型加持后,数字人“更像人”了吗?

科技云报道原创。 北京冬奥运AI 虚拟人手语主播、杭州亚运会数字人点火、新华社数字记者、数字航天员小诤…当随着越来越多数字人出现在人们生活中&#xff0c;整个数字人行业也朝着多元化且广泛的应用方向发展&#xff0c;快速拓展到不同行业、不同场景。 面向C端&#xff0…

appium

app元素抓取在线工具 Appium Inspector by Appium Pro appium安装&#xff08;通过node.js安装&#xff09; Python3Appium安装使用教程_python_脚本之家 Node version is 18.17.1

CST软件中变更求解器和宏的使用技巧【操作教程】

变更求解器 变更CST MWS中的Solver&#xff01; Home > Simulation > Setup Solver CST Microwave Studio (CST MWS)总共有六个Solver。用户根据仿真目的和应用方向选择合适的Solver&#xff0c;才可以快速获得准确的结果。变更或选择Solver时&#xff0c;在Setup Sol…

DNS正反向解析

1.先连接X-shell 主服务器&#xff1a;192.168.32.168&#xff08;server&#xff09; 从服务器&#xff1a;192.168.32.169&#xff08;node&#xff09; 2.给主从服务器做准备工作 [rootserver ~]# setenforce 0 setenforce: SELinux is disabled [rootserver ~]# systemc…

盲盒App开发:探索未知惊喜,解锁潮流新玩法

在追求个性和独特体验的时代&#xff0c;盲盒文化以其独特的魅力&#xff0c;迅速占领了年轻人的心。为了满足这一市场需求&#xff0c;我们倾力打造了一款全新的盲盒App&#xff0c;带你开启一段充满未知与惊喜的探索之旅。 这款盲盒App融合了创新科技与潮流文化&#xff0c;…

Point-Nerf复现

Point-Nerf复现 0.0我自己的复现工程0.1相关库介绍0.1.1 pytorch0.1.2 h5py0.1.3 Scikit-Image0.1.4 imageio 0.2.复现简介0.3.参考链接 0.0我自己的复现工程 代码和安装包下载地址   所需库的安装包在package文件夹下&#xff0c;代码在code文件夹下&#xff0c;测试数据在…

FFmpeg: 简易ijkplayer播放器实现--05ijkplayer–连接UI界面和ffplay.c

文章目录 ijkplayer时序图消息循环--回调函数实现播放器播放时状态转换播放停止 ijkmediaPlay成员变量成员函数 ijkplayer时序图 stream_open: frame_queue_init packet_queue_init init_clock 创建read_thread线程 创建video_refresh_thread线程 消息循环–回调函数实现 ui …

003Node.js创建第一个web服务

如果用PHP来编写后端代码&#xff0c;需要用Apache或者Nginx的服务器,来处理客户的请求响应。对于Node.js时&#xff0c;不仅实现了应用&#xff0c;同时还实现了整个HTTP服务器. 安装 Node Snippets插件&#xff08;编程自带提示&#xff09; console.log(你好nodejs); //表…

PyCharm远程链接AutoDL

AutoDL使用方法&#xff1a; Step1&#xff1a;确认您安装的PyCharm是社区版还是专业版&#xff0c;只有专业版才支持远程开发功能。 Step2&#xff1a;开机实例 复制自己实例的SSH指令&#xff0c;比如&#xff1a;ssh -p 38076 rootregion-1.autodl.com 在ssh -p 38076 roo…

vue点击上传图片并实现图片预览功能,并实现多张图片放到一个数组中进行后端请求(使用原生input)

一、将 File 对象转成 BASE64 字符串 &#xff08;FileReader&#xff09; <template><div><!-- 用来显示封面的图片 --><!-- <img src"/assets/images/cover.jpg" alt"" class"cover-img" ref"imgRef" />…