内存分页管理的实验报告
作者: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);
}
转载于:https://www.cnblogs.com/alon/archive/2009/04/24/1442702.html