13

ucore lab2

 2 years ago
source link: http://www.kongjunblog.xyz/2021/04/ucore-lab2.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ucore lab2

ucore lab2

练习0:填写已有实验

使用可视化diff/mege工具meld可以轻松完成填写代码的任务。只需要注意lab 2对lab 1中的文件进行了修改,不能把lab 1中的代码照搬过去。

1954702-20200504211732676-1440099870.png

练习1:实现first-fit连续物理内存分配算法

物理地址空间的探查

在实现物理内存的分配之前必须先探查出当前物理内存的布局和大小,根据这些信息计算出操作系统可操控的内存大小并将其划分为等大的物理页。

物理地址空间的探查在bootloader中完成,探查出的信息存放在物理地址0x8000,,C程序使用结构体struct e80map对这些数据进行操作。

xxxxxxxxxx
// 定义在kern/mm/memlayout.h中
struct e820map {
    int nr_map;         //探查到的内存块总数
    struct {
        uint64_t addr;  //内存块的起始物理地址
        uint64_t size;  //内存块的大小
        uint32_t type;  //内存块的类型
    } __attribute__((packed)) map[E820MAX];
};

物理页的初始化

探查到的内存使用物理地址描述,ucore设置了物理内存地址到虚拟内存地址的临时映射关系:

xxxxxxxxxx
虚拟地址 = 物理地址 + 0xC0000000

ucore支持的最大物理地址空间是KMEMSIZE(0x380000000)

空闲块以物理页(4096字节)为为单位,空闲块中的第一个物理页代表整个空闲块,每一个物理页都对应着一个记录它的结构struct Page

xxxxxxxxxx
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
};
  • flags设置为Reserved时,该物理页被硬件保留,操作系统无法使用
  • flags未被设置为Reserved且未被设置为Property时,该物理页已被操作系统分配
  • flags未被设置为Reserved且设置为Property时,该物理页空闲
  • 当物理页是空闲块的第一个物理页时,property生效,代表该空闲块的大小(以物理页为单位)

物理页的初始化的核心在于:将探查到的物理内存与相应的Page关联起来。

物理页的初始化由kern/mm/pmm.c中的page_init()init_memmap()完成。page_init负责确定探查到的物理内存块与对应的struct Page之间的映射关系,Page的初始化工作由init_memmap()调用内存管理器pmm_managerinit_memmap()方法完成。

page_init()的算法为:

  1. 遍历memmap指针指向的结构e820map中的元素,求出其中的最高物理地址,计算出可用地址总大小
  2. 计算出所需的Page结构,并顺序存放在程序的.bss段之上,形成以物理地址的PPN为索引的Page数组
  3. 再次遍历e820map结构,将所有的Page设置为Reserved,调用init_memmap初始化操作系统可用的且地址在支持范围内物理内存对应的Page结构

init_memmap的算法为:

  1. 将所有可用的Pageflags设置为Property,引用计数设置为0,property设置为0,初始化page_link
  2. 空闲块的第一个物理块的property设置为该空闲块的大小,将其加入到空闲链表末尾
xxxxxxxxxx
default_init_memmap(struct Page *base, size_t n) {
    assert(n > 0);
    struct Page *p = base;
    for (; p != base + n; p ++) {
        // 在查找可用内存并分配struct Page数组时就已经将将全部Page设置为Reserved
        // 将Page标记为可用的:清除Reserved,设置Property,并把property设置为0( 不是空闲块的第一个物理页 )
        assert(PageReserved(p));
        p->flags = p->property = 0;
        SetPageProperty(p);
        set_page_ref(p, 0);
        list_init(&(p->page_link));
    }
    cprintf("Page address is %x\n", (uintptr_t)base);
    base->property = n;
    nr_free += n;
    list_add(free_list.prev, &(base->page_link));
}

只有操作系统可使用的物理页使用该函数进行初始化,因此不能被使用的物理页的Page中的Reserved属性不会被修改,确保了所有的物理页都有正确的Page

e820map结构中数组map中的内存块是从低地址从高地址排列的,设置内存块对应的Page时都是插入到链表的尾部,因此最后形成的空闲链表是从低地址到高地址的有序链表。

为了便于内存块的边界,我的实现要求空闲块中只有第一个物理页的property域不为0,其他物理页的property必须为0。

初始化完成后的物理内存布局:

xxxxxxxxxx
+-------------+-------------+--------------+------------+------------------------+
|             |   e820map   |              |    Pages   |     Free memory        |
+-------------+-------------+--------------+------------+------------------------+
^             ^                            ^            ^                        ^
|             |                            |            |                        |
0          0x8000                 .bss段结束位置(end)  freemem                0x380000000

物理页的分配

设计目标:

  • 能标识请求失败的原因,便于调试
  • 每次分配的内存块都是空闲链表中的最低地址空闲块
  • 正确处理空闲块分割、
xxxxxxxxxx
static struct Page *
default_alloc_pages(size_t n) {
    assert(n > 0);
    /* There are not enough physical memory */
    if (n > nr_free) {
        warn("memory shortage");
        return NULL;
    }
    struct Page *page = NULL;
    struct Page *p    = NULL;
    list_entry_t *le = &free_list;
    /* try to find empty space to allocate */
    while ((le = list_next(le)) != &free_list) {
        p = le2page(le, page_link);
        if (p->property >= n) {
            page = p;
            break;
        }
    }
    /* external fragmentation */
    if (page == NULL) {
        warn("external fragmentation: There are enough memory, but can't find continuous space to allocate");
        return NULL;
    }
    unsigned int property = page->property;
    /* modify pages in allocated block(except of first page)*/
    p = page + 1;
    for (; p < page + n; ++p) {
        ClearPageProperty(p);
        // property is zero, so we needn't modify it.
    }
    /* modify first page of allcoated block */
    ClearPageProperty(page);
    page->property = n;
    nr_free -= n;
    /*
     * If block size is bigger than requested size, split it;
     * */
    if (property > n) {
        p = page +
        p->property = property - n;
        list_add_after(&(page->page_link), &(p->page_link));
    }
    list_del(&(page->page_link));
    return page;
}

物理页的回收

default_free_pages(struct Page *page, size_t n)设计目标:

  • 能够正确地处理参数:当1n超过已分配块的页数时,回收整个块;当n小于也分配块的页数时,回收n页,剩下的内存不回收;当base指向的内存块不是已分配块的起始地址时,从base开始回收
  • 能够正确的合并空闲块(我的实现回收在base块高地址的所有相邻空闲块)
  • 能够正确分割已分配块:default_free_pages要求回收已分配块中的任意页,剩下的未回收的部分作为新的已分配块
  • 在回收后,空闲链表仍然是有序的
  1. 根据basen合理分割欲回收的内存块
  2. 合并邻接与base块的空闲块
  3. 将代表新空闲块的page_link插入到有序空闲链表中
xxxxxxxxxx
static void
default_free_pages(struct Page *base, size_t n) {
    assert(n > 0);
    /* if @base is not the beginning of the alloacted block which @base points in,
     * change the #property filed of the allocated block.
     */
    /* find the beginning of the allocated block.
     * only begging page's #property fild is non-zero.
     */
    struct Page *begin = base;
    size_t count = 0;
    for ( ; begin->property == 0; ++count, --begin) {
        assert(!PageReserved(begin) && !PageProperty(begin));
    }
    /* If @base is not the beginning of the allocated block,
     * split the allocated block into two part.
     * One part is @begin to @base,
     * other part is @base to the end of the original part.
     */
    if (begin != base) {
        base->property  = begin->property - count;
        begin->property = count;
    }
    /* If @n is bigger than the number of pages in the @base block,
     * it is not an error, just free all pages in block.
     */
    if (n > base->property) {
        n = base->property;
    }
    /* If @n is smaller than the number of pages in @base block,
     * split @base block into two block.
     */
    else if (n < base->property) {
        (base + n)->property = base->property - n;
        base->property = n;
    }
    /* modify status information */
    struct Page *p = base;
    for (; p != base + n; ++p) {
        assert(!PageReserved(p) && !PageProperty(p));
        p->flags = 0;
        SetPageProperty(p);
    }
     // extern struct Page *pages;
     // struct Page *pages_end = pages + npage;
     // unsigned int property, old_base_property = base->property;
     // list_entry_t *pos = NULL; //insert new free block after @pos
     // /* merge free blocks next to current freeing block */
     // p = base + base->property;
     // while ((p < pages_end) && PageProperty(p)) {
     //     property        = p->property;
     //     pos             = (p->page_link).prev;
     //     base->property += p->property;
     //     p->property     = 0;
     //     list_del(&p->page_link);
     //     p += property;
     // }
     // /* merge free blocks before current freeing block */
     // p = base - 1;
     // while ((p >= pages)) {
     //     while (p->property == 0)  {
     //         --p;
     //         if ((p < pages) || (p->property != 0)) break;
     //     }
     //     if ((p >= pages) && (p->property != 0)) {
     //         p->property    += base->property;
     //         base->property  = 0;
     //         base            = p;
     //         pos             = (p->page_link).prev;
     //         list_del(&p->page_link);
     //     }
     // }
     // /* There is no free blocks adjcent to @base block. */
     // if (base->property == old_base_property) {
     //     list_entry_t *le = &free_list;
     //     while ((le = list_next(le)) != &free_list) {
     //         if (le2page(le, page_link) > base) {
     //             pos = le->prev;
     //             break;
     //         }
     //     }
     //     /* free list is empty or @base points to  the upmost free block */
     //     if (le == &free_list)
     //         pos = free_list.prev;
     // }
     // list_add(pos, &base->page_link);
     // nr_free += n;
     /* merge adjcent free blocks */
     list_entry_t *le = list_next(&free_list), *pos = free_list.prev, *merge_before_ptr = NULL;
     unsigned int old_base_property = base->property;
     /* merge free blocks */
     while (le != &free_list) {
         p = le2page(le, page_link);
         /* free_list is ascending sorted, only one free block before @base block will be merged */
         if ((p + p->property == base)) {
             p->property      += base->property;
             base->property    = 0;
             base              = p;
             pos               = le->prev;
             merge_before_ptr  = le;
             list_del(le);
         }
         if ((base + base->property) == p) {
             base->property += p->property;
             p->property     = 0;
             pos             = le->prev;
             list_del(le);
         }
         le = list_next(le);
     }
     /* if there may be free blocks before @base block, try to merge them */
     if (merge_before_ptr != NULL) {
         le = merge_before_ptr->prev;
         while (le != &free_list) {
             p = le2page(le, page_link);
             if (p + p->property == base) {
                 p->property    += base->property;
                 base->property  = 0;
                 base            = p;
                 pos             = le->prev;
                 list_del(le);
             }
             le = list_prev(le);
         }
     }
     /* @pos indicate position in whith @base's page_link should insert;
      * only when there are no adjcent free blocks, should we try to find insertion position
      */
     if (base->property == old_base_property) {
         le = list_next(&free_list);
         while (le != &free_list) {
             if (le2page(le, page_link) > base) {
                 assert((base + base->property) < le2page(le, page_link));
                 pos = le->prev;
                 break;
             }
             le = list_next(le);
         }
     }
     list_add(pos, &base->page_link);
     nr_free += n;
}

运行结果:

1954702-20200508134101235-1736261067.png

  1. 空闲链表是升序的,从合并空闲块时从链表头开始遍历,最多只能够合并一个在base块之前(低地址)的空闲块。为了将base块之前的空闲块全部合并,不得不在第一次合并后,从base块之前的空闲块向链表头遍历。
  2. 使用链表,分配、合并都要遍历链表,时间复杂度为O(n)。可以使用平衡二叉树替代链表,将时间复杂度降低到O(n*logn)。

default_check有bug

//风格的注释中的代码功能和下面未加注释的代码功能相同,但是注释中的代码通过查找欲回收的块相邻的Pageflags域来查找、合并空闲块。这段代码是正确的,却无法通过default_check测试。

default_check无法修改内存布局,只能临时篡改空闲链表,制造出没有可用内存或只有特定数目的可用内存的假象。当实现不通过空闲链表查找邻接空闲块时,就会“看穿”测试代码制造的假象,发现并合并空闲块、修改怕property域,测试检查对应的property时就会出错。

这个实现应该是正确的,但是因为default_check自身的缺陷,无法通过完整地通过default_check,只能通过basic_check及之前的检查。

无法通过的default_check代码如下(之后的代码进行的是相同的检查):

xxxxxxxxxx
    struct Page *p0 = alloc_pages(5), *p1, *p2;
    assert(p0 != NULL);
    assert(!PageProperty(p0));
    list_entry_t free_list_store = free_list;   //制造没有可用内存的假象,但是邻接空闲块仍然存在
    list_init(&free_list);
    assert(list_empty(&free_list));
    assert(alloc_page() == NULL);
    unsigned int nr_free_store = nr_free;
    nr_free = 0;
    free_pages(p0 + 2, 3);         //只回收一部分内存。该内存块高地址处有相邻的空闲块
    assert(alloc_pages(4) == NULL);
    assert(PageProperty(p0 + 2) && p0[2].property == 3);
    assert((p1 = alloc_pages(3)) != NULL);
    assert(alloc_page() == NULL);
    assert(p0 + 2 == p1);

测试用例分配了大小为5页的内存,但只回收其中第3页及之后的内存。

回收时,注释中的代码发现在欲回收的块之上(更高地址)的Pageflags被设置为PG_Property,这表明存在邻接空闲块,所以将这三页和其上的空闲块合并成了一个空闲块,p0[2]是这个新空闲块的第一页,property域被修改成新空闲块的页数。因此PageProperty(p0+2)为真,但p0[2].property == 3为假,测试失败。

练习2:实现寻找虚拟地址对应的页表项

页目录项和页表项中每个组成部分的含义及对ucore而言的潜在用处(还有很多不清楚的地方!!!)

intel手册还没读到详细介绍,有很多不清楚的地方,等读到了再补。

ucore使用的是页大小为 4K 的 32-bit paging,页目录项和页表项结构如下如下:

32bit-paging

页目录项

  • bit 0(P): resent 位,若该位为 1 ,则 PDE 存在,否则不存在。
  • bit 1(R/W): read/write 位,若该位为 0 ,则只读,否则可写。
  • bit 2(U/S): user/supervisor位。
  • bit 3(PWT): page-level write-through,若该位为1则开启页层次的写回机制。
  • bit 4(PCD): page-level cache disable,若该位为1,则禁止页层次的缓存。
  • bit 5(A): accessed 位,若该位为1,表示这项曾在地址翻译的过程中被访问。
  • bit 6: 该位忽略。
  • bit 7(PS): 这个位用来确定 32 位分页的页大小,当该位为 1 且 CR4 的 PSE 位为 1 时,页大小为4M,否则为4K。
  • bit 11:8: 这几位忽略。
  • bit 32:12: 页表的PPN(页对齐的物理地址)。

页表项

页表项除了第 7 , 8 位与 PDE 不同,其余位作用均相同。

  • bit 7(PAT): 如果支持 PAT 分页,间接决定这项访问的 4 K 页的内存类型;如果不支持,这位保留(必须为 0 )。
  • bit 8(G): global 位。当 CR4 的 PGE 位为 1 时,若该位为 1 ,翻译是全局的;否则,忽略该位。

其中被忽略的位可以被操作系统用于实现各种功能;和权限相关的位可以用来增强ucore的内存保护机制;access 位可以用来实现内存页交换算法。

出现页访问异常时,硬件执行的工作(待续...)

intel手册还没读到这部分内容,等读到了再补。

get_pte函数的实现

get_pte函数的语义为:根据页目录pgdir来获取或创建指向线性地址la的 PTE ,是否创建页表取决于create

步骤

  1. 计算la1对应的 PDE 地址。
  2. 若该 PDE 不存在(PTE 所在的页表不存在)且create为 不为 0 ,创建页表并设置 PTE。
  3. 若该 PDE 不存在且create为 0 ,返回NULL
  4. 若该 PDE 存在,直接返回 PTE 虚拟地址。

PTE 内容的设置是调用者的职责,get_pte只需要给调用者一个可访问的 PTE即可。

xxxxxxxxxx
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
    assert(pgdir != NULL);
    struct Page *struct_page_vp;    // virtual address of struct page
    uint32_t pdx = PDX(la), ptx = PTX(la);  // index of PDE, PTE
    pde_t *pdep, *ptep;
    pte_t *page_pa;         // physical address of page
    pdep = pgdir + pdx;
    ptep = (pte_t *)KADDR(PDE_ADDR(*pdep)) + ptx;
    // if PDE exists
    if (test_bit(0, pdep)) {
        return ptep;
    }
    /* if PDE not exsits, allocate one page for PT and create corresponding PDE */
    if ((!test_bit(0, pdep)) && create) {
        struct_page_vp = alloc_page();          // allocate page for PT
        assert(struct_page_vp != NULL);         // allocate successfully
        set_page_ref(struct_page_vp, 1);        // set reference count
        page_pa = (pte_t *)page2pa(struct_page_vp); // convert virtual address to physical address
        ptep = KADDR(page_pa + ptx);            // virtual address of PTE
        *pdep = (PADDR(ptep)) | PTE_P | PTE_U | PTE_W;  // set PDE
        memset(ptep, 0, PGSIZE);                // clear PTE content
        return ptep;
    }
    return NULL;

练习3:释放某虚地址所在的页并取消对应二级页表项的映射

Page数组元素与页目录项、页表项的对应关系

直到现在,ucore还没有实现进程,所以暂时认为只有一个页目录。页目录定义在kern/mm/entry.s中,不是通过alloc_page()创建的,所以

Page数组元素与页目录项、页表项存在对应关系。所有的物理页都有一个描述它的Page结构。所有的页表都是通过alloc_page()分配的,每个页表项都存放在一个Page结构描述的物理页中;如果 PTE 指向某物理页,同时也有一个Page结构描述这个物理页。

(1)可以通过 PTE 的地址计算其所在的页表的Page结构,(2)可以通过 PTE 指向的物理地址计算出该物理页对应的Page结构。

  • (1): 将虚拟地址向下对齐到页大小,换算成物理地址(减 KERNBASE), 再将其右移 PGSHIFT(12)位获得在pages数组中的索引PPN&pages[PPN]就是所求的Page结构地址。
  • (2): PTE 按位与 0xFFF获得其指向页的物理地址,再右移 PGSHIFT(12)位获得在pages数组中的索引PPN&pages[PPN]就 PTE 指向的地址对应的Page结构。

C代码如下:

xxxxxxxxxx
// (1) this function don't exist in ucore
struct Page* page_for_pte(pde_t *ptep) {
    return &pages[PPN(PADDR(ROUNDDOWN(ptep, PGSIZE))))
}
// (2) this function exists in ucore
static inline struct Page *
pte2page(pte_t pte) {
    if (!(pte & PTE_P)) {
        panic("pte2page called with invalid pte");
    }
    return pa2page(PTE_ADDR(pte));
}

让虚拟地址等于物理地址的方法

ucore 设置虚拟地址到物理地址的映射分为两步:

  1. lab 2 中 ucore的入口点kern_entry()(定义在 kern/init/entry.s)中,设置了一个临时页表,将虚拟地址 KERNBASE ~ KERNBASE + 4M 映射到物理地址 0 ~ 4M ,并将 eip 修改到对应的虚拟地址。ucore 所有代码和本实验操作的所有数据结构(Page数组)都在这个虚拟地址范围内。
  2. 在确保程序可以正常运行后,调用boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W);将虚拟地址KERNBASE ~ KERNBASE + KMEMSIZE。

因为在编译链接时 ld 脚本 kern/tools/kernel.ld设置链接地址(虚拟地址),代码段基地址为0xC0100000(对应物理地址0x00100000),必须将该地址修改为0x00100000以确保内核加载正确。

xxxxxxxxxx
    /* Load the kernel at this address: "." means the current address */
     /* . = 0xC0100000; */
     . = 0x00100000;
    .text : {
        *(.text .stub .text.* .gnu.linkonce.t.*)
    }

在第1步中,ucore 设置了虚拟地址 0 ~ 4M 到物理地址 0 ~ 4M 的映射以确保开启页表后kern_entry能够正常执行,在将 eip 修改为对应的虚拟地址(加KERNBASE)后就取消了这个临时映射。因为我们要让物理地址等于虚拟地址,所以保留这个映射不变(将清除映射的代码注释掉)。

xxxxxxxxxx
next:
    # unmap va 0 ~ 4M, it's temporary mapping
    #xorl %eax, %eax
    #movl %eax, __boot_pgdir

ucore的代码大量使用了KERNBASE+物理地址等于虚拟地址的映射,为了尽可能降低修改的代码数,仍使用宏KERNBASEVPT(lab2中没有用到,为了避免bug仍然修改它),但是将他们减去0x38000000。

xxxxxxxxxx
// #define KERNBASE            0xC0000000
#define KERNBASE            0x00000000
// #define VPT                 0xFAC00000
#define VPT                 0xC2C00000

修改了KERNBASE后,虚拟地址和物理地址的关系就变成了:

xxxxxxxxxx
physical address + 0 == virtual address

接下来ucore的虚拟地址应该会等于物理地址,但是事情并没有这么顺利。如果仅做了这些修改,ucore会在boot_map_segment设置“好”页表后异常终止或跳转到别的地方执行。阅读源代码无法发现错误。

GDB调试发现boot_map_setment()在设置好boot_pgdir[0](0 ~ 4M)后,设置boot_pgdir[1]get_pte会取得目录项boot_pgdir[0]指向的页表。也就是说,页目录项 PDE 0 和 PDE 1共同指向同一个页表__boot_pt1,在设置虚拟地址4 ~ 8M 到物理地址 4 ~ 8M 的映射时,同时将虚拟地址地址0 ~ 4M 映射到了 4 ~ 8M ,导致ucore运行异常。

查看页表可以发现boot_pgdir[0]boot_pgdir[1]的内容相同!这导致了调用get_pte()时,0 ~ 8M的虚拟地址会返回同一个 PTE __boot_pt1,出现上述现象。

1954702-20200511190659682-1452075084.png

奇怪的是,kern_entry中将boot_pgdir[1]设置为0(.space指令),而不是boot_pgdir[0]

xxxxxxxxxx
.section .data.pgdir
.align PGSIZE
__boot_pgdir:
.globl __boot_pgdir
    # map va 0 ~ 4M to pa 0 ~ 4M (temporary)
    .long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
    .space (KERNBASE >> PGSHIFT >> 10 << 2) - (. - __boot_pgdir) # pad to PDE of KERNBASE
    # map va KERNBASE + (0 ~ 4M) to pa 0 ~ 4M
    .long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
    .space PGSIZE - (. - __boot_pgdir) # pad to PGSIZE

为了修复这个问题,在boot_map_segment()中,先清除boot_pgdir[1]的 present 位,再进行其他操作。这是get_pte会分配一个物理页作为boot_pgdir[1]指向的页表。

xxxxxxxxxx
static void
boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm)
{
    boot_pgdir[1] &= ~PTE_P;
    ...
}

虚拟地址到物理地址的映射改变了,不可能通过check_pgdir()check_boot_pgdir()的测试,所以要注释掉这两行调用。

最终运行结果如下:

1954702-20200511192232310-1591161817.png

page_remove_pte函数的实现

page_remove_pte语义为:清除 PTE 指向的内存对应的 PTE 和 Page结构。

步骤:

  1. 判断ptep指向的 PTE 是否存在,若不存在,不需要进行处理。
  2. ptep指向的 PTE 存在,计算其指向的内存对应的Page结构,递减引用计数,若已无虚拟地址指向该页,将其释放。
  3. 清除 PTE 并刷新 TLB。
xxxxxxxxxx
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
    assert(pgdir != NULL);
    assert(ptep != NULL);
    pde_t *pdep = pgdir + PDX(la);      // virtual address of PDE
    // PTE pointed by ptep must reside in page pointed by PDE
    assert(PDE_ADDR(*pdep) == PADDR(ROUNDDOWN(ptep, PGSIZE)));
    // if PDE exists
    if (test_bit(0, ptep)) {
    // Page struct related with la pointed by PTE
        struct Page *page = pte2page(*ptep);
    // decrease page reference and free this page when page reference reachs 0
        page_ref_dec(page);
        if (page_ref(page) == 0)
            free_page(page);
    // clear PTE pointed by ptep
        clear_bit(PTE_P, ptep);
    // flush TLB
        tlb_invalidate(pgdir, la);
    }
    // for debug
    else
        cprintf("test_bit(PTE_P, ptep) error\n");
}

运行结果

1954702-20200510180113562-214373689.png

1954702-20200510180357112-732211178.png

OS

此博客中的热门博文

使用 Vim 搭建 C/C++ 开发环境

刚接触 Vim 的同学往往因为无法搭建开发环境而“从入门到放弃”,本文旨在帮助这些同学搭建开发环境,聚焦于最核心的开发需求,忽略换配色调字体之类的细枝末节。如果需要开箱即用的 vim 配置(发行版),可以使用 Spacevim 。 本文使用 neovim-nightly,但也适用于 Vim 8.2+,不需要读者有任何 VimL 基础,以 C/C++ 为例,但应该适用于任何语言。   插件管理 在 Vim 中,插件只是一些脚本,存放在特定的目录中,运行时将它们所在的目录加入到 runtimepath 中。Vim 8 内置了插件管理功能,但不支持高级的插件管理功能。Vimmers 实现了多个插件管理器,可以自动下载、更新、安装插件,还可以延迟加载、按需加载,提高启动速度。 上古时期流行手动或使用 Vundle 管理插件,以上两种方式已经落伍了,这里介绍目前比较流行的三个插件管理器: vim-plug :简单易用高效,是目前最流行的插件管理器 dein.vim :功能强大,但使用复杂 vim-pathogen :另一款流行的插件管理器,没有用过不做评价 以上三款插件管理器风格各不相同,都有大量用户,功能相当完善,根据自己的喜好选取即可。推荐新手选择 vim-plug,对启动时间特别敏感的同学可以考虑 dein.vim,我的配置在安装 70 余个插件的情况下,启动仅需 60 余秒。 使用 vim-plug 安装插件只需要在 .vimrc 中写入以下代码: call plug#begin('~/.vim/plugged') " 括号里面是插件目录                                 " 只能在 plug#begin() 和 plug#end() 之间写安装插件的命令 Plug 'junegunn/vim-easy-align'   " 用户名/插件名,默认从 github 下载安装 Plug 'https://github.com/junegunn/vim-github-dashboard.git' " 从特定 URL 下载安装 call plug#end() 使用 dein.vim 安装插件: ​ x set runtimepath+=

Ibex 架构介绍

  Ibex 是什么? Ibex was initially developed as part of the PULP platform under the name "Zero-riscy" , and has been contributed to lowRISC who maintains it and develops it further. It is under active development. Ibex 是一个产品级的 32 位开源 RISC-V 处理器,使用 SystemVerilog 编写,麻雀虽小(11000 行左右),五章俱全。支持 RV32I、RV32C、RV32M、RV32B 等拓展,支持了 M-Mode 和 U-Mode,完整实现了 RISC-V 指令集规定的控制状态寄存器、中断异常、调试支持等,适用于嵌入式系统。 总体架构如下: 流水线 Ibex 默认使用两级流水线,但也支持三级流水线(实验性特性)。两级流水分别为: 取值(IF):通过预取缓冲区(prefetch buffer)从内存中取值,可以一个周期取一条指令,只要指令侧内存支持。 译码/执行(ID/EX):译码并立即执行,所有的操作,包括寄存器读写、内存访问都在该阶段进行。 Ibex 支持多周期指令,每条指令都至少需要两个周期才能通过流水线,周期数更大的指令将导致流水线停顿多个周期。指令类型及其停顿周期如下: Instruction Type Stall Cycles Description Integer Computational 0 Integer Computational Instructions are defined in the RISCV-V RV32I Base Integer Instruction Set. CSR Access 0 CSR Access Instruction are defined in ‘Zicsr’ of the RISC-V specification. Load/Store 1 - N Both loads and stores stall for at least one cycle to await a response. For loads this response is t

Vim 最强调试插件:Vimspector

    Vimspector 是一个基于 DAP(debug adapter protocol) 的Vim多语言调试插件,理论上能够支持所有支持语言(只要有对应的 DAP)。这个插件仍在实验阶段,可能会有各种bug,但是对C/C++、Python 等流行的语言已经进行了充分的测试。 这篇文章以调试 C/C++ 程序为例,介绍 vimspector 的配置与使用。 依赖 安装 vimspector 调试适配器 配置 调试适配器配置 调试会话配置 配置选项 变量 预定义变量 自定义变量 默认值 类型转换 多配置共存 断点 示例 调试 Vim 调试 qemu-riscv64 中的 OS 内核 我自己的配置 快捷键 GDB 前端推荐(题外话) 依赖 带 Python3.6+ 支持的 Vim 8.2 或更高版本 带 Python3.6+ 支持的 Neovim-0.4.3 或更高版本(最好是 Nightly 版本) 由于 vimspector 的作者主要在 GNU/Linux 上使用 Vim 开发,因此 Vimspector 作者 puremourning 明确表示在 vimspector 的充分测试并稳定后才会提供对 Neovim 的完整支持(见 issue coc.nvim: Debug Adapter Protocol Support #322 ),因此目前对于 Neovim 和 Windows 的支持都处于实验阶段。我个人建议在 Vim 中使用本插件。   安装 vimspector 使用 vim-plug 安装: xxxxxxxxxx Plug 'puremourning/vimspector' 使用 dein.vim 安装: xxxxxxxxxx call dein#add('puremourning/vimspector') 调试适配器 最新版的 vimspector 可以在 Vim 中通过命令 :VimspectorInstall 安装调试适配器,按 Tab 键可以补全。 也可以使用安装脚本安装,进入 vimspector 的安装目录,执行: xxxxxxxxxx ./install_gadget.py <language-name> install_gadget.py 会自动下载 <language

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK