MIT6.828——LAB2:memory management

Part1:Physical Page Management

内存管理两个组件:

  1. 物理内存分配器:pages(4096B)
  2. 虚拟内存:将内核和用户软件使用的虚拟地址映射到物理内存中的地址
/*
 * Virtual memory map:                                Permissions
 *                                                    kernel/user
 *
 *    4 Gig -------->  +------------------------------+
 *                     |                              | RW/--
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     :              .               :
 *                     :              .               :
 *                     :              .               :
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
 *                     |                              | RW/--
 *                     |   Remapped Physical Memory   | RW/--
 *                     |                              | RW/--
 *    KERNBASE, ---->  +------------------------------+ 0xf0000000    --+
 *    KSTACKTOP        |     CPU0's Kernel Stack      | RW/--  KSTKSIZE |
 *                     | - - - - - - - - - - - - - - -|                 |
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP  |
 *                     +------------------------------+                 |
 *                     |     CPU1's Kernel Stack      | RW/--  KSTKSIZE |
 *                     | - - - - - - - - - - - - - - -|               PTSIZE
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP  |
 *                     +------------------------------+                 |
 *                     :              .               :                 |
 *                     :              .               :                 |
 *    MMIOLIM ------>  +------------------------------+ 0xefc00000    --+
 *                     |       Memory-mapped I/O      | RW/--  PTSIZE
 * ULIM, MMIOBASE -->  +------------------------------+ 0xef800000
 *                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xef000000
 *                     |           RO ENVS            | R-/R-  PTSIZE
 * UTOP,UENVS ------>  +------------------------------+ 0xeec00000
 * UXSTACKTOP -/       |     User Exception Stack     | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebff000
 *                     |       Empty Memory (*)       | --/--  PGSIZE
 *    USTACKTOP  --->  +------------------------------+ 0xeebfe000
 *                     |      Normal User Stack       | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebfd000
 *                     |                              |
 *                     |                              |
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     .                              .
 *                     .                              .
 *                     .                              .
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
 *                     |     Program Data & Heap      |
 *    UTEXT -------->  +------------------------------+ 0x00800000
 *    PFTEMP ------->  |       Empty Memory (*)       |        PTSIZE
 *                     |                              |
 *    UTEMP -------->  +------------------------------+ 0x00400000    --+
 *                     |       Empty Memory (*)       |                 |
 *                     | - - - - - - - - - - - - - - -|                 |
 *                     |  User STAB Data (optional)   |               PTSIZE
 *    USTABDATA ---->  +------------------------------+ 0x00200000      |
 *                     |       Empty Memory (*)       |                 |
 *    0 ------------>  +------------------------------+               --+
 *
 * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
 *     "Empty Memory" is normally unmapped, but user programs may map pages
 *     there if desired.  JOS user programs map pages temporarily at UTEMP.
 */

boot_alloc(uint32_t n):在物理地址上,从空闲位置开始分配n个字节(需要与PGSIZE对齐),不进行初始化。

static void *
boot_alloc(uint32_t n)
{
	static char *nextfree; // virtual address of next byte of free memory
	char *result;
    
	if (!nextfree)
	{
		extern char end[];
		nextfree = ROUNDUP((char *)end, PGSIZE);
	}
	// LAB 2: Your code here.
	if (n == 0)
		return nextfree;
	result = nextfree;
	nextfree = ROUNDUP(nextfree + n, PGSIZE);
	if ((uint32_t)nextfree - KERNBASE > (npages * PGSIZE))
		panic("Out of memory!\n");
	return result;
}

mem_init(void)函数:

  1. 首先调用i386_detect_memory函数,其功能是检测现在系统中有多少可用的内存空间。
  2. kern_pgdir = (pde_t *) boot_alloc(PGSIZE)
    memset(kern_pgdir, 0, PGSIZE)
    分配一个页,作为内核的页目录表,kern_pgdir是虚拟地址,并将此页初始化为0。
  3. kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P
    UVPT是虚拟地址,存放的是内核页表,因此,需要在此位置上添加一个映射:将kern_pgdir这个虚拟地址映射到实际的物理地址上,并且添加权限:内核与用户只读。
    // A linear address 'la' has a three-part structure as follows:
    //
    // +--------10------+-------10-------+---------12----------+
    // | Page Directory |   Page Table   | Offset within Page  |
    // |      Index     |      Index     |                     |
    // +----------------+----------------+---------------------+
    //  \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
    //  \---------- PGNUM(la) ----------/
    //
    // The PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.
    // To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),
    // use PGADDR(PDX(la), PTX(la), PGOFF(la)).
    
    执行以上代码后的物理内存布局如下:
    lab2_part1_物理内存1
  4. pages = (struct PageInfo*)boot_alloc(sizeof(struct PageInfo)*npages)
    memset(pages, 0, sizeof(struct PageInfo)*npages)
    npages表示可以分配多少页,每一页都需要使用struct PageInfo结构进行记录(包括了指向下一页的指针和当前页引用次数)。因此,需要分配足够的内存空间保存pages数组pages数组的每一项是一个PageInfo结构,对应一个物理页的信息。
    执行以上代码后的物理内存布局如下: lab2_part1_物理内存2
  5. page_init()
    初始化pages数组,初始化page_free_list,但是某些页不能被使用,需要修改其状态。
    下图为物理地址page使用的分布情况: lab2_part1_page分布情况
//根据上面的图以及源代码中的提示,很容易就写出来了
void page_init(void)
{
	size_t i;
	pages[0].pp_ref = 1;
	pages[0].pp_link = NULL;
	for (i = 1; i < npages_basemem; ++i)
	{
		pages[i].pp_ref = 0;
		//头插法
		pages[i].pp_link = page_free_list;
		page_free_list = &pages[i];
	}

	size_t io_hole_end = EXTPHYSMEM / PGSIZE;
	for (; i < io_hole_end; ++i)
	{
		pages[i].pp_ref = 1;
		pages[i].pp_link = NULL;
	}

	//nextfree就是下一个free page
	//调用boot_alloc(0),可返回其虚拟指针
	size_t next_free_page = PADDR(boot_alloc(0)) / PGSIZE;
	for (; i < next_free_page; ++i)
	{
		pages[i].pp_ref = 1;
		pages[i].pp_link = NULL;
	}

	for (; i < npages; ++i)
	{
		pages[i].pp_ref = 0;
		//头插法
		pages[i].pp_link = page_free_list;
		page_free_list = &pages[i];
	}
}

mem_init(void)函数之后的流程请看part2
page_alloc(int alloc_flags)page_free(struct PageInfo *pp)比较简单,按照提示即可完成。

总结一下

part1总共写了五个函,从名字来说可以分三类,boot,memory和page层面。

  • boot,启动层面,boot_alloc() 在启动时可分配N个byte空间。通过调用这个函数,可以将物理内存分割成多个页。
  • memory,内存层面,mem_init() 通过调用boot_alloc() 获得npage*sizeof(Page_Info)个byte的页面,也就是npages个页面。并使用数据结构PageInfo来记录所有的页。
  • page,页面层面,page_init()是页面的初始化,初始化做的就是标记页表中哪些可以用,哪些不能用,把能用的链接起来。page_alloc( )就是分配这些能用的页面,page_free()就是释放掉页面。
    所以,part1基本就完成了内存层面初始化页面以及页面的基本操作(包括删除和分配)。

Part2:Virtual Memory

虚拟地址、逻辑地址、线性地址、物理地址的区别:

段⻚式内存管理实现的⽅式:
先将程序划分为多个有逻辑意义的段,也就是前⾯提到的分段机制;
接着再把每个段划分为多个⻚,也就是对分段划分出来的连续空间,再划分固定⼤⼩的⻚;
这样,地址结构就由段号、段内⻚号和⻚内位移三部分组成
由此,逻辑地址是由段+偏移量组成,逻辑地址与虚拟地址没有明确分别,虚拟地址(逻辑地址)从段号到页号的翻译成为线性地址,然后线性地址通过页号+页内位移翻译成物理地址
lab2_part2_虚拟线性物理地址
lab2_part2_虚拟线性物理

名词说明
页目录表存放各个页目录项的表,页目录常驻内存,页目录表的物理地址存在寄存器CR3中
页目录项存放各个二级页表起始物理地址,即页表页
二级页表存放页表项
页表项页表项的高20位存放各页的对应的物理地址的高20位

pgdir_walk(pde_t *pgdir, const void *va, int create)
给定一个页目录表指针 pgdir ,该函数应该返回线性地址va所对应的页表项指针(page table entry,PTE),不存在则根据create参数决定是否创建

// A linear address 'la' has a three-part structure as follows:
//
// +--------10------+-------10-------+---------12----------+
// | Page Directory |   Page Table   | Offset within Page  |
// |      Index     |      Index     |                     |
// +----------------+----------------+---------------------+
//  \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
//  \---------- PGNUM(la) ----------/
//
// The PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.
// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),
// use PGADDR(PDX(la), PTX(la), PGOFF(la)).
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
	pde_t *va_dir_entry = &pgdir[PDX(va)];//获得页目录项物理地址
	if (!(*va_dir_entry & PTE_P))//如果不存在
	{
		if (!create)//不需要创建
			return NULL;
		struct PageInfo *pp = page_alloc(ALLOC_ZERO);//需要创建,则分配一页作为页表
		if (pp == NULL)
			return NULL;
		pp->pp_ref++;
		*va_dir_entry = page2pa(pp) | PTE_U | PTE_P | PTE_W;//将页表的物理地址和权限写入页目录项指针
    }		//	   转化为虚拟地址(获得PPN)			+ 页表偏移量=va对应的页表项指针
	return (pte_t *)KADDR(PTE_ADDR(*va_dir_entry)) + PTX(va);
}

代码流程图:
lab2_part2_pgdir_walk工作流程
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)

把虚拟地址空间范围[va, va+size)映射到物理空间[pa, pa+size)的映射关系加入到页目录表pgdir中

static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
	// Fill this function in
	size_t pgs = size / PGSIZE;
	if (size % PGSIZE != 0)
		pgs++;//	总共需要映射的页数
	for (int i = 0; i < pgs; ++i)
	{
		pte_t *pg_table_entry = pgdir_walk(pgdir, (void *)va, 1);//获得va对应的页表项指针
		if (pg_table_entry == NULL)
		{
			panic("no enough memory to allocate for page!");
			return;
		}
		*pg_table_entry = pa | perm | PTE_P;//将页表项指针指向物理地址pa并赋予权限。完成映射!
		pa += PGSIZE;//映射下一页
		va += PGSIZE;
	}
}

page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
把一个物理页pp与虚拟地址va建立映射关系

int page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
	// Fill this function in
	pte_t *pg_table_entry = pgdir_walk(pgdir, va, 1);
	if (pg_table_entry == NULL)
		return -E_NO_MEM;
	if (*pg_table_entry & PTE_P)//此页表项是否已被映射
	{
		if (PTE_ADDR(*pg_table_entry) == page2pa(pp))//映射的是否就是pp
		{
			*pg_table_entry = page2pa(pp) | perm | PTE_P;//如果是,只需修改权限
			return 0;
		}
		page_remove(pgdir, va);//如果不是,则需要先移除此映射
	}
	pp->pp_ref++;
	*pg_table_entry = page2pa(pp) | PTE_P | perm;
	pgdir[PDX(va)] |= perm;//更新页目录项的权限
	return 0;
}

page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
返回虚拟地址va所映射的物理页的PageInfo结构体的指针,如果pte_store参数不为0,则把这个物理页的页表项地址存放在pte_store中

struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
	// Fill this function in
	pte_t *pg_table_entry = pgdir_walk(pgdir, va, 0);//查找对应的页表项
	//	没有被映射				或	映射地址无效
    if (pg_table_entry == NULL || !(*pg_table_entry & PTE_P))
		return NULL;
	if (pte_store != NULL)
		*pte_store = pg_table_entry;
	return pa2page(PTE_ADDR(*pg_table_entry));返回页表项的PPN
}

page_remove(pde_t *pgdir, void *va)
把虚拟地址va和物理页的映射关系删除

void page_remove(pde_t *pgdir, void *va)
{
	// Fill this function in
	pte_t *pg_table_entry = NULL;
	struct PageInfo *pp = page_lookup(pgdir, va, &pg_table_entry);
	if (pp == NULL)//如果是NULL,说明已经没有被映射了或者映射无效了,可直接返回
		return;
	page_decref(pp);//释放pp物理页
	tlb_invalidate(pgdir, va);//刷新TLB
	*va_table_entry = 0;
}

TLB:缓存常访问的页表或页目录

总结一下

part2主要工作就是建立虚拟内存与物理内存的映射,包括了查找虚拟地址对应的页表项
lab2_part2_虚拟与物理映射函数关系

Part3:Kernel Address Space

ULIM以上的地址空间只能被内核读写,用户不可读写
[UTOP,ULIM]之间的空间可以同时被内核和用户读取,但都不能修改,主要包括了页目录表、pages数组、envs数组(下一个lab)
UTOP以上为内核空间,其以下的空间给用户进程使用,用户可以读写
继续mem_init(void)函数:
6. boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U)
通过kern_pgdir将物理地址上的pages数组映射到虚拟地址UPAGES处,并设置权限:用户只读
7. boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W)
把由bootstack变量所标记的物理地址范围映射给内核的堆栈,bootstack定义在/kernel/entry.S中,但是还是不太明白bootstack所指向的物理地址在哪,没找到。
8. boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W)
最后将KERNBASE以上的虚拟内存全部映射,虚拟地址范围是[KERNBASE, 232],物理地址范围是[0,232 - KERNBASE];物理地址可能没有2^32 - KERNBASE那么多,但依然映射
9. lcr3(PADDR(kern_pgdir))
将kern_pgdir加载进CR3寄存器
执行完mem_init()后,虚拟地址到物理地址的映射图:
lab2_part3_虚拟到物理映射图

参考资料

MIT 6.828 JOS学习笔记15. Lab 2.1 - fatsheep9146 - 博客园 (cnblogs.com)
MIT-6.828-JOS-lab2:Memory management - gatsby123 - 博客园 (cnblogs.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nobugnolife

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值