码迷,mamicode.com
首页 > 其他好文 > 详细

MIT 操作系统实验 MIT JOS lab2

时间:2014-10-12 02:47:37      阅读:349      评论:0      收藏:0      [点我收藏+]

标签:操作系统

MIT JOS lab2


在/kern/kdebug.c 里面会看到这段代码,关注下面的__STAB_BEGIN__ 那段代码

bubuko.com,布布扣

__STAB_BEGIN__   __STAB_END__相关定义在/kern/kernel.ld里面

下面我给出了kernel.ld的主要内容

ENTRY(_start)

SECTIONS
{
	/* Link the kernel at this address: "." means the current address */
	. = 0xF0100000;

	/* AT(...) gives the load address of this section, which tells
	   the boot loader where to load the kernel in physical memory */
	.text : AT(0x100000) {
		*(.text .stub .text.* .gnu.linkonce.t.*)
	}揭示了内核被加载到0x100000线性地址处

	PROVIDE(etext = .);	/* Define the 'etext' symbol to this value */

	.rodata : {
		*(.rodata .rodata.* .gnu.linkonce.r.*)
	}

	/* Include debugging information in kernel memory */
	.stab : {
		PROVIDE(__STAB_BEGIN__ = .);//这里也定义了__STAB_BEGIN__等变量是0xF0100000
		*(.stab);
		PROVIDE(__STAB_END__ = .);
		BYTE(0)		/* Force the linker to allocate space
				   for this section */
	}

	.stabstr : {
		PROVIDE(__STABSTR_BEGIN__ = .);
		*(.stabstr);
		PROVIDE(__STABSTR_END__ = .);
		BYTE(0)		/* Force the linker to allocate space
				   for this section */
	}

	/* Adjust the address for the data segment to the next page */
	. = ALIGN(0x1000); //把数据段和bss段放到下一页

	/* The data segment */
	.data : {
		*(.data)
	}

	PROVIDE(edata = .);

	.bss : {
		*(.bss)
	}

	PROVIDE(end = .); //下一页的起始就是kernel代码段的结束位置

	/DISCARD/ : {
		*(.eh_frame .note.GNU-stack)
	}
}


bubuko.com,布布扣


首先要了解struct Stab是用来记录调试信息的结构体

关于struct stab我做了一个简介:http://blog.csdn.net/cinmyheart/article/details/39972701


kdebug.c的注释也讲的很清楚

// stab_binsearch(stabs, region_left, region_right, type, addr)
//
//	Some stab types are arranged in increasing order by instruction
//	address.  For example, N_FUN stabs (stab entries with n_type ==
//	N_FUN), which mark functions, and N_SO stabs, which mark source files.
//
//	Given an instruction address, this function finds the single stab
//	entry of type 'type' that contains that address.
//
//	The search takes place within the range [*region_left, *region_right].
//	Thus, to search an entire set of N stabs, you might do:
//
//		left = 0;
//		right = N - 1;     /* rightmost stab */
//		stab_binsearch(stabs, &left, &right, type, addr);
//
//	The search modifies *region_left and *region_right to bracket the
//	'addr'.  *region_left points to the matching stab that contains
//	'addr', and *region_right points just before the next stab.  If
//	*region_left > *region_right, then 'addr' is not contained in any
//	matching stab.
//
//	For example, given these N_SO stabs:
//		Index  Type   Address
//		0      SO     f0100000
//		13     SO     f0100040
//		117    SO     f0100176
//		118    SO     f0100178
//		555    SO     f0100652
//		556    SO     f0100654
//		657    SO     f0100849
//	this code:
//		left = 0, right = 657;
//		stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184);
//	will exit setting left = 118, right = 554.
//

这个stab_binsearch被函数debuginfo_eip调用,而这个函数就是为了填充struct Eipdebuginfo结构体而存在的。

始终记住这点,debuginfo_eip是为了填充struct Eipdebuginfo结构体那么在补充debuginfo_eip的时候就不会觉得迷失.

bubuko.com,布布扣

上面应该是老版实验的部分


其实下面才是14年版本的第一个exercise 1,如果上面的没看懂?没关系...


Exercise 1. In the file  kern/pmap.c , you must implement code for the following functions (probably in the order given).

boot_alloc()
mem_init()  (only up to the call to  check_page_free_list(1) )
page_init()
page_alloc()
page_free()

            check_page_free_list()  and  check_page_alloc()  test your physical page allocator. You should boot JOS and see whether  check_page_alloc()  reports success. Fix your code so that it passes. You may find it helpful to add your own  assert() s to verify that your assumptions are correct.


首先把内存分布理清楚

由/boot/main.c可知这里把kernel的img的ELF header读入到物理地址0x10000


这里可以回顾JOS lab1的一个小问,当时是问的bootloader怎么就能准确的吧kernle 镜像读入到对应的地址呢?

这里就是main.c在作用.

bubuko.com,布布扣

这里往ELFHDR即0x10000处读入了8个SECTSIZE,从注释//is this a valid ELF? 开始,bootmain下面的部分就开始读入kernel 镜像到物理内存ph->p_pa处


bubuko.com,布布扣

由对应的反汇编知道把0x1001c处的值(52),和0x10000相加,得到ph,即指向结构体struct Proghdr的指针

这下是铁打的0x100000,ph->p_pa的值,这里会把 kernel 镜像到物理内存ph->p_pa处即 0x100000   

这是由kernel.ld决定的 内核的起始物理地址

bubuko.com,布布扣

52是结构体Proghdr内e_phoff的偏移量,12是struct Proghdr内p_pa的偏移量


然后 bootmain 的最后一句跳转到0x10000c 处,开始执行 entry.S 的代码. 这里不记得了就去看lab 1

内存分布就清楚了

bubuko.com,布布扣




注意到kernel结束之后就是free memory了,而在free memory的最开始存放的是pgdir,这块内存同样由boot_alloc申请

bubuko.com,布布扣

而实验要求我们去填补函数boot_alloc()

bubuko.com,布布扣

这里注意要4k页面对齐

bubuko.com,布布扣

被要求开辟npages数目的结构体PageInfo空间,由pages指向该空间


紧接着就开始page_init()了

bubuko.com,布布扣

page_init()中,通过page_free_list这个全局中间变量,把后一个页面的pp_link指向前一个页面,于是这里就把所有的pages都链接起来了


会达到什么效果?看下面的图


bubuko.com,布布扣




bubuko.com,布布扣


把有颜色(蓝,红)的部分标记为已经使用,白色部分标记为空闲

void
page_init(void)
{
	// The example code here marks all physical pages as free.
	// However this is not truly the case.  What memory is free?
	//  1) Mark physical page 0 as in use.
	//     This way we preserve the real-mode IDT and BIOS structures
	//     in case we ever need them.  (Currently we don't, but...)
	//  2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
	//     is free.
	//  3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
	//     never be allocated.
	//  4) Then extended memory [EXTPHYSMEM, ...).
	//     Some of it is in use, some is free. Where is the kernel
	//     in physical memory?  Which pages are already in use for
	//     page tables and other data structures?
	//
	// Change the code to reflect this.
	// NB: DO NOT actually touch the physical memory corresponding to
	// free pages!
	size_t i;
	uint32_t pa;
	page_free_list = NULL;
	for (i = 0; i < npages; i++) {
		if(i == 0)
		{
			pages[0].pp_ref = 1;
			pages[0].pp_link = NULL;
			continue;
		}
		else if(i < npages_basemem)
		{
			pages[i].pp_ref = 0;
			pages[i].pp_link = page_free_list;
			page_free_list = &pages[i];
		}
		else if(i <= (EXTPHYSMEM/PGSIZE) || i < (((uint32_t)boot_alloc(0) - KERNBASE) >> PGSHIFT))
		{
			pages[i].pp_ref++;
			pages[i].pp_link = NULL;
		}
		else
		{
			pages[i].pp_ref = 0;
			pages[i].pp_link = page_free_list;
			page_free_list = &pages[i];
		}

		pa = page2pa(&pages[i]);

		if((pa == 0 || (pa >= IOPHYSMEM && pa <= ((uint32_t)boot_alloc(0) - KERNBASE) >> PGSHIFT )) && (pages[i].pp_ref == 0))
		{
			cprintf("page error: i %d\n",i);
		}
	}
}



                  page_alloc函数的实现. 就是把当前free list中的空闲页释放一个,然后更新page_free_list,让ta指向下一个空闲页即可

struct PageInfo *
page_alloc(int alloc_flags)
{
	struct Page* pp = NULL;
	if(!page_free_list)
	{
		return NULL;
	}

	pp = page_free_list;

	page_free_list = page_free_list->pp_link;

	if(alloc_flags & ALLOC_ZERO)
	{
		memset(page2kva(pp),0,PGSIZE);
	}

	return pp;
}

对应的page_free就是把pp描述的page加入到free list当中去,使得pp成为最新的page_free_list

void
page_free(struct PageInfo *pp)
{
	// Fill this function in
	// Hint: You may want to panic if pp->pp_ref is nonzero or
	// pp->pp_link is not NULL.

	assert(pp->pp_ref == 0 || pp->pp_link == NULL);

	pp->pp_link = page_free_list;
	page_free_list = pp;
}

pgdir_walk是很关键的函数

首先明确pde_t pte_t 都是描述的物理内存地址

下图是page directory 和 page table的组织形式

bubuko.com,布布扣

pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{	//pgdir 本身是虚拟地址 解引用得到的是page directory的物理地址(QAQ 多么痛的领悟~)
	pde_t *pde = NULL;
	pte_t *pgtable = NULL;
	
	struct PageInfo *pp;

	pde = &pgdir[PDX(va)];
	if(*pde & PTE_P)
	{
		pgtable = (KADDR(PTE_ADDR(*pde)));
	}
	else
	{
		if(!create ||
		   !(pp = page_alloc(ALLOC_ZERO)) ||
		   !(pgtable = (pte_t*)page2kva(pp)))
		{
			return NULL;
		}

		pp->pp_ref++;
		*pde = PADDR(pgtable) | PTE_P |PTE_W | PTE_U;
	}

	return &pgtable[PTX(va)];

}

create 标志是1 如果当前va地址所属的页不存在,那么申请开辟这页,

create 如果标志是0.仅仅是查询va地址所属的页是否存在. 存在就返回对应的page table的入口地址,不存在就返回NULL.




boot_map_region该函数把虚拟地址[va,va+size)的区域映射到物理地址pa开始的内存中去

static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{

	uintptr_t  va_next = va;
	physaddr_t pa_next = pa;
	pte_t *    pte = NULL;//page table entrance

	ROUNDUP(size,PGSIZE);//page align

	assert(size%PGSIZE == 0 || cprintf("size:%x \n",size));

	int temp = 0;
	for(temp = 0;temp < size/PGSIZE;temp++)
	{
		pte = pgdir_walk(pgdir,va_next,1);

		if(!pte)
		{
			return;
		}
		
		*pte = PTE_ADDR(pa_next) | perm | PTE_P;
		pa_next += PGSIZE;
		va_next += PGSIZE;
	}
}


page_lookup函数检测va虚拟地址的虚拟页是否存在

不存在返回NULL,

存在返回描述该虚拟地址关联物理内存页的描述结构体PageInfo的指针.... (PageInfo 结构体仅用来描述物理内存页)

不要对函数参数做检查

struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
	pte_t* pte = pgdir_walk(pgdir,va,0);

	if(!pte)
	{
		return NULL;
	}

	*pte_store = pte;

	return pa2page(PTE_ADDR(*pte));
}




先熟悉一下这个

http://blog.csdn.net/cinmyheart/article/details/39994769

不然下面函数的最后一行代码不明白


             page_remove 清除va所在虚拟内存页,怎么清除?把这个虚拟页的关联物理页的page table entrance 置为NULL就OK啦(原本page table entrance解引用得到的就是物理页地址)

void
page_remove(pde_t *pgdir, void *va)
{
	pte_t*  pte = pgdir_walk(pgdir,va,0);
	pte_t** pte_store = &pte;
	struct PageInfo* pp = page_lookup(pgdir,va,pte_store);

	if(!pp)
	{
		return ;
	}

	page_decref(pp);
	**pte_store = 0; //关键一步
	tlb_invalidate(pgdir,va);
	
}





page_insert 把pp描述的物理页与虚拟地址va关联起来

如果va所在的虚拟内存页不存在,那么pgdir_walk的create为1,创建这个虚拟页

如果va所在的虚拟内存页存在,那么取消当前va的虚拟内存页也和之前物理页的关联,并且为va建立新的物理页联系——pp所描述的物理页

int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{

	pte_t* pte = pgdir_walk(pgdir,va,0);
	physaddr_t ppa = page2pa(pp);

	if(pte)
	{
		if(*pte & PTE_P)
		{
			page_remove(pgdir,va);//取消va与之物理页之间的关联
		}	

		if(page_free_list == pp)
		{
			page_free_list = page_free_list->pp_link;//update the new free_list header
		}
	}
	else
	{
		pte = pgdir_walk(pgdir,va,1);
		if(!pte)
		{
			return -E_NO_MEM;
		}	
	}

	*pte = page2pa(pp) | PTE_P | perm; //建立va与pp描述物理页的联系

	pp->pp_ref++;
	tlb_invalidate(pgdir,va);
	return 0;
}





Part 2: Virtual Memory


终于到part 2了....


Virtual, Linear, and Physical Addresses


              In x86 terminology, a virtual address consists of a segment selector and an offset within the segment. A
linear address is what you get after segment translation but before page translation. A physical address
is what you finally get after both segment and page translation and what ultimately goes out on the
hardware bus to your RAM


A C pointer is the "offset" component of the virtual address. 

哈哈,写了也有段时间的C语言了,指针的本质是什么,段内偏移


            From code executing on the CPU, once we‘re in protected mode (which we entered first thing in boot/boot.S ), there‘s no way to directly use a linear or physical address. All memory references are interpreted as virtual addresses and translated by the MMU, which means all pointers in C are virtual addresses.


* 解引用都是对于虚拟地址来做的,如果你对物理地址解引用,硬件会把ta当作虚拟地址来操作

                 If you cast a physaddr_t  to a pointer and dereference it, you may be able to load and store to the resulting address (the hardware will interpret it as a virtual address), but you probably won‘t get the memory location you intended.


bubuko.com,布布扣

这问题也有点太简单了...肯定是uintptr_t      : -)





Part 3: Kernel Address Space


             JOS divides the processor‘s 32-bit linear address space into two parts. User environments (processes),
which we will begin loading and running in lab 3, will have control over the layout and contents of the
lower part, while the kernel always maintains complete control over the upper part. 


注意下面ULIM是分界线,ULIM一下是内核地址空间,以上是用户空间

bubuko.com,布布扣




这个页面布局代表的是启用地址转换以后,无论是操作系统还是用户程序,看到的虚拟内存布局,这也就是说,操
操 作系统和用户程序使用的是同一套页目录和页表



bubuko.com,布布扣


特别注意,内核这个部分的函数参数的指针不能做“常规的类型检查”直接return,我为这个bug....从晚上6点debug到现在(12点XX)


先放出前面的部分,后面的challenge部分 waiting for update.






MIT 操作系统实验 MIT JOS lab2

标签:操作系统

原文地址:http://blog.csdn.net/cinmyheart/article/details/39827321

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!