内存分页管理的实验报告

内存分页管理的实验报告
作者:huang_2008
报告时间:2006-8-22
实验时间:3天
实验代码:
HelloOS所有代码 http://flash.xxsyzx.com/learnos/

内存分页管理设想:
以HelloOS(实验系统名称)为基础进行内存分页管理实验,386PC在保护模式下寻址最大是4GB,而内存分页一共页可以映射4GB的内存地址,这些映射的地址叫做线性地址,而实际内存上的地址叫做物理地址。管理办法是每个进程都拥有一个页目录,可以映射4GB的内存地址的内容。但是通常进程不会用到这么大的内容,所以可以开始先分配几页内存给程序运行(一页内存=4KB),当进程需要时候,才给它申请内存空间。如果内存不足,可以通过页交换技术,把少用到的内存交换到磁盘上,就得到内存空间,并通过页目录和页表进行映射。这样即使在内存只有64MB的PC上,进程也可以在4GB的空间里进行工作。
每个进程都是独立的内存空间,互不影响,例如进程A往内存0xF0000000处写入了字符’a’,进程B往内存0xF0000000处入了字符’z’,则在进程A中看到内存0xF0000000的字符是’a’,而不会是’b’。这就需要给每个进程都创建一个页目录。在进程切换的时候,页目录也随着变换。

HelloOS的内存管理:
下面是4GB的内存线性空间
0-1GB        1GB-2GB        2GB-3GB        3GB-4GB
用户空间        用户空间        用户空间        内核空间
每个进程把4GB的空间或分为用户空间0-3GB和内核空间3GB-4GB。其中每个进程的内核空间都是共享的,而用户空间独立,存放进程的代码、数据和堆栈。

实现代码:
/*
* memory.c 内存分页管理
* 060818-060821
* Email: [email]huang_2008@msn.com[/email]
* QQ: 357339036
* Huang Guan
*
* 首先将物理内存从8MB开始分成以页为单位存放
* 并用数组表示每个页面的状态,是否被使用
* 而前8MB为内核, 并被所有进程映射
* get_free_page()是获取一个空闲的物理页,页大小固定为4KB
* free_page()是释放一个物理页
*
* 进程内存利用图
* ----------------------------------------------------------------
* |0            |1GB             |2GB             |3GB           |4GB
* |             |                |                |              |
* ----------------------------------------------------------------
* 0-4GB, 内核和用户空间完全相同.
* C0000000h-C0100000h 内核程序区 1MB
* C0100000h-C0800000h 内核系统分配区
*
*/

#include <string.h>
#include <printk.h>
#include <memory.h>
#include <vmalloc.h>
#include <process.h>

#define _MB( a ) (a<<20)
//这里是BIOS读取的内存信息个数
#define MEMBLOCK_NUM (*((unsigned short*)0xC0090008))       
//BIOS读取的内存信息指针
#define ards (*((struct ards_block**)0xC0090004))       

#define L2P(m) ( (t_32)m - 0xC0000000 ) //内核前8MB线性地址转换到物理地址
#define P2L(m) ( (t_32)m + 0xC0000000 ) //物理地址转换到内核线性地址
#define PAGE_SIZE 4096        //4KB every page
#define PAGE_ADDR(i) ((t_32)p_first_page+i*PAGE_SIZE)        //由页面索引获得该页所表示的物理地址

#define CURRENT_PAGE_DIR( m ) ((t_32*)((current->pgd&0xFFFFF000)+(m>>22)*4)) //页目录内容
#define CURRENT_PAGE_TABLE( m ) ( (t_32*)((*CURRENT_PAGE_DIR(m)&0xFFFFF000)+((m>>12)&1023)*4) ) //页表内容

#define PG_W 2        //页面可写
#define PG_U 4        //页面为用户级
#define PG_P 1        //页面存在

/* 这是BIOS中断获得的内存信息结构, 多个 */
struct ards_block{
        t_32 base_low;
        t_32 base_high;        //没用到
        t_32 length_low;
        t_32 length_high;        //没用到
        t_32 type;        //1表示操作系统可用
};

/* 这个结构用于说明一页物理内存的使用情况 */
struct page_info{
        t_16 i_count;        //使用该页的引用计数,0表示没有被引用。
        t_16 reserved;        //未使用
};

struct page_info *p_pages;        //物理页面使用情况数组
struct page_info *p_first_page;        //指向第一个可分配物理页面地址
t_32 nr_p_pages = 0;        //物理内存所有的页数目

t_32 mem_size = 0;        //内存大小

//初始化分页系统, start是物理可用页面开始处,跳过内核保留的, end是结束处.
void page_init(t_32 start, t_32 end );       
void do_page_fault( t_32 err_code, const struct stack_frame *r );

t_32* kernel_pgt ;        //256个内核专用页表, 每个进程的内核部分都要映射到这里
t_32* pgd0;        //进程0的页目录


//为内核安装分页
void setup_paging()
{
  //获取物理内存大小
  t_32 i,j;
  
  //获取内存大小
  for(i=0; i<MEMBLOCK_NUM; i++ )
  {
          printk("mem_base: 0x%X    mem_len: 0x%X    type: 0x%X\n",
                  ards[i].base_low, ards[i].length_low, ards[i].type );
          mem_size+= ards[i].length_low;
  }

        // 内核专用内存页分配 1MB到8MB的空闲空间,这样内核就可以用vmalloc来申请空间了
        vmalloc_init( P2L(_MB(2)), P2L(_MB(8)), P2L(_MB(1)), P2L(_MB(2)) );
       
        // 初始化可分配物理内存, 以页为单位
        // page_init( 物理地址开始, 物理地址结束 );
        page_init( _MB(8), mem_size );
       
  mem_size /= _MB(1);// 以MB为单位
  
  printk("mem_size: %d MB\n", mem_size);

  //内核256*1024个页表项, 共享使用.
  kernel_pgt = (t_32*)vmalloc(256*PAGE_SIZE);        //内核0-1GB的页表, 占1MB

  memset( kernel_pgt, 0, 256*PAGE_SIZE );
  //初始化内核页面, 先把内核的前8MB映射到物理地址
  for(i=0; i< _MB(8)/PAGE_SIZE; i++)
  {
          kernel_pgt[i] = i*PAGE_SIZE | PG_U | PG_W | PG_P;
  }
  
  //申请一个内核进程的页目录的物理页面
  pgd0 = vmalloc(PAGE_SIZE);
  //printk("get_free_page(); 0x%X\n", pgd0);
        memset( pgd0, 0, PAGE_SIZE);
  current->pgd = (t_32)pgd0;        //内核进程的页目录地址
  //初始化进程0的页目录, pgd0为进程0的页目录
  //线性地址前1MB保留指向物理地址
  for(i=0; i<1; i++)
  {
          pgd0[i] = (L2P((t_32)kernel_pgt))+PAGE_SIZE*i | PG_U  | PG_W | PG_P;        //内核可读可写页
  }
  //768后256个页表指向内核页表 3-4GB
  for(i=0; i<256; i++)
  {
          pgd0[i+768] = (L2P((t_32)kernel_pgt))+PAGE_SIZE*i | PG_U  | PG_W | PG_P;        //内核可读可写页
  }
                       
        //设置页目录基址cr3的值,并且开启分页功能cr0的PG标志,跳转刷新预取指令。
        set_pdbr( (t_32)pgd0 );
        //安装页面异常处理
        isr_install_handler( 14, do_page_fault );
        __asm__("movl %cr0,%eax\n\t \
        orl $0x80000000,%eax\n\t \
        movl %eax,%cr0\n\t \
        jmp 1f\n\t \
        1:");
       
        printk("kernel paging is setuped successfully!\n");
}

//更新cr3
void set_pdbr(t_32 pd)
{
        pd = L2P(pd);
  asm("mov %0, %%eax"::"m"(pd));
  asm("mov %eax, %cr3");
}


//分页初始化
void page_init(t_32 start, t_32 end )
{
        t_32 size = end - start;
        nr_p_pages  = size / PAGE_SIZE;        //看分成多少个页
        p_first_page = (struct page_info*)start;
        p_pages = vmalloc(nr_p_pages*sizeof(struct page_info));
        //初始化
        memset( p_pages, 0, nr_p_pages*sizeof(struct page_info) );
}


//获取空闲的物理内存页
void* get_free_page()
{
        t_32 i;
        static t_32 skip = 0;
        void* page;
retry:
        for(i=skip; i<nr_p_pages; i++)
                if( p_pages[i].i_count == 0 )
                {
                        skip = i+1;
                        p_pages[i].i_count ++;
                        page = (void*)PAGE_ADDR(i);
                        return page;
                }
        if( skip>0 ){
                skip = 0;
                goto retry;
        }
        return NULL;
}

//调试:显示所有使用的物理内存页面信息
void dump_page()
{
        int i;
        printk("used page:\n");
        for(i=0; i<nr_p_pages; i++)
                if( p_pages[i].i_count )
                {
                        printk("%d ",i);
                }
        printk("\ndump_ok\n");
}

//处理页面异常
void do_page_fault( t_32 err_code, const struct stack_frame *r )
{
        t_32 m_addr;        //出错内存地址
        __asm__("movl %%cr2, %0" : "=r"( m_addr ) );
               
        if( !(err_code & PG_P) )
  //缺页处理
        {
                //由地址计算出哪个PDE, 取当前进程页目录中的PDE指针
                t_32 *dir = CURRENT_PAGE_DIR( m_addr );
                //printk("addr: 0x%X  dir&0x%X value:0x%X\n" , m_addr, dir, *dir );
                if(!(*dir))        //如果这个页目录项是空的,则创建
                {
                        /*
                         * 在内核空间申请一个页表空间 4KB
                         * 因为这里如果是用get_free_page()申请的物理内存返回的是物理地址,
                         * 无法直接访问物理内存,所以无法写入PTE.
                         * 因此用到内核空间.
                         */
                        *dir = L2P((t_32)vmalloc(PAGE_SIZE));       
                        memset( (void*)*dir, 0, PAGE_SIZE);
                        if(*dir==0)
                        {
                                panic("no enough memory.\n");
                        }
#if 0
                        //初始化整个目录, 意味着一次获取映射4MB内容
                        {
                                t_32 i;
                                t_32* pgt = (t_32*)*dir;
                                for(i=0; i<1024; i++ )
                                {
                                        pgt[i] = (t_32)get_free_page();
                                        if(pgt[i]==0)
                                        {
                                                panic("no enough memory.\n");
                                        }
                                        pgt[i] = pgt[i] | PG_W | PG_P | PG_U;
                                }
                        }
#endif
                        *dir = *dir | PG_W | PG_P | PG_U;
                        //printk("created dir! dir:0x%X  *dir:0x%X\n", dir, *dir);
                        return;
                }
                //由物理地址计算出哪个页表项               
                t_32 *table = CURRENT_PAGE_TABLE( m_addr );
                //printk("addr: 0x%X  table0x%X value:0x%X\n" , m_addr, table, *table );
                if(!(*table))
                {
                        *table = (t_32)get_free_page();
                        if(*table==0)
                        {
                                panic("no enough memory.\n");
                        }
                        *table = *table | PG_W | PG_P | PG_U;
                        //printk("create table! table:0x%X *table:0x%X\n", table, *table);
                }
                return;
        }
        panic("page protected! err_code:0x%X  eip:0x%X\n", err_code, r->eip);
}

posted on 2009-04-24 10:15  alon  阅读(1122)  评论(0编辑  收藏  举报

导航