ucore lab2学习笔记整理

本次实验主要完成ucore内核对物理内存的管理。

ucore被启动后,需要探测系统的物理内存布局来了解哪些物理内存空间是可用的。ucore是使用e820h中断来获取内存信息,而这个中断必须在实模式下使用,因此必须在bootloader引导进入保护模式前进行,这些收集到的数据将保存在物理地址0x8000处,通过代码中定义的e820map结构体进行映射。

启动分页机制

ucore在80386中的分页机制实现了基本平坦模型的段页式内存管理,这是为后续的虚拟内存做好准备。

CR开头的寄存器为控制寄存器。

CR0寄存器包含系统控制标志,这些标志控制着处理器的运行模式和状态。分页(CR0 的第 31 位)。置 1 启用分页,置 0 不启用分页。当禁用分页 时,所有的线性地址都当作物理地址对待。

CR3寄存器包含页目录表的物理基地址和二个标志(PCD和PWT)。该寄存器也被称为页目录基地址寄存器(PDBR)。ucore中用boot_cr3(mm/pmm.c)来记录这个值。

程序中使用的地址都是逻辑地址,逻辑地址/虚拟地址通过GDT/LDT可以转换为线性地址,线性地址可以通过页表来转化为物理地址。

lab2在运行中分为了4个阶段:

1、bootloader阶段,此时virt addr = linear addr = phy addr

2、第二个阶段从kern_entry开始,但是还没执行enable_paging,尚未开启分页机制之前,线性地址都是等于物理地址。此时虚拟地址到线性地址的映射更新了,新的映射关系为 virt addr - 0xC0000000 = linear addr = phy addr

3、第三个阶段从enable_paging函数开始,到执行gdt_init函数。执行完enable_paging函数中的加载CR0指令,即让CR0寄存器中的PG位置1,启动分页,接下来就是段页式的映射关系了。此时段映射还没有为分页机制的开启而再次更新调整,所以映射关系比较微妙,如下所示

virt addr - 0xC0000000 = linear addr = phy addr + 0xC0000000# 物理地址 在0~4MB之外的三者映射关系

virt addr - 0xC0000000 = linear addr = phy addr# 物理地址在0~4MB之内的三者的映射关系

如上的特殊关系是因为pmm_init中的一条代码:

boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)];因为页目录表中每一项代表一个页表,一个页表可以管理4MB的内存大小,因为pgdir中每一项表示4MB的物理内存地址。即线性地址从0开始的4MB和从KERNBASE开始的4MB通过分页机制映射到同一段内存空间中。

4、从get_init函数开始,此时段映射更新调整,并取消了临时的映射关系,形成了我们期望的映射关系。映射关系为virt addr = linear addr = phy addr + 0xC0000000

ucore中页的大小为4KB,每个页都是使用Page数据结构来表示。

struct Page{
int ref;//页帧的引用次数,若有一个虚拟页映射到此页上,则加一
uint32_t flags;//表示该页帧的状态
unsigned int property;//连续内存空闲块的大小,连续空闲块的首个页帧才会设置此属性
list_entry_t page_link;//通用数据结构,链表查找用的。
}

ucore使用了free_area_t这个数据结构进行管理大量零散的空闲连续内存块。

typedef struct{
list_entry_t free_list;//首个空闲连续内存快的entry
unsigned int nr_free;//此链表中的连续空闲块的个数
}free_area_t;
npage=maxpa/PGSIZE;//得到需要管理的物理页个数
pages=(struct Page *)ROUNDUP((void *)end,PGSIZE);//这个地址是bootloader加载ucore的结束地址后以PGSIZE对齐后的地址。
uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);//由于管理内存的所有页帧还需要npage个Page数据结构所以还需要腾出内存空间给Page,后续的内存地址即为空闲物理内存空间。
/* pmm_init - initialize the physical memory management */
static void
page_init(void) {
   struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);//指针寻址使用的是线性虚拟地址。
   uint64_t maxpa = 0;
​
   cprintf("e820map:\n");
   int i;
   //遍历memmap中的每一项
   for (i = 0; i < memmap->nr_map; i ++) {
       uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;
       cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n",
               memmap->map[i].size, begin, end - 1, memmap->map[i].type);
       if (memmap->map[i].type == E820_ARM) {
           if (maxpa < end && begin < KMEMSIZE) {
               maxpa = end;
          }
      }
  }
   //如果maxpa超过了定义约束的最大可用物理内存空间,则进行调整
   if (maxpa > KMEMSIZE) {
       maxpa = KMEMSIZE;
  }

   extern char end[];//end是ucore kernel加载后定义的第二个全局变量,该变量所在内存地址后续空间均没有被使用,因此以该地址为起点,存放用于管理物理内存的Page数据结构。
​
   npage = maxpa / PGSIZE;
   pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);
​
   for (i = 0; i < npage; i ++) {
       SetPageReserved(pages + i);//遍历每一个物理页,默认标记为保留
  }
​
   uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);
​
   for (i = 0; i < memmap->nr_map; i ++) {
       uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;
       if (memmap->map[i].type == E820_ARM) {
           if (begin < freemem) {
           //限制空闲地址的最小值
               begin = freemem;
          }
           if (end > KMEMSIZE) {
           //限制空闲地址的最大值
               end = KMEMSIZE;
          }
           if (begin < end) {
           //用PGSIZE对齐地址
               begin = ROUNDUP(begin, PGSIZE);
               end = ROUNDDOWN(end, PGSIZE);
               if (begin < end) {
               //空闲内存块的映射,将其纳入物理内存管理器中,用于后续的物理内存管理
                   init_memmap(pa2page(begin), (end - begin) / PGSIZE);
              }
          }
      }
  }
}

代码部分

pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
  // 该函数通过线性地址来找到找到对应的页表项(二级页表项),并返回这个页表项的虚拟地址
  	// 获得指定页目录表项的地址
 		pde_t *pdep = &pgdir[PDX(la)];
    // 判断当前页目录项的Present存在位是否为1(对应的二级页表是否存在)
    if (!(*pdep & PTE_P)) {
        // 对应的二级页表不存在
        // *page指向的是这个新创建的二级页表基地址
        struct Page *page;
        if (!create || (page = alloc_page()) == NULL) {
             // 如果create参数为false或是alloc_page分配物理内存失败
            return NULL;
        }
        // 二级页表所对应的物理页 引用数为1
        set_page_ref(page, 1);
        // 获得被page变量管理的页帧的物理地址
        uintptr_t pa = page2pa(page);
        // 将上述指定页帧全部填满0,函数的参数要求是虚拟地址,因此需要将物理地址转化为虚拟地址
        memset(KADDR(pa), 0, PGSIZE);
        // la对应的一级页目录项进行赋值,使其指向新创建的二级页表(页表中的数据被MMU直接处理,为了映射效率存放的都是物理地址)
        // 或PTE_U/PTE_W/PET_P 标识当前页目录项是用户级别的、可写的、已存在的
        *pdep = pa | PTE_U | PTE_W | PTE_P;
    }

    // 要想通过C语言中的数组来访问对应数据,需要的是数组基址(虚拟地址),而*pdep中页目录表项中存放了对应二级页表的一个物理地址
    // 由于内存中的每个页帧都是4KB大小整齐分配,因此每个页帧的物理地址的低12位必然都是0,这低12位有其他作用和二级页表的地址无关
  	//PDE_ADDR将*pdep的低12位抹零对齐(指向二级页表的起始基地址),再通过KADDR转为内核虚拟地址,进行数组访问
    // PTX(la)获得la线性地址的中间10位部分,即二级页表中对应页表项的索引下标。这样便能得到la对应的二级页表项了
    return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
}

分配物理内存页的功能由default_alloc_pages函数完成

/**
 * 接受一个合法的正整数参数n,为其分配N个物理页面大小的连续物理内存空间.
 * 并以Page指针的形式,返回最低位物理页(最前面的)。
 * 
 * 如果分配时发生错误或者剩余空闲空间不足,则返回NULL代表分配失败
 * */
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) {
        // 将le节点转换为关联的Page结构
        struct Page *p = le2page(le, page_link);
        if (p->property >= n) {
            // 发现一个满足要求的,空闲页数大于等于N的空闲块
            page = p;
            break;
        }
    }
    // 如果page != null代表找到了,分配成功。反之则分配物理内存失败
    if (page != NULL) {
        if (page->property > n) {
            // 如果空闲块的大小不是正合适(page->property != n)
            // 按照指针偏移,找到按序后面第N个Page结构p
            struct Page *p = page + n;
            // p其空闲块个数 = 当前找到的空闲块数量 - n
            p->property = page->property - n;
            SetPageProperty(p);
            // 按对应的物理地址顺序,将p加入到空闲链表中对应的位置
            list_add_after(&(page->page_link), &(p->page_link));
        }
        // 在将当前page从空间链表中移除
        list_del(&(page->page_link));
        // 闲链表整体空闲页数量自减n
        nr_free -= n;
        // 清楚page的property(因为非空闲块的头Page的property都为0)
        ClearPageProperty(page);
    }
    return page;
}
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
    if (*ptep & PTE_P) {
        // 如果对应的二级页表项存在
        // 获得*ptep对应的Page结构
        struct Page *page = pte2page(*ptep);
        // 关联的page引用数自减1
        if (page_ref_dec(page) == 0) {
            // 如果自减1后,引用数为0,需要free释放掉该物理页
            free_page(page);
        }
        // 清空当前二级页表项(整体设置为0)
        *ptep = 0;
        // 由于页表项发生了改变,需要TLB快表
        tlb_invalidate(pgdir, la);
    }
}

释放物理内存页的功能由default_free_pages函数完成

/**
 * 释放掉自base起始的连续n个物理页,n必须为正整数
 * */
static void
default_free_pages(struct Page *base, size_t n) {
    assert(n > 0);
    struct Page *p = base;

    // 遍历这N个连续的Page页,将其相关属性设置为空闲
    for (; p != base + n; p ++) {
        assert(!PageReserved(p) && !PageProperty(p));
        p->flags = 0;
        set_page_ref(p, 0);
    }

    // 由于被释放了N个空闲物理页,base头Page的property设置为n
    base->property = n;
    SetPageProperty(base);

    // 下面进行空闲链表相关操作
    list_entry_t *le = list_next(&free_list);
    // 迭代空闲链表中的每一个节点
    while (le != &free_list) {
        // 获得节点对应的Page结构
        p = le2page(le, page_link);
        le = list_next(le);
        if (base + base->property == p) {
            // 如果当前base释放了N个物理页后,尾部正好能和Page p连上,则进行两个空闲块的合并
            base->property += p->property;
            ClearPageProperty(p);
            list_del(&(p->page_link));
        }
        else if (p + p->property == base) {
            // 如果当前Page p能和base头连上,则进行两个空闲块的合并
            p->property += base->property;
            ClearPageProperty(base);
            base = p;
            list_del(&(p->page_link));
        }
    }
    // 空闲链表整体空闲页数量自增n
    nr_free += n;
    le = list_next(&free_list);

    // 迭代空闲链表中的每一个节点
    while (le != &free_list) {
        // 转为Page结构
        p = le2page(le, page_link);
        if (base + base->property <= p) {
            // 进行空闲链表结构的校验,不能存在交叉覆盖的地方
            assert(base + base->property != p);
            break;
        }
        le = list_next(le);
    }
    // 将base加入到空闲链表之中
    list_add_before(le, &(base->page_link));
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值