实验目的
(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结构体。但是:
可以看到这个结构体只有指向下一个节点的指针。请解释这个链表中的空闲物理块保存在哪里呢?
首先观察kallow.c中,用于内存页管理的结构体的定义。起初,锁struct spinlock lock是未启动的,调用kinit()后锁才被使用。其中struct run *freelist就是空闲物理块链表。
kinit函数用于初始化内核的内存管理系统,以便内核可以分配和释放物理内存。kinit函数中,调用了initlock函数和freerange函数。
initlock函数初始化内核锁kmem.lock,锁被用于在内核中对物理内存的分配和释放进行同步。
freerange函数的作用是将一段物理地址空间从pa_start到pa_end之间的内存页设置为空闲状态,以便后续的内存分配可以使用这些空闲内存。
PGROUNDUP是一个宏定义,用于将一个地址向上对齐到页面边界。它的作用是保证对齐后的地址恰好是一个页面的起始地址。
PGROUNDUP((uint64)pa_start)用于将参数pa_start指向的物理地址向上对齐到页面边界,并将对齐后的地址赋值给指针变量p。这是因为内存管理系统中的页面大小固定为4KB,因此需要将空闲内存区域的起始地址对齐到页面边界,以便后续的内存分配可以按照页面大小进行。
然后,使用一个循环来遍历从p开始的每个页面,并将每个页表的首地址赋给p,直到p达到pa_end为止。在循环中,对于每个页面,调用kfree函数将其设置为空闲状态。
kfree函数用于将一个页框释放回空闲页框列表中。
首先判断该首地址是否内存对齐(即pa是否是页表首地址),以及是否在end----PHYSTOP范围内。
接着,函数会将 pa 所在的页框转换成 struct run 结构,然后将这个结构添加到内核管理的空闲页框列表 kmem.freelist 的头部。
最后,函数释放 kmem.lock,解除对内存管理数据结构的独占访问,允许其他线程执行相应的内存管理操作。
kalloc函数的作用是分配一个4096字节的物理内存页并返回内核可以使用的指针。如果无法分配内存,则返回0。
首先通过acquire()函数获得了kmem.lock的锁。然后,它从内核空间的自由空闲列表中获取一个空闲的物理内存页,即获取一个struct run结构体。
如果成功获取,则更新freelist,相当于从空闲页列表中移除r指向的内存页。然后,通过release()函数释放kmem.lock的锁。
如果成功获取了一个物理内存页,则将该页用5填充,以便之后检测该页帧是否被错误地使用。
最后,将物理页帧的指针转换为void指针并返回。
如果自由空闲列表为空,则函数返回空指针。在这种情况下,需要调用者考虑如何处理分配失败的情况。
kmem 是在操作系统内核的 memlayout.h 文件中定义的全局变量,它是用于管理内核空间内存的数据结构。在 kalloc() 和 kfree() 中,都会访问 kmem 中的 freelist 成员,以进行空闲内存块的分配和释放。
在xv6中,kmem中的freelist指针指向空闲物理块链表,链表中每个节点都是一个struct run结构体,表示一个空闲的物理内存块。每个物理内存块都是以一页大小(PGSIZE)为单位的,而每个物理内存块的地址就是该节点(struct run)的地址。所以kmem中的freelist指针实际上指向了一个struct run类型的内存块,也就是空闲物理块链表中的第一个节点。链表中每个节点只包含指向下一个节点的指针,因此可以通过这些指针遍历整个链表,找到一个空闲的物理内存块。
当需要分配一个物理内存块时,会从空闲物理块链表中取出第一个节点(即kmem.freelist指针所指向的节点),然后将kmem.freelist指针指向下一个节点,这样就从链表中移除了这个物理内存块。当需要释放一个物理内存块时,会将其转换成一个struct run类型的节点,并将其加入到空闲物理块链表的头部,成为新的第一个节点。
因此,空闲物理块并没有被保存在一个特定的数据结构中,而是作为一个物理内存块的一部分,通过链表的指针链接在一起,形成一个空闲物理块链表。
(2)、大作业partI-4其中一个问题:
(vm.c L151) kpgdir = setupkvm();
通过setupkvm函数,创建了调度器所用的页表。请深入setupkvm函数内部,确定在创建页表过程中,总共调用了多少次kalloc函数分配4K物理块用于存放页表项?
请编程验证大作业partI-4的理论推导结果,在终端中输出在setupkvm函数中调用kalloc函数的次数。
xv6 采用了二级页表结构,其中每个物理页框大小为 4KB。
由于该版本没有setupkvm函数,通过查询得知,函数变为uvmalloc函数
以下是调用kalloc的部分
这里出现两个名字,oldsz和newsz,我们需要理解这两个变量,否则无法继续。
oldsz:一个uint64类型的值,表示进程已经占用的虚拟内存的末尾地址,函数会从这个地址继续往后分配新的内存。
newsz:一个uint64类型的值,表示要为进程分配到的新的内存的末尾地址。
我们每次执行一个指令,xv6就会调用这个函数,新分配一些空间。
为了验证实验,我们需要输出每次新增了多少个页表,我们使用temp记录他
在vm.c开头定义temp变量
然后每次调用kalloc则对temp+1
如果成功跳出循环,表示执行成功。在此处输出新分配空间大小和调用kalloc函数的次数。
运行xv6,执行ls指令,结果如下
使用计算器进行验证可知,结果正确
实验小结
通过此次实验,加深了对操作系统建立页表,分配页表,管理页表以及扩展分配内存的了解。阅读xv6代码后,对操作系统的页表相关操作的底层实现有了更为清晰的印象和深刻的理解,明白了内存空间的分配与回收的基本原理。
(by 归忆)