ucore lab2 操作系统实验

LAB2:

知识准备

(通过操作系统原理教材、gitbook实验指导书、清华大学教学视频以及其他相关的资料进行学习)

1.特权级以及特权级的转换

(在清华大学教学视频中格外详细讲述了此内容,故结合视频内容并查阅相关资料掌握了此内容)

特权级的目的:用于操作系统和CPU提供给不同应用程序隔离空间、不能让用户程序任意访问操作系统空间

X86的特权级共4个,分别为:ring 0内核级,ring 1和2 操作系统的一些服务、ring 3 应用程序级(用户态),不过在Linux以及ucore中只使用了两个级别:ring 0内核级----用于操作系统访问数据、运行以及执行一些特权指令, ring 3 应用程序级(用户态)----访问应用程序层面的数据以及执行用户代码等。由于保护机制的存在,使得如果处于用户态去访问特权指令,会导致中断。

很重要!了解CPL、DPL、RPL以及它们的区别 https://blog.csdn.net/bfboys/article/details/52420211

CPL:当前特权级(Current Privilege Level),保存在 CS段寄存器(选择子)的最低两位, CPL 就是当前活动代码段的特权级,并且它定义了当前所执行程序的特权级别)

DPL:描述符特权(Descriptor Privilege Level),存储在段描述符中的权限位,用于描述对应段所属的特权等级,也就是段本身真正的特权级。

RPL:请求特权级RPL(Request Privilege Level) ,RPL保存在段选择子的最低两位。 每个段选择子有自己的RPLRPL说明的是进程对段访问的请求权限,意思是当前进程想要的请求权限。
特权级
特权级的检查:
在这里插入图片描述
下面说明操作系统如何实现特权级的切换(基于lab1中对中断的理解):

(在lab1的challenge中已经实现特权级切换,通过调用软中断i()的方式,在此处进行细节说明):

由lab1可知任务门描述符、中断门描述符、陷阱门描述符的格式:
在这里插入图片描述
发生中断时操作系统将由用户态跳转至内核态,在内核态的堆栈中保存相关信息。在这里插入图片描述
(1)实现从特权级0到特权级3的切换(内核态到用户态):

按照我的理解就是通过构造一个能够从ring0返回到ring3的栈来实现,这个中断栈通过内核实现模拟,保存的信息包括EIP、ERROR CODE、CS、EFLAGS。之后通过IRET指令完成数据的更新,完成寄存器的更新,从而实现转换,具体可以用下图表示:在这里插入图片描述
(2)实现特权级3到特权级0的转变(用户态到内核态)

通常操作系统会采用软中断或者叫做trap的方式完成。实际上,发生中断时已经实现了从用户态切换到内核态,为了实现这种切换,我们需要建立好中断门,中断门中的中断描述符表指出了中断发生后跳转至何处,并且发生中断时我们必须保存SS、ESP等信息。但是,中断会根据保存的这些信息返回到用户态中,为了实现停留在内核态,我们对CS进行修改,将其指向内核态的代码段,其次,我们将CS的CPL设为0,在此处还需要根据要执行的指令修改EIP,这样最后执行IRET指令时,CPU会将堆栈信息取出并返回到EIP以及CS所指内容去执行,从而便实现了从ring3到ring0的转换。
在这里插入图片描述
为了实现特权级的切换,实际上还需要访问TSS(Task State Segment)任务状态段。简单来说,任务状态段就是内存中的一个数据结构。这个结构中保存着和任务相关的信息。当发生任务切换的时候会把当前任务用到的寄存器内容(CS/ EIP/ DS/SS/EFLAGS…)保存在TSS 中以便任务切换回来时候继续使用。
在这里插入图片描述
为了访问TSS,还需要访问全局描述符表。全局描述符表(GDT)保存者TSS的地址,TSS最终会被加载进内存中。其中有一个Task Register 的cache缓存,最终通过基址加上偏移来确定Task所在的具体位置。在这里插入图片描述
通过上述内容,我了解了什么是特权级以及如何实现特权级的检验以及切换。并且对LAB1中的中断进行了回顾与深化,对中断的相关内容有了更为深刻以及细节的认识。基本掌握了特权级的相关知识。

2.物理内存检测:

(参考自本节的附录A、B以及视频教学以及相关资料)

显然,进行物理内存空间分配前,我们必须知道现在物理内存空间的信息,包括物理内存有多大、哪些地址空间可用,哪些地址空间不可用以及它们是否是连续的可用空间等。一般来说,获取内存大小的方法由 BIOS 中断调用和直接探测两种,其中BIOS中断调用方法通常只能在实模式下完成,直接探测的方法必须在保护模式下完成。在本实验中,我们通过e820h中断获取内存信息。因为e820h中断必须在实模式下使用,所以我们在 bootloader 进入保护模式之前调用这个 BIOS 中断,并且把 e820 映射结构保存在物理地址0x8000处。具体实现如下:

首先,需要知道BIOS是通过系统内存映射地址描述符(Address Range Descriptor)格式来表示系统物理内存布局,其具体表示为

Offset  Size    Description
00h    8字节   base address               #系统内存块基地址
08h    8字节   length in bytes            #系统内存大小
10h    4字节   type of address range     #内存类型

之后看一下(Values for System Memory Map address type)

Values for System Memory Map address type:
01h    memory, available to OS
02h    reserved, not available (e.g. system ROM, memory-mapped device)
03h    ACPI Reclaim Memory (usable by OS after reading ACPI tables)
04h    ACPI NVS Memory (OS is required to save this memory between NVS sessions)
other  not defined yet -- treat as Reserved

然后看e820map的定义:

struct e820map 
{
    int nr_map;
    struct {
        uint64_t addr;
        uint64_t size;
        uint32_t type;
    } __attribute__((packed)) map[E820MAX];
};

因此可以通过调用INT 15h BIOS中断,递增di的值(20的倍数),让BIOS帮我们查找出一个一个的内存布局entry,并放入到一个保存地址范围描述符结构的缓冲区中,供后续的ucore进一步进行物理内存管理。

在此处可以查看boot/bootasm.S 中利用汇编元具体实现物理内存检测的过程:

probe_memory:
#对0x8000处的32位单元清零,即给位于0x8000处的struct
#e820map的成员变量nr_map清零
    movl $0, 0x8000
    xorl %ebx, %ebx
#表示设置调用INT 15H BIOS中断后,BIOS返回的映射地址描述符的start address    
    movw $0x8004, %di
start_probe:
    movl $0xE820, %eax
#设置地址范围描述符的大小为20字节,其大小等于struct e820map的成员变量map的大小
    movl $20, %ecx
#设置edx为"SMAP",(这是通常的一个约定)
    movl $SMAP, %edx
#调用ini 0x15中断,要求BIOS返回一个用地址范围描述符表示的内存段信息
    int $0x15
#如果eflags的CF位为0,则表示还有内存段需要探测
    jnc cont
#如果探测有问题,则结束探测    
    movw $12345, 0x8000
    jmp finish_probe
cont:
#设置下一个BIOS返回的映射地址描述符的start address
    addw $20, %di
#递增struct e820map的成员变量nr_map
    incl 0x8000
#如果INT0x15返回的ebx为0,则探测结束,否则继续探测
    cmpl $0, %ebx
    jnz start_probe
finish_probe:

上述代码正常执行完毕后,在0x8000地址处保存了从BIOS中获得的内存分布信息。

从具体代码我们可以看出,要实现物理内存空间的探测基本上是以下三步骤:

  1. 设置一个存放内存映射地址描述符的物理地址(在此为0x8000)
  2. 将e820作为参数传递给INT 15h中断
  3. 通过检测eflags的CF位来判断探测是否结束。如果CF位为0,则表示探测没有结束,那么就需要设置存放下一个内存映射地址描述符的物理地址,返回步骤2继续进行;否则物理内存检测就此结束。

通过代码我们也能知道实现物理内存检测后在0x8000地址处保存了从BIOS中获得的内存分布信息,此信息按照struct e820map的设置来进行填充,之后便开始执行进入保护模式的过程。

3.连续物理内存分配

连续分配方式,是指为用户程序分配一个不小于指定大小的连续的内存空间。连续物理内存分配会产生碎片,包括内部碎片(已经被分配出去的的内存空间大于请求所需的内存空间)和外部碎片(还没有分配出去,但是由于太小而无法分配给申请空间的新进程的内存空间空闲块),实际上可以通过紧凑的方式(操作系统不时地对进程进行移动和整理)或者分区交换的方式(通过抢占并回收处于等待状态进程的分区以增大可用内存空间)解决外部碎片。

动态分区分配:当程序被加载执行时,分配一个进程指定大小可变的分区(块、内存块),分区的地址是连续的。操作系统需要维护的数据结构包括所有进程的已分配分区以及空闲分区。

常见的动态分区的分配策略如下:

首次适应(First Fit)算法:空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。该算法的缺点在于:低地址部分由于不断被划分,会留下许多难以利用的小空闲分区,并且,每次都从低地址开始检索,将增大可用空闲区间查找的开销。

最佳适应(Best Fit)算法:空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区,即找到一个满足要求且最小的空闲分区分配给作业。实际上从宏观上看,存储器将留下许多难以利用的小空闲区。

最坏适应(Worst Fit)算法:空闲分区以容量递减的次序链接。找到第一个能满足要求的空闲分区,即挑选出最大的分区。该算法优点是使剩下的空闲区不至于太小,故产生碎片的几率减小。因此该算法对中小作业有利,但对大作业不利。

4.段页式管理内存
1.以页为单位管理物理内存

在获得可用物理内存范围后,系统需要建立相应的数据结构来管理以物理页(按4KB对齐,且大小为4KB的物理内存单元)为最小单位的整个物理内存,以配合后续涉及的分页管理机制。

我们通过查阅代码可以了解:在kern/mm/memlayout.h中,给出了page的定义:

//用来描述页
//描述物理空间的页
struct Page {
   
    int ref;                        // page frame's reference counter
  //ref表示这个页被页表的引用量
 //如果这个页被页表引用了,即在某页表中有一个页表项设置了一个虚拟页到这个Page管理的物理页的映射关系
 //,就会把Page的ref加一;反之,若页表项取消,即映射关系解除,就会把Page的ref减一。
    uint32_t flags;         // array of flags that describe the status of the page frame
    //flags标记是否可以被分配
    unsigned int property;    // the num of free block, used in first fit pm manager
    //property用来记录连续空闲页的数量,
    list_entry_t page_link;         // free list link
    //双向链表,空闲页构成链表
};

由于所有的连续内存空闲块可用一个双向链表管理起来,便于分配和释放,为此定义了一个free_area_t数据结构,包含了一个list_entry结构的双向链表指针和记录当前空闲页的个数的无符号整型变量nr_free。

/* free_area_t - maintains a doubly linked list to record free (unused) pages */
typedef struct {
   
    list_entry_t free_list;         // the list header
    //列表的头
    unsigned int nr_free;           // # of free pages in this free list
    //空闲页的数量
} free_area_t;

在pmm.c中,可以通过下面代码段,更好地理解“管理页级物理内存空间所需的Page结构的内存空间从哪里开始,占多大空间”以及“空闲内存空间的起始地址在哪里“这两个问题。

//end指向bootloader加载ucore的结束地址
    extern char end[];
   //需要管理的物理页个数为
    npage = maxpa / PGSIZE;
   //由于bootloader加载ucore的结束地址以上的空间没有被使用,
   //所以我们可以把end按页大小为边界去整后,作为管理页级物理内存空间所需的Page结构的内存空间
    pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);
   //对地址空间进行标记
    for (i = 0; i < npage; i ++) {
   
        SetPageReserved(pages + i);
    }
//从地址0到地址pages+ sizeof(struct Page) * npage)结束的物理内存空间设定为已占用物理内存空间
//(起始0~640KB的空间是空闲的),地址pages+ sizeof(struct Page) * npage)以上的空间为空闲物理内存空间,这时的空闲空间起始地址为
    uintptr_t freemem = PADDR((uintptr_t)pages + sizeof
  • 31
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值