ChCore-lab2

news/2024/11/9 0:19:48/文章来源:https://www.cnblogs.com/mumujun12345/p/18536158

lab 2: Memory Manage(working)

新的环境好像不支持arm架构了,总是会在make build触发错误
exec chbuild not found. 我们于是只能使用utm平台+qemu模拟amd64架构的ubuntu系统来进行运行。
首先我们还是先进行make build来获得我们想要的环境。

1 Buddy System

练习题1: 完成 kernel/mm/buddy.c 中的 split_chunkmerge_chunkbuddy_get_pages、 和 buddy_free_pages 函数中的 LAB 2 TODO 1 部分,其中 buddy_get_pages 用于分配指定阶大小的连续物理页,buddy_free_pages 用于释放已分配的连续物理页。

我们首先先来了解一下我们需要完成的任务。

  1. 从内存中获取自由链表的情况。
  2. 在分配相对应的链表时,根据阶次进行相对应地拆分,同时修改自由链表的填写情况。
  3. 释放已经分配的页,并将他们合成。

我们首先阅读一下kernel/mm/buddy.c的代码。

第一部分:初始化init_buddy()

在这一部分,我们首先初始化了物理内存池的相关数据。例如起始地址,起始页表,起始内存大小等。这里我们的每块伙伴页表的大小等常量的定义位于kernel/include/mm/buddy.h中。

这里页表的最大阶次规定为14,并且其伙伴页表的最大大小为0x1000, 因此我们的伙伴页表阶次为12.

接下来需要了解我们所需要的各个结构体组成。我们在kernel/include/mm/buddy.hkernel/include/common/list.h里面可以找到相关的叙述。

page结构体

  • 节点(list_head)
  • 分配状态(int)
  • 阶次(int)
  • ...

free_list结构体

  • free_list(list_head)
  • 自由页表的个数(nr_free)

物理线程池

  • 起始地址(虚拟地址)
  • 地址大小
  • 页表相关数据
  • 内存锁
  • 自由链表(不同阶次的)
  • 物理页表数目

list_head结构体

  • 前向指针
  • 后向指针
  • 初始化双向链表
  • 增加,添加,删除,清零,寻找下一个容器,列表下标,简单迭代器...

其中list_head里有一些宏定义十分有意思。我们之后会来看一看。

1.1 获得未分配的页表

首先我们先要分配伙伴链表。这时我们需要

  • 寻找空闲链表。
  • 从空闲链表去除伙伴块。
  • 检查伙伴块阶次并根据情况进行分裂。

因此,首先我们先完成buddy_get_pages()部分。在这一部分中,我们要为伙伴系统分配空闲的物理页,并将其放入到自由链表中。

struct page *buddy_get_pages(struct phys_mem_pool *pool, int order)
{int cur_order;struct list_head *free_list;struct page *page = NULL;if (unlikely(order >= BUDDY_MAX_ORDER)) {kwarn("ChCore does not support allocating such too large ""contious physical memory\n");return NULL;}lock(&pool->buddy_lock);/* LAB 2 TODO 1 BEGIN *//** Hint: Find a chunk that satisfies the order requirement* in the free lists, then split it if necessary.*//* BLANK BEGIN */// UNUSED(cur_order);// UNUSED(free_list);for (cur_order = order; cur_order < BUDDY_MAX_ORDER; cur_order++){// 检查是否具有空闲链表。if(pool -> free_lists[cur_order].nr_free != 0) {// 获取对应阶次的空闲链表头。free_list = &(pool->free_lists[cur_order].free_list);// 获取这个链表节点所对应的页表头。检查这一条链表是否为空。if (!list_empty(free_list)){/*** 强行从free_list->next作为其成员,计算出page结构的首地址* 然后分配一段新的内存空间给这个页表。* 因此我们需要将list_head->next,也就是第一个页表节点传入,计算出其在page中的偏移量* 然后让编译器进行类型转换,将包含有链表指向的地址的一段地址解释成为新的结构体。*/ page = list_entry(free_list->next, struct page, node);// 分配空间之后其余的值均为随机值需要进行更改。page -> allocated = 0;page -> order = cur_order;page -> pool = pool;break;}}}if (page == NULL)goto out;// 分配了页表,删除掉自由链表内的该值。减少改阶次下的链表数。list_del(&page -> node);pool -> free_lists[cur_order].nr_free -= 1;// 必要的情况下分裂链表。page = split_chunk(pool, order, page);page -> allocated = 1;/* BLANK END *//* LAB 2 TODO 1 END */
out: __maybe_unusedunlock(&pool->buddy_lock);return page;
}

1.2 分裂部分

接下来我们要完成分裂链表部分,采用递归进行。注意每次我们都需要减少order后获得新的伙伴块,直到获得的伙伴块和我们需要的order相同。
我们需要自行不断减少页表的order数,通过get_buddy_chunk()的方式,从当前的页表中获得其子页(伙伴页),直到符合条件为止。
采用递归的方式进行分裂,我们可以得到:

__maybe_unused static struct page *split_chunk(struct phys_mem_pool *__maybe_unused pool,int __maybe_unused order,struct page *__maybe_unused chunk)
{/* LAB 2 TODO 1 BEGIN *//** Hint: Recursively put the buddy of current chunk into* a suitable free list.*//* BLANK BEGIN */int cur_order = chunk -> order;if (cur_order == order)return chunk;else{// 分裂链表。chunk->order -= 1;struct list_head *free_list = &(pool -> free_lists[chunk->order].free_list);struct page *buddy_chunk;buddy_chunk = get_buddy_chunk(pool, chunk);buddy_chunk -> allocated = 0;buddy_chunk -> order = chunk -> order;buddy_chunk -> pool = chunk -> pool;buddy_chunk -> slab = chunk -> slab;list_add(&(buddy_chunk -> node), free_list);pool -> free_lists[buddy_chunk->order].nr_free += 1;return split_chunk(pool, order, chunk);}       /* BLANK END *//* LAB 2 TODO 1 END */
}

接下来完成释放页表部分。只需要将页表标记成空闲后,尝试合并页表块,将合并的页表块加入到对应order的自由链表之中。

void buddy_free_pages(struct phys_mem_pool *pool, struct page *page)
{int order;struct list_head *free_list;lock(&pool->buddy_lock);/* LAB 2 TODO 1 BEGIN *//** Hint: Merge the chunk with its buddy and put it into* a suitable free list.*//* BLANK BEGIN */// UNUSED(free_list);// UNUSED(order);page->allocated = 0;page = merge_chunk(pool, page);list_add(&page->node, &pool->free_lists[page->order].free_list);pool->free_lists[page->order].nr_free += 1;/* BLANK END *//* LAB 2 TODO 1 END */unlock(&pool->buddy_lock);
}

递归式合并伙伴。只需要每次获取chunk直到无法获取更高阶次的为止。注意检查阶次是否到达合并顶点,是否被分配或伙伴页表为空的情况。

/* The most recursion level of merge_chunk is decided by the macro of* BUDDY_MAX_ORDER. */
__maybe_unused static struct page * merge_chunk(struct phys_mem_pool *__maybe_unused pool,struct page *__maybe_unused chunk)
{/* LAB 2 TODO 1 BEGIN *//** Hint: Recursively merge current chunk with its buddy* if possible.*//* BLANK BEGIN */if (chunk->order == BUDDY_MAX_ORDER - 1) return chunk;struct page *buddy = get_buddy_chunk(pool, chunk);if (buddy == NULL || buddy->allocated == 1 || buddy->order != chunk->order) return chunk;else {list_del(&buddy->node);--pool->free_lists[buddy->order].nr_free;if (chunk > buddy) chunk = buddy;chunk->order += 1;return merge_chunk(pool, chunk);}/* BLANK END *//* LAB 2 TODO 1 END */
}

伙伴系统完成。
其中一个很重要的宏定义函数完成了通过结构体成员构造新结构体的方式。即我们有:

list_entry(ptr, type, field);
// 第一个参数是我们需要的结构体成员,第二个是我们想要转换成的类型,
// 第三个是在想要转换成为的类型中的成员。
// 本质上计算了成员与结构体开始地址的差值,
// 并告诉编译器当前所需要的成员所在的结构体的首地址。
// 宏定义如下:
#define list_entry(ptr, type, field) \container_of(ptr, type, field)#define container_of(ptr, type, field) \((type *)((void *)(ptr) - (void *)(&(((type *)(0))->field))))

2. SLAB分配器

完成 kernel/mm/slab.c 中的 choose_new_current_slaballoc_in_slab_implfree_in_slab 函数中的 LAB 2 TODO 2 部分,其中 alloc_in_slab_impl 用于在 slab 分配器中分配指定阶大小的内存,而 free_in_slab 则用于释放上述已分配的内存。

在SLAB分配时,我们需要先观察其具有的结构体。

我们可以发现,SLAB指针(slab_pointer,也就是我们第一个要完成函数的传入指针类型)包含着一个指针和一个list_head成员。结合课上知识,第一个为当前slab分配器的头指针current,第二个则是所谓的partial指针,其中含有所有拥有空闲块。因此,我们需要获得partial指针的next,也就是第一个空闲的SLAB。这样我们就有:

static void choose_new_current_slab(struct slab_pointer * __maybe_unused pool)
{/* LAB 2 TODO 2 BEGIN *//* Hint: Choose a partial slab to be a new current slab. *//* BLANK BEGIN */// 检查是否为空。if(list_empty(&pool -> partial_slab_list))pool -> current_slab = NULL;else{// 选择一个没有分配的物理页块,返回并从partial中删除。pool -> current_slab = list_entry(pool -> partial_slab_list.next, struct slab_header, node);list_del(pool -> partial_slab_list.next);}/* BLANK END *//* LAB 2 TODO 2 END */
}

接下来需要分配指定阶次大小的内存。首先,我们可以看到在函数中,首次分配内存时我们需要初始化我们的每个slab,形成固定大小的slab块并且组织好slab的free_list. free_list是一个链表结构,每个指针存储着指向下一个没有被分配的物理内存页。将slab的header槽中的free_list_head转换成slab_slot_list类型的一个指针后,其成员next_free就是下一个未被分配的物理页。我们具有以下的图示结构:(有点难理解)

接下来我们阅读我们的函数。首先看函数给我们前面的部分:

static void *alloc_in_slab_impl(int order)
{struct slab_header *current_slab;struct slab_slot_list *free_list;void *next_slot;UNUSED(next_slot);// ...
}

这一部分给出了我们需要的几个变量。首先,current_slab自然无需多言是我们需要分配的slab。其次,free_list是一个slab_slot_list类型,有:

/* Each free slot in one slab is regarded as slab_slot_list. */
struct slab_slot_list {void *next_free;
};

结合slab_header的free_list_head,我们可以发现,free_list_head可以转换成slab_slot_list类型。而这个free_list_head有next_free指针,指向的正好是下一个自由的slot。要注意的是,每一个slab的第一个slot在本系统中不存储实际的物理页,相当于是头指针,因此计算当前free_slot的数目时需要减去1。
这样我们有:

        /* LAB 2 TODO 2 BEGIN *//** Hint: Find a free slot from the free list of current slab.* If current slab is full, choose a new slab as the current one.*//* BLANK BEGIN */// 首先,我们先寻找当前slab的free_list. 也就是slab_header中的free_list_head.// 注意类型的转换。从注释中可以得到答案。free_list = (struct slab_slot_list*) current_slab -> free_list_head;next_slot = free_list -> next_free;current_slab -> free_list_head = next_slot;current_slab -> current_free_cnt--;// 检查是否slab已满。注意slab_header的第一个slot用于储存头信息!因此需要-1if (current_slab -> current_free_cnt == 0) choose_new_current_slab(&slab_pool[order]); // 分配新的slab/* BLANK END *//* LAB 2 TODO 2 END */

接下来就是free_in_slab. 我们要进行的是释放这个slot,并将其放回free_list.

        struct page *page;struct slab_header *slab;struct slab_slot_list *slot;int order;slot = (struct slab_slot_list *)addr;page = virt_to_page(addr);if (!page) {kdebug("invalid page in %s", __func__);return;}slab = page->slab;order = slab->order;lock(&slabs_locks[order]);try_insert_full_slab_to_partial(slab);

首先从物理地址转换成slot,这样我们就可以操作其next_free指针指向下一个离他最近的free_slot.随后,判断是否是一个全满的slab释放出空slot,将其插入partial_list(用于指明当前内存池中的具有空闲槽的slab)中。
接下来,理论上我们应该去寻找离我们最近的free_slot,让我们的next_free指向它。但是我们并没有一个插入链表的接口。因此我们可以用另一种方法,让这个新的slot成为我们的slab新的free_list_head,让原先的free_list_head指向我们的slot。如下图所示:

这样我们就有:

        /* LAB 2 TODO 2 BEGIN *//** Hint: Free an allocated slot and put it back to the free list.*//* BLANK BEGIN */slot -> next_free = slab -> free_list_head;slab -> free_list_head = (void*) slot;slab -> current_free_cnt += 1;UNUSED(slot);/* BLANK END *//* LAB 2 TODO 2 END */

这样我们完成了简单的slab。

3. kmalloc

完成 kernel/mm/kmalloc.c 中的 _kmalloc 函数中的 LAB 2 TODO 3 部分,在适当位置调用对应的函数,实现 kmalloc 功能

根据提示,首先我们直接调用alloc_in_slab()功能,分配slab给我们的addr。当此时的size与当前slab的size相比过大时,我们则需要重新从伙伴系统中获得新的物理页,再转换成slab分配器,获得我们的地址。

if (size <= SLAB_MAX_SIZE) {/* LAB 2 TODO 3 BEGIN *//* Step 1: Allocate in slab for small requests. *//* BLANK BEGIN */UNUSED(addr);UNUSED(order);addr = alloc_in_slab(size, real_size);/* BLANK END */
#if ENABLE_MEMORY_USAGE_COLLECTING == ONif(is_record && collecting_switch) {record_mem_usage(*real_size, addr);}
#endif} else {/* Step 2: Allocate in buddy for large requests. *//* BLANK BEGIN */order = size_to_page_order(size);addr = _get_pages(order, is_record);/* BLANK END *//* LAB 2 TODO 3 END */}

这样我们完成了实验二第一部分,应该获得30分。

待续...

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

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

相关文章

lab2

lab 2: Memory Manage(working) 新的环境好像不支持arm架构了,总是会在make build触发错误 exec chbuild not found. 我们于是只能使用utm平台+qemu模拟amd64架构的ubuntu系统来进行运行。 首先我们还是先进行make build来获得我们想要的环境。 1 Buddy System练习题1: 完成 k…

开源 - Ideal库 - 常用时间转换扩展方法(二)

分享时间转换的扩展方法,包括TimeOnly、DateOnly与DateTime间的转换,并介绍代码结构设计与组织、单元测试、文档的重要性,以partial类、功能分类优化代码结构,后续将上传至Nuget。书接上回,我们继续来分享一些关于时间转换的常用扩展方法。01、时间转日期时间 TimeOnly 该…

杂题部分

杂题部分3131. 找出与数组相加的整数 I思路 快排+直接找差值。 void quickSort(int *arr,int low,int high) {if(high<=low) return;int i=low;int j=high;int pivot=arr[low];while(1){// 1. 自左到右搜索,直到比pivot大后停止。while(arr[i]<=pivot){i++;if(i==high) …

20222312 2024-2025-2 《网络与系统攻防技术》实验四报告

一、恶意代码文件类型标识、脱壳与字符串提取对提供的rada恶意代码样本,进行文件类型识别,脱壳与字符串提取,以获得rada恶意代码的编写作者 (1).通过kali中的file命令查看文件格式和可运行平台,即exe文件,Win32平台 通过PEID查壳文件发现使用UPX壳二、使用IDA Pro静态或…

点阵LED电路分析

以点阵的左上角LED为例,即 A1 LED为例,进行电路分析 9号脚接着LED的阳极,所以9号脚需是高电平,13号脚连着LED的阴极,所以13号脚需是低电平 9号脚连接着Q10的集电极,欲使9号脚为高电平,则需要Q10导通 Q10的发射极连接着+5V电压,欲使Q10导通,则基极需为低电平,即LEDC0为…

苹果手机和电脑数据互传

利用共享的文件夹 这个方法简单来说就是iPhone通过远程连接到Windows服务器,利用共享的文件夹来进行照片中转。 注意:iPhone和Windows必须要连接到同一个局域网内! ● Windows设置 第一步,获取Windows电脑的IP地址。 具体操作是先打开“Windows设置-网络和Internet-网络和共…

由一个业务需求引发的对 ASP.NET 全局变量的调研及结果

在单机模式下,使用哪种技术来存储身份状态信息比较安全可靠呢?前言 前段时间使用 ASP.NET MVC + Form Auth 做了一个单机小项目,当时对于采用什么方式来存储登录状态有些纠结,通常的做法是使用 Cookie 或者 Session,但是我想有没有更好的方式来存储登录状态呢?于是花了点…

苹果手机数据传输

利用共享的文件夹 这个方法简单来说就是iPhone通过远程连接到Windows服务器,利用共享的文件夹来进行照片中转。 注意:iPhone和Windows必须要连接到同一个局域网内! ● Windows设置 第一步,获取Windows电脑的IP地址。 具体操作是先打开“Windows设置-网络和Internet-网络和共…

c语言中返回整数值的长度

001、方法1 while循环[root@PC1 test]# ls test.c [root@PC1 test]# cat test.c ## 测试c程序 #include <stdio.h>int get_length(int a) {int length = 0;while(a > 0){length++;a /= 10;}return length; }int main(void) {int a;printf("a = "…

Blender 常用建模操作

常用简单介绍 挤出 快捷键:E 挤出是2个动作,生成加移动,所以右键撤销只能撤销移动内插 快捷键:I 内插仅是一个动作倒角 快捷键:Ctrl+B 滚动滚轮可以增加倒角的段数环切 快捷键:Ctrl+R 滚动滚轮可以增加倒角的段数挤出详细介绍 沿轴线挤出或者自动挤出挤出流形可以向内挤出…

Stack模块的设置

TEAM: Topological Evolution-aware Framework for Traffic Forecasting–Extended Version Motivation 为了捕捉复杂的时空动态,许多基于深度学习的方法最近被提出,并由于其学习非线性动力学[35,59]的能力,在挑战数据集上显示出了有希望的结果。这些方法通常建立在图神经网…