Lab2

 

lab2

特权级管理

linux和μcore只使用0和3

段选择子

DPL 段描述符 中描述特权 门

RPL段寄存器 DS ES FS GS(寄存器最低两位)

CPL 段寄存器 CS SS (寄存器最低两位)

操作系统对于特权的合法性检查

访问门时:CPL<=DPL[门]&CPL>=DPL[段](低优先级可以访问高优先级的代码)

访问段时:MAX(CPL,RPL)<=DPL[段](发出请求的一方优先级高于目标)

特权级切换

通过中断实现特权级转换

TSS(任务状态段)存储信息

TSS 全称task state segment,是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息。 

分配tss内存->初始化tss->将ts描述符填写入GDT中->设置Tss选择子

MMU 内存管理单元

段描述符:基址与限制

段选择子里的隐藏部分:基址一直存放在隐藏部分,知道选择子发生变化,才会更新基址

μcore映射两次:页将虚拟地址0xC0000000映射到物理0地址

段时又将其映射回去

页机制概述

1562221169759

二级页表:pde pte

1562221427231

页表项:

1562221465422

### 使能页机制:

设置cr0的第31位置1

建立页表

1562221692355

地址映射主要是页机制,段机制主要是用于安全管理方面

1562222383450

练习

1

主要是实现了一个ffma算法,这里利用的是链表结构维护一个空闲页面链表(双向),需要注意很多标志位的东西

typedef struct {
    list_entry_t free_list;         // the list header
    unsigned int nr_free;           // # of free pages in this free list
} free_area_t;

free_area结构

初始化函数就是将空闲链表初始化,指针指向自己,空闲内存页数目置零。

static void
default_init(void) {
    list_init(&free_list);
    nr_free = 0;
}

初始化整个内存空间

static void
default_init_memmap(struct Page *base, size_t n) {
    assert(n > 0);
    struct Page *p = base;
    for (; p != base + n; p ++) {
        assert(PageReserved(p));
        p->flags = p->property = 0;
        set_page_ref(p, 0);
    }
    base->property = n;
    SetPageProperty(base);
    nr_free += n;
    list_add_before(&free_list, &(base->page_link));
}

这里需要看一下page结构:(这里的page结构只是页描述符)

struct Page {
    int ref;                        // page frame's reference counter
    uint32_t flags;                 // array of flags that describe the status of the page frame
    unsigned int property;          // the num of free block, used in first fit pm manager
    list_entry_t page_link;         // free list link
};

ref就是有多少个link指向它,flag用于存储一些标志位,property就是这个空闲块里面有多少空闲页,而后的就是一个指针,用于组织链表。

/* Flags describing the status of a page frame */
#define PG_reserved                 0       // the page descriptor is reserved for kernel or unusable
#define PG_property                 1       // the member 'property' is valid

flag信息

property意思是描述符中的property是否被使用。

这里的话因为结构体以及真实的页存储在不同的物理空间内,而且页描述符结构体中也没有记录页地址的数据项。不过好在初始化的内存空间是连续的,结构体数组也是同样连续的,所以直接通过下标访问即可达到描述符以及真实页面的映射。理解了这个的话后续的操作就简单了。

初始化内存空间:

static void
default_init_memmap(struct Page *base, size_t n) {
    assert(n > 0);
    struct Page *p = base;
    for (; p != base + n; p ++) {
        assert(PageReserved(p));
        p->flags = p->property = 0;
        set_page_ref(p, 0);
    }
    base->property = n;
    SetPageProperty(base);
    nr_free += n;
    list_add_before(&free_list, &(base->page_link));
}

操作过程代码表现的很直观了,这里不做赘述。

alloc:

static struct Page *
default_alloc_pages(size_t n) {
    assert(n > 0);
    if (n > nr_free) {
        return NULL;
    }
    struct Page *page = NULL;
    list_entry_t *le = &free_list;
    // TODO: optimize (next-fit)
    while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link);
        if (p->property >= n) {
            page = p;
            break;
        }
    }
    if (page != NULL) {
        if (page->property > n) {
            struct Page *p = page + n;
            p->property = page->property - n;
            SetPageProperty(p);
            list_add_after(&(page->page_link), &(p->page_link));
        }
        list_del(&(page->page_link));
        nr_free -= n;
        ClearPageProperty(page);
    }
    return page;
}

由于是双向链表,利用的算法是ffma,所以第一个满足大小的内存块就能被直接alloc,只需比较需要的n以及property。alloc前还需要考虑是否需要分块操作,分块后的内存加入freelist中,只需要注意一些page结构体内部的一些标志位就可以了。

free:

static void
default_free_pages(struct Page *base, size_t n) {
    assert(n > 0);
    struct Page *p = base;
    for (; p != base + n; p ++) {
        assert(!PageReserved(p) && !PageProperty(p));
        p->flags = 0;
        set_page_ref(p, 0);
    }
    base->property = n;
    SetPageProperty(base);
    list_entry_t *le = list_next(&free_list);
    while (le != &free_list) {
        p = le2page(le, page_link);
        le = list_next(le);
        // TODO: optimize
        if (base + base->property == p) {
            base->property += p->property;
            ClearPageProperty(p);
            list_del(&(p->page_link));
        }
        else if (p + p->property == base) {
            p->property += base->property;
            ClearPageProperty(base);
            base = p;
            list_del(&(p->page_link));
        }
    }
    nr_free += n;
    le = list_next(&free_list);
    while (le != &free_list) {
        p = le2page(le, page_link);
        if (base + base->property <= p) {
            assert(base + base->property != p);
            break;
        }
		else if(p + p->property <= base)
		{
			assert(p + p->property != base);
		}
        le = list_next(le);
    }
    list_add_before(le, &(base->page_link));
}

和alloc操作逆过程类似,但是释放的时候需要考虑向前合并以及向后合并的过程,同样需要注意的也是page描述符中的数据项填写,代码本身很好理解,但是最后的内存释放成功判断有些出入

while (le != &free_list) {
        p = le2page(le, page_link);
        if (base + base->property <= p) {
            assert(base + base->property != p);
            break;
        }
    /*******/
		else if(p + p->property <= base)
		{
			assert(p + p->property != base);
		}
    /*******/
        le = list_next(le);
    }

这里就是内存释放后的检测代码,两段注释间为本人后续添加的代码,因为个人觉得答案中代码有些问题,因为在答案代码中,只是检查了释放内存块的尾部与freelist中各个free内存的头部地址大小比较,用于判断是否出现了未合并情况,但是这个检查不够完全,忽视了freelist中的别的块与释放内存块的头部的比较,而且不理解为何会判断了后就要直接break,可能原先的代码逻辑已经将另外的情况排除,但个人觉得检查还是充分较好。简而言之,要确保合法的内存释放后应该不存在重叠以及未合并现象。

2

练习2主要就是二级页表中页表项的获取:

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
     */

    pde_t *pdep = &pgdir[PDX(la)];   // (1) find page directory entry
    struct Page *  p;
    if (!(*pdep & PTE_P)) {              // (2) check if entry is not present
        if(! create){
            p = alloc_page();
        }                  // (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
        set_page_ref(p,1);                  // (4) set page reference
        uintptr_t pa = page2pa(p); // (5) get linear address of page
        memset(KADDR(pa),0,PGSIZE) ;               // (6) clear page content using memset
		*pdep = pa | PTE_U | PTE_W | PTE_P;//set flag
    }
    return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];//addr in page table or page directory entry[pagetable index] -> to kernal va

}

主要难点就是二级页表这个东西比较抽象,而且变量名的缩写比较容易混杂,根据提示基本可以写出,但是需要注意数据类型以及各个宏(macro)的用法。

3

页表项的删除,和2差不多,看着提示就基本写出来了。

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 0
    if (0) {                      //(1) check if this page table entry is present
        struct 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 * p = pte2page(*ptep);
        page_ref_dec(p);
        if(p->ref == 0)
            free_page(p);
        *ptep = 0;
        tlb_invalidate(pgdir, la);
	}   
}