/*
 * mm.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * メモリー管理プログラム
 */


#include"types.h"
#include"config.h"
#include"lib.h"
#include"lock.h"
#include"interrupt.h"
#include"proc.h"
#include"mp.h"
#include"segment.h"
#include"errno.h"
#include"fs.h"
#include"device.h"
#include"mm.h"
/****************************/
extern void test_program();
/*****************************/


enum{
	PAGE_NUM=0x400,				/* Number of pages in a table or a directory */
	HEAP_NUM=11,				/* kmalloc用MEM構造体最大値 */

	INT_BIT=sizeof(int)*8,		/* Number of integer bit */

	PT_TYPE_PAGING=0x9b,		/* ページバックアップデバイスパーティション番号（仮） */
	EMPTY_PB_SIZE=PAGE_SIZE/sizeof(uint),
};


/*
 * 物理メモリーテーブル用link struct
 * 割り当て先のpage tableとprocessを保持する
 */
typedef struct PAGE_TBL_ADD{
	uint *page_table;
	PROC *proc;
	struct PAGE_TBL_ADD *next;
}PAGE_TBL_ADD;

/*
 * 物理メモリーテーブル構造体
 * Pageごと
 */
typedef struct PHYS_PAGE{
	ushort ref_count;				/* reference count */
	ushort heap_num;				/* Heap number */
	PAGE_TBL_ADD *page_tbl_add;		/* Link of page table address struct */
	struct PHYS_PAGE *next;
	struct PHYS_PAGE *prev;
}PHYS_PAGE;

typedef struct FREE{
	char* last;
	struct FREE *prev;
	struct FREE *next;
}FREE;

typedef struct{
	uint size;
	FREE *free;
	int gate;
}HEAP;


/* メモリーサイズ、setup.Sで代入 */
uint all_memory_size;

/*
 * メモリーテーブルリンク
 */
/* free_mem_top (kernel mem) → (user mem) free_mem_end */
static PHYS_PAGE free_mem_top={0,0,NULL,NULL,NULL};		/* 空き物理メモリーリンクトップ */
static PHYS_PAGE free_mem_end={0,0,NULL,NULL,NULL};		/* 空き物理メモリーリンクエンド */

/* used_mem_top (new kernel mem) → (old kernel mem) → (new user mem) → (old user mem) used_mem_end */
static PHYS_PAGE used_mem_top={0,0,NULL,NULL,NULL};		/* 使用中（横取可）物理メモリーリンクトップ */
static PHYS_PAGE used_mem_end={0,0,NULL,NULL,NULL};		/* 使用中（横取可）物理メモリーリンクエンド */
static PHYS_PAGE *used_usrmem_new=NULL;					/* 使用中のユーザー空間物理メモリー */

static int page_alloc_gate=0;							/* PHYS_PAGE lock gate */

/* kfree用番兵 */
static FREE guard[HEAP_NUM];

/* kmalloc用構造体 */
static HEAP heap[]={
	{0,NULL,0},
	{0x8,   &guard[1], 0},
	{0x10,  &guard[2], 0},
	{0x20,  &guard[3], 0},
	{0x40,  &guard[4], 0},
	{0x80,  &guard[5], 0},
	{0x100, &guard[6], 0},
	{0x200, &guard[7], 0},
	{0x400, &guard[8], 0},
	{0x800, &guard[9], 0},
	{0x1000,&guard[10],0}
};


/*
 * Physical addressからPHYS_PAGE addressを返す
 * parameters : Physical address
 * return : PHYS_PAGE address
 */
extern inline PHYS_PAGE *get_physpage_from_addr(void *addr)
{
	return (PHYS_PAGE*)((uint)addr/PAGE_SIZE*sizeof(PHYS_PAGE)+PHYSICAL_PAGE_TABLE_ADD);
}


/*
 * PHYS_PAGE addressからPhysical addresを返す
 * parameters : PHYS_PAGE address
 * return : Physical address
 */
extern inline void *get_addr_from_physpage(PHYS_PAGE *memtable)
{
	return (void*)(((uint)memtable-PHYSICAL_PAGE_TABLE_ADD)/sizeof(PHYS_PAGE)*PAGE_SIZE);
}


/*
 * 論理アドレスからページテーブルの該当ページアドレスを取得する。
 * parameters : 論理アドレス
 * return : 該当ページアドレス
 */
extern inline uint *get_page_addr(uint addr)
{
	uint *page_dir;
	uint *page_tbl;


	page_dir=get_current_task()->pagedir;
	page_tbl=(uint*)(page_dir[addr>>22]&0xfffff000);
	return &page_tbl[(addr>>12)&0x3ff];
}


/*
 * Page tableのread write flagをクリアーする
 * parameters : Page table address
 */
extern inline void clear_rwflag(uint *page)
{
	int i;


	for(i=0;i<PAGE_SIZE/sizeof(uint);++i)page[i]&=0xfffffffd;
}


/***********************************************************************************
 *
 * 初期化関数
 *
 ***********************************************************************************/

static uint find_all_memsize();
static void init_physical_page_table();
static int mount_page_backup_dev(const char*);


/*
 * メモリーシステムの初期化
 */
void init_mm()
{
	/* Find size of physical memory  */
	printk("Memory size = %d MB\n",find_all_memsize()/0x100000);

	/* 物理メモリーテーブルの設定 */
	init_physical_page_table();

	/* ページング用記憶領域の初期化 */
/*	int mount_page_backup_dev();*/
}


/***********************************************************************************
 *
 * メモリー管理関数
 *
 ***********************************************************************************/

static int buckup_page(void*);


/*
 * Find size of all physical memory
 */
uint find_all_memsize()
{
	enum{EXIST_MEM_SIZE=0x800000};		/* メモリー測定単位 */

	uint volatile *point;


	/* メモリーを単位ごとに測定する*/
	for(point=(uint*)EXIST_MEM_SIZE-1;;point+=EXIST_MEM_SIZE/sizeof(uint))
	{
		*point=0x5a5a5a5a;
		if(*point!=0x5a5a5a5a)break;
		else
		{
			*point=0xf0f0f0f0;
			if(*point!=0xf0f0f0f0)break;
		}
	}

	return (all_memory_size=(uint)point-EXIST_MEM_SIZE+sizeof(uint));
}


/*
 * 物理メモリーテーブルを初期化する。
 */
void init_physical_page_table()
{
	int i,last;
	PHYS_PAGE *p=(PHYS_PAGE*)PHYSICAL_PAGE_TABLE_ADD;


	/* 最初の1Mbyte分メモリーを割り当てる（0初期化）。 */
	memset(p,0,0x100000/PAGE_SIZE*sizeof(PHYS_PAGE));

	/* 残りをフリーに設定する。 */
	for(i=0x100000/PAGE_SIZE,last=all_memory_size/PAGE_SIZE;i<last;++i)
	{
		p[i].ref_count=0;
		p[i].heap_num=0;
		p[i].page_tbl_add=NULL;
		p[i].next=&p[i+1];
		p[i].prev=&p[i-1];
	}
	free_mem_top.next=&p[0x100000/PAGE_SIZE];
	free_mem_end.prev=&p[i-1];
	p[0x100000/PAGE_SIZE].prev=&free_mem_top;
	p[i-1].next=&free_mem_end;

	/* メモリーテーブル分メモリーを割り当てる（横取不可）。 */
	last=ROUNDUP(PHYSICAL_PAGE_TABLE_ADD+all_memory_size/PAGE_SIZE*sizeof(PHYS_PAGE),PAGE_SIZE)/PAGE_SIZE;
	if(&p[PHYSICAL_PAGE_TABLE_ADD/PAGE_SIZE]==free_mem_top.next)
		free_mem_top.next=&p[last];
	else p[PHYSICAL_PAGE_TABLE_ADD/PAGE_SIZE-1].next=&p[last];

	/* Used linkを初期化する。 */
	used_mem_top.next=&used_mem_end;
	used_mem_end.prev=&used_mem_top;
	used_usrmem_new=&used_mem_end;
}


/*
 * 物理メモリーページの参照カウントを増やす。
 * parameters : 物理アドレス
 */
void link_page(void *add)
{
	PHYS_PAGE *p;


	p=get_physpage_from_addr(add);

	enter_spinlock(&page_alloc_gate);
	{
		if(++p->ref_count==2)
		{
			/* Used linkから削除する */
			p->prev->next=p->next;
			p->next->prev=p->prev;
			if(used_usrmem_new==p)used_usrmem_new=p->next;
		}
	}
	exit_spinlock(&page_alloc_gate);
}


/*
 * 参照カウントを減らし、物理メモリーページを開放する。
 * parameters : 物理アドレス
 */
void unlink_page(void *add)
{
	PROC *proc;
	PHYS_PAGE *p;
	PAGE_TBL_ADD *q=NULL,**r;


	p=get_physpage_from_addr(add);

	enter_spinlock(&page_alloc_gate);
	{
		switch(--p->ref_count)
		{
			case -1:			/* 既に使われていない、または割り当て不可のページ */
				p->ref_count=0;
				break;
			case 0:
				/* Used linkから削除する */
				p->prev->next=p->next;
				p->next->prev=p->prev;
				if(used_usrmem_new==p)used_usrmem_new=p->next;

				if(add<(void*)KERNEL_DATA_END)
				{
					p->next=free_mem_top.next;
					p->prev=&free_mem_top;
					free_mem_top.next=p;
					p->next->prev=p;
				}
				else
				{
					p->prev=free_mem_end.prev;
					p->next=&free_mem_end;
					free_mem_end.prev=p;
					p->prev->next=p;
				}

				p->heap_num=0;
				q=p->page_tbl_add;
				p->page_tbl_add=NULL;

				break;
			case 1:
				/* used linkに加える。 */
				if(add<(void*)KERNEL_DATA_END)
				{
					p->prev=&used_mem_top;
					p->next=used_mem_top.next;
					used_mem_top.next=p;
					p->next->prev=p;
				}
				else
				{
					p->prev=used_usrmem_new->prev;
					p->next=used_usrmem_new;
					used_usrmem_new->prev=p;
					p->prev->next=p;
					used_usrmem_new=p;
				}
			default:
				/* Page table address linkから削除する */
				proc=cputask[get_current_cpu()].current_task;
				for(q=p->page_tbl_add,r=&p->page_tbl_add;q!=NULL;r=&q->next,q=q->next)
					if(q->proc==proc)
					{
						*r=q->next;
						break;
					}
		}
	}
	exit_spinlock(&page_alloc_gate);

	kfree(q);
}


/*
 * カーネル空間へ物理ページを割り当てる。
 * 割り当てたページは横取り不可
 * return : Allocate addres or NULL
 */
void *alloc_kernel_page()
{
	void *address=NULL;
	bool used=0;
	PHYS_PAGE *p;


	enter_spinlock(&page_alloc_gate);
	{
		p=free_mem_top.next;
		if((p!=&free_mem_end)&((address=get_addr_from_physpage(p))<(void*)KERNEL_DATA_END))
		{
			p->ref_count=1;
			free_mem_top.next=p->next;
			p->next->prev=&free_mem_top;
		}
		else if(((p=used_mem_top.next)!=&used_mem_end)&(used_usrmem_new!=p))
		{
			address=get_addr_from_physpage(p);
			used_mem_top.next=p->next;
			p->next->prev=&used_mem_top;
			used=1;
		}
	}
	exit_spinlock(&page_alloc_gate);

	/* メモリーの内容をバックアップする。 */
	if(used&&(buckup_page(address)==-1))return NULL;

	return address;
}


/*
 * ユーザー空間へ物理ページを割り当てる。
 * 割り当てたページは横取り可
 * parameters : ページテーブルアドレス
 * return : Allocate addres or error=NULL
 */
void *alloc_user_page(uint *page_tbl)
{
	void *address=NULL;
	bool used=0;
	PHYS_PAGE *p;
	PAGE_TBL_ADD *ptadd;


	enter_spinlock(&page_alloc_gate);
	{
		if((p=free_mem_end.prev)!=&free_mem_top)
		{
			address=(void*)get_addr_from_physpage(p);
			p->ref_count=1;
			free_mem_end.prev=p->prev;
			p->prev->next=&free_mem_end;

		}
		else if((p=used_mem_end.prev)!=&used_mem_top)
		{
			address=(void*)get_addr_from_physpage(p);
			used_mem_end.prev=p->prev;
			p->prev->next=&used_mem_end;
			used=1;
			if(used_usrmem_new==p)used_usrmem_new=&used_mem_end;
		}
		else
		{
			exit_spinlock(&page_alloc_gate);
			return NULL;
		}

		/* used linkに加える。 */
		if(address<(void*)KERNEL_DATA_END)
		{
			p->prev=&used_mem_top;
			p->next=used_mem_top.next;
			used_mem_top.next=p;
			p->next->prev=p;
		}
		else
		{
			p->prev=used_usrmem_new->prev;
			p->next=used_usrmem_new;
			used_usrmem_new->prev=p;
			p->prev->next=p;
			used_usrmem_new=p;
		}
	}
	exit_spinlock(&page_alloc_gate);

	/* メモリーの内容をback up */
	if(used&&(buckup_page(address)==-1))return NULL;

	/* ページテーブルアドレスリンクに加える */
	if((ptadd=kmalloc(sizeof(PAGE_TBL_ADD)))==NULL)
	{
		unlink_page(address);
		return NULL;
	}
	ptadd->page_table=page_tbl;
	ptadd->proc=get_current_task();
	ptadd->next=p->page_tbl_add;
	p->page_tbl_add=ptadd;

	return address;
}


/*
 * 2の冪乗サイズごとにページサイズメモリーを割り当て分割する
 * 最大割当サイズはページサイズ
 * parameters : Size by byte
 * return : address
 */
void *kmalloc(size_t size)
{
	char *addr=NULL;
	int i;


	if((size==0)||(size>PAGE_SIZE))return NULL;		/* Require size is over */

	for(i=1;size>heap[i].size;++i);					/* Require size=heap[i].size */

	enter_spinlock(&heap[i].gate);
	{
		if(heap[i].free==&guard[i])
		{
			if((heap[i].free=(FREE*)alloc_kernel_page())==NULL)goto OUT;

			/*
			 * 開放するとき、addressから割当サイズがわかるようにする
			 */
			get_physpage_from_addr((void*)heap[i].free)->heap_num=i;

			heap[i].free->prev=&guard[i];
			heap[i].free->next=&guard[i];
			guard[i].prev=heap[i].free;
			guard[i].next=heap[i].free;
			heap[i].free->last=(char*)heap[i].free+PAGE_SIZE;
		}

		addr=(char*)heap[i].free->last-heap[i].size;

		if(heap[i].free==(FREE*)addr)
		{
			heap[i].free=((FREE*)addr)->next;
			((FREE*)addr)->prev->next=((FREE*)addr)->next;
			((FREE*)addr)->next->prev=((FREE*)addr)->prev;
		}
		else heap[i].free->last=addr;
	}
OUT:
	exit_spinlock(&heap[i].gate);

	return addr;
}


/*
 * メモリー開放。
 * parameters : Address
 */
void kfree(void *ptr)
{
	uint i;
	FREE *p,*end;


	if(ptr==NULL)return;

	if((i=(int)get_physpage_from_addr(ptr)->heap_num)==0)return;

	enter_spinlock(&heap[i].gate);
	{
		/* Free linkは昇順 */
		for(p=heap[i].free;p!=&guard[i];p=p->next)
			if(p>(FREE*)ptr)break;

		/* Linkに挿入する */
		p->prev->next=(FREE*)ptr;
		((FREE*)ptr)->prev=p->prev;
		((FREE*)ptr)->next=p;
		((FREE*)ptr)->last=(char*)ptr+heap[i].size;
		p->prev=(FREE*)ptr;

		/* アドレスが続いていればメモリーをつなげる */
		end=((FREE*)ptr)->prev;
		do
		{
			p=p->prev;

			if(p->last==(char*)p->next)
			{
				p->last=p->next->last;
				p->next=p->next->next;
			}

			/* 開放したメモリーがPAGE_SIZEならページを開放する */
			if(!((uint)p&PAGE_BOUND)&&(p->last>=(char*)p+PAGE_SIZE))
			{
				if(p->last>(char*)p+PAGE_SIZE)
				{
					p->prev->next=(FREE*)((char*)p+PAGE_SIZE);
					p->prev->next->next=p->next;
					p->prev->next->prev=p->prev;
					p->prev->next->last=p->last;
					p->next->prev=p->prev->next;
				}
				else
				{
					p->prev->next=p->next;
					p->next->prev=p->prev;
				}
				unlink_page(p);
				break;
			}

		}while(p!=end);

		/* Free memory link の先頭を代入する */
		heap[i].free=guard[i].next;
	}
	exit_spinlock(&heap[i].gate);
}


/***********************************************************************************
 *
 * ページング関数
 *
 * ページアウトの条件
 * ・参照カウントが１であること
 *
 ***********************************************************************************/

typedef struct{
	int next;
	int gate;
	uint index[EMPTY_PB_SIZE];
}PAGE_BACKUP;

/* ページバックアップ空き領域インデックス。 */
static PAGE_BACKUP page_backup={0,0};
static int pb_din;
static int pb_secters;


/*
 * カーネルページテーブルを設定する
 * return : Page directory or NULL
 */
uint *init_idle_page()
{
	void *stack;
	uint *idle_pagedir,*pagetable;
	uint i;



	/* カーネルページディレクトリの設定 */
	if((idle_pagedir=(uint*)alloc_kernel_page())==NULL)return NULL;
	memset(idle_pagedir,0,PAGE_SIZE);
	for(i=0;i<KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE;++i)
		idle_pagedir[i]=i*PAGEDIR_ALLOC_SIZE|PAGE_PRESEN|PAGE_RW|PAGE_4M;

	/* カーネルスタックページを割り当てる */
	if((pagetable=(uint*)alloc_kernel_page())==NULL)return NULL;
	memset(pagetable,0,PAGE_SIZE);
	idle_pagedir[KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE]=(uint)pagetable|PAGE_PRESEN|PAGE_RW;
	if((stack=alloc_kernel_page())==NULL)return NULL;
	pagetable[1023]=(uint)stack|PAGE_PRESEN|PAGE_RW;

	/* メモリーマップドIOページ用ディレクトリの設定 */
	for(i=IOMAP_BEG/PAGEDIR_ALLOC_SIZE;i<1024;++i)
	   	idle_pagedir[i]=i*PAGEDIR_ALLOC_SIZE|PAGE_PRESEN|PAGE_RW|PAGE_4M|PAGE_CASH_DISABLE;

	/* ページング開始 */
	asm volatile(
			"movl	%%eax,%%cr3\n"			/* Set page directory address */
			"movl	$0x10,%%eax\n"
			"movl	%%eax,%%cr4\n"			/* 4Mページ拡張 */
			"movl	%%cr0,%%eax\n"
			"orl	$0x80010000,%%eax\n"	/* Enable paging,Enable wp(スーパーバイザからの書き込み保護) */
			"movl	%%eax,%%cr0\n"
			"jmp	1f\n"
		"1:"
		::"a"(idle_pagedir)
	);

	return idle_pagedir;
}


/*
 * ページバックアップ用デバイスのマウント。
 * parameters : device path
 * return : 0 or error=-1
 */
int mount_page_backup_dev(const char *dev_path)
{
	int fd;
	int din;
	int secters;
	DEV_STAT dev_stat;
	uint i,j,last,index;


	/* Device open */
	if((fd=sys_open(dev_path,0))==-1)return -1;
	if((din=get_inode(fd))==-1)return -1;
	pb_din=din;

	/* パーティションタイプの確認。 */
	if(get_dev_stat(din,&dev_stat)==-1)return -1;
	if(dev_stat.prt_type!=PT_TYPE_PAGING)return -1;

	/* 空きページバックアップの設定。 */
	pb_secters=secters=PAGE_SIZE/dev_stat.sect_size;
	last=dev_stat.all_sect-EMPTY_PB_SIZE;
	index=secters*(EMPTY_PB_SIZE-1);
	j=0;
	for(i=ROUNDUP(index+secters,2);i<last;i+=secters)
	{
		page_backup.index[j++]=i;

		if(j>=EMPTY_PB_SIZE)
		{
			j=0;
			if(write_direct(din,page_backup.index,secters,index)!=secters)return -1;
			index=i;

			/* 進行状況表示。 */
			printk("Mount page backup device %d%%\r",i/(last/100));
		}
	}
	page_backup.index[j]=0;
	if(write_direct(din,page_backup.index,secters,index)!=secters)return -1;

	/* 進行状況表示。 */
	printk("Mount page backup device 100%%\n");

	/* 最初のページバックアップインデックスの設定。 */
	i=0;
	for(j=0;j<EMPTY_PB_SIZE;++j)
	{
		if(i>=last)
		{
			page_backup.index[j]=0;
			break;
		}
		page_backup.index[j]=i;
		i+=secters;
	}

	return 0;
}


/*
 * 空きページバックアップを取得する。
 * return : ブロック or error=-1
 */
int get_empty_bp()
{
	int blk;


	enter_spinlock(&page_backup.gate);
	{
		if((blk=page_backup.index[page_backup.next])==0)return -1;		/* 空きブロックなし。 */

		if(page_backup.next++==EMPTY_PB_SIZE)
		{
			if(read_cache(pb_din,page_backup.index,pb_secters,blk)!=pb_secters)return -1;
			page_backup.next=0;
		}
	}
	exit_spinlock(&page_backup.gate);

	return blk;
}


/*
 * 空きページバックアップを追加する。
 * parameters : 空きページバックアップブロック
 * return : 0 or error=-1
 */
int add_empty_bp(int blk)
{
	enter_spinlock(&page_backup.gate);
	{
		if(--page_backup.next<0)
		{
			if(write_cache(pb_din,page_backup.index,pb_secters,blk)!=pb_secters)return -1;
			page_backup.next=EMPTY_PB_SIZE;
		}

		page_backup.index[page_backup.next]=blk;
	}
	exit_spinlock(&page_backup.gate);

	return 0;
}


/*
 * Buck up used page into page store device
 * parameters : physical address
 * returns : 0 or Error number
 */
int buckup_page(void *addr)
{
	PHYS_PAGE *phys_page;
	PAGE_TBL_ADD *p;
	int cpu;
	int blk;


	phys_page=get_physpage_from_addr(addr);
	cpu=get_current_cpu();
	p=phys_page->page_tbl_add;

	/* page tableを不在に設定 */
	*p->page_table&=~PAGE_PRESEN;

	/* processが実行中ならpage tableをTBLからフラッシュする */
	if((p->proc->cpu!=cpu)&&(cputask[p->proc->cpu].current_task==p->proc))
		issue_interrupt_to_cpu(p->proc->cpu,FLASH_PAGEDIR_VECT);

	/* Store page */
	if((blk=get_empty_bp())==0)goto ERR;
	if(write_cache(pb_din,addr,pb_secters,blk)!=pb_secters)goto ERR;
	*p->page_table=blk;

	return 0;

ERR:
	*p->page_table|=PAGE_PRESEN;

	return -1;
}


/*
 * ページバックアップデバイスからメモリーに書き戻す。
 * parmeters : memory buffer,device block
 * return : 0 or error=-1
 */
int restore_page(void *buf,uint blk)
{
	/* デバイスから読み込み。 */
	if(read_cache(pb_din,buf,pb_secters,blk)!=pb_secters)return -1;

	/* デバイスの領域を開放する。 */
	if(add_empty_bp(blk)==-1)return -1;

	return 0;
}


/******************************** 未修正 ***********************************************
 *
 * Set pagetable
 * parameters : Load page address,Sorce page address,Page flag,Load process
 * return : 0 or Error number
 *
int load_user_page(void *dist,int flag,PROC *proc)
{
	void *page;
	uint *pagetable;


	enter_spinlock(&proc->pagedir_gate);
	{
		pagetable=(uint*)proc->pagedir[(uint)dist/PAGEDIR_ALLOC_SIZE];

	    if(pagetable==0)
	    {
	    	***page tableを割り当てる
	    	if((pagetable=(uint*)alloc_kernel_page())==NULL)goto ERROR;
	    	memset(pagetable,0,PAGE_SIZE);
	    	proc->pagedir[(uint)dist/PAGEDIR_ALLOC_SIZE]=(uint)pagetable|PAGE_PRESEN|PAGE_USER|PAGE_RW;
	    }
	    else pagetable=(uint*)((uint)pagetable&PAGE_ROUNDDOWN);

		****pageを割り当てる
		if((page=alloc_user_page(&pagetable[((uint)dist&(PAGEDIR_ALLOC_SIZE-1))/PAGE_SIZE]))==NULL)goto ERROR;
		pagetable[((uint)dist&(PAGEDIR_ALLOC_SIZE-1))/PAGE_SIZE]=(uint)page|PAGE_PRESEN|PAGE_USER|flag;
	}
	exit_spinlock(&proc->pagedir_gate);

	return 0;

ERROR:
	exit_spinlock(&proc->pagedir_gate);

	return -ENOMEM;
}


 *
 * page tableを開放する
 * text sectionとstack以外のdata sectionを開放する。user stackは1pageだけ残して開放する。
 * parameters : process
 *
void free_user_page(PROC *proc)
{
	uint *page;
	int i,j;


	enter_spinlock(&proc->pagedir_gate);
	{
		***user stackの最初のpageだけ残して開放する
		page=(uint*)(proc->pagedir[USER_STACK_BEG/PAGEDIR_ALLOC_SIZE]&PAGE_ROUNDDOWN);
	    for(j=0;j<PAGE_SIZE/sizeof(uint)-1;++j)
	    {
	    	if(page[j]==0)continue;
	    	else if(page[j]&PAGE_PRESEN)
	    		unlink_page((void*)page[j]);
			else
			{
				if(--page_store_ref_table[page[j]>>1]==0)
					clear_page_store_use(page[j]>>1);
			}

			page[j]=0;
		}

		***残りのuser pageをすべて開放する
		for(i=USER_BEG/PAGEDIR_ALLOC_SIZE;i<USER_STACK_BEG/PAGEDIR_ALLOC_SIZE;++i)
	    	if(proc->pagedir[i]&PAGE_PRESEN)
	    	{
	    		page=(uint*)(proc->pagedir[i]&PAGE_ROUNDDOWN);
	    		for(j=0;j<PAGE_SIZE/sizeof(uint);++j)
	    		{
	    			if(page[j]==0)continue;
	    			else if(page[j]&PAGE_PRESEN)unlink_page((void*)page[j]);
					else
					{
						if(--page_store_ref_table[page[j]>>1]==0)
							clear_page_store_use(page[j]>>1);
					}
				}

				***page tableを開放する
				unlink_page(page);

				***page direstoryを0クリアー
				proc->pagedir[i]=0;
			}
	}
	exit_spinlock(&proc->pagedir_gate);
}
*****************************************************************************************************/

/*
 * ページフォルト例外
 * parameters : page fault address,Exception frame
 */
void page_fault(uint fault_addr,EXCEPT_FRAME frame)
{
	/* エラーコードビットマスク。 */
	enum{
		PRESEN=1,
		RW_MODE=2,
		USER_MODE=4,
		RSVBIT=8,
	};

	void *alloc_page;
	uint *current_page;


	/* 特権ページアクセス違反。 */
	if(((fault_addr<KERNEL_END)||(fault_addr>IOMAP_BEG))&&(frame.error&USER_MODE))
	{
		printk("Page privilege violation!\n");

		/* プロセスクローズ。 */
/************************************************/
		cli();
		idle();
/************************************************/
		return;
	}

	current_page=get_page_addr(fault_addr);

	/* ページ不在。 */
	if((frame.error&PRESEN)==0);
	{
		/* 物理ページの割当て。 */
		if((alloc_page=alloc_user_page(current_page))==NULL)
		{
			printk("Fail alloc physical page!\n ");

			/* プロセスクローズ。 */
/************************************************/
			cli();
			idle();
/************************************************/
		}

		/* バックアップあり。 */
		if(*current_page!=0)
			if(restore_page(alloc_page,*current_page)==-1)
			{
				printk("Fail restore page!\n ");

				/* プロセスクローズ。 */
/************************************************/
				cli();
				idle();
/************************************************/
			}

		*current_page|=(uint)alloc_page|PAGE_PRESEN|PAGE_RW|PAGE_USER;

		return;
	}

	/* コピーオンライト処理。 */
	if((frame.error&RSVBIT)!=RSVBIT)
	{
		/* 物理ページの割当て。 */
		if((alloc_page=alloc_user_page(current_page))==NULL)
		{
			printk("Fail alloc physical page!\n ");

			/* プロセスクローズ。 */
/************************************************/
			cli();
			idle();
/************************************************/
		}

		/* ページ内容を割り当てたページにコピーする。 */
		memcpy(alloc_page,(void*)(*current_page&~0xfff),PAGE_SIZE);

		*current_page|=(uint)alloc_page|PAGE_PRESEN|PAGE_RW|PAGE_USER;

		return;
	}

	/* 原因不明または回復不能のエラー。 */
	printk("Can not recover page fault!");

	/* プロセスクローズ。 */
/************************************************/
	cli();
	idle();
/************************************************/
}


/***********************************************************************************
 *
 * システムコール用関数
 *
 ***********************************************************************************/


/*
 * forkによりメモリーコンテキストをコピーする
 * page directory、page tableのコピー、子プロセススタックポインタのアドレス
 * parameters : Destination page directory,Return stack offset
 * return : New Page directory or NULL
 */
uint *fork_page(PROC *org,PROC *proc,void **stack)
{
	uint *dir,*org_page,*page;
	int i,j;


	/* Page directoryをコピーする */
	if((dir=(uint*)alloc_kernel_page())==NULL)return NULL;
	memcpy(dir,org->pagedir,PAGE_SIZE);

	/* Kernel stackを割り当てる */
	if((page=(uint*)alloc_kernel_page())==NULL)goto ERROR1;

	dir[KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE]=(uint)page|PAGE_PRESEN|PAGE_RW;
	org_page=(uint*)(org->pagedir[KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE]&PAGE_ROUNDDOWN);
	if((*stack=alloc_kernel_page())==NULL)goto ERROR2;
	memcpy(*stack,(void*)(org_page[1023]&PAGE_ROUNDDOWN),PAGE_SIZE);
	page[1023]=(uint)*stack|PAGE_PRESEN|PAGE_RW;
	memset(page,0,1023*sizeof(uint));

	/* User領域をコピーオンライトに設定する */
	for(i=USER_BEG/PAGEDIR_ALLOC_SIZE;i<USER_END/PAGEDIR_ALLOC_SIZE;++i)
		if(dir[i]!=0)
		{
			org_page=(uint*)(dir[i]&PAGE_ROUNDDOWN);
			enter_spinlock(&org->pagedir_gate);
			{
				clear_rwflag(org_page);
			}
			exit_spinlock(&org->pagedir_gate);

			for(j=0;j<PAGE_NUM;++j)
			{
				if(org_page[j]==0)break;
				if((org_page[j]&PAGE_PRESEN)==0)
					/* ページを割り当てる。 */

				link_page((void*)org_page[j]);
			}
		}

	return dir;


ERROR2:
	unlink_page((void*)page[1023]);
	unlink_page(page);
ERROR1:
	unlink_page(dir);

	return NULL;
}
