(三)6.828 Operating System lab2: Memory Management

Introduction


In this lab, you will write the memory management code for your operating system. Memory management has two components.

The first component is a physical memory allocator for the kernel, so that the kernel can allocate memory and later free it. Your allocator will operate in units of 4096 bytes, called pages. Your task will be to maintain data structures that record which physical pages are free and which are allocated, and how many processes are sharing each allocated page. You will also write the routines to allocate and free pages of memory.

The second component of memory management is virtual memory, which maps the virtual addresses used by kernel and user software to addresses in physical memory. The x86 hardware's memory management unit (MMU) performs the mapping when instructions use memory, consulting a set of page tables. You will modify JOS to set up the MMU's page tables according to a specification we provide.

  lab2主要和内存管理的代码相关,内存管理主要包括两部分。

  第一部分是物理内存管理,如何分配和释放内存,分配器以页(4096bytes)为单位进行分配,需要完成的任务是维护一个数据结构记录哪些内存空间被释放哪些被占用,以及多少进程共享着分配的页,也要写代码完成内存空间的分配与释放。

  第二部分是虚拟内存的实现,x86硬件的MMU单元会建立页表产生映射,任务是在JOS上根据需求修改MMU的页表。

Lab 2 contains the following new source files, which you should browse through:

  • inc/memlayout.h
  • kern/pmap.c
  • kern/pmap.h
  • kern/kclock.h
  • kern/kclock.c

memlayout.h describes the layout of the virtual address space that you must implement by modifying pmap.c. memlayout.h and pmap.h define the PageInfo structure that you'll use to keep track of which pages of physical memory are free. kclock.c and kclock.h manipulate the PC's battery-backed clock and CMOS RAM hardware, in which the BIOS records the amount of physical memory the PC contains, among other things. The code in pmap.c needs to read this device hardware in order to figure out how much physical memory there is, but that part of the code is done for you: you do not need to know the details of how the CMOS hardware works.

Pay particular attention to memlayout.h and pmap.h, since this lab requires you to use and understand many of the definitions they contain. You may want to review inc/mmu.h, too, as it also contains a number of definitions that will be useful for this lab.

Before beginning the lab, don't forget to add -f 6.828 to get the 6.828 version of QEMU.

Part 1: Physical Page Management


The operating system must keep track of which parts of physical RAM are free and which are currently in use. JOS manages the PC's physical memory with page granularity so that it can use the MMU to map and protect each piece of allocated memory.

You'll now write the physical page allocator. It keeps track of which pages are free with a linked list of struct PageInfo objects (which, unlike xv6, are not embedded in the free pages themselves), each corresponding to a physical page. You need to write the physical page allocator before you can write the rest of the virtual memory implementation, because your page table management code will need to allocate physical memory in which to store page tables.

  操作系统必须知道RAM中哪些地址是空闲的哪些是正在使用的,JOS以页为粒度管理内存空间,这样JOS可以利用MMU去做映射以及保护已使用空间。

  任务时写一个内存页分配器,利用struct pageinfo对象的链表记录空闲页,链表每个节点对应一个物理页空间,在写虚拟内存实现前需要先写一个内存页分配器,因为内存页分配器要先给页表管理的代码分配空间,这样才能存储页表,实现后面的虚拟内存机制。

  事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射),等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。还有进程运行过程中,要动态分配内存,比如malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。

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.

  在kern/pmap.c文件里实现函数功能。check_page_free_list() 和check_page_alloc() 检测内存页分配器,可以启动JOS看check_page_alloc()是否成功。

  第一个实现的函数是boot_alloc(),可按照函数前的注释实现相应功能,实现细节如下:

// This simple physical memory allocator is used only while JOS is setting
// up its virtual memory system.  page_alloc() is the real allocator.
//
// If n>0, allocates enough pages of contiguous physical memory to hold 'n'
// bytes.  Doesn't initialize the memory.  Returns a kernel virtual address.
//
// If n==0, returns the address of the next free page without allocating
// anything.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list list has been set up.
static void *
boot_alloc(uint32_t n)
{
	static char *nextfree;	// virtual address of next byte of free memory
	char *result;

	// Initialize nextfree if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment:
	// the first virtual address that the linker did *not* assign
	// to any kernel code or global variables.
	if (!nextfree) {
		extern char end[];
		nextfree = ROUNDUP((char *) end, PGSIZE);
	}

	// Allocate a chunk large enough to hold 'n' bytes, then update
	// nextfree.  Make sure nextfree is kept aligned
	// to a multiple of PGSIZE.
	//
	// LAB 2: Your code here.

	if(!n) return nextfree;
	result = nextfree;
	nextfree += ROUNDUP(n,PGSIZE);

	return result;
}

  boot_alloc()是一个简单的内存分配器,在内核载入初期发挥作用,等jos搭建好了虚拟内存机制,内存分配器的工作会交给page_alloc()执行。

  参数n代表要分配内存的大小, boot_alloc()申请nbytes大小内存,并返回内存空间首地址。如果n>0,分配连续的以页为单位的内存存储n bytes,返回下一个空闲页的首地址。如果n==0,直接返回nextfree(未分配空间首地址)。

  end指向内核bss段的末尾,利用objdump -h kernel可以看到:

HemingbeardeMacBook-Pro:kern hemingbear$ objdump -h kernel

kernel:	file format ELF32-i386

Sections:
Idx Name          Size      Address          Type
  0               00000000 0000000000000000 
  1 .text         0000196a 00000000f0100000 TEXT 
  2 .rodata       000007b0 00000000f0101980 DATA 
  3 .stab         00004d11 00000000f0102130 DATA 
  4 .stabstr      00008f4e 00000000f0106e41 
  5 .data         0000a300 00000000f0110000 DATA 
  6 .bss          00000650 00000000f011a300 BSS
  7 .comment      00000011 0000000000000000 
  8 .shstrtab     0000004c 0000000000000000 
  9 .symtab       00000780 0000000000000000 
 10 .strtab       0000043a 0000000000000000 

  可以通过地址看出bss是内核最后一段段信息,所以此时的end指向的是第一个未被使用的虚拟内存地址。

  代码中另一个重要的函数是ROUNDUP(),在inc/types.h宏定义:

// Round up to the nearest multiple of n
#define ROUNDUP(a, n)						\
({								\
	uint32_t __n = (uint32_t) (n);				\
	(typeof(a)) (ROUNDDOWN((uint32_t) (a) + __n - 1, __n));	\
})

  ROUNDUP()使用来整块整块读取页,最后返回下一页的首地址。  

  第二个实现的函数是mem_init()。会用到物理页描述符(PageInfo 结构),进入memlayout.h观察结构如何定义:

/*
 * Page descriptor structures, mapped at UPAGES.
 * Read/write to the kernel, read-only to user programs.
 *
 * Each struct PageInfo stores metadata for one physical page.
 * Is it NOT the physical page itself, but there is a one-to-one
 * correspondence between physical pages and struct PageInfo's.
 * You can map a struct PageInfo * to the corresponding physical address
 * with page2pa() in kern/pmap.h.
 */
struct PageInfo {
	// Next page on the free list.
	struct PageInfo *pp_link;

	// pp_ref is the count of pointers (usually in page table entries)
	// to this page, for pages allocated using page_alloc.
	// Pages allocated at boot time using pmap.c's
	// boot_alloc do not have valid reference count fields.

	uint16_t pp_ref;
};

  每一个物理页描述符(PageInfo)的实体存储一个内存页的元数据(并不是页本身),内存页和PageInfo实体是一一对应的,通过PageInfo实体和page2pa()函数可以找到对应的内存地址。

  物理页描述符(PageInfo)是一个链表。其中,pp_ref表示有多少指针指向该页,pp_link表示空闲内存列表中的下一页,非空闲页面的pp_link为NULL,结构如图所示:

    mem_init()中先初始化了页目录kern_pgdir,也是利用boot_alloc函数,这是kern之后分配的第一份内存地址空间。然后添加两行代码:

	//
	// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
	// The kernel uses this array to keep track of physical pages: for
	// each physical page, there is a corresponding struct PageInfo in this
	// array.  'npages' is the number of physical pages in memory.  Use memset
	// to initialize all fields of each struct PageInfo to 0.
	// Your code goes here:
	pages = (struct PageInfo*) boot_alloc(npages * sizeof(struct PageInfo));
	memset(pages, 0, npages * sizeof(struct PageInfo));

  因为物理内存空间每一页都要与一个PageInfo结构体对应,n个页的结构体放在pages数组中,图中增加的代码是为n个页的结构体申请空间。

  第三个实现的函数是page_init()。作用是初始化页结构和内存空闲列表,同时初始化要记录一些我们已经使用的内存空间,这样不会再把这些空间再次分配。在初始化好以后,就不再使用boot_alloc函数分配内存,只通过page_free_list(page_alloc()函数)分配和回收内存。

   page_init()  代码注释中对初始化的实现提了四点要求:

  1.   将物理内存page 0设为使用,为real-mode的IDT(中断描述符表)和BIOS结构预留空间,以防后面用到
  2.   剩下的基础空间[PGSIZE, npages_basemem * PGSIZE)是空闲的。
  3.   之后为IO hole[IOPHYSMEM, EXTPHYSMEM)分配空间,包括VGAdisplay、16-bit devices expansion ROMs和BIOS ROM这段不能再被分配。
  4.   之后都是额外空间[EXTPHYSMEM, ...)。

//
// Initialize page structure and memory free list.
// After this is done, NEVER use boot_alloc again.  ONLY use the page
// allocator functions below to allocate and deallocate physical
// memory via the page_free_list.
//
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;
	for (i = 0; i < npages; i++) {
		// 1.mark physical page 0 as in use,This way we preverse the real-mode IDT and BIOS structures incase we ever need them.
		// mark物理页page 0已用,其中保存了IDT(中断操作符表)和BIOS结构
		// 地址范围0x000~0xFFF
		if(i == 0){
			pages[i].pp_ref = 1;
			pages[i].pp_link = NULL;
		}
		// 2.the rest of base memory,[PGSIZE, npages_basemem * PGSIZE) is free
		// mark剩下的base memory可用
		// 地址范围0x1000 ~ 0xA0000
		else if(i>=1 && i<npages_basemem){
			pages[i].pp_ref = 0;
			pages[i].pp_link = page_free_list;
			page_free_list = &pages[i];
		}
		// 3.then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must never be allocated.
		// 从memlayou.h可以查看IOPHYSMEM	== 0x0A0000  EXTPHYSMEM == 0x100000,这部分包括VGA display等硬件信息
		// 地址范围0xA000 ~ 0x100000
		else if(i>=IOPHYSMEM/PGSIZE && i < EXTPHYSMEM/PGSIZE){
			pages[i].pp_ref = 1;
			pages[i].pp_link = NULL;
		}
		// 4.Then extended memory 中用到的内存空间,这段内存用来存放kern
		// (int)(boot_alloc(0))-KERNBASE)就是kern占用内存大小
		else if(i >= EXTPHYSMEM/ PGSIZE && i < ((int)(boot_alloc(0))-KERNBASE)/PGSIZE){
			pages[i].pp_ref = 1;
			pages[i].pp_link = NULL;
		}
		// 4.Then extended memory 中没有用到的内存空间
		else{
			pages[i].pp_ref = 0;
			pages[i].pp_link = page_free_list;
			page_free_list = &pages[i];
		}
		
	}
}

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

  其中有一些重要参数。一个是从0x00000~0xA0000,这部分也叫basemem,是可用的。紧接着是0xA0000~0x100000,这部分叫做IO hole,是不可用的,主要被用来分配给外部设备了。再紧接着就是0x100000以后,这部分叫做extmem,是可用的,这是最重要的内存区域。

  这里要注意第四种情况,boot_alloc(0)返回的是第一个能够分配页面的虚拟地址,需要利用((int)(boot_alloc(0))-KERNBASE)转为物理地址。在memlayout.h里可以看到#define    KERNBASE    0xF0000000。

 

  第四个实现的函数是page_alloc()。实现一个页的分配,只是页管理上的分配,也就是只从page_free_list中拿走一个链表节点,设置链表节点为已用,然后更新page_free_list指向下一个。

//
// Allocates a physical page.  If (alloc_flags & ALLOC_ZERO), fills the entire
// returned physical page with '\0' bytes.  Does NOT increment the reference
// count of the page - the caller must do these if necessary (either explicitly
// or via page_insert).
//
// Be sure to set the pp_link field of the allocated page to NULL so
// page_free can check for double-free bugs.
//
// Returns NULL if out of free memory.
//
// Hint: use page2kva and memset
struct PageInfo *
page_alloc(int alloc_flags)
{
	// Fill this function in
	if(page_free_list == NULL)  return NULL;
	struct PageInfo *pp = page_free_list;
	page_free_list = page_free_list->pp_link;
	pp->pp_link = NULL;
	if(alloc_flags & ALLOC_ZERO){
		memset(page2kva(pp),0,PGSIZE);
	}

	return pp;
}

  page_alloc做的事情就是开辟一个页空间,page2kva将物理地址转为虚拟地址,内核现在自己的虚拟地址上开辟空间准备存储数据,当CPU运行真正需要这些数据时,才拷贝进内存。

 

  第五个实现的函数是page_free(),依据注释实现就行。

//
// Return a page to the free list.
// (This function should only be called when pp->pp_ref reaches 0.)
//
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.
	if(pp->pp_ref != 0 || pp->pp_link != NULL){
		panic("page_free call failed!");
	}
	pp->pp_link = page_free_list;
	page_free_list = pp;
}

  编译运行查看成功结果:

6828 decimal is 15254 octal!
Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_free_list() succeeded!
check_page_alloc() succeeded!

Part 2: Virtual Memory


Before doing anything else, familiarize yourself with the x86's protected-mode memory management architecture: namely segmentation and page translation.

  先了解分段和分页机制,可以查看https://blog.csdn.net/hjw199666/article/details/102662074

exercise 2

Exercise 2. Look at chapters 5 and 6 of the Intel 80386 Reference Manual, if you haven't done so already. Read the sections about page translation and page-based protection closely (5.2 and 6.4). We recommend that you also skim the sections about segmentation; while JOS uses paging for virtual memory and protection, segment translation and segment-based protection cannot be disabled on the x86, so you will need a basic understanding of it.

  exercise2设置的是段页内容的阅读任务。

  这里要注意到的是,程序数据存放的地址都是虚拟地址,操作系统调用MMU转换为物理地址,所以要注意编写程序的时候交给系统的都得是虚拟地址。lab2主要用了分页,通过分页机制将虚拟地址转化为物理地址的流程如上图所示。

  以下是课程给出的介绍文档:

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.

           Selector  +--------------+         +-----------+
          ---------->|              |         |           |
                     | Segmentation |         |  Paging   |
Software             |              |-------->|           |---------->  RAM
            Offset   |  Mechanism   |         | Mechanism |
          ---------->|              |         |           |
                     +--------------+         +-----------+
            Virtual                   Linear                Physical

A C pointer is the "offset" component of the virtual address. In boot/boot.S, we installed a Global Descriptor Table (GDT) that effectively disabled segment translation by setting all segment base addresses to 0 and limits to 0xffffffff. Hence the "selector" has no effect and the linear address always equals the offset of the virtual address. In lab 3, we'll have to interact a little more with segmentation to set up privilege levels, but as for memory translation, we can ignore segmentation throughout the JOS labs and focus solely on page translation.

Recall that in part 3 of lab 1, we installed a simple page table so that the kernel could run at its link address of 0xf0100000, even though it is actually loaded in physical memory just above the ROM BIOS at 0x00100000. This page table mapped only 4MB of memory. In the virtual memory layout you are going to set up for JOS in this lab, we'll expand this to map the first 256MB of physical memory starting at virtual address 0xf0000000 and to map a number of other regions of virtual memory.

  在系统中,CPU得到一个虚拟地址,通过分段翻译后,会得到一个线性地址(linear address),线性地址通过分页翻译后,就可以得到物理地址。在boot.S中我们设置了段基址为0,这样selector其实没发挥作用,虚拟地址就自动等同线性地址,在lab3中,可能会涉及到段机制,但是现在只需要注意分页机制就行。

  回顾lab1的part3,我们建立了一个简单的页表然我们能够运行0xf0100000这样的高位地址程序,它实际加载的物理位置是ROM BIOS(0x00100000)上方,现在我们要加大这些映射关系。

  在lab2中,虚拟地址通过page director  和 page table转化成物理地址的过程如下图所示。

exercise 3

Exercise 3. While GDB can only access QEMU's memory by virtual address, it's often useful to be able to inspect physical memory while setting up virtual memory. Review the QEMU monitor commands from the lab tools guide, especially the xp command, which lets you inspect physical memory. To access the QEMU monitor, press Ctrl-a c in the terminal (the same binding returns to the serial console).

Use the xp command in the QEMU monitor and the x command in GDB to inspect memory at corresponding physical and virtual addresses and make sure you see the same data.

Our patched version of QEMU provides an info pg command that may also prove useful: it shows a compact but detailed representation of the current page tables, including all mapped memory ranges, permissions, and flags. Stock QEMU also provides an info mem command that shows an overview of which ranges of virtual memory are mapped and with what permissions.

  分别在qemu和gdb中用xp指令和x指令去查看地址内容,了解虚拟地址和物理地址的区别。(没搞懂)

 

知识点: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.

The JOS kernel often needs to manipulate addresses as opaque values or as integers, without dereferencing them, for example in the physical memory allocator. Sometimes these are virtual addresses, and sometimes they are physical addresses. To help document the code, the JOS source distinguishes the two cases: the type uintptr_t represents opaque virtual addresses, and physaddr_t represents physical addresses. Both these types are really just synonyms for 32-bit integers (uint32_t), so the compiler won't stop you from assigning one type to another! Since they are integer types (not pointers), the compiler will complain if you try to dereference them.

The JOS kernel can dereference a uintptr_t by first casting it to a pointer type. In contrast, the kernel can't sensibly dereference a physical address, since the MMU translates all memory references. 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.

To summarize:

C typeAddress type
T*  Virtual
uintptr_t  Virtual
physaddr_t  Physical

  我们通过CPU运行程序,一旦进入保护模式(boot.S第一件事就是切换为保护模式),我们就无法直接使用线性或者物理地址。所有的内存应用都是由MMY在执行时才转换成物理地址的,也就是说,C中所有指针指向的都是虚拟地址。

  JOS内核经常需要把地址当成一个可见值或者整数操控,比如物理内存分配器操作很多地址,有些地址是物理地址,有些地址是虚拟地址,方便起见,JOS用type uintptr_t表示虚拟地址,physaddr_t表示物理地址,这两个类型都是32位整数(uint32_t),所以两个类型之间的相互赋值不会引起编译器报错。

  JOS内核能够解析uintptr_t类型的地址,你可以把它当做指针使用,然后由MMU转换为物理地址访问数据。如果尝试用指针的方式解析physaddr_t类型的地址,MMU会把它当做虚拟地址转换成物理地址访问数据,可能会有结果,但不是你想要的值。

  注意表格内容,搞清楚不同类型代表哪种地址。

Question

Question

  1. Assuming that the following JOS kernel code is correct, what type should variable x have, uintptr_t or physaddr_t?
    	mystery_t x;
    	char* value = return_a_pointer();
    	*value = 10;
    	x = (mystery_t) value;

  x肯定是uintptr_t类型,要明确的是任何指针指向的都是虚拟地址。

The JOS kernel sometimes needs to read or modify memory for which it knows only the physical address. For example, adding a mapping to a page table may require allocating physical memory to store a page directory and then initializing that memory. However, the kernel, like any other software, cannot bypass virtual memory translation and thus cannot directly load and store to physical addresses. One reason JOS remaps of all of physical memory starting from physical address 0 at virtual address 0xf0000000 is to help the kernel read and write memory for which it knows just the physical address. In order to translate a physical address into a virtual address that the kernel can actually read and write, the kernel must add 0xf0000000 to the physical address to find its corresponding virtual address in the remapped region. You should use KADDR(pa) to do that addition.

The JOS kernel also sometimes needs to be able to find a physical address given the virtual address of the memory in which a kernel data structure is stored. Kernel global variables and memory allocated by boot_alloc() are in the region where the kernel was loaded, starting at 0xf0000000, the very region where we mapped all of physical memory. Thus, to turn a virtual address in this region into a physical address, the kernel can simply subtract 0xf0000000. You should use PADDR(va) to do that subtraction.

  JOS用 KADDR(pa) 函数,在已知物理地址上加上0xf0000000 得到对应的虚拟地址。

  JOS用PADDR(va) 函数,在已知虚拟地址上减去0xf0000000 得到对应的物理地址。

知识点:Reference counting

In future labs you will often have the same physical page mapped at multiple virtual addresses simultaneously (or in the address spaces of multiple environments). You will keep a count of the number of references to each physical page in the pp_ref field of the struct PageInfo corresponding to the physical page. When this count goes to zero for a physical page, that page can be freed because it is no longer used. In general, this count should equal to the number of times the physical page appears below UTOP in all page tables (the mappings above UTOP are mostly set up at boot time by the kernel and should never be freed, so there's no need to reference count them). We'll also use it to keep track of the number of pointers we keep to the page directory pages and, in turn, of the number of references the page directories have to page table pages.

Be careful when using page_alloc. The page it returns will always have a reference count of 0, so pp_ref should be incremented as soon as you've done something with the returned page (like inserting it into a page table). Sometimes this is handled by other functions (for example, page_insert) and sometimes the function calling page_alloc must do it directly.

  在未来的实验中,会经常遇到同一块物理页映射到多个虚拟地址上去的情况。要通过pp_ref这个变量来记录一个物理页被引用的次数,当这个变量记数为0时,此页没再被使用就可以回收。总的来说,这个变量应该等于该在页表中出现的次数。

  在用page_alloc的时候要小心,它返回的的页应该pp_ref为0,假如你要使用此页要记得将变量+1,有些时候别的方程会完成这部分工作,比如page_insert,但有些时候调用时要注意增加引用次数。

exercise 4:Page Table Management

Now you'll write a set of routines to manage page tables: to insert and remove linear-to-physical mappings, and to create page table pages when needed.

Exercise 4. In the file kern/pmap.c, you must implement code for the following functions.

        pgdir_walk()
        boot_map_region()
        page_lookup()
        page_remove()
        page_insert()
	

check_page(), called from mem_init(), tests your page table management routines. You should make sure it reports success before proceeding.

  现在实现以上的函数完成一个二级页表,功能包括插入、删除页表中线性地址到物理地址的映射,以及需要时创建页表。

  第一个实现的函数是pgdir_walk()。用于查找一个虚拟内存对应的页表项,如果页表不存在,则根据需要可以创建一个。先明确几个概念:

  • 页目录表,页目录是内存虚拟地址映射到物理地址的起点,所有的映射都要从页目录开始,页目录常驻内存,页目录表和页表结构没什么不同,只是页目录表是一级页表,页表是二级页表。
  • 页目录项,页目录项中存放着二级页表的起始物理地址。
  • 页表,页表用于存放页表项。
  • 页表项,页表项用于将最终将虚拟地址映射到物理地址,页表项的高20位在放着物理页面的起始地址。  

  inc/mmu.h中有用的代码段:

// page directory index
#define PDX(la)		((((uintptr_t) (la)) >> PDXSHIFT) & 0x3FF)

// page table index
#define PTX(la)		((((uintptr_t) (la)) >> PTXSHIFT) & 0x3FF)

  pgdir_walk代码:

// Given 'pgdir', a pointer to a page directory, pgdir_walk returns
// a pointer to the page table entry (PTE) for linear address 'va'.
// This requires walking the two-level page table structure.
//
// The relevant page table page might not exist yet.
// If this is true, and create == false, then pgdir_walk returns NULL.
// Otherwise, pgdir_walk allocates a new page table page with page_alloc.
//    - If the allocation fails, pgdir_walk returns NULL.
//    - Otherwise, the new page's reference count is incremented,
//	the page is cleared,
//	and pgdir_walk returns a pointer into the new page table page.
//
// Hint 1: you can turn a PageInfo * into the physical address of the
// page it refers to with page2pa() from kern/pmap.h.
//
// Hint 2: the x86 MMU checks permission bits in both the page directory
// and the page table, so it's safe to leave permissions in the page
// directory more permissive than strictly necessary.
//
// Hint 3: look at inc/mmu.h for useful macros that manipulate page
// table and page directory entries.
//
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
	// 参数:页目录指针,线性地址(JOS中等同于虚拟地址),若页目录项不存在是否创建
	// Fill this function in
	uint32_t pgdir_index = PDX(va);		//页目录索引
	uint32_t pgtable_index = PTX(va);	//页表索引
	pte_t *pte;						//页表项

	if( pgdir[pgdir_index] & PTE_P){
		pte = KADDR(PTE_ADDR(pgdir[pgdir_index]));
	}
	else{
		if(create){
			struct PageInfo *pp = page_alloc(ALLOC_ZERO);
			if(pp){
				++pp->pp_ref;
				// 设置页表指针
				pte = (pte_t*)page2kva(pp);
				// 设置页目录项修改页目录的flag,根据 check_page 函数中用到的属性。
                pgdir[pgdir_index] = PADDR(pte) | PTE_P | PTE_W | PTE_U;
			}else{
				return NULL;
			}
		}
		else{
			return NULL;
		}
	}


	return &pte[pgtable_index];
}

  第二个实现的函数是boot_map_region()。用于将指定的虚拟页映射到物理页。 

//
// Map [va, va+size) of virtual address space to physical [pa, pa+size)
// in the page table rooted at pgdir.  Size is a multiple of PGSIZE, and
// va and pa are both page-aligned.
// Use permission bits perm|PTE_P for the entries.
//
// This function is only intended to set up the ``static'' mappings
// above UTOP. As such, it should *not* change the pp_ref field on the
// mapped pages.
//
// Hint: the TA solution uses pgdir_walk
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
	// Fill this function in
	pte_t *pte;
	size_t pgNum = PGNUM(size);		//获得页数
	for(size_t i=0;i<pgNum;++i,va += PGSIZE,pa += PGSIZE){
		pte = pgdir_walk(pgdir, (void *)va, 1);		//查找页表项
		if(!pte) return;
		*pte = pa | perm | PTE_P;			// 设置页表项
	}

}

  第三个实现的函数是page_lookup()。用于查找一个虚拟页对应的物理页的物理页描述符(PageInfo结构)。

//
// Return the page mapped at virtual address 'va'.
// If pte_store is not zero, then we store in it the address
// of the pte for this page.  This is used by page_remove and
// can be used to verify page permissions for syscall arguments,
// but should not be used by most callers.
//
// Return NULL if there is no page mapped at va.
//
// Hint: the TA solution uses pgdir_walk and pa2page.
//
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
	// Fill this function in
	pte_t *pte = pgdir_walk(pgdir,va,0);
	if(!pte) return NULL;
	if(pte_store){
		*pte_store = pte;		//保存一个指向页表指针的指针
	}
	return pa2page(PTE_ADDR(*pte));		//返回物理页描述符
}

  第四个实现的函数是page_remove()。用于在页表中删除指定虚拟地址到物理地址的映射。

//
// Unmaps the physical page at virtual address 'va'.
// If there is no physical page at that address, silently does nothing.
//
// Details:
//   - The ref count on the physical page should decrement.
//   - The physical page should be freed if the refcount reaches 0.
//   - The pg table entry corresponding to 'va' should be set to 0.
//     (if such a PTE exists)
//   - The TLB must be invalidated if you remove an entry from
//     the page table.
//
// Hint: The TA solution is implemented using page_lookup,
// 	tlb_invalidate, and page_decref.
//
void
page_remove(pde_t *pgdir, void *va)
{
	// Fill this function in
	pte_t *pte;
	pte_t **pte_store = &pte;
	struct PageInfo *pp = page_lookup(pgdir,va,pte_store);
	// If there is no physical page at that address, silently does nothing.
	if(!pp) return;

	// The ref count on the physical page should decrement.
	// The physical page should be freed if the refcount reaches 0.
	page_decref(pp);	// 减少pp物理页的引用,page_decref里包括了pagefree

	// The pg table entry corresponding to 'va' should be set to 0.
	**pte_store = 0;	// 页表项清0,取消映射,**pte_store == *pte

	// The TLB must be invalidated if you remove an entry from the page table.
	tlb_invalidate(pgdir,va);	//TLB是放在CPU里的高速页表缓存,内存中的页表有一页表项失效要通知TLB同步。

}

  第五个实现的函数是page_insert()。用于在页表中增加一个虚拟地址到物理地址的映射。

//
// Map the physical page 'pp' at virtual address 'va'.
// The permissions (the low 12 bits) of the page table entry
// should be set to 'perm|PTE_P'.
//
// Requirements
//   - If there is already a page mapped at 'va', it should be page_remove()d.
//   - If necessary, on demand, a page table should be allocated and inserted
//     into 'pgdir'.
//   - pp->pp_ref should be incremented if the insertion succeeds.
//   - The TLB must be invalidated if a page was formerly present at 'va'.
//
// Corner-case hint: Make sure to consider what happens when the same
// pp is re-inserted at the same virtual address in the same pgdir.
// However, try not to distinguish this case in your code, as this
// frequently leads to subtle bugs; there's an elegant way to handle
// everything in one code path.
//
// RETURNS:
//   0 on success
//   -E_NO_MEM, if page table couldn't be allocated
//
// Hint: The TA solution is implemented using pgdir_walk, page_remove,
// and page2pa.
//
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
	// Fill this function in
	pte_t *pte = pgdir_walk(pgdir,va,1);

	// 没有空间返回 -E_NO_MEM
	if(!pte) return -E_NO_MEM;

	// 虚拟地址va已经映射到一个物理页
	if(*pte & PTE_P){
		// 如果映射到同一个物理页,仅更改权限
		if(page2pa(pp) == PTE_ADDR(*pte)){
			*pte = page2pa(pp) | perm |PTE_P;
			return 0;
		}
		// 如果不同,删除当前映射
		else{
			page_remove(pgdir,va);
		}
	}
	// 增加新映射关系
	*pte = page2pa(pp) | perm |PTE_P;
	++pp->pp_ref;
	return 0;
}

  编译运行,查看结果:

6828 decimal is 15254 octal!
Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_free_list() succeeded!
check_page_alloc() succeeded!
check_page() succeeded!

  链接https://blog.csdn.net/fang92/article/details/47322241中,用了一个图清晰表达了part2实现的二级页表:

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. The dividing line is defined somewhat arbitrarily by the symbol ULIM in inc/memlayout.h, reserving approximately 256MB of virtual address space for the kernel. This explains why we needed to give the kernel such a high link address in lab 1: otherwise there would not be enough room in the kernel's virtual address space to map in a user environment below it at the same time.

You'll find it helpful to refer to the JOS memory layout diagram in inc/memlayout.h both for this part and for later labs.

  JOS把32位线性地址(在JOS中也就是虚拟地址)空间分成了两部分。如下图,用户空间(进程空间)控制了下半部分,这部分我们会在lab3中实现,内核控制着上半部分。上下部分的分界线是inc/memlayout.h中的ULIM,会给内核保留大约256MB的虚拟地址,这就是为什么内核的链接地址需要安排在较高的一个位置,这样分配完内核需要的空间后底下还能留下较多的内存空间给用户使用,不然用户空间可能会不够。

  JOS内存分布画在inc/memlayout.h如下所示:

/*
 * 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.
 */

知识点:Permissions and Fault Isolation

Since kernel and user memory are both present in each environment's address space, we will have to use permission bits in our x86 page tables to allow user code access only to the user part of the address space. Otherwise bugs in user code might overwrite kernel data, causing a crash or more subtle malfunction; user code might also be able to steal other environments' private data.

The user environment will have no permission to any of the memory above ULIM, while the kernel will be able to read and write this memory. For the address range [UTOP,ULIM), both the kernel and the user environment have the same permission: they can read but not write this address range. This range of address is used to expose certain kernel data structures read-only to the user environment. Lastly, the address space below UTOP is for the user environment to use; the user environment will set permissions for accessing this memory.

  由于用户进程只能访问用户空间,不能访问内核空间,我们需要利用页表项中的访问权限位(permission bits)控制能否访问权限,否则用户进程的代码可能重写内核数据,造成安全问题甚至系统崩溃。

  用户进程没有权限访问ULIM上面的内存,只有内核才能读写这部分内存。对用[UTOP,ULIM)的地址范围,内核和用户进程有同样的权限,都可以读但是不能执行写操作,这部分地址是内核把部分只读的内核数据结构暴露给用户进程使用。最后,UTOP下面的地址空间是留给用户进程使用的,用户进程可以读写这部分地址空间。

Exercise 5

Now you'll set up the address space above UTOP: the kernel part of the address space. inc/memlayout.h shows the layout you should use. You'll use the functions you just wrote to set up the appropriate linear to physical mappings.

Exercise 5. Fill in the missing code in mem_init() after the call to check_page().

Your code should now pass the check_kern_pgdir() and check_page_installed_pgdir() checks.

  补写mem_init()函数中调用check_page()后的内容,完成UTOP上面那部分内存空间虚拟地址到物理地址的映射。

  exercise 5映射三段虚拟地址到物理地址:

  1.UPAGES地址空间,用于存储物理页面数据结构,也就是exercise1中完成的东西。UPAGES空间虚拟地址为(0xef000000~0xef400000):

 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xef000000

  映射代码:

	//
	// Now we set up virtual memory

	//
	// Map 'pages' read-only by the user at linear address UPAGES
	// Permissions:
	//    - the new image at UPAGES -- kernel R, user R
	//      (ie. perm = PTE_U | PTE_P)
	//    - pages itself -- kernel RW, user NONE
	// Your code goes here:
	// 第一个参数为前面建立好的页目录kern_pgdir;第二个参数是虚拟地址;
	// 第三个参数是映射内存块大小;第四个参数是映射到的物理地址,第五个参数是页表项标志,PTE_U表示用户可读
	boot_map_region(kern_pgdir,(uintptr_t)UPAGES,ROUNDUP(npages*sizeof(struct PageInfo),PGSIZE),PADDR(pages),PTE_U | PTE_P);

  2.内核栈地址空间。内核栈的最高位置是KSTACKTOP,整个内核栈的范围是[KSTACKTOP-PTSIZE, KSTACKTOP),这里我们只用映射[KSTACKTOP-KSTKSIZE, KSTACKTOP)这个范围。[KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE)范围内的空间不用映射,这是因为假如内核栈数据溢出,只会报错,不会因为溢出内核其他数据被改写,也就是保护内核数据的一块地址空间。

  内核栈地址空间虚拟地址为0xef800000~0xf0000000):

 *    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

  映射代码:

	//
	// Use the physical memory that 'bootstack' refers to as the kernel
	// stack.  The kernel stack grows down from virtual address KSTACKTOP.
	// We consider the entire range from [KSTACKTOP-PTSIZE, KSTACKTOP)
	// to be the kernel stack, but break this into two pieces:
	//     * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
	//     * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed; so if
	//       the kernel overflows its stack, it will fault rather than
	//       overwrite memory.  Known as a "guard page".
	//     Permissions: kernel RW, user NONE
	// Your code goes here:
	// 第一个参数为前面建立好的页目录kern_pgdir;第二个参数是虚拟地址;
	// 第三个参数是映射内存块大小;第四个参数是映射到的物理地址,第五个参数是页表项标志
	boot_map_region(kern_pgdir,KSTACKTOP-KSTKSIZE,KSTKSIZE,PADDR(bootstack),PTE_W | PTE_P);

  3.内核空间。将内核空间的虚拟地址[KERNBASE, 2^32)映射到物理地址[0, 2^32 - KERNBASE)。

  内核空间虚拟地址为(0xf0000000~0xffffffff):

/*
 * Virtual memory map:                                Permissions
 *                                                    kernel/user
 *
 *    4 Gig -------->  +------------------------------+
 *                     |                              | RW/--
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     :              .               :
 *                     :              .               :
 *                     :              .               :
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
 *                     |                              | RW/--
 *                     |   Remapped Physical Memory   | RW/--
 *                     |                              | RW/--
 *    KERNBASE, ---->  +------------------------------+ 0xf0000000      --+

  映射代码:

	//
	// Map all of physical memory at KERNBASE.
	// Ie.  the VA range [KERNBASE, 2^32) should map to
	//      the PA range [0, 2^32 - KERNBASE)
	// We might not have 2^32 - KERNBASE bytes of physical memory, but
	// we just set up the mapping anyway.
	// Permissions: kernel RW, user NONE
	// Your code goes here:
	// 第一个参数为前面建立好的页目录kern_pgdir;第二个参数是虚拟地址;
	// 第三个参数是映射内存块大小;第四个参数是映射到的物理地址,第五个参数是页表项标志
	boot_map_region(kern_pgdir,KERNBASE,ROUNDUP((0xFFFFFFFF-KERNBASE),PGSIZE),0,PTE_W | PTE_P);

  运行结果:

HemingbeardeMacBook-Pro:lab hemingbear$ make qemu
+ cc kern/pmap.c
+ ld obj/kern/kernel
+ mk obj/kern/kernel.img
qemu-system-i386 -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::25501 -D qemu.log 
6828 decimal is 15254 octal!
Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_free_list() succeeded!
check_page_alloc() succeeded!
check_page() succeeded!
check_kern_pgdir() succeeded!
check_page_free_list() succeeded!
check_page_installed_pgdir() succeeded!
Welcome to the JOS kernel monitor!

Question

2.What entries (rows) in the page directory have been filled in at this point? What addresses do they map and where do they point? In other words, fill out this table as much as possible:

EntryBase Virtual AddressPoints to (logically):
1023?Page table for [252,256)MB of phys memory
1022??
9600xf0000000Page table for [0,4) MB of phys memory
9580xef800000ULIM
.??
20x00800000Page table for [8,12)MB of phys memory
10x00400000Page table for [4,8)MB of phys memory
00x00000000Page table for [0,4)MB of phys memory

3.We have placed the kernel and user environment in the same address space. Why will user programs not be able to read or write the kernel's memory? What specific mechanisms protect the kernel memory?

  页表项包含标志位,其中有一个标志位是权限位,可以限制用户进程读写内核数据,如果没有将 PTE_U 置 1 则用户无权限读写。

4.What is the maximum amount of physical memory that this operating system can support? Why?

  pages数组存储了,物理页描述符,pages数组最大为4MB,每个物理页描述符PageInfo占用8bytes,最多能支持4MB/8bytes=512k页,每页容量4KB,物理内存最大容量为4KB * 512K = 2GB。

5.How much space overhead(开支) is there for managing memory, if we actually had the maximum amount of physical memory? How is this overhead broken down?

  当我们到达最高内存,一个一级页表以及所有的二级页表都要运行,一级页表和二级页表的每个页表项4Bytes,且每张表有1024个页表项,所以共开支(1+1024)*4Bytes = 4100KB。加上pages数组的4MB,一共8196KB。

  如果想要削减开支,可使每个页的容量变大,这样页表项的数量减少,开支也相应减少。

6.Revisit the page table setup in kern/entry.S and kern/entrypgdir.c. Immediately after we turn on paging, EIP is still a low number (a little over 1MB). At what point do we transition to running at an EIP above KERNBASE? What makes it possible for us to continue executing at a low EIP between when we enable paging and when we begin running at an EIP above KERNBASE? Why is this transition necessary?

    # Now paging is enabled, but we're still running at a low EIP
    # (why is this okay?).  Jump up above KERNBASE before entering
    # C code.
    mov $relocated, %eax
    jmp *%eax
relocated:

  语句jmp *%eax即转到 eax 所存的地址执行,在这里完成了跳转。relocated 部分代码主要设置了栈指针以及调用 kern/init.c。由于在 kern/entrypgdir.c 中将 0~4MB 和 KERNBASE ~ KERNBASE + 4 MB 的虚拟地址都映射到了 0~4MB 的物理地址上,因此无论 EIP 在高位和低位都能执行。必需这么做是因为如果只映射高位地址,那么在开启分页机制的下一条语句就会crash。

 

Challenge

Challenge! Extend the JOS kernel monitor with commands to:

  • Display in a useful and easy-to-read format all of the physical page mappings (or lack thereof) that apply to a particular range of virtual/linear addresses in the currently active address space. For example, you might enter 'showmappings 0x3000 0x5000' to display the physical page mappings and corresponding permission bits that apply to the pages at virtual addresses 0x3000, 0x4000, and 0x5000.

  • Explicitly set, clear, or change the permissions of any mapping in the current address space.

  • Dump the contents of a range of memory given either a virtual or physical address range. Be sure the dump code behaves correctly when the range extends across page boundaries!

  • Do anything else that you think might be useful later for debugging the kernel. (There's a good chance it will be!)

  能够打印JOS物理页面映射的相关信息。

  首先,仿照其他monitor指令在monitor.h中定义:

// Functions implementing monitor commands.
int mon_help(int argc, char **argv, struct Trapframe *tf);
int mon_kerninfo(int argc, char **argv, struct Trapframe *tf);
int mon_backtrace(int argc, char **argv, struct Trapframe *tf);
int mon_showmapping(int argc, char **argv, struct Trapframe *tf);

    然后,添加showmappings命令行:

static struct Command commands[] = {
	{ "help", "Display this list of commands", mon_help },
	{ "kerninfo", "Display information about the kernel", mon_kerninfo },
	{ "backtrace", "Display backtrace info", mon_backtrace },
	{ "showmappings", "Display mappings info", mon_showmappings },
};

  最后,实现mon_showmappings()函数:

int
mon_showmappings(int argc, char **argv, struct Trapframe *tf){
	if(argc != 3){
		cprintf("Usage:showmappings 0xbegin_addr 0xend_addr\n");
		return -1;
	}
	char *errchar;
	// long int strtol(const char *nptr,char **endptr,int base); 将字符串转为整数,相比atoi会检测转换是否成功,
	// 将非法字符写入endptr,并以 base 为进制。
	uintptr_t start_addr = strtol(argv[1],&errchar,16);
	if(*errchar){
		cprintf("Invalid begin_addr,just need address number which 16base.\n");
		return -1;
	}

	uintptr_t end_addr = strtol(argv[2],&errchar,16);
	if(*errchar){
		cprintf("Invalid end_addr,just need address number which 16base.\n");
		return -1;
	}
	if(start_addr > end_addr){
		cprintf("end_addr must bigger than start_addr\n");
		return -1;
	}

	// 4K对齐
	start_addr = ROUNDUP(start_addr,PGSIZE);
	end_addr = ROUNDUP(end_addr,PGSIZE);

	uintptr_t cur_addr = start_addr;
	while(cur_addr <= end_addr){
		pte_t *pte = pgdir_walk(kern_pgdir,(void*)cur_addr,0);
		// 查找页表,页表未找到或者页表项还没插入(PTE_P==0)
		if(!pte || (*pte & PTE_P)){
			cprintf("virtual address [%08x] - not mapped\n",cur_addr);
		}else{
			cprintf("virtual address [%08x] - physical address [%08x], permission: ",cur_addr,PTE_ADDR(*pte));
			char perm_PS = (*pte &PTE_PS) ? 'S':'-';
			char perm_W = (*pte &PTE_W) ? 'W':'-';
			char perm_U = (*pte &PTE_U) ? 'U':'-';
			cprintf("-%c----%c%cP\n", perm_PS, perm_U, perm_W);
		}
		cur_addr += PGSIZE;
	}
	return 0;
}

  使用命令行打印结果:

K> showmappings
Usage:showmappings 0xbegin_addr 0xend_addr
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K> showmappings 0xefff0000 0xf0000000
virtual address [efff0000] - physical address [00000000], permission: --------P
virtual address [efff1000] - physical address [00000000], permission: --------P
virtual address [efff2000] - physical address [00000000], permission: --------P
virtual address [efff3000] - physical address [00000000], permission: --------P
virtual address [efff4000] - physical address [00000000], permission: --------P
virtual address [efff5000] - physical address [00000000], permission: --------P
virtual address [efff6000] - physical address [00000000], permission: --------P
virtual address [efff7000] - physical address [00000000], permission: --------P
virtual address [efff8000] - not mapped
virtual address [efff9000] - not mapped
virtual address [efffa000] - not mapped
virtual address [efffb000] - not mapped
virtual address [efffc000] - not mapped
virtual address [efffd000] - not mapped
virtual address [efffe000] - not mapped
virtual address [effff000] - not mapped
virtual address [f0000000] - not mapped
K> 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值