ucore 实验物理内存管理篇

实验汲取知识

  • 基于段页式内存地址的转换机制
  • 页表的建立和使用方法
  • 物理内存的管理方法

首先了解如何发现系统中的物理内存;然后了解如何建立对物理内存的初步管理,即了解连续物理内存管理;最后了解页表相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。

本次采用的首次适应算法firstfit分配

如何设计物理内存页的分配算法,最后比较详细地分析了在80386的段页式硬件机制下,ucore操作系统把段式内存管理的功能弱化,并实现以分页为主的页式内存管理的过程。

实验工作内容

在代码分析上,建议根据执行流程来直接看源代码,并可采用GDB源码调试的手段来动态地分析ucore的执行过程。内存管理相关的总体控制函数是pmm_init函数,它完成的主要工作包括:

  1. 初始化物理内存页管理器框架pmm_manager;
初始化连续物理页,加入到空闲链表
//<<它初始化了一段连续的物理页,并将它们添加到空闲页链表中。>>
static void default_init_memmap(struct Page *base, size_t n) {//初始化分配内存assert(n > 0); // 确保要初始化的页数大于0struct Page *p = base; // 定义一个指向物理页的指针,并初始化为输入参数 base,即起始页// 循环遍历从 base 到 base + n - 1 的物理页for (; p != base + n; p ++) {assert(PageReserved(p)); // 确保当前页 p 是保留页(已被内核占用)// 将当前页的标志位 flags 和属性 property 清零,表示这些页现在是空闲的,没有被任何进程占用p->flags = p->property = 0; // 将当前页的引用计数设置为 0,表示当前页没有被引用set_page_ref(p, 0); }// 将起始页 base 的属性 property 设置为 n,表示这个连续的内存块包含了 n 个页base->property = n; // 将起始页 base 标记为保留页,表示这个内存块已经被占用SetPageProperty(base); // 增加空闲页计数器 nr_free,表示有 n 个页现在是空闲的nr_free += n; // 将起始页 base 添加到空闲页链表 free_list 中,表示这个内存块是空闲的,可以用于后续的分配操作list_add(&free_list, &(base->page_link)); 
}
分配物理页,并把剩余合并
//<<这段代码的主要功能是根据请求的页数从空闲页链表中分配一页或多页的物理内存。 >>static struct Page *default_alloc_pages(size_t n) {assert(n > 0); // 确保请求分配的页数大于0if (n > nr_free) { // 如果请求的页数大于空闲页数,则无法满足分配要求,返回 NULLreturn NULL;}struct Page *page = NULL; // 初始化分配的页为 NULLlist_entry_t *le = &free_list; // 初始化链表指针 le 指向空闲页链表的头部// 遍历空闲页链表,查找第一个空闲块大小大于等于请求页数的页while ((le = list_next(le)) != &free_list) {struct Page *p = le2page(le, page_link); // 获取当前空闲页的结构体指针if (p->property >= n) { // 如果当前空闲页大小满足请求页数page = p; // 将当前空闲页指针保存到 pagebreak; // 结束循环}}if (page != NULL) { // 如果找到了符合条件的空闲页list_del(&(page->page_link)); // 将找到的空闲页从空闲页链表中删除if (page->property > n) { // 如果找到的空闲页大于请求的页数struct Page *p = page + n; // 计算剩余空闲页的地址,pos来算的p->property = page->property - n; // 更新剩余空闲页的大小list_add(&free_list, &(p->page_link)); // 将剩余空闲页添加到空闲页链表中}nr_free -= n; // 更新空闲页数ClearPageProperty(page); // 清除空闲页的保留标志}return page; // 返回分配的页的指针
}
释放物理页
//这段代码主要实现了释放内存页的功能。首先,它确保了要释放的内存页数量大于 0,并且要释放的内存页已经被标记为保留页。
//然后,它将要释放的内存页添加到空闲页链表中,并尝试将连续的空闲页合并起来。
//最后,它更新了空闲页的数量并结束了函数的执行。
static void default_free_pages(struct Page *base, size_t n) {assert(n > 0); // 确保要释放的内存页数量大于 0assert(PageReserved(base)); // 确保要释放的内存页已经被标记为保留页,即已被内核占用,保留页就是内核占用// 初始化指向空闲页链表的指针 le,声明循环中的指针 plist_entry_t *le = &free_list;struct Page * p;// 循环遍历空闲页链表,找到一个比 base 大的空闲页地址,p 指向的是第一个大于 base 的空闲页while((le = list_next(le)) != &free_list) {p = le2page(le, page_link);if(p > base){break;}}// 将要释放的内存页添加到空闲页链表中for(p = base; p < base + n; p++){list_add_before(le, &(p->page_link));}// 将释放的内存页的标志重置base->flags = 0; // 清除页的标志set_page_ref(base, 0); // 设置页的引用计数为 0ClearPageProperty(base); // 清除页的保留属性SetPageProperty(base); // 设置页的属性为保留base->property = n; // 设置页的属性为 n,表示该页是 n 个连续的页// 如果释放的内存页后面的空闲页也是连续的,则合并这些连续的空闲页p = le2page(le, page_link);if(base + n == p){base->property += p->property; // 合并连续的空闲页p->property = 0; // 将原来的空闲页属性清零}// 如果释放的内存页前面的空闲页也是连续的,则合并这些连续的空闲页le = list_prev(&(base->page_link));p = le2page(le, page_link);if(le != &free_list && p == base - 1){while(le != &free_list){if(p->property){p->property += base->property; // 合并连续的空闲页base->property = 0; // 将原来的空闲页属性清零break;}le = list_prev(le);p = le2page(le, page_link);}}nr_free += n; // 更新空闲页的数量return; // 结束函数
}
通过线性地址获取二级页表,返回虚拟地址
//get_pte - get pte and return the kernel virtual address of this pte for la
//        - if the PT contains this pte didn't exist, alloc a page for PT
//        通过线性地址(linear address)得到一个页表项(二级页表项)(Page Table Entry),并返回该页表项结构的内核虚拟地址
//        如果应该包含该线性地址对应页表项的那个页表不存在,则分配一个物理页用于存放这个新创建的页表(Page Table)
// parameter: 参数
//  pgdir:  the kernel virtual base address of PDT   页目录表(一级页表)的起始内核虚拟地址
//  la:     the linear address need to map              需要被映射关联的线性虚拟地址
//  create: a logical value to decide if alloc a page for PT   一个布尔变量决定对应页表项所属的页表不存在时,是否将页表创建
// return vaule: the kernel virtual address of this pte  返回值: la参数对应的二级页表项结构的内核虚拟地址
pte_t * get_pte(pde_t *pgdir, uintptr_t la, bool create) {/* LAB2 EXERCISE 2: YOUR CODE** If you need to visit a physical address, please use KADDR()* please read pmm.h for useful macros** Maybe you want help comment, BELOW comments can help you finish the code** Some Useful MACROs and DEFINEs, you can use them in below implementation.* MACROs or Functions:*   PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.*   KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address.*   set_page_ref(page,1) : means the page be referenced by one time*   page2pa(page): get the physical address of memory which this (struct Page *) page  manages*   struct Page * alloc_page() : allocation a page*   memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s*                                       to the specified value c.* DEFINEs:*   PTE_P           0x001                   // page table/directory entry flags bit : Present*   PTE_W           0x002                   // page table/directory entry flags bit : Writeable*   PTE_U           0x004                   // page table/directory entry flags bit : User can access*/
#if 0pde_t *pdep = NULL;   // (1) find page directory entryif (0) {              // (2) check if entry is not present// (3) check if creating is needed, then alloc page for page table// CAUTION: this page is used for page table, not for common data page// (4) set page referenceuintptr_t pa = 0; // (5) get linear address of page// (6) clear page content using memset// (7) set page directory entry's permission}return NULL;          // (8) return page table entry
#endif// PDX(la) 根据la的高10位获得对应的页目录项(一级页表中的某一项)索引(页目录项)// &pgdir[PDX(la)] 根据一级页表项索引从一级页表中找到对应的页目录项指针pde_t *pdep = &pgdir[PDX(la)];// 判断当前页目录项的Present存在位是否为1(对应的二级页表是否存在)if (!(*pdep & PTE_P)) {// 对应的二级页表不存在// *page指向的是这个新创建的二级页表基地址struct Page *page;if (!create || (page = alloc_page()) == NULL) {// 如果create参数为false或是alloc_page分配物理内存失败return NULL;}// 二级页表所对应的物理页 引用数为1set_page_ref(page, 1);// 获得page变量的物理地址uintptr_t pa = page2pa(page);// 将整个page所在的物理页格式胡,全部填满0memset(KADDR(pa), 0, PGSIZE);// la对应的一级页目录项进行赋值,使其指向新创建的二级页表(页表中的数据被MMU直接处理,为了映射效率存放的都是物理地址)// 或PTE_U/PTE_W/PET_P 标识当前页目录项是用户级别的、可写的、已存在的*pdep = pa | PTE_U | PTE_W | PTE_P;}// 要想通过C语言中的数组来访问对应数据,需要的是数组基址(虚拟地址),而*pdep中页目录表项中存放了对应二级页表的一个物理地址// PDE_ADDR将*pdep的低12位抹零对齐(指向二级页表的起始基地址),再通过KADDR转为内核虚拟地址,进行数组访问// PTX(la)获得la线性地址的中间10位部分,即二级页表中对应页表项的索引下标。这样便能得到la对应的二级页表项了return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
}
释放映射
//总的来说,这段代码的作用是从页表中移除指定线性地址 la 相关的页表项,并释放与之相关的物理页,最后确保 TLB 中的条目失效,以保证内存访问的一致性和正确性。
//page_remove_pte - free an Page sturct which is related linear address la
//                - and clean(invalidate) pte which is related linear address la
//note: PT is changed, so the TLB need to be invalidate 
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {/* LAB2 EXERCISE 3: YOUR CODE** Please check if ptep is valid, and tlb must be manually updated if mapping is updated** Maybe you want help comment, BELOW comments can help you finish the code** Some Useful MACROs and DEFINEs, you can use them in below implementation.* MACROs or Functions:*   struct Page *page pte2page(*ptep): get the according page from the value of a ptep*   free_page : free a page*   page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free.*   tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being*                        edited are the ones currently in use by the processor.* DEFINEs:*   PTE_P           0x001                   // page table/directory entry flags bit : Present*/
#if 0if (0) {                      //(1) check if this page table entry is presentstruct Page *page = NULL; //(2) find corresponding page to pte//(3) decrease page reference//(4) and free this page when page reference reachs 0//(5) clear second page table entry//(6) flush tlb}
#endif
if (*ptep & PTE_P) {struct Page *page = pte2page(*ptep);if (page_ref_dec(page) == 0) {free_page(page);}*ptep = 0;tlb_invalidate(pgdir, la);}
}

总结

pmm_manager 接口:在实现 pmm_manager 框架时,理解了每个函数的作用和接口约定。这些函数包括初始化函数 init、内存映射初始化函数 init_memmap、分配页面函数 alloc_pages、释放页面函数 free_pages 等,以及处理各种异常情况和错误场景,比如内存不足、页表项不存在、TLB 刷新等。运用了 pmm.h 中定义的宏和函数,如 PDXPTXKADDRpage2pa 等,这些宏和函数可以帮助我更方便地操作物理内存管理相关的数据结构和地址转换

  1. PDXPTX 宏用于从线性地址中提取页目录索引和页表索引。给定一个线性地址,这些宏通过位运算操作,提取出对应的页目录项和页表项在页目录和页表数组中的索引。

  2. KADDR 宏用于将物理地址转换为内核虚拟地址。在操作系统内核中,为了方便对物理地址进行访问,需要将物理地址映射到虚拟地址空间中。KADDR 宏就是用来完成这一转换的。

  3. page2pa 函数用于从 struct Page 结构体中提取出物理地址。在操作系统中,用 struct Page 结构体来表示一个物理页,其中包含了页的属性和地址等信息。page2pa 函数就是从这个结构体中提取出物理地址的函数。

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

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

相关文章

算法--目录

algorithm: 十种排序算法 二分法-各种应用 algorithm: 拓扑排序 算法中的背包问题 最长子序列问题 前缀和-解题集合 差分数组-解题

第41篇:有限状态机<四>

Q&#xff1a;本期我们介绍有限状态机的应用之二&#xff1a;米里状态机“1101”序列检测器。 A&#xff1a;摩尔状态机1101序列检测器有5个状态&#xff0c;而米里状态机只有4个状态。当状态为s_3且输入为1时&#xff0c;状态机输出1。这里输出与输入一起被标志在状态转移箭头…

vue3 动态class和style

1、需求&#xff1a;一个删除的弹窗&#xff0c;点击会提示“是否需要删除XXXXX&#xff08;name&#xff09;”&#xff0c;但是name不固定&#xff0c;所以删除弹窗的width不能写死。&#xff08;如果不设置width&#xff0c;本项目的弹窗会自适应变得特别长&#xff09;

mybatis自制插件+注解实现数据脱敏

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 mybatis自制插件注解实现数据脱敏 前言数据脱敏的实现方式构思从哪个地方进行脱敏&#xff1f;它怎么知道我什么数据需要脱敏 项目实现拦截器实现注解实现枚举实现效果图展示 前言 在数字时代&#x…

Java哈希查找(含面试大厂题和源码)

哈希查找&#xff08;Hash Search&#xff09;是一种基于哈希表&#xff08;Hash Table&#xff09;的数据查找方法。哈希表通过使用哈希函数将键&#xff08;Key&#xff09;映射到表中的位置来存储数据&#xff0c;从而实现快速的数据访问。哈希查找的效率通常取决于哈希函数…

hive了解系列一

“ 随着智能手机的普及&#xff0c;互联网时代红利的爆发&#xff0c;用户数量和产生的数据也越发庞大。为了解决这个问题&#xff0c;提高数据的使用价值。 Hadoop生态系统就被广泛得到应用。 在早期&#xff0c;Hadoop生态系统就是为处理如此大数据集而产生的一个合乎成本效益…

力扣第20题有效的括号

typedef char STDataType; //动态栈 #define allocator_may_return_null 1typedef struct ST {STDataType* _a;int _top;//栈顶元素int _capacity;//最大容量 }Stack; //初始化栈 void StackInit(Stack *pst);//入栈 void StackPush(Stack* pst, STDataType x);//出栈 void Sta…

英语写作中“大量的”“重大的”“显著的”substantial、considerable、significant的用法

一般“大量的”“重大的”会用a great number of 、a great amount of 、a plenty of 、great等&#xff0c;这些表达都过于trivial &#xff0c;用好substantial、considerable、significant 会对写作增色不少。 一、对于可数事物&#xff0c;用a considerable/substantial n…

小程序变更主体需要多久?

小程序迁移变更主体有什么作用&#xff1f;小程序迁移变更主体的好处有很多哦&#xff01;比如可以获得更多权限功能、公司变更或注销时可以保证账号的正常使用、收购账号后可以改变归属权或使用权等等。小程序迁移变更主体的条件有哪些&#xff1f;1、新主体必须是企业主体&am…

每日OJ题_BFS解决最短路①_力扣1926. 迷宫中离入口最近的出口

目录 力扣1926. 迷宫中离入口最近的出口 解析代码 力扣1926. 迷宫中离入口最近的出口 1926. 迷宫中离入口最近的出口 难度 中等 给你一个 m x n 的迷宫矩阵 maze &#xff08;下标从 0 开始&#xff09;&#xff0c;矩阵中有空格子&#xff08;用 . 表示&#xff09;和墙&…

Hibernate入门经典与注解式开发大全

本博文主要讲解介绍Hibernate框架&#xff0c;ORM的概念和Hibernate入门&#xff0c;相信你们看了就会使用Hibernate了! 什么是Hibernate框架&#xff1f; Hibernate是一种ORM框架&#xff0c;全称为 Object_Relative DateBase-Mapping&#xff0c;在Java对象与关系数据库之间建…

【uniapp踩坑记】使用z-paging组件,微信小程序端加载不出来问题解决

使用z-paging组件&#xff0c;h5端加载正常&#xff0c;微信小程序端显示空白 今天做分页列表&#xff0c;在插件市场找到了z-paging&#xff0c;照着示例代码写了进去&#xff0c;在h5端能正常使用&#xff0c;在小程序端一直显示空白 尝试过以下无效操作&#xff1a; 1.清除所…

Scrapy 框架基础

Scrapy框架基础Scrapy框架进阶 Scrapy 框架基础 【一】框架介绍 【1】简介 Scrapy是一个用于网络爬取的快速高级框架&#xff0c;使用Python编写他不仅可以用于数据挖掘&#xff0c;还可以用于检测和自动化测试等任务 【2】框架 官网链接https://docs.scrapy.org/en/late…

WPS二次开发系列:WPS SDk功能就概览

作者持续关注WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 作者通过深度测试使用了WPS SDK提供的Demo&#xff0…

给你的 vscode 扩展增加测试设置

文章目录 1. 目的2. vitest 作为 vscode 扩展单元测试3. vscode-test 集成测试4. 自定义 Runner 集成测试5. 小结 1. 目的 vscode 作为当前最多人使用的编辑器和开发工具&#xff0c;其最强大之处就是有成熟的插件社区&#xff0c;但是使用过程中难免就会遇到插件功能不够称心…

Python 内置函数 format() 详解

在 Python 中&#xff0c;format() 是一个内置函数&#xff0c;用于格式化字符串。它提供了灵活的方式来将变量插入到字符串中&#xff0c;并控制它们的显示格式。让我们深入了解一下这个函数的用法、参数、示例以及注意事项。 1. format() 函数概述 format() 函数是 Python …

SPI 设备驱动编写流程:SPI 设备数据收发处理流程

一. 简介 前面一篇文章学习了SPI设备驱动数据收发过程中&#xff0c;涉及的结构体与函数&#xff0c;文章如下&#xff1a; SPI 设备驱动编写流程&#xff1a;SPI 设备数据收发处理流程中涉及的结构体与函数-CSDN博客 本文学习SPI设备驱动中数据收发处理过程。 二. SPI 设备…

P8649 [蓝桥杯 2017 省 B] k 倍区间(同余定理)

# [蓝桥杯 2017 省 B] k 倍区间 ## 题目描述 给定一个长度为 $N$ 的数列&#xff0c;$A_1,A_2, \cdots A_N$&#xff0c;如果其中一段连续的子序列 $A_i,A_{i1}, \cdots A_j(i \le j)$ 之和是 $K$ 的倍数&#xff0c;我们就称这个区间 $[i,j]$ 是 $K$ 倍区间。 你能求出数列…

ThreadX在STM32上的移植:通用启动文件tx_initialize_low_level.s

在嵌入式系统开发中&#xff0c;实时操作系统&#xff08;RTOS&#xff09;的选择对于系统性能和稳定性至关重要。ThreadX是一种广泛使用的RTOS&#xff0c;它以其小巧、快速和可靠而闻名。在本文中&#xff0c;我们将探讨如何将ThreadX移植到STM32微控制器上&#xff0c;特别是…

#381. 四边形继承练习

太爽了 甚至还现学了叉积判断线段是否相交和求面积的方法 先给出我的代码&#xff1a; #include <iostream> #include <vector> #include <iomanip> #include <cmath>using namespace std;//下面需要补充多个类的声明及实现代码 const double EPS 1…