首页 > 系统相关 >操作系统:内核基础实现(三)内存分配的初步实现

操作系统:内核基础实现(三)内存分配的初步实现

时间:2024-11-06 16:20:31浏览次数:1  
标签:实现 PTE PAGE MEM 内核 PDE uint32 内存

本随笔对应项目代码(更新中):https://github.com/himuhuan/HimuOS

位图的实现

KrBitMap 结构用作任意长度的常规用途一维位图的标头. 内核使用位图作为一种经济方式来跟踪一组可重用项。

struct KrBitMap {
    uint32_t Length;
    PRIVATE_DATA_MEMBER BYTE *_buffer;
};

void KrBitMapInit(struct KrBitMap *btmp, BYTE *bitsBuffer, uint32_t len);

BOOL KrBitMapCheckBit(const struct KrBitMap *btmp, uint32_t idx);

int KrBitMapFindClearBits(struct KrBitMap *btmp, uint32_t cnt);

void KrBitMapSet(struct KrBitMap *btmp, uint32_t idx, BYTE val);

#define KCHECKBIT(flag, idx) ((BYTE)(1 << idx) & (flag))
#define KSETBIT(flag, idx)   ((flag) |= (1 << idx))
#define KCLEARBIT(flag, idx) ((flag) &= ~(1 << idx))

KrBitMap 结构的使用方,必须使用 KrBitMapInit 对其进行初始化。结构本身不负责内存的分配与释放。

我们约定 PRIVATE_DATA_MEMBER 标记的成员必须以下划线加小写驼峰开头表示该调用方不应在代码中直接访问它。

使用示例

    BYTE buffer[4] = {0x00, 0x00, 0x00, 0x00}; // 32 bits
    struct KrBitMap bitmap;

    // 初始化位图
    KrBitMapInit(&bitmap, buffer, sizeof(buffer));
    KASSERT(KrBitMapFindClearBits(&bitmap, 1) == 0); // 第0位未设置
    KrBitMapSet(&bitmap, 0, 1);
    KrBitMapSet(&bitmap, 1, 1);

    KASSERT(KrBitMapFindClearBits(&bitmap, 1) == 2);
    KASSERT(KrBitMapFindClearBits(&bitmap, 2) == 2);
    KrBitMapSet(&bitmap, 2, 1);
    KASSERT(KrBitMapFindClearBits(&bitmap, 1) == 3); // 第3位未设置
    KrBitMapSet(&bitmap, 3, 0);
    KrBitMapSet(&bitmap, 2, 0);
    KASSERT(KrBitMapFindClearBits(&bitmap, 2) == 2); // 找不到连续2个未设置位
    KrBitMapSet(&bitmap, 3, 1);                      // 设置第3位
    KASSERT(KrBitMapFindClearBits(&bitmap, 2) == 4); // 找不到未设置的位

内存池 (非分页池)

HimuOS 将物理内存划分为内核内存池与用户内存池。

位图资源

HimuOS 将 PHY 0x3DF000PHY 0x3FF000 保留为维护内存池所需位图区域。在位图内 1 位就表示一个自然页 (4KB). 因此在这区域的位图最多可以映射 4,294,967,296 byte(4GiB).

HimuOS 最大支持内存
HimuOS 还需额外为用户内核内存池分别保留相同长度的位图以维护虚拟地址映射,因此最大支持内存为 2GiB, 其中用户进程最多申请大约 1 GiB 内存空间。

内存池的初始化

初始化时指定内核、用户内存池的起始物理地址与大小,以及建立位图映射管理物理页的使用情况。

注意:如图所示,HimuOS从内核堆栈顶部 (0x400000) 到PDE和PTE区域保留 0x100000 的空间,所以内核内存池从 0x600000 开始分配。

内存池大小分配策略是,除去物理内存保留为内核所用的低 0x500000 空间、PDE,内核 PTE 表空间的剩余内存,内核用户内存池均分。

所有物理内存的大小已被 LOADER 加载到内存 0xB10 处。

static void MemPoolInit(uint32_t totalMem) {
    /* 1 PDE + 1 PTE (0 PTE) + 254 PTE (769 - 1022) = 256 PAGE */
    uint32_t pageTableSize = MEM_PAGE_SIZE * 256;
    /* NOTE: HimuOS reserves space of 0x100000 from the top of the kernel stack to the PDE and PTE regions*/
    uint32_t usedMem         = pageTableSize + KRNL_PHY_STACK_TOP + 0x100000;
    uint32_t freeMem         = totalMem - usedMem;
    uint16_t allFreePages    = freeMem / MEM_PAGE_SIZE;
    uint16_t krnlFreePages   = allFreePages / 2;
    uint16_t userFreePages   = allFreePages - krnlFreePages;
    uint32_t krnlPoolBtmpLen = krnlFreePages / 8;
    uint32_t userPoolBtmpLen = userFreePages / 8;

    /* kernel pool */
    gKernelPool.PhyAddrStart = usedMem;
    gKernelPool.PoolSize     = krnlFreePages * MEM_PAGE_SIZE;
    KrBitMapInit(&gKernelPool.PoolBitmap, (BYTE *)MEM_BITMAP_BASE, krnlPoolBtmpLen);

    /* user pool */
    gUserPool.PhyAddrStart = gKernelPool.PhyAddrStart + krnlFreePages * MEM_PAGE_SIZE;
    gUserPool.PoolSize     = userFreePages * MEM_PAGE_SIZE;
    KrBitMapInit(&gUserPool.PoolBitmap, (BYTE *)MEM_BITMAP_BASE + krnlPoolBtmpLen, userPoolBtmpLen);

    /* kernel heap virtual address */
    gKernelVrAddr.VrAddrStart = MEM_KRNL_HEAP_START;
    KrBitMapInit(&gKernelVrAddr.VrAddrBitmap, (BYTE *)MEM_BITMAP_BASE + krnlPoolBtmpLen + userPoolBtmpLen,
                 krnlPoolBtmpLen);
    // clang-format off
#if _KDBG
    PrintStr("Memory Summary\n");
    PrintStr("  Available/Total: "); PrintInt(freeMem); PrintChar('/'); PrintInt(totalMem); PrintStr(" BYTES\n");
    PrintStr("  Kernel Pool: 0x"); PrintHex(gKernelPool.PhyAddrStart); PrintStr(" -> 0x"); 
    PrintHex(gKernelPool.PhyAddrStart + gKernelPool.PoolSize);
    PrintStr(" ("); PrintInt(gKernelPool.PoolSize); PrintStr(" bytes)\n");
    PrintStr("  User Pool: 0x"); PrintHex(gUserPool.PhyAddrStart); PrintStr(" -> 0x"); 
    PrintHex(gUserPool.PhyAddrStart + gUserPool.PoolSize);
    PrintStr(" ("); PrintInt(gUserPool.PoolSize); PrintStr(" bytes)\n");
#endif
    // clang-format on
}

虚拟地址

每个内存池只负责分配内存页,而对应的虚拟地址映射由结构 KR_VIRTUAL_ADDRESS 维护。

struct KR_VIRTUAL_ADDRESS {
    struct KR_BITMAP VrAddrBitmap;
    uint32_t         VrAddrStart;
};

以下代码获取在虚拟地址池中符合大小条件的首地址

static void *GetUnallocatedVrAddr(enum KR_MEMORY_POOL_TYPE type, uint32_t pageCnt) {
    int32_t vrAddrStart = 0, bitIdx = -1;
    if (type == MEMORY_POOL_KERNEL) {
        bitIdx = KrBitMapFindClearBits(&gKernelVrAddr.VrAddrBitmap, pageCnt);
        if (bitIdx == -1)
            return NULL;
        KrBitMapSetBits(&gKernelVrAddr.VrAddrBitmap, bitIdx, pageCnt, 1);
        vrAddrStart = gKernelVrAddr.VrAddrStart + bitIdx * MEM_PAGE_SIZE;
    }
    return (void *)vrAddrStart;
}

PTE & PDE 操作

对于 PDE,PTE 的动态操作基于以下事实:

  1. 在保护模式下必须对虚拟地址进行操作
  2. x86 的虚拟地址结构为:
    • 高10位:用来定位页目录表中的一个页目录项 (PDE)(页目录项中包含页表的物理地址
    • 中间10位:用于在某个页表中定位页表项 (PTE)
    • 低12位:页内偏移量
  3. 在 LOADER 阶段我们已将 最后一项 PDE 的物理地址指向 第 0 PDE

于是当给定任意虚拟地址 VAddr, 可以通过以下方式获取该地址对应的 PTE 虚拟地址:

CPU 通过三次定位寻址到真实的物理地址
将高10位指向最后 PDE,将虚拟地址的 PDE 当作 PTE,将虚拟地址的 PTE 地址(因为是中10位,乘以4以符合 12 位物理页偏移)乘以 4 当作 物理页偏移量.
由于高10位指向最后 PDE,而且最后一项 PDE 的物理地址指向 PDE 表头,这相当于“原地跳转到头部”,如此以来便使得CPU在效果上只进行了“两次”寻址。也即最后停留到 PTE 实际物理地址处

uint32_t *GetVrAddrPte(uint32_t vrAddr) {
    return (uint32_t *)(0xFFC00000 + ((vrAddr & 0xFFC00000) >> 10) + (VR_ADDR_MID_PART(vrAddr) << 2));
}

同理,PDE 获取,进行两次“原地跳转到头部”操作,并将 PDE 部分(高10位)用于物理页偏移

uint32_t *GetVrAddrPde(uint32_t vrAddr) { return (uint32_t *)(0xFFFFF000 + (VR_ADDR_HIGH_PART(vrAddr) << 2)); }

虚拟地址与物理地址的关联

在分配内存时,首先向内存池请求一个新的物理页 (返回物理地址):

static void *AllocOnePhyPage(struct KR_MEMORY_POOL *pool) {
    int idx = KrBitMapFindClearBits(&pool->PoolBitmap, 1);
    if (idx == -1)
        return NULL;
    KrBitMapSet(&pool->PoolBitmap, idx, 1);
    return (void *)(idx * MEM_PAGE_SIZE + pool->PhyAddrStart);
}

之后我们将分配好的物理页与虚拟地址关联:

/* Establishing a mapping between virtual addresses and physical addresses through PDE and PTE */
static void AddPageTableMap(void *virAddrPtr, void *phyPageAddrPtr) {
    uint32_t  vrAddr = (uint32_t)virAddrPtr, phyPageAddr = (uint32_t)phyPageAddrPtr;
    uint32_t *pde = GetVrAddrPde(virAddrPtr);
    uint32_t *pte = GetVrAddrPte(virAddrPtr);

    if (*pde & MEM_PAGE_P_1) { /* PDE exists */
        if (*pte & MEM_PAGE_P_1)
            KPanic("Double allocation of PTE addresses that have already been allocated");
        *pte = (phyPageAddr | MEM_PAGE_US_U | MEM_PAGE_RW_W | MEM_PAGE_P_1);
    } else { /* PDE not exists */
        uint32_t pdePhyAddr = (uint32_t)AllocOnePhyPage(&gKernelPool);
        *pde                = (pdePhyAddr | MEM_PAGE_US_U | MEM_PAGE_RW_W | MEM_PAGE_P_1);
        memset((void *)((int)pte & 0xFFFFF000), 0, MEM_PAGE_SIZE);
        KASSERT(!(*pte & MEM_PAGE_P_1));
        *pte = (phyPageAddr | MEM_PAGE_US_U | MEM_PAGE_RW_W | MEM_PAGE_P_1);
    }
}

由于分配内存时,虚拟地址必须是从 GetUnallocatedVrAddr 获取的,因此理论上不可能存在 PTE 与其存在映射。如果有,那就直接引发 panic。

如果 PDE P位标记为不存在,说明该 PDE 没有对应的页表,于是从内核内存池中直接获取一物理页用于 PDE 对应的 PTE 页表。由于 pte 对应的是虚拟地址对应 pte 的虚拟地址。如果只取高20位,那么该地址就表示 pte 所在表的表头,清空该页表项所在的页(以保证没有垃圾数据)。

内存池的整页分配

综上所述,要分配一物理页并与虚拟地址关联,大致步骤如下:

  1. 内核初始化时,初始化内存池
  2. 通过 GetUnallocatedVrAddr 获取尚未分配物理页的虚拟地址页
  3. AllocOnePhyPage 获取空闲的物理页
  4. 添加虚拟地址页到物理页的映射,修改虚拟地址对应的 PTE, 使其 PTE 项记录物理页地址。

使用 API KrAllocMemPage 从内存池获取中分配 pageCnt 自然页:

void *KrAllocMemPage(enum KR_MEMORY_POOL_TYPE type, uint32_t pageCnt) {
    KASSERT(pageCnt > 0);
    struct KR_MEMORY_POOL *pool;
    void                  *pagePhyAddr, *vrAddrStart;
    uint32_t               vrAddr;

    vrAddrStart = GetUnallocatedVrAddr(type, pageCnt);
    if (vrAddrStart == 0)
        return NULL;
    vrAddr = (uint32_t)vrAddrStart;
    pool   = (type & MEMORY_POOL_KERNEL) ? &gKernelPool : &gUserPool;
    while (pageCnt-- > 0) {
        pagePhyAddr = AllocOnePhyPage(pool);
        if (pagePhyAddr == NULL)
            return NULL;
        AddPageTableMap((void *)vrAddr, pagePhyAddr);
        vrAddr += MEM_PAGE_SIZE;
    }
    return vrAddrStart;
}

void *KrAllocKernelMemPage(uint32_t pageCnt) {
    void *krnlPage = KrAllocMemPage(MEMORY_POOL_KERNEL, pageCnt);
    if (krnlPage != NULL)
        memset(krnlPage, 0, pageCnt * MEM_PAGE_SIZE);
    return krnlPage;
}

标签:实现,PTE,PAGE,MEM,内核,PDE,uint32,内存
From: https://www.cnblogs.com/himu-qaq/p/18530490

相关文章

  • ssm安徽新华学院实验中心管理系统的设计与实现+jsp
    前言本安徽新华学院实验中心管理系统的设计目标是实现安徽新华学院实验中心的信息化管理,提高管理效率,使得安徽新华学院实验中心管理工作规范化、科学化、高效化。本文重点阐述了安徽新华学院实验中心管理系统的开发过程,以实际运用为开发背景,基于SSM框架,运用了JSP技术和MYS......
  • Jetson AGX Orin平台相机驱动r35.4.1升级到r35.5.0版本,vi无数据导致内核崩溃问题【有
    1.问题描述在r35.4.1中的驱动程序中相机采集正常;升级到r35.5.0没有问题后,当使用v4l2-ctl命令打印帧速率时,当没有连接传感器或传感器没有启动流时,在dmesg中发生以下错误:[1432.454398]tegra-camrtc-capture-vitegra-capture-vi:uncorr_err:requesttimedoutafter250......
  • python webdriver-manager 实现selenium 免下载安装webdriver
    selenium在自动化测试中,通常需要使用浏览器驱动来与浏览器进行交互。然而,手动下载、安装、以及管理这些驱动非常麻烦,尤其是当驱动版本频繁更新时。为此,webdriver-manager库提供了一个极简的方案,自动帮我们下载、更新和管理驱动,使Selenium代码更简洁优雅。webdriver-managergit......
  • ssm052游戏攻略网站的设计与实现+vue(论文+源码)-kaic
      毕业设计(论文)题目:游戏攻略网站设计与实现      摘 要现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本游戏攻略网站就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完......
  • .NET使用SqlSugar实现单列批量更新的几种实现和对比
    说明:SqlSugarCore版本:5.1.4.169方式1使用SqlSugar的Updateable特点:代码可读性好,易于维护支持事务和异常处理适用场景:中小型数据量更新优点:代码简洁易于调试缺点:性能相对较低内存占用较大publicasyncTask<int>BatchUpdateColumnAsync(stringtab......
  • #渗透测试#SRC漏洞挖掘# 操作系统-Linux系统基础04之内存管理
    免责声明本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章阅读。                            ......
  • 用nginx来实现搭建Hexo个人博客
    一、配置基础环境1.1关闭防火墙systemctlstopfirewalldsetenforce02.2配置阿里云yum源mkdirshell#创建shell目录cdshell#进入目录vialiyun.sh#创建名字为aliyun的文件名的shell脚本cataliyun.sh#查看,将以下内容填入#!/bin/bashr......
  • vue实现天地图电子围栏
    一、文档vue3javascriptWGS84、GCj02相互转换天地图官方文档注册登录然后申请应用key,通过CDN引入<scriptsrc="http://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥"type="text/javascript"></script>二、分析所谓电子围栏1、就是在地图商通过经纬度将点标注出......
  • QCustomPlot添加自定义的图例,实现隐藏、删除功能(一)
    文章目录实现步骤:实现代码:代码讲解:功能说明:优化建议:其他参考:要实现一个支持勾选并可以控制曲线显示和隐藏的自定义QCPLegend类,可以通过继承QCPLegend并重写其相关方法来实现。我们需要添加一个自定义的复选框元素,并捕捉用户交互来实现曲线的隐藏......
  • MySQL 索引的底层实现原理与优化策略
    在数据库中,索引是提升查询性能的关键工具。MySQL中的索引机制可以显著加快数据检索速度,尤其在数据量庞大的情况下,合理使用索引可以使得原本耗时的操作变得高效。然而,滥用或错误地使用索引也可能对性能产生负面影响。本文将深入探讨MySQL索引的底层实现原理、常用类型及其......