JOS LAB2,Memory and Paging.PART 2

Posted on Wed, 24 Nov 2010 10:17:05 -1100

在PART1说完分页机制后,我们开始讨论具体的实现代码吧 !

第一个函数:

void
i386_vm_init(void)
{
    pde_t* pgdir;
    uint32_t cr0;
    size_t n;
    //////////////////////////////////////////////////////////////////////
    // create initial page directory.
    pgdir = boot_alloc(PGSIZE, PGSIZE);
    memset(pgdir, 0, PGSIZE);
    boot_pgdir = pgdir;
    boot_cr3 = PADDR(pgdir);
    //////////////////////////////////////////////////////////////////////
    // Recursively insert PD in itself as a page table, to form
    // a virtual page table at virtual address VPT.
    // (For now, you don't have understand the greater purpose of the
    // following two lines.)

    // Permissions: kernel RW, user NONE
    pgdir[PDX(VPT)] = PADDR(pgdir)|PTE_W|PTE_P;

    // same for UVPT
    // Permissions: kernel R, user R 
    pgdir[PDX(UVPT)] = PADDR(pgdir)|PTE_U|PTE_P;

    //////////////////////////////////////////////////////////////////////
    // Allocate an array of npage 'struct Page'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 Page in this
    // array.  'npage' is the number of physical pages in memory.
    // User-level programs will get read-only access to the array as well.
    // Your code goes here:

    //*****************
    //SUNUS,Nov 15,2010
    pages = boot_alloc(npage * sizeof(struct Page), PGSIZE);
    ROUNDUP(pages,PGSIZE);
    cprintf("pages is %08x\n",PADDR(pages));
    /* I don't know how to deal with the permission stuffs,so leave it ! - SUNUS*/

    //////////////////////////////////////////////////////////////////////
    // Now that we've allocated the initial kernel data structures, we set
    // up the list of free physical pages. Once we've done so, all further
    // memory management will go through the page_* functions. In
    // particular, we can now map memory using boot_map_segment or page_insert
    page_init();

    check_page_alloc();

    page_check();

    //////////////////////////////////////////////////////////////////////
    // 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:

    //SUNUS,Nov,23,2010
    size_t actual_ptsize;
    actual_ptsize = ROUNDUP(npage * sizeof(struct Page),PGSIZE);

    boot_map_segment(pgdir, UPAGES, actual_ptsize, PADDR(pages), (PTE_W|PTE_U));

    //////////////////////////////////////////////////////////////////////
    // 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:

    //SUNUS,Nov,23,2010
    boot_map_segment(pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack),PTE_W|PTE_P);
    boot_map_segment(pgdir, KSTACKTOP - PTSIZE, PTSIZE - KSTKSIZE, 0, 0 ); // I don't know if it's necessary -sunus//
    //////////////////////////////////////////////////////////////////////
    // 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: 
    boot_map_segment(pgdir, KERNBASE, 0x0fffffff + 1, 0, PTE_W|PTE_P);
    // Check that the initial page directory has been set up correctly.
    check_boot_pgdir();

    //////////////////////////////////////////////////////////////////////
    // On x86, segmentation maps a VA to a LA (linear addr) and
    // paging maps the LA to a PA.  I.e. VA => LA => PA.  If paging is
    // turned off the LA is used as the PA.  Note: there is no way to
    // turn off segmentation.  The closest thing is to set the base
    // address to 0, so the VA => LA mapping is the identity.

    // Current mapping: VA KERNBASE+x => PA x.
    //     (segmentation base=-KERNBASE and paging is off)

    // From here on down we must maintain this VA KERNBASE + x => PA x
    // mapping, even though we are turning on paging and reconfiguring
    // segmentation.

    // Map VA 0:4MB same as VA KERNBASE, i.e. to PA 0:4MB.
    // (Limits our kernel to <4MB)
    pgdir[0] = pgdir[PDX(KERNBASE)];

    // Install page table.
    lcr3(boot_cr3);

    // Turn on paging.
    cr0 = rcr0();
    cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_TS|CR0_EM|CR0_MP;
    cr0 &= ~(CR0_TS|CR0_EM);
    lcr0(cr0);

    // Current mapping: KERNBASE+x => x => x.
    // (x < 4MB so uses paging pgdir[0])

    // Reload all segment registers.
    asm volatile("lgdt gdt_pd");
    asm volatile("movw %%ax,%%gs" :: "a" (GD_UD|3));
    asm volatile("movw %%ax,%%fs" :: "a" (GD_UD|3));
    asm volatile("movw %%ax,%%es" :: "a" (GD_KD));
    asm volatile("movw %%ax,%%ds" :: "a" (GD_KD));
    asm volatile("movw %%ax,%%ss" :: "a" (GD_KD));
    asm volatile("ljmp %0,$1f\n 1:\n" :: "i" (GD_KT));  // reload cs
    asm volatile("lldt %%ax" :: "a" (0));

    // Final mapping: KERNBASE+x => KERNBASE+x => x.

    // This mapping was only used after paging was turned on but
    // before the segment registers were reloaded.
    pgdir[0] = 0;

    // Flush the TLB for good measure, to kill the pgdir[0] mapping.
    lcr3(boot_cr3);
}

这是开始的对内存初始化的函数,我们先来到

    static void*
boot_alloc(uint32_t n, uint32_t align)
{
    extern char end[];
    void *v;

    // Initialize boot_freemem 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 -
    // i.e., the first virtual address that the linker
    // did _not_ assign to any kernel code or global variables.
    if (boot_freemem == 0)
	boot_freemem = end;

    // LAB 2: Your code here:
    //	Step 1: round boot_freemem up to be aligned properly
    //		(hint: look in types.h for some handy macros)
    //	Step 2: save current value of boot_freemem as allocated chunk
    //	Step 3: increase boot_freemem to record allocation
    //	Step 4: return allocated chunk	
    // SUNUS Nov,14,2010
    boot_freemem = ROUNDUP(boot_freemem, align);
    v = boot_freemem;
    boot_freemem += n; 
    if(PADDR(boot_freemem)  > (maxpa))
      {
	cprintf("v is %08x , mapa is %08x\n",PADDR(boot_freemem),maxpa);
	cprintf("Memory Not Enough!\n");
	return NULL;
      }
    return v;
}

这里的 ROUNDUP是向上取整函数,这里的作用是让boot_freemem按4KB内存对齐。这样可以让寻址的效率达到最大。

该函数作用则是分配n字节大小的内存,且起始地址按align方式对齐。

而分配的方式,其实则是让boot_freemem指针不断自增,从而让boot_freemem的值 之前,end之后的地址为“已使用内存”.并返回下一个可使用内存地址。maxpa为最大物理地址,在之前的函数中已经被赋值,这里先不深究。

这里来到了

page_init(void),这里注意的是对于一个页是否可用的标志是pages[x].pp->ref,pp->ref位对页面引用的计数器,若等于0,则可用

对于初始化,要注意系统的有几个页面是不可用的(系统预留或处于安全原因):

pages[0],IOPHYSMEM~EXTPHYSMEM预留的IO HOLE还有内核位置[KERNBASE~boot_freemem]

    void
page_init(void)
{

    /*Nov 16,2010 Sunus */

    int i;
    pages[0].pp_ref = 1;
    LIST_INIT(&page_free_list);
    for(i = 1 ; i < npage ; i++)
      {
    pages[i].pp_ref = 0;
     if((i >= IOPHYSMEM/PGSIZE && i < EXTPHYSMEM/PGSIZE)  /*IO HOLE*/
         || (i >= PADDR(KERNBASE)/PGSIZE && i < (size_t)ROUNDUP(PADDR(boot_freemem),PGSIZE)/PGSIZE)) /*KERNBASE-boot_freemem*/
      {
        pages[i].pp_ref = 1;
        continue;
      }
    LIST_INSERT_HEAD(&page_free_list, &pages[i], pp_link);
      }
}

 

JOS LAB2,Memory and Paging.PART 1

Posted on Wed, 24 Nov 2010 06:17:31 -1100

和JOS的内存管理分页等等完了那么多天,终于可以勉强跑起来了,有必要写点啥。主要是针对JOS kern/pmap.c的一些体会理解,有不对的地方欢迎留言!先按照系统启动的进度来对一些重要的函数进行说明吧。

在开始之前,先说明几种地址

liner address(线性地址) 代码中使用较多的地址

virtual address(虚拟地址)

physical address(物理地址)也就是内存实际地址,基本在代码中很少直接出现

三者一个比较简单的关系就是

linear address 经过 分段机制得到 virtual address

vitural address 经过 分页机制(paging) 得到 physical.

分页机制是可选的在80286之后才有。如果分页机制没有启用 则可以说va = pa

pmap.c的重点则是对分页机制的实现,在开始实际讨论代码之前 先大体地说说分页机制是怎么工作的。

 

在系统中,有1个页目录(page directory)本身是一个有1024个元素,每个元素4byte(32位)的数组。其中的每个元素称为页目录项(page directory entry)。每个页目项录对应着着一个page table(页表)。页表大体与页目录相似,也是每个页表有1024个元素,称为页表项(page table entry),每个元素4byte。而不同的地方则是每个页表项对应着一块4kb的页。(内存的储存单位。在分页机制中,内存被分成一页一页的,JOS中每页4kb,以后也会有4MB为一页的).通过这种分页机制,让软件对内存地址的访问达到了 4GB(1024 * 1024 * 4kb)。即使系统的物理地址达不到4GB,通过分页机制,仍然可以让程序访问到高端内存。

程序中一般使用的线性地址是这样,分为3部分

31~~~~~~22~~~~~~11~~~~~0

|PDE-INDEX|PTE-INDEX|OFFSET| ; 最高10位是对页目录的索引范围位0~1024,中间10位是对页表的索引,最后十位是偏移

32位的PDE,实际上分为2部分

31~~~~~~~~12~~~~~~~0

|PTE--ADDRESS|ATTRIBUTE|   ;    高20位组成了一个地址,低12位中包含一些属性位。

32位的PTE,是这样

31~~~~~~~~~12~~~~~~~0

|PAGE-ADDRESS|ATTRIBUTE| ;     同样,高20位是一个地址,低12位包含一些属性位。

理解这些地址之后,我们看看系统是怎么完成这个二级寻址的(0-4GB).

在这里,页目录为pgdir,对于给定的la,先取前10位。JOS中用了个宏PDX(la);则pgdir[PDX(la)]则为la对应的页目录项,

该页目录项中的高20位则是对应的页表的地址。则PTX(la)则是对应的页表项。最后,页表项对应的地址(4KB的倍数)再加上便宜则可以得到最终的物理地址。

如图:

注意看000C8000H这个地址是怎么转移到0011000的。弄清楚之后我们继续:D

取000C8000H的高10位地址。为0,来到页目录的第0项,页目录项为00003003H.

取页目录项的高20位地址,为00003H(页表项的地址)。取000C8000H的12-21位(0011001000B = C8H),注意一个页的大小是位4KB,所以此时得到的C8H还需乘以4。C8H * 4 = 320H。即页表的第C8项的地址为00003000+320H = 00003320H.

而该页表项的内容则为对应的页地址如图,00110000H.由于此处的000C8000的偏移(低12位为0)所以最终000C8000H对应的地址为0011000H + 0 = 0011000H.