/*
* linux/mm/memory.c
*
* (C) 1991 Linus Torvalds
*/
/*
* demand-loading started 01.12.91 - seems it is high on the list of
* things wanted, and it should be easy to implement. - Linus
*/
/*
* Ok, demand-loading was easy, shared pages a little bit tricker. Shared
* pages started 02.12.91, seems to work. - Linus.
*
* Tested sharing by executing about 30 /bin/sh: under the old kernel it
* would have taken more than the 6M I have free, but it worked well as
* far as I could see.
*
* Also corrected some "invalidate()"s - I wasn't doing enough of them.
*/
#include
#include
#include
#include
#include
volatile void do_exit(long code);
static inline volatile void oom(void)
{
printk("out of memory\n\r");
do_exit(SIGSEGV);
}
/* 刷新页变换高速缓冲,cr3指向页目录表的基地址
为了获得最大的地址转换效率,处理器把最近使用的页表数据存储在一个芯片内的缓存中。
只有当所要的地址转换信息没有在缓存中时,才有访问两级页表的必要。
*/
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))
/* 下面的宏定义要与head.s配合使用,改变他们的大小也一定要改变head.s中相关变量大小 */
#define LOW_MEM 0x100000 /* 内存低址(0-1m),供内核使用 */
#define PAGING_MEMORY (15*1024*1024) /* 主内存,15m,可进行页面映射 */
#define PAGING_PAGES (PAGING_MEMORY>>12) /* 页总数(3840) */
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) /* 将指定的线形地址转变为页标号 */
#define USED 100 /* 页面占用标志 */
/* 判断给定的线形地址是否位于当前进程的代码段中 */
#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \
current->start_code + current->end_code)
static long HIGH_MEMORY = 0;
/* 将from处的一页内容拷贝到to处,from,to都是物理地址 */
#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")
/* mem-map是内存映射字节图,如果某一页内存没被映射,则置相应下标为0,可以通过
MAP_NR(addr)来转换某一线形地址对应的下标 */
static unsigned char mem_map [ PAGING_PAGES ] = {0,};
/*
* Get physical address of first (actually last :-) free page, and mark it
* used. If no free pages left, return 0.
*/
/* 从物理内存中取一页空闲的内存
它是从最后的一页内存开始向前搜索的,这样可以提高搜索效率
*/
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");
__asm__("std ; repne ; scasb\n\t" /* %eax和%edi想比较,如果mem_map下标为0,则找到空的一页内存,没找到返回 */
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t" /* 将%edi+1处的内容置为1,因为%edi始终指向前一个字符,所以要加1,才表示刚才对应的地址 */
"sall $12,%%ecx\n\t" /* %ecx左移12位,变为在主存中的线形地址 */
"addl %2,%%ecx\n\t" /* 加上内存低址,为在实际内存中的地址 */
"movl %%ecx,%%edx\n\t" /* %edx保存空闲内存在实际内存中的地址 */
"movl $1024,%%ecx\n\t" /* 下面循环对这一页内存从后开始向前进行清0 */
"leal 4092(%%edx),%%edi\n\t" /* %edi指向一页内存的最后4个字节 */
"rep ; stosl\n\t" /* 将对应地址清0 */
"movl %%edx,%%eax\n" /* %edx指向页的开始地址,并有%eax返回给系统 */
"1:"
:"=a" (__res)
:"" (0),"i" (LOW_MEM),"c" (PAGING_PAGES), /* "i"由gcc分配,为内存低址.%ecx为主内存的页总数 */
"D" (mem_map+PAGING_PAGES-1) /* %edi被指向最后一页内存 */
:"di","cx","dx");
return __res;
}
/*
* Free a page of memory at physical address 'addr'. Used by
* 'free_page_tables()'
*/
/* 把物理地址addr处的内存释放掉,这个函数将会被free_page_tables()用到 */
void free_page(unsigned long addr)
{
if (addr < LOW_MEM) return; /* 如果要释放的是内核地址,则直接返回 */
if (addr >= HIGH_MEMORY) /* 超出主内存大小,内核出错死机 */
panic("trying to free nonexistent page");
addr -= LOW_MEM; /* 将addr转换成mem_map中的下标 */
addr >>= 12;
if (mem_map[addr]--) return; /* 如果mem_map不为空,减1即置为0后,返回 */
mem_map[addr]=0; /* 如果释放的本来就是空的内存,内核出错死机 */
panic("trying to free free page");
}
/*
* This function frees a continuos block of page tables, as needed
* by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
*/
/* 释放页表,这个函数要被exit()调用,目前只能处理4m的内存 */
int free_page_tables(unsigned long from,unsigned long size)
{
unsigned long *pg_table;
unsigned long * dir, nr;
if (from & 0x3fffff) /* 验证from是否在4m的边界处,不是则内核出错死机 */
panic("free_page_tables called with wrong alignment");
if (!from) /* 试图释放内核和高速缓冲的空间,内核出错死机 */
panic("Trying to free up swapper memory space");
/* 计算size所占的页目录项数,即页表数,一个页表可以管理4m(2^22)内存。加0x3fffff是为了求整 */
size = (size + 0x3fffff) >> 22;
/* 计算目录项的地址,目录表的地址是内存地址0开始的(见head.s)。*dir的值才为页表的开始地址 */
dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
/* 循环处理页表 */
for ( ; size-->0 ; dir++) {
if (!(1 & *dir)) /* 页表不存在的话,就继续 */
continue;
/* pg_table为页表开始的地址,与上0xfffff000,是屏蔽了12位的属性位 */
pg_table = (unsigned long *) (0xfffff000 & *dir);
/* 循环处理页表项对应的一页内存 */
for (nr=0 ; nr<1024 ; nr++) {
if (1 & *pg_table) /* 如果对应的一页内存存在的话,就将其释放掉 */
free_page(0xfffff000 & *pg_table); /* 取内存的物理地址,与上0xfffff000,是屏蔽了12位的属性位 */
*pg_table = 0; /* 释放掉内存后,将对应表项置0 */
pg_table++; /* 处理下一页表项 */
}
free_page(0xfffff000 & *dir); /* 释放掉页表 */
*dir = 0; /* 释放页表后,将对应项置0 */
}
invalidate(); /* 刷新页高速缓冲 */
return 0;
}
/*
* Well, here is one of the most complicated functions in mm. It
* copies a range of linerar addresses by copying only the pages.
* Let's hope this is bug-free, 'cause this one I don't want to debug :-)
*
* Note! We don't copy just any chunks of memory - addresses have to
* be divisible by 4Mb (one page-directory entry), as this makes the
* function easier. It's used only by fork anyway.
*
* NOTE 2!! When from==0 we are copying kernel space for the first
* fork(). Then we DONT want to copy a full page-directory entry, as
* that would lead to some serious memory waste - we just copy the
* first 160 pages - 640kB. Even that is more than we need, but it
* doesn't take any more memory - we don't copy-on-write in the low
* 1 Mb-range, so the pages can be shared with the kernel. Thus the
* special case for nr=xxxx.
*/
/* 从from处拷贝页表到to处,大小为size */
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
unsigned long * from_page_table;
unsigned long * to_page_table;
unsigned long this_page;
unsigned long * from_dir, * to_dir;
unsigned long nr;
if ((from&0x3fffff) || (to&0x3fffff)) /* 验证from是否在4m的边界处,不是则内核出错死机 */
panic("copy_page_tables called with wrong alignment");
/* 计算起始目录项的地址,目录表的地址是内存地址0开始的(见head.s)。*dir的值才为页表的开始地址 */
from_dir = (unsigned long *) ((from>>20) & 0xffc);
/* 计算目的目录项的地址 */
to_dir = (unsigned long *) ((to>>20) & 0xffc);
/* 计算size所占的页目录项数,即页表数,一个页表可以管理4m(2^22)内存。加0x3fffff是为了求整 */
size = ((unsigned) (size+0x3fffff)) >> 22;
/* 开始复制页表 */
for( ; size-->0 ; from_dir++,to_dir++) {
if (1 & *to_dir) /* 目的页表如果存在了,则内核出错死机 */
panic("copy_page_tables: already exist");
if (!(1 & *from_dir)) /* 起始页表不存在,则继续下一页表 */
continue;
/* 起始页表的地址,与上0xfffff000,是屏蔽了12位的属性位 */
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
/* 分配一页内存所放页表,分配出错则返回-1 */
if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */
/* 目的页表指向刚分配的一页内存,并设置属性为存在,可读写,用户权限 */
*to_dir = ((unsigned long) to_page_table) | 7;
/* 如果from从内核地址开始,只复制开头640k */
nr = (from==0)?0xA0:1024;
/* 开始复制页表项 */
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
this_page = *from_page_table; /* 将页表项的内容复制给临时变量this_page */
if (!(1 & this_page)) /* 如果页表项的内容为空,即没有映射指定的一页内存,则继续 */
continue;
this_page &= ~2; /* 将页面设为只读 */
*to_page_table = this_page; /* 目的页表项的值变为this_page */
/* 如果目的页表项的地址在主内存中,则要在相应的mem_map中置位 */
if (this_page > LOW_MEM) {
*from_page_table = this_page; /* 令源表项也为只读 */
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++;
}
}
}
invalidate(); /* 刷新页变换高速缓冲 */
return 0;
}
/*
* This function puts a page in memory at the wanted address.
* It returns the physical address of the page gotten, 0 if
* out of memory (either when trying to access page-table or
* page.)
*/
/* 将一页物理内存映射到指定的线性地址上 */
unsigned long put_page(unsigned long page,unsigned long address)
{
unsigned long tmp, *page_table;
/* NOTE !!! This uses the fact that _pg_dir=0 */
/* 这里的页目录表基地址为0 */
/* 如果物理内存地址在内核区或是超出主内存范围,则打印出错信息 */
if (page < LOW_MEM || page >= HIGH_MEMORY)
printk("Trying to put page %p at %p\n",page,address);
/* 如果物理地址在mem-map对应的标志为0的话,则发出警告 */
if (mem_map[(page-LOW_MEM)>>12] != 1)
printk("mem_map disagrees with %p at %p\n",page,address);
/* 取线性地址的页目录表项的地址,页目录表的地址为0 */
page_table = (unsigned long *) ((address>>20) & 0xffc);
if ((*page_table)&1) /* 如果页表存在的话,就把页表的实际地址赋值给page_talbe */
page_table = (unsigned long *) (0xfffff000 & *page_table);
else {
if (!(tmp=get_free_page())) /* 如果不存在的话,就重新申请一页内存 */
return 0;
*page_table = tmp|7; /* 将page_talbe指向新申请的一页内存,属性设置为存在,可读写,用户 */
page_table = (unsigned long *) tmp; /* 将page_table指向新申请的一页内存的基地址 */
}
page_table[(address>>12) & 0x3ff] = page | 7; /* 将page赋值给页表偏移(address>>12) & 0x3ff处 */
/* no need for invalidate */
/* 不需要刷新页高速缓存 */
return page;
}
/* 取消写保护的页面,*table_entry为页表项的地址 */
void un_wp_page(unsigned long * table_entry)
{
unsigned long old_page,new_page;
/* 将页表的地址复制给old_page */
old_page = 0xfffff000 & *table_entry;
/* 如果页表所在的地址在主内存中,并且这个页面没有被其他进程共享,则将页表设置为可写,然后返回 */
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
*table_entry |= 2;
invalidate(); /* 刷新页高速缓冲 */
return;
}
/* 如果页表被2个以上进程共享,则要申请一页内存 */
if (!(new_page=get_free_page()))
oom();
/* 将共享计数减1 */
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]--;
/* 将新页表置为存在,可读写,用户权限 */
*table_entry = new_page | 7;
invalidate(); /* 新页高速缓冲 */
copy_page(old_page,new_page); /* 将老页表的内容拷贝到新页表处 */
}
/*
* This routine handles present pages, when users try to write
* to a shared page. It is done by copying the page to a new address
* and decrementing the shared-page counter for the old page.
*
* If it's in code space we exit with a segment error.
*/
/* 写共享页面时,复制页面 */
void do_wp_page(unsigned long error_code,unsigned long address)
{
#if 0
/* we cannot do this yet: the estdio library writes to code space */
/* stupid, stupid. I really want the libc.a from GNU */
if (CODE_SPACE(address)) /* 如果线性地址在代码段中,则终止程序 */
do_exit(SIGSEGV);
#endif
/* 求页表项的地址 */
un_wp_page((unsigned long *)
(((address>>10) & 0xffc) + (0xfffff000 &
*((unsigned long *) ((address>>20) &0xffc)))));
}
/* 验证线性地址是否可写 */
void write_verify(unsigned long address)
{
unsigned long page;
/* 如果对应页表为空的话,直接返回 */
if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
return;
page &= 0xfffff000;
page += ((address>>10) & 0xffc);
/* 经过运算后page为页表项的内容,指向实际的一页物理地址 */
if ((3 & *(unsigned long *) page) == 1) /* 验证页面是否可写,不可写则执行un_wp_page,取消写保护 */
un_wp_page((unsigned long *) page);
return;
}
/* 取一页空闲内存并映射到指定的线性地址处 */
void get_empty_page(unsigned long address)
{
unsigned long tmp;
if (!(tmp=get_free_page()) || !put_page(tmp,address)) {
free_page(tmp); /* 0 is ok - ignored */
oom();
}
}
/*
* try_to_share() checks the page at address "address" in the task "p",
* to see if it exists, and if it is clean. If so, share it with the current
* task.
*
* NOTE! This assumes we have checked that p != current, and that they
* share the same executable.
*/
/* 尝试对当前指定地址处的页面进行共享处理,address是相对于进程起始地址 */
static int try_to_share(unsigned long address, struct task_struct * p)
{
unsigned long from;
unsigned long to;
unsigned long from_page;
unsigned long to_page;
unsigned long phys_addr;
from_page = to_page = ((address>>20) & 0xffc); /* address对应的页目录表项 */
from_page += ((p->start_code>>20) & 0xffc); /* p进程目录项 */
to_page += ((current->start_code>>20) & 0xffc); /* 当前进程目录项 */
/* is there a page-directory at from? */
from = *(unsigned long *) from_page; /* 页表是否存在 */
if (!(from & 1))
return 0;
from &= 0xfffff000; /* from为页表的地址 */
from_page = from + ((address>>10) & 0xffc); /* from-page为页表项的地址 */
phys_addr = *(unsigned long *) from_page; /* phys_addr为实际的一页物理内存地址 */
/* is the page clean and present? */
if ((phys_addr & 0x41) != 0x01) /* 判断这个页面是否存在,干净 */
return 0;
phys_addr &= 0xfffff000; /* 去除属性位,得到实际的一页物理内存 */
if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM) /* 物理内存在内核区域或是超出主内存范围,则返回 */
return 0;
to = *(unsigned long *) to_page; /* to表示当前页表 */
if (!(to & 1)) /* 如果不存在,则申请一页新内存 */
if (to = get_free_page())
*(unsigned long *) to_page = to | 7; /* 设置属性为存在,可读写,用户权限。并复制给to_page */
else
oom();
to &= 0xfffff000;
to_page = to + ((address>>10) & 0xffc); /* to_page 为页表项的地址 */
if (1 & *(unsigned long *) to_page) /* 如果页表项已经存在,内核出错死机 */
panic("try_to_share: to_page already exists");
/* share them: write-protect */
/* 将这个页面进行共享,并置写保护 */
*(unsigned long *) from_page &= ~2;
*(unsigned long *) to_page = *(unsigned long *) from_page;
invalidate(); /* 刷新页高速缓存 */
phys_addr -= LOW_MEM;
phys_addr >>= 12;
mem_map[phys_addr]++; /* 将共享的页表计数加1 */
return 1;
}
/*
* share_page() tries to find a process that could share a page with
* the current one. Address is the address of the wanted page relative
* to the current data space.
*
* We first check if it is at all feasible by checking executable->i_count.
* It should be >1 if there are other tasks sharing this inode.
*/
/* 在进程数组中查找共享同一可执行程序的2个进程 */
static int share_page(unsigned long address)
{
struct task_struct ** p;
/* 判断当前进程是否有对应的可执行文件 */
if (!current->executable)
return 0;
/* 判断是否有其他进程共享这个可执行文件 */
if (current->executable->i_count < 2)
return 0;
/* 从最后一个进程,向前搜索与当前进程共享这个可执行文件的进程 */
for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
if (!*p)
continue;
if (current == *p)
continue;
if ((*p)->executable != current->executable)
continue;
/* 调用try_to_share将进程p的address对应的内存与当前进程共享 */
if (try_to_share(address,*p))
return 1;
}
return 0;
}
void do_no_page(unsigned long error_code,unsigned long address)
{
int nr[4];
unsigned long tmp;
unsigned long page;
int block,i;
/* 去掉属性位,得到实际的地址 */
address &= 0xfffff000;
/* tmp为相对于当前进程地址的偏移 */
tmp = address - current->start_code;
/* 如果当前进程没有对应的可执行程序或是tmp超出当前进程的数据段,则要重新申请一页内存并映射到address处 */
if (!current->executable || tmp >= current->end_data) {
get_empty_page(address);
return;
}
/* 执行共享页面处理,成功则返回 */
if (share_page(tmp))
return;
/* 否则,申请一页新的内存 */
if (!(page = get_free_page()))
oom();
/* remember that 1 block is used for header */
block = 1 + tmp/BLOCK_SIZE;
for (i=0 ; i<4 ; block++,i++)
nr[i] = bmap(current->executable,block);
bread_page(page,current->executable->i_dev,nr);
i = tmp + 4096 - current->end_data;
tmp = page + 4096;
while (i-- > 0) {
tmp--;
*(char *)tmp = 0;
}
/* 将page指向的物理内存映射到address上 */
if (put_page(page,address))
return;
/* 出错,则释放刚申请的一页内存并结束程序 */
free_page(page);
oom();
}
/* 内存初始化 */
void mem_init(long start_mem, long end_mem)
{
int i;
HIGH_MEMORY = end_mem;
/* 先将所有内存都设为已用状态 */
for (i=0 ; i
/* 在将主内存标记为可用状态 */
i = MAP_NR(start_mem);
end_mem -= start_mem;
end_mem >>= 12;
while (end_mem-->0)
mem_map[i++]=0;
}
/* 内存调试函数 */
void calc_mem(void)
{
int i,j,k,free=0;
long * pg_tbl;
/* 查找内存有多少空页 */
for(i=0 ; i
printk("%d pages free (of %d)\n\r",free,PAGING_PAGES);
for(i=2 ; i<1024 ; i++) {
if (1&pg_dir[i]) {
pg_tbl=(long *) (0xfffff000 & pg_dir[i]);
for(j=k=0 ; j<1024 ; j++)
if (pg_tbl[j]&1)
k++;
printk("Pg-dir[%d] uses %d pages\n",i,k);
}
}
}
hoker 于 2009-08-12 12:26:32发表:
给个流程图 阿
寂寞男孩 于 2007-05-25 21:57:34发表:
志同道合的朋友们那,你们每天上论坛灌水的时候是不是都提心吊胆,怕被斑竹删帖子抠积分,自己辛辛苦苦赚来权限付诸东流?
如果是这样,你们难道不想建立自己论坛吗?
做自己论坛的斑竹,每天为自己的论坛掷帖,一切的一切都是自己的!!!
志同道合的朋友们,有志气有理想的你们,不要犹豫了,这不需要什么!
你们不用会C语言,不用会数据库,不用会网页制作!
为一需要的 只是你们的智慧和火热的心
来和我们一起建立自己的论坛吧
http://pic1.netsh.com/eden/bbs/838688/
来这里找我们
QQ576592106
邮箱:fengleibafang@sinz.com