操作系统实验三 内存分配与回收

实验目的与要求

    实验目的:

(1)、掌握计算机操作系统管理进程、处理机、存储器、文件系统的基本方法。

(2)、了解进程的创建、撤消和运行,进程并发执行;自行设计解决哲学家就餐问题的并发线程,了解线程(进程)调度方法;掌握内存空间的分配与回收的基本原理;通过模拟文件管理的工作过程,了解文件操作命令的实质。

(3)、了解现代计算机操作系统的工作原理,具有初步分析、设计操作系统的能力。

(4)、通过在计算机上编程实现操作系统中的各种管理功能,在系统程序设计能力方面得到提升。

实验要求:

(1)、回答以下问题:

kmem中的freelist指针指向空闲物理块链表。空闲物理块链表中的节点为run结构体。但是:

可以看到这个结构体只有指向下一个节点的指针。请解释这个链表中的空闲物理块保存在哪里呢?

(2)、大作业partI-4其中一个问题:

(vm.c L151) kpgdir = setupkvm();

通过setupkvm函数,创建了调度器所用的页表。请深入setupkvm函数内部,确定在创建页表过程中,总共调用了多少次kalloc函数分配4K物理块用于存放页表项?

请编程验证大作业partI-4的理论推导结果,在终端中输出在setupkvm函数中调用kalloc函数的次数。

(1)、回答以下问题:

kmem中的freelist指针指向空闲物理块链表。空闲物理块链表中的节点为run结构体。但是:

可以看到这个结构体只有指向下一个节点的指针。请解释这个链表中的空闲物理块保存在哪里呢?

空闲物理块的实际数据保存在run结构体的后面,run结构体本身不包含任何与该数据相关的字段。具体分析过程如下:

查看kinitfreerange函数的定义,如图1-1所示。

图 1- 1 kinit和freerange函数定义

分析其具体执行步骤,freerangepa_start地址向上取整到4096字节(一页)的边界,确保从整页开始释放,并遍历从pa_startpa_end的每个4096字节的页,对每个页调用kfree,它的功能是将给定地址范围内的内存添加到空闲列表中。

Init函数使用initlock函数初始化kmem结构体中的lock,为物理内存分配器创建一个自旋锁,以提供同步,接着调用freerange函数,将从end(内核的结束地址)到PHYSTOP(物理内存的结束地址)的内存区域添加到空闲列表中,完成了对内核内存分配器的初始化。

Kfree的定义如图1-2所示。

图 1- 2 kfree函数定义

它首先检查pa是否是4096字节对齐的,并且是否位于合法的物理内存范围内,使用memset将该页的内容设置为1,以防止悬挂指针引用旧数据。接着将pa转换为run结构体指针r,获取kmem.lock以确保原子操作。并将r的next指针设置为当前的freelist头部。最后更新kmem.freelist为新的头部r并释放kmem.lock,完成将一个物理页标记为空闲,并添加到空闲列表中。

Kalloc的定义如图1-3所示。

图 1- 3 kalloc函数定义

它首先获取kmem.lock以确保原子操作,检查freelist是否为空。如果不为空,将freelist的头部节点保存到r,并更新freelist为下一个节点,释放kmem.lock。如果r不为空,使用memset将页内容设置为5,以确保页面被初始化。最后返回r作为物理页的指针,完成从空闲列表中分配一个物理页。

结合代码和注释,可以得知run结构体仅作为链表中的节点,它后面的内存(即同一物理页内的内存)就是空闲物理块的实际数据。由于所有的run结构体都位于它们各自代表的空闲物理页的开始位置,因此它们后面的内存就是可供分配的空闲空间。kmem结构体中的freelist指针指向空闲物理块链表,这个链表用于跟踪所有可用的物理页。每个空闲物理块由run结构体表示,该结构体仅包含一个指向下一个空闲块的指针。空闲物理块的实际数据保存在run结构体的后面,run结构体本身不包含任何与该数据相关的字段。这种设计允许使用整个物理页,没有额外的元数据开销,并且可以通过简单的链表操作来管理这些页。

(2)、大作业partI-4其中一个问题:

(vm.c L151) kpgdir = setupkvm();

通过setupkvm函数,创建了调度器所用的页表。请深入setupkvm函数内部,确定在创建页表过程中,总共调用了多少次kalloc函数分配4K物理块用于存放页表项?

请编程验证大作业partI-4的理论推导结果,在终端中输出在setupkvm函数中调用kalloc函数的次数。

一开始找不到源码和setupkvm函数感到很奇怪,查阅资料后发现该xv6版本没有这个函数,而在这个版本中,起到这个作用的函数为uvmalloc,如图2-1所示。

图 2- 1 uvmalloc函数

函数执行内容如图2-2所示。

图 2- 2 umv函数内容

其中,oldsz是一个uint64类型的值,表示进程已经占用的虚拟内存的末尾地址,函数会从这个地址继续往后分配新的内存。Newsz是一个uint64类型的值,表示要为进程分配到的新的内存的末尾地址。

下面是这个函数的步骤解释:

  • 初始化:定义一个字符指针 mem 用于指向新分配的内存,以及一个 uint64 类型的变量 a 用于循环计数。

  • 边界检查:如果 newsz 小于 oldsz,则直接返回 oldsz,不进行内存扩展。

  • 内存对齐:使用 PGROUNDUP(oldsz) 将 oldsz 对齐到页面大小,确保它是页面大小的整数倍。

  • 循环分配内存:从 oldsz 开始,每次增加一个页面大小 PGSIZE,直到达到 newsz。在循环中:

使用 kalloc() 分配一页物理内存,如果 kalloc() 返回 0 表示分配失败,则调用 uvmdealloc(pagetable, a, oldsz) 释放之前分配的所有内存,并返回 0 表示出错。

使用 memset 将新分配的内存初始化为 0。

使用 mappages 函数将新分配的物理内存 mem 映射到进程的虚拟地址空间中。mappages 的参数包括页表 pagetable,虚拟地址 a,页面大小 PGSIZE,物理地址 (uint64)mem,以及权限位 PTE_R|PTE_U|xperm。如果 mappages 返回非零值,表示映射失败,此时释放物理内存 mem,调用 uvmdealloc(pagetable, a, oldsz) 释放之前分配的内存,并返回 0。

  • 打印信息:在每次成功分配和映射内存后,打印出相关信息,包括新分配的虚拟地址 a,物理内存的地址 mem,新分配的空间大小,页面大小,以及总共分配的页面数。

  • 返回新大小:如果所有内存分配和映射都成功,函数最终返回新的内存大小 newsz。

每次执行一个指令,xv6就会调用这个函数,新分配一些空间。我们可以用一个变量记录每次执行时调用malloc的次数,如图2-3所示。

图 2- 3 记录调用malloc的次数

输出信息,进行检验。如图2-4所示。

图 2- 4 输出信息

进入系统输入ls命令,结果如图2-5所示。

图 2- 5 检验结果

之前的实验已经得知kalloc每次分配4KB,16*4096=65536 bytes,与之前的推导相符。

++++++++++++++++++++++++++++++++++++++++++++++++++++++

其他(例如感想、建议等等)。

通过这次实验,我对操作系统的内存管理有了更深入的理解。特别是,我学习了如何通过编程来管理和分配物理内存,以及如何设置页表来实现虚拟内存管理。这个过程不仅加深了我对操作系统底层工作原理的认识,也锻炼了我的编程技能和问题解决能力。

在实现 setupkvm 函数和理解 kalloc 调用次数的过程中,我意识到了内存管理的复杂性和精细性。每个函数调用和内存分配都需要精确控制,以确保系统的稳定性和效率。同时,我也体会到了调试和验证代码的重要性,这对于保证代码的正确性和性能至关重要。

此外,我还学习了如何通过阅读和分析源代码来理解程序的行为,这对于我未来的学习和工作都是非常宝贵的技能。总的来说,这次实验不仅提升了我的技术能力,也增强了我的分析和逻辑思维能力。

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值