ucore lab2

主要姓名:冯成 学号:19021221183 学院:电子工程学院

【嵌牛导读】

本章实验是上一讲的续集,只要是管理物理内存。

首先必须要注意一点的是在lab2和lab1中并没有建立起完整的虚拟机制,但是gdb是采用虚拟地址的,所以会发现部分调试信息时错误的。以lab2为例:可以注意到在lab2中的tools/kernel.ld中有这一行代码. = 0xC0100000; 。这表示kernel的文件的虚拟地址起点在0xC0100000。而lab2中在kernel_entry中开启了分页机制,开启分页机制后CPU寻址采用虚拟地址,而此时lab2中没有完善虚拟机制,所以不能简单的直接使用gdb进行调试。如下示例。

page_init(void) {
    struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);
    uint64_t maxpa = 0;
    cprintf("maxpa = %d, &maxpa =%p \n", maxpa, (void *)&maxpa);

    int a=12;
    cprintf("a = %d, &a =%p \n", a, (void  *)&a);
    cprintf("e820map:\n");
    int i;
    for (i = 0; i < memmap->nr_map; i ++) {
        ...
    }
}



第一次循环中i变量初始化为0,但是当我们在gdb中使用print i时会得到下面结果。(下面是在vscode中的gdb调试结果,可能数值会不一样)。

-exec print i
$2 = -1072667567

为了说明这个问题,在原始代码中增加了两行测试代码,即

int a=12;
cprintf("a = %d, &a =%p \n", a, (void  *)&a);

我们在gdb中打印a的地址会得到如下结果,

-exec print &a
$3 = (int *) 0xc0118ec8
-exec print a
$4 = -1072667557

但是程序的输出为

a = 12, &a =0xc0118f44

很明显gdb的结果是错误的,两个地址之间有f44-ec8=7c的偏差。我们可以给gdb中的地址加上这个偏差得到一个真实的地址。
以循环中i的值为例,首先我们在gdb中使用print &i获取到i的地址,如

-exec print &i
$5 = (int *) 0xc0118f20

该地址加上偏移量得到0xc0118f9c。于是真实的i的地址就在0xc0118f20位置。我们验证如下

-exec x/4xb 0xc0118f9c 
0xc0118f9c: 0x00    0x00    0x00    0x00

接下来的几次循环中分别为

0x01    0x00    0x00    0x00
0x02    0x00    0x00    0x00
0x03    0x00    0x00    0x00

目前发现问题
1, 为什么不能输出正确的maxpa地址,程序输出结果为

maxpa = 0, &maxpa =0x0

2, 为什么不能调试entry.S中的代码。

但是可以通过
objdump -S -d kernel来得到运行信息。

   6
   7 c0100000 <kern_entry>:
   8 c0100000:   b8 00 a0 11 00          mov    $0x11a000,%eax
   9 c0100005:   0f 22 d8                mov    %eax,%cr3
  10 c0100008:   0f 20 c0                mov    %cr0,%eax
  11 c010000b:   0d 2f 00 05 80          or     $0x8005002f,%eax
  12 c0100010:   83 e0 f3                and    $0xfffffff3,%eax
  13 c0100013:   0f 22 c0                mov    %eax,%cr0
  14 c0100016:   8d 05 1e 00 10 c0       lea    0xc010001e,%eax
  15 c010001c:   ff e0                   jmp    *%eax
  16
  17 c010001e <next>:
  18 c010001e:   31 c0                   xor    %eax,%eax
  19 c0100020:   a3 00 a0 11 c0          mov    %eax,0xc011a000
  20 c0100025:   bd 00 00 00 00          mov    $0x0,%ebp
  21 c010002a:   bc 00 90 11 c0          mov    $0xc0119000,%esp
  22 c010002f:   e8 02 00 00 00          call   c0100036 <kern_init>



ubuntu@VM-0-9-ubuntu:~/os_kernel_lab/labcodes/lab2/bin$ readelf -a kernel
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0xc0100000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          130828 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         12
  Section header string table index: 11

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        c0100000 001000 006198 00  AX  0   0  1
  [ 2] .rodata           PROGBITS        c01061a0 0071a0 001350 00   A  0   0 32
  [ 3] .stab             PROGBITS        c01074f0 0084f0 00c8dd 0c   A  4   0  4
  [ 4] .stabstr          STRTAB          c0113dcd 014dcd 002b32 00   A  0   0  1
  [ 5] .data             PROGBITS        c0117000 018000 002a36 00  WA  0   0 4096
  [ 6] .data.pgdir       PROGBITS        c011a000 01b000 002000 00  WA  0   0 4096
  [ 7] .bss              NOBITS          c011c000 01d000 000f28 00  WA  0   0 32
  [ 8] .comment          PROGBITS        00000000 01d000 00002b 01  MS  0   0  1
  [ 9] .symtab           SYMTAB          00000000 01d02c 001d40 10     10 124  4
  [10] .strtab           STRTAB          00000000 01ed6c 001148 00      0   0  1
  [11] .shstrtab         STRTAB          00000000 01feb4 000058 00      0   0  1


lab2 导读:

首先相对于lab1,lab2实现了对物理内存得管理,开启了分页机制,也实现了页分配机制。
在entry.S文件中,主要的多了页机制的初始化,如:

# kernel builtin pgdir
# an initial page directory (Page Directory Table, PDT)
# These page directory table and page table can be reused!
.section .data.pgdir
.align PGSIZE
__boot_pgdir:
.globl __boot_pgdir
    # map va 0 ~ 4M to pa 0 ~ 4M (temporary)
    # .space a, b 表示将a大小的内存设置为b,如果省略“,b”则设置为0.
    .long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
    # 从4M到KERNBASE设置为0
    .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
    # 实际上就是从KERNBASE+4M到4G设置为0

# 实际上__boot_pgdir就是页目录,__boot_pt1是二级页表。
# 虚拟内存为0 ~ 4M和 KERNBASE + (0 ~ 4M)都使用了同一个二级页目录,即__boot_pt1
# 表明这两个虚拟地址对应同一个物理地址。
.set i, 0
__boot_pt1:
.rept 1024
    .long i * PGSIZE + (PTE_P | PTE_W)
    .set i, i + 1
.endr

为什么要这样做?
要理解这里就必须明白为什么在kern_entry的第一行是movl $REALLOC(__boot_pgdir), %eax,因为此时并没有开启分页机制,CPU使用的是物理地址,而在编译链接时整个程序被链接在虚拟地址为KERNBASE的偏移地方。所以此时的__boot_pgdir地址(虚拟)是大于KERNBASE,但是实际上__boot_pgdir物理地址可能在0到1GB之间,所以必须使用REALLOC宏来得到真实的__boot_pgdir物理地址。而next中已经是开启了分页机制了,所以此时对__boot_pgdir的访问不需要使用REALLOC宏了。
lab1中我们知道ucore在物理内存0x00100000以上,我们假设,在kern_entry中,eip一直是0x001...。假设执行 movl %eax, %cr0前(开启分页机制前),eip是0x001xxx,执行后为eip是0x001xx1(CPU流水线)。此时的eip应该是下一行,但是因为开起了分页机制,所以我们必须把0x001xx1映射到真实的物理地址,也就是把vm 0-4m映射为pm 0-4m。当通过跳转执行到next后,此时的eip是KERNBASE + 0x001.....。此后所有的访问也都是基于KERNBASE 的,所以第一个页表项就是无用的,就需要清除(next的第一二行代码)。

kern_entry:
    # load pa of boot pgdir
    movl $REALLOC(__boot_pgdir), %eax
    movl %eax, %cr3

    # enable paging
    movl %cr0, %eax
    orl $(CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP), %eax
    andl $~(CR0_TS | CR0_EM), %eax
    movl %eax, %cr0

    # update eip
    # now, eip = 0x1.....
    leal next, %eax
    # set eip = KERNBASE + 0x1.....
    jmp *%eax
next:

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

    # set ebp, esp
    movl $0x0, %ebp
    # the kernel stack region is from bootstack -- bootstacktop,
    # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h
    movl $bootstacktop, %esp
    # now kernel stack is ready , call the first C function
    call kern_init

首先在kern_init中多了pmm_init函数
该函数主要做了以下操作,根据bios探索的内存,找到一个最大值(小于等于KMEMSIZE),然后把从当前空闲位置(即ucore代码数据段后)到最大值这个范围内的物理地址使用Page结构体描述。然后将这个范围内的有效地址PGSIZE对齐并进行初始化(设置属性),放在空闲链表里面。

相较于lab1,lab中需要完善的代码就是对物理内存得管理。首先可以通过阅读default_pmm.c了解简单的FFMA算法,虽然注释上说需要完善,default_init, default_init_memmap, default_alloc_pages, default_free_pages.但是实际上这些都已经完善了,需要修改的地方只有一点,就是将list_add中的list_add_after改为list_add_before。因为default_pmm.c 中的default_check函数中有以下代码,为了方便理解,我们删掉了代码中不影响的代码,但是整体表达意思没变:

    struct Page *p0 = alloc_pages(5), *p1, *p2;

    free_pages(p0 + 2, 3);
    p2 = p0 + 1;
    free_page(p0);
    assert((p0 = alloc_page()) == p2 - 1);

上边代码表明,刚释放一个页,然后再申请一个页,一定是刚才释放的那一页。这就需要分配器从列表的首部获取空闲Page,每次将释放的空闲页表也放到首部,所以我们释放空闲链表需要使用list_add_before而不是list_add_after。

流程

检查check_pgdir
assert(get_page(boot_pgdir, 0x0, NULL) == NULL);

// 获取la地址对应的Page结构体信息
struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store)
{
    pte_t *ptep = get_pte(pgdir, la, 0);
    if (ptep_store != NULL)
    {
        *ptep_store = ptep;
    }
    if (ptep != NULL && *ptep & PTE_P)
    {
        return pte2page(*ptep);
    }
    return NULL;
}


pte_t * get_pte(pde_t *pgdir, uintptr_t la, bool create)
{

    // (1) find page directory entry
    // (2) check if entry is not present
    // (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
    // (4) set page reference
    // (5) get linear address of page
    // (6) clear page content using memset
    // (7) set page directory entry's permission
    // (8) return page table entry
    // 这里的代码,先按自己理解的写,只要逻辑写对,然后运行,看看哪里报错,然后查看错误代码。
    // 比如boot_pgdir[hight12]到底存储促你地址还是物理地址
    pde_t *pdep = NULL;
    uint32_t hight12 = PDX(la); // 保留高12位
    uint32_t twopageentry; // 二级页表入口
    // NOTE:根据entry.S中代码,一级的目录项都有  (PTE_P | PTE_U | PTE_W)

    if (create && !(boot_pgdir[hight12] & PTE_P)) // 不存在并且需要创建新页·
    {
        // 分配一个页存储一个二级页目录,也就是1024个pte项。
        // 32位系统下,1024个pte项,刚好是4k。
        struct Page *temp = alloc_page();
        if(temp==NULL)
        {
            return NULL;
        }
        uint32_t pa = page2pa(temp);  // 获取到这个Page的、真实物理地址
        set_page_ref(temp, 1);
        memset(KADDR(pa), 0, PGSIZE);  // 将这个Page的内存区域清空
        

        // 实际上boot_pgdir[hight12]这里也可以存储物理地址,只要约定好就可以了,但是
        // check_pgdir  中有一行代码:ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1];
        // 这表明boot_pgdir[hight12]存储的是虚拟地址
        // boot_pgdir[hight12] = ((uint32_t)KADDR(pa) | (PTE_P | PTE_U | PTE_W));
        boot_pgdir[hight12] = pa | (PTE_P | PTE_U | PTE_W);
    }
    twopageentry = KADDR(PDE_ADDR(boot_pgdir[hight12])); // PED_ADDR 是将低12位清零的
    if (boot_pgdir[hight12] & PTE_P)
    {
        // PTX 保留高22位
        pdep = &(((pte_t *)twopageentry)[PTX(la)]);
    }

    return pdep;
}


struct Page *
alloc_pages(size_t n)
{
    struct Page *page = NULL;
    bool intr_flag;
    // 开关中断
    local_intr_save(intr_flag);
    {
        page = pmm_manager->alloc_pages(n);
    }
    local_intr_restore(intr_flag);
    return page;
}

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;
    while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link);
        if (p->property >= n) {   // 找到第一个大于n的空闲块
            page = p;
            break;
        }
    }
    if (page != NULL)  // 将空闲块中剩余的页放在空闲链表里面
    {
        list_del(&(page->page_link));
        if (page->property > n)
        {
            struct Page *p = page + n;
            SetPageProperty(p);
            p->property = page->property - n;
            list_add(&free_list, &(p->page_link));
        }
        nr_free -= n;
        ClearPageProperty(page);
    }
    return page;
}


// page_remove_pte - free an Page sturct which is related linear address la
//                 - and clean(invalidate) pte which is related linear address la
// note: PT is changed, so the TLB need to be invalidate
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep)
{

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

}

首先必须要注意一点的是在lab2和lab1中并没有建立起完整的虚拟机制,但是gdb是采用虚拟地址的,所以会发现部分调试信息时错误的。以lab2为例:可以注意到在lab2中的tools/kernel.ld中有这一行代码. = 0xC0100000; 。这表示kernel的文件的虚拟地址起点在0xC0100000。而lab2中在kernel_entry中开启了分页机制,开启分页机制后CPU寻址采用虚拟地址,而此时lab2中没有完善虚拟机制,所以不能简单的直接使用gdb进行调试。如下示例。

page_init(void) {
    struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);
    uint64_t maxpa = 0;
    cprintf("maxpa = %d, &maxpa =%p \n", maxpa, (void *)&maxpa);

    int a=12;
    cprintf("a = %d, &a =%p \n", a, (void  *)&a);
    cprintf("e820map:\n");
    int i;
    for (i = 0; i < memmap->nr_map; i ++) {
        ...
    }
}

第一次循环中i变量初始化为0,但是当我们在gdb中使用print i时会得到下面结果。(下面是在vscode中的gdb调试结果,可能数值会不一样)。

-exec print i
$2 = -1072667567

为了说明这个问题,在原始代码中增加了两行测试代码,即

int a=12;
cprintf("a = %d, &a =%p \n", a, (void  *)&a);

我们在gdb中打印a的地址会得到如下结果,

-exec print &a
$3 = (int *) 0xc0118ec8
-exec print a
$4 = -1072667557

但是程序的输出为

a = 12, &a =0xc0118f44

很明显gdb的结果是错误的,两个地址之间有f44-ec8=7c的偏差。我们可以给gdb中的地址加上这个偏差得到一个真实的地址。
以循环中i的值为例,首先我们在gdb中使用print &i获取到i的地址,如

-exec print &i
$5 = (int *) 0xc0118f20

该地址加上偏移量得到0xc0118f9c。于是真实的i的地址就在0xc0118f20位置。我们验证如下

-exec x/4xb 0xc0118f9c 
0xc0118f9c: 0x00    0x00    0x00    0x00

接下来的几次循环中分别为

0x01    0x00    0x00    0x00
0x02    0x00    0x00    0x00
0x03    0x00    0x00    0x00

目前发现问题
1, 为什么不能输出正确的maxpa地址,程序输出结果为

maxpa = 0, &maxpa =0x0

2, 为什么不能调试entry.S中的代码。

但是可以通过
objdump -S -d kernel来得到运行信息。

   6
   7 c0100000 <kern_entry>:
   8 c0100000:   b8 00 a0 11 00          mov    $0x11a000,%eax
   9 c0100005:   0f 22 d8                mov    %eax,%cr3
  10 c0100008:   0f 20 c0                mov    %cr0,%eax
  11 c010000b:   0d 2f 00 05 80          or     $0x8005002f,%eax
  12 c0100010:   83 e0 f3                and    $0xfffffff3,%eax
  13 c0100013:   0f 22 c0                mov    %eax,%cr0
  14 c0100016:   8d 05 1e 00 10 c0       lea    0xc010001e,%eax
  15 c010001c:   ff e0                   jmp    *%eax
  16
  17 c010001e <next>:
  18 c010001e:   31 c0                   xor    %eax,%eax
  19 c0100020:   a3 00 a0 11 c0          mov    %eax,0xc011a000
  20 c0100025:   bd 00 00 00 00          mov    $0x0,%ebp
  21 c010002a:   bc 00 90 11 c0          mov    $0xc0119000,%esp
  22 c010002f:   e8 02 00 00 00          call   c0100036 <kern_init>



ubuntu@VM-0-9-ubuntu:~/os_kernel_lab/labcodes/lab2/bin$ readelf -a kernel
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0xc0100000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          130828 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         12
  Section header string table index: 11

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        c0100000 001000 006198 00  AX  0   0  1
  [ 2] .rodata           PROGBITS        c01061a0 0071a0 001350 00   A  0   0 32
  [ 3] .stab             PROGBITS        c01074f0 0084f0 00c8dd 0c   A  4   0  4
  [ 4] .stabstr          STRTAB          c0113dcd 014dcd 002b32 00   A  0   0  1
  [ 5] .data             PROGBITS        c0117000 018000 002a36 00  WA  0   0 4096
  [ 6] .data.pgdir       PROGBITS        c011a000 01b000 002000 00  WA  0   0 4096
  [ 7] .bss              NOBITS          c011c000 01d000 000f28 00  WA  0   0 32
  [ 8] .comment          PROGBITS        00000000 01d000 00002b 01  MS  0   0  1
  [ 9] .symtab           SYMTAB          00000000 01d02c 001d40 10     10 124  4
  [10] .strtab           STRTAB          00000000 01ed6c 001148 00      0   0  1
  [11] .shstrtab         STRTAB          00000000 01feb4 000058 00      0   0  1


lab2 导读:

首先相对于lab1,lab2实现了对物理内存得管理,开启了分页机制,也实现了页分配机制。
在entry.S文件中,主要的多了页机制的初始化,如:

# kernel builtin pgdir
# an initial page directory (Page Directory Table, PDT)
# These page directory table and page table can be reused!
.section .data.pgdir
.align PGSIZE
__boot_pgdir:
.globl __boot_pgdir
    # map va 0 ~ 4M to pa 0 ~ 4M (temporary)
    # .space a, b 表示将a大小的内存设置为b,如果省略“,b”则设置为0.
    .long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
    # 从4M到KERNBASE设置为0
    .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
    # 实际上就是从KERNBASE+4M到4G设置为0

# 实际上__boot_pgdir就是页目录,__boot_pt1是二级页表。
# 虚拟内存为0 ~ 4M和 KERNBASE + (0 ~ 4M)都使用了同一个二级页目录,即__boot_pt1
# 表明这两个虚拟地址对应同一个物理地址。
.set i, 0
__boot_pt1:
.rept 1024
    .long i * PGSIZE + (PTE_P | PTE_W)
    .set i, i + 1
.endr

为什么要这样做?
要理解这里就必须明白为什么在kern_entry的第一行是movl $REALLOC(__boot_pgdir), %eax,因为此时并没有开启分页机制,CPU使用的是物理地址,而在编译链接时整个程序被链接在虚拟地址为KERNBASE的偏移地方。所以此时的__boot_pgdir地址(虚拟)是大于KERNBASE,但是实际上__boot_pgdir物理地址可能在0到1GB之间,所以必须使用REALLOC宏来得到真实的__boot_pgdir物理地址。而next中已经是开启了分页机制了,所以此时对__boot_pgdir的访问不需要使用REALLOC宏了。
lab1中我们知道ucore在物理内存0x00100000以上,我们假设,在kern_entry中,eip一直是0x001...。假设执行 movl %eax, %cr0前(开启分页机制前),eip是0x001xxx,执行后为eip是0x001xx1(CPU流水线)。此时的eip应该是下一行,但是因为开起了分页机制,所以我们必须把0x001xx1映射到真实的物理地址,也就是把vm 0-4m映射为pm 0-4m。当通过跳转执行到next后,此时的eip是KERNBASE + 0x001.....。此后所有的访问也都是基于KERNBASE 的,所以第一个页表项就是无用的,就需要清除(next的第一二行代码)。

kern_entry:
    # load pa of boot pgdir
    movl $REALLOC(__boot_pgdir), %eax
    movl %eax, %cr3

    # enable paging
    movl %cr0, %eax
    orl $(CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP), %eax
    andl $~(CR0_TS | CR0_EM), %eax
    movl %eax, %cr0

    # update eip
    # now, eip = 0x1.....
    leal next, %eax
    # set eip = KERNBASE + 0x1.....
    jmp *%eax
next:

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

    # set ebp, esp
    movl $0x0, %ebp
    # the kernel stack region is from bootstack -- bootstacktop,
    # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h
    movl $bootstacktop, %esp
    # now kernel stack is ready , call the first C function
    call kern_init

首先在kern_init中多了pmm_init函数
该函数主要做了以下操作,根据bios探索的内存,找到一个最大值(小于等于KMEMSIZE),然后把从当前空闲位置(即ucore代码数据段后)到最大值这个范围内的物理地址使用Page结构体描述。然后将这个范围内的有效地址PGSIZE对齐并进行初始化(设置属性),放在空闲链表里面。

相较于lab1,lab中需要完善的代码就是对物理内存得管理。首先可以通过阅读default_pmm.c了解简单的FFMA算法,虽然注释上说需要完善,default_init, default_init_memmap, default_alloc_pages, default_free_pages.但是实际上这些都已经完善了,需要修改的地方只有一点,就是将list_add中的list_add_after改为list_add_before。因为default_pmm.c 中的default_check函数中有以下代码,为了方便理解,我们删掉了代码中不影响的代码,但是整体表达意思没变:

    struct Page *p0 = alloc_pages(5), *p1, *p2;

    free_pages(p0 + 2, 3);
    p2 = p0 + 1;
    free_page(p0);
    assert((p0 = alloc_page()) == p2 - 1);

上边代码表明,刚释放一个页,然后再申请一个页,一定是刚才释放的那一页。这就需要分配器从列表的首部获取空闲Page,每次将释放的空闲页表也放到首部,所以我们释放空闲链表需要使用list_add_before而不是list_add_after。

流程

检查check_pgdir
assert(get_page(boot_pgdir, 0x0, NULL) == NULL);

// 获取la地址对应的Page结构体信息
struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store)
{
    pte_t *ptep = get_pte(pgdir, la, 0);
    if (ptep_store != NULL)
    {
        *ptep_store = ptep;
    }
    if (ptep != NULL && *ptep & PTE_P)
    {
        return pte2page(*ptep);
    }
    return NULL;
}


pte_t * get_pte(pde_t *pgdir, uintptr_t la, bool create)
{

    // (1) find page directory entry
    // (2) check if entry is not present
    // (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
    // (4) set page reference
    // (5) get linear address of page
    // (6) clear page content using memset
    // (7) set page directory entry's permission
    // (8) return page table entry
    // 这里的代码,先按自己理解的写,只要逻辑写对,然后运行,看看哪里报错,然后查看错误代码。
    // 比如boot_pgdir[hight12]到底存储促你地址还是物理地址
    pde_t *pdep = NULL;
    uint32_t hight12 = PDX(la); // 保留高12位
    uint32_t twopageentry; // 二级页表入口
    // NOTE:根据entry.S中代码,一级的目录项都有  (PTE_P | PTE_U | PTE_W)

    if (create && !(boot_pgdir[hight12] & PTE_P)) // 不存在并且需要创建新页·
    {
        // 分配一个页存储一个二级页目录,也就是1024个pte项。
        // 32位系统下,1024个pte项,刚好是4k。
        struct Page *temp = alloc_page();
        if(temp==NULL)
        {
            return NULL;
        }
        uint32_t pa = page2pa(temp);  // 获取到这个Page的、真实物理地址
        set_page_ref(temp, 1);
        memset(KADDR(pa), 0, PGSIZE);  // 将这个Page的内存区域清空
        

        // 实际上boot_pgdir[hight12]这里也可以存储物理地址,只要约定好就可以了,但是
        // check_pgdir  中有一行代码:ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1];
        // 这表明boot_pgdir[hight12]存储的是虚拟地址
        // boot_pgdir[hight12] = ((uint32_t)KADDR(pa) | (PTE_P | PTE_U | PTE_W));
        boot_pgdir[hight12] = pa | (PTE_P | PTE_U | PTE_W);
    }
    twopageentry = KADDR(PDE_ADDR(boot_pgdir[hight12])); // PED_ADDR 是将低12位清零的
    if (boot_pgdir[hight12] & PTE_P)
    {
        // PTX 保留高22位
        pdep = &(((pte_t *)twopageentry)[PTX(la)]);
    }

    return pdep;
}


struct Page *
alloc_pages(size_t n)
{
    struct Page *page = NULL;
    bool intr_flag;
    // 开关中断
    local_intr_save(intr_flag);
    {
        page = pmm_manager->alloc_pages(n);
    }
    local_intr_restore(intr_flag);
    return page;
}

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;
    while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link);
        if (p->property >= n) {   // 找到第一个大于n的空闲块
            page = p;
            break;
        }
    }
    if (page != NULL)  // 将空闲块中剩余的页放在空闲链表里面
    {
        list_del(&(page->page_link));
        if (page->property > n)
        {
            struct Page *p = page + n;
            SetPageProperty(p);
            p->property = page->property - n;
            list_add(&free_list, &(p->page_link));
        }
        nr_free -= n;
        ClearPageProperty(page);
    }
    return page;
}


// page_remove_pte - free an Page sturct which is related linear address la
//                 - and clean(invalidate) pte which is related linear address la
// note: PT is changed, so the TLB need to be invalidate
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep)
{

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

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容