/*
* 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 <signal.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <linux/kernel.h>
volatile void do_exit(long code);//程序退出处理函数
static inline volatile void oom(void)//显示内存已用完,出错信息
{
printk("out of memory\n\r");
do_exit(SIGSEGV);
}
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))//刷新页变换高速缓冲区宏函数
/* these are not to be changed without changing head.s etc */
#define LOW_MEM 0x100000//内存低端1M
#define PAGING_MEMORY (15*1024*1024)//分页内存15M,主内存区最多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;//全局变量,物理内存最高端地址
#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")//复制1页
static unsigned char mem_map [ PAGING_PAGES ] = {0,};//物理内存映射字节图(1个字节代表1页内存)
/*
* 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)//获取首个(实际上是最后一个空闲页面),并标为已用,如果没有空闲就返回0
{//从未图末端开始向前扫描所有页面标志,若有空闲页面(内存位图字节为0)
register unsigned long __res asm("ax");
__asm__("std ; repne ; scasb\n\t"
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t"
"sall $12,%%ecx\n\t"
"addl %2,%%ecx\n\t"
"movl %%ecx,%%edx\n\t"
"movl $1024,%%ecx\n\t"
"leal 4092(%%edx),%%edi\n\t"
"rep ; stosl\n\t"
"movl %%edx,%%eax\n"
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1)
:"di","cx","dx");
return __res;
}
/*
* Free a page of memory at physical address 'addr'. Used by
* 'free_page_tables()'
*/
void free_page(unsigned long addr)//释放物理地址addr开始的1页面内存
{
if (addr < LOW_MEM) return;//小于内存低端1M时,表示在内核程序和高速缓冲区内,不理采
if (addr >= HIGH_MEMORY)//大于物理内存最高端地址,则内核停止工作
panic("trying to free nonexistent page");
addr -= LOW_MEM;
addr >>= 12;//页面号addr=(addr-LOW_MEM)/4096,此地址从内存低端开始的页面号
if (mem_map[addr]--) return;//物理内存映射字节图,映射字节不等于0,则减1返回,此是该映射字节值应该为0,表示页面已释放
mem_map[addr]=0;//如果该映射字节本来就是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.
*/
int free_page_tables(unsigned long from,unsigned long size)//释放内存页表指定的内存块并置表项空闲
{//from:起始线性基地址,size释放的长度
unsigned long *pg_table;
unsigned long * dir, nr;
if (from & 0x3fffff)//是不是在4M的边界外,一个页目录项对应于一个页表(1024个页表项),每个页表项对应于一个内存页,一个内存页等于4K,所以一个页目录项可以达到4M物理内存
panic("free_page_tables called with wrong alignment");
if (!from)//from==0,则出错
panic("Trying to free up swapper memory space");
size = (size + 0x3fffff) >> 22;//计算size长度所占有的页目录项数,也就是所占页表数
dir = (unsigned long *) ((from>>20) & 0xffc);//对应的起始目录项, /* _pg_dir = 0 */
for ( ; size-->0 ; dir++) {
if (!(1 & *dir))
continue;
pg_table = (unsigned long *) (0xfffff000 & *dir);//取页目录项的值,也就是对应页表 的地址
for (nr=0 ; nr<1024 ; nr++) {//对这个页表进行处理(有1024个页表项)
if (1 & *pg_table)//pg_table是数组的头指针,*pg_table 是数组中某个元素中所存的值,这里就指向具体的物理内存地址
free_page(0xfffff000 & *pg_table);//释放每一个页表项所占用 的内存,,释放物理地址开始的1页面内存
*pg_table = 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.
*/
int copy_page_tables(unsigned long from,unsigned long to,long size)//复制页目录表中的页目录项和页表中的页表项
{//from、to都为线性地址,,复制前后则共享所指向的物理内存
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))//源、目都 要在4M的边界地址上
panic("copy_page_tables called with wrong alignment");
from_dir = (unsigned long *) ((from>>20) & 0xffc); //源地址的起始目录项/* _pg_dir = 0 */
to_dir = (unsigned long *) ((to>>20) & 0xffc);//目标地址的起始目录项
size = ((unsigned) (size+0x3fffff)) >> 22;//计算size长度所占有的页目录项数,也就是所占页表数
for( ; size-->0 ; from_dir++,to_dir++) {//复制
if (1 & *to_dir)//目的目录项指向的页表已存在,则出错
panic("copy_page_tables: already exist");
if (!(1 & *from_dir))//源目录项无效,则指向下一个目录项
continue;
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);//取源页目录项的值,也就是对应源页表 的地址
if (!(to_page_table = (unsigned long *) get_free_page()))//获取首个(实际上是最后一个空闲页面)
return -1; /* Out of memory, see freeing */
*to_dir = ((unsigned long) to_page_table) | 7;//设置目的目录项的信息
nr = (from==0)?0xA0:1024;//根据 当前处理的页目录项所对应的页表,设置需要复制的页表项数
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {//对当前页表进行复制过程
this_page = *from_page_table;//取源
if (!(1 & this_page))//如果没有用,则继续下一个项
continue;
this_page &= ~2;//复位RW标志
*to_page_table = this_page;//将该页表项复制到目的页表中
if (this_page > LOW_MEM) {//大于内存低端1M,进行物理内存映射字节图
*from_page_table = this_page;
this_page -= LOW_MEM;
this_page >>= 12;//计算页面号
mem_map[this_page]++;//本页面号引用次数加1
}
}
}
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)//物理内存页面映射到线性地址空间指定处,,
{//page主内存中的一个页面,address线性地址
unsigned long tmp, *page_table;
/* NOTE !!! This uses the fact that _pg_dir=0 */
if (page < LOW_MEM || page >= HIGH_MEMORY)//合理的地址
printk("Trying to put page %p at %p\n",page,address);
if (mem_map[(page-LOW_MEM)>>12] != 1)//取页面号==(page-LOW_MEM)>>12,,当前页面是否被用
printk("mem_map disagrees with %p at %p\n",page,address);
page_table = (unsigned long *) ((address>>20) & 0xffc);//address在页目录表中对应的页目录项,
if ((*page_table)&1)//如如该目录项有效,即指定的页表在内存中
page_table = (unsigned long *) (0xfffff000 & *page_table);//则将此指定页表的地址放在page_table变量中
else {//如果没有效
if (!(tmp=get_free_page()))//获取首个(实际上是最后一个空闲页面)
return 0;
*page_table = tmp|7;
page_table = (unsigned long *) tmp;//将新申请的页表的地址放在page_table变量中
}
page_table[(address>>12) & 0x3ff] = page | 7;//(address>>12) & 0x3ff:address该页表项在页表中所对应的索引值,将page的地址给他
/* no need for invalidate */
return page;
}
void un_wp_page(unsigned long * table_entry)//取消写保护页面函数,页面异常中断中写保护异常的处理函数
{//table_entry为页表项指针,是物理地址,它指向的就是物理地址
unsigned long old_page,new_page;
old_page = 0xfffff000 & *table_entry;//取指定页表项中物理页面地址
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {//物理页面地址大于LOW_MEM,且其中页面映射字节图数组中值为1(页面引用数为1,不共享)
*table_entry |= 2;//置RW标志为可写,后退出,完成本函数的任务
invalidate();//刷新页变换高速缓冲区
return;
}
if (!(new_page=get_free_page()))//如果共享则获取得首个(实际上是最后一个空闲页面)
oom();//显示内存已用完,出错信息
if (old_page >= LOW_MEM)//物理页面地址大于LOW_MEM
mem_map[MAP_NR(old_page)]--;//老页面(也就是上面共享的页面)映射字节图数组中值减1,也就是引用数减1,(能达到取消共享的目的吗?)
*table_entry = new_page | 7;//指向新液面地址
invalidate();//刷新页变换高速缓冲区
copy_page(old_page,new_page);//复制1页,将原来页面的内容复制到新页面上来
}
/*
* 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)//执行写保护页面函数,在page.s中被调用,写时复制
{//error_code:进程在写保护页面时由CPU自动产生,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):线性地址所对应的页表项在页表中的偏移地址
(((address>>10) & 0xffc) + (0xfffff000 &//(0xfffff000 &*((unsigned long *) ((address>>20) &0xffc)))):线性 地址所对应的页目录项所指向的页表的地址值
*((unsigned long *) ((address>>20) &0xffc)))));//前面两个项加就可得到线性地址所对应的页表项指针(物理地址)
}
void write_verify(unsigned long address)//写页面验证 函数
{//address:为线性地址
unsigned long page;
if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))//取线性地址所对应的页目录项,同时判断页表是否存在
return;//不存在则返回,对于不存在的页面,没有共享和复制,当程序访问不存在的页面时即可产生缺页中断
page &= 0xfffff000;//从目录项中取页面地址
page += ((address>>10) & 0xffc);//从页面中取页表项指针
if ((3 & *(unsigned long *) page) == 1) //该页面不可写且存在 /* non-writeable, present */
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); //释放物理地址addr开始的1页面内存 /* 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.
*/
static int try_to_share(unsigned long address, struct task_struct * p)//可以认为当前进程是由进程P执行FORK()而产生的进程
{//在佫务P中检查ADDRESS地址处的页面,看是否存在,是否干净,如果干净的话,就与当前任务共享
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中地址address处页面所对应的4G线性空间中实际页目录项
to_page += ((current->start_code>>20) & 0xffc);////当前进程中地址address处页面所对应的4G线性空间中实际页目录项
/* is there a page-directory at from? */
from = *(unsigned long *) from_page;//取P进程页目录项内容
if (!(from & 1))
return 0;
from &= 0xfffff000;//取此页目录项所指向的页表指针(页表地址)
from_page = from + ((address>>10) & 0xffc);//取此页表中address所在的页表项指针(地址)
phys_addr = *(unsigned long *) from_page;//取此页表项的内容(0---11位为页面相应的属性标志位,12---31位来表示页面的具体的物理地址)
/* 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;//取当前进程页目录项内容
if (!(to & 1))//如果该内容最后一位(0比特位为0)则说明,对应的页表不存在,于是申请一空闲页
if (to = get_free_page())//获取首个(实际上是最后一个空闲页面)
*(unsigned long *) to_page = to | 7;//to_page指向新申请页面地址
else
oom();
to &= 0xfffff000;//取页表地址
to_page = to + ((address>>10) & 0xffc);//取页表项指针(地址)
if (1 & *(unsigned long *) to_page)//该页表项的内容最后一位(0比特位为1),则说明已经占有了一个内存页,达不到共享P进程内存页,则内核出错
panic("try_to_share: to_page already exists");
/* share them: write-protect */
*(unsigned long *) from_page &= ~2;//P进程的页表项置写保护标志
*(unsigned long *) to_page = *(unsigned long *) from_page;//当前进程 的页表项复制P进程的页表项
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.
*//主要用于父进程和子进程之间的共享
static int share_page(unsigned long address)//共享页面处理函数
{//试图找到一个进程,能与当前进程共享内存
struct task_struct ** p;
if (!current->executable)//当前进程执行文件I 节点指针,本进程是否有对应的执行文件
return 0;
if (current->executable->i_count < 2)//对执行文件I 节点的引用次数,如果为1则说明系统中只有一个进程运行该执行文件,达不对共享的目的,返回
return 0;
for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {//否则,搜索任务数驵,找可以与当前进程共享页面的进程
if (!*p)//为空时
continue;
if (current == *p)//是当前进程时
continue;
if ((*p)->executable != current->executable)//P进程执行文件节点与当前进程不相同时
continue;
if (try_to_share(address,*p))//在佫务P中检查ADDRESS地址处的页面,看是否存在,是否干净,如果干净的话,就与当前任务共享
return 1;
}
return 0;
}
void do_no_page(unsigned long error_code,unsigned long address)// 缺页中断处理函数,page.s中被调用
{//error_code:由CPU因缺页而自动生成,address:产生异常的页面线性地址
int nr[4];
unsigned long tmp;
unsigned long page;
int block,i;
address &= 0xfffff000;//address处缺页页面地址
tmp = address - current->start_code;//缺页页面对应的逻辑地址
if (!current->executable || tmp >= current->end_data) {//当前进程执行文件I节点指针为空或指定地址超出(代码+数据)长度时,说明进程在申请新的内存页面存放栈和堆的数据 ,所以这样直接申请一个新的空闲页面返回就可以
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);// 根据current->executable节点信息取文件数据块block 在设备上对应的逻辑块号
bread_page(page,current->executable->i_dev,nr);//读设备上一个页面(4 个缓冲块)的内容到内存指定的地址
i = tmp + 4096 - current->end_data;//超出的字节长度值
tmp = page + 4096;//指向页面尾
while (i-- > 0) {//页面尾i 个字节清0
tmp--;
*(char *)tmp = 0;
}
if (put_page(page,address))//物理内存页面映射到线性地址空间指定处,,
return;
free_page(page);//释放物理地址addr开始的1页面内存
oom();//显示内存已用完,出错信息
}
void mem_init(long start_mem, long end_mem)//物理内存管理 初始化,main.c程序中调用
{//对1M以上内存区域进行初始化操作,start_mem为主内存区起始位置,end_mem为实际物理内存最大地址
int i;
HIGH_MEMORY = end_mem;//内存最高地址
for (i=0 ; i<PAGING_PAGES ; i++)//对于16M内存来说,从1M到16M 范围内的所有的内存页面对应的内存映射字节数组项置为占用状态
mem_map[i] = USED;
i = MAP_NR(start_mem);//主内存区域起始位置处页面号
end_mem -= start_mem;
end_mem >>= 12;//主内存区域的页面总数
while (end_mem-->0)//主内存区域中所有的内存页面对应的内存映射字节数组项清0,置为空闲状态,(对于16M内存来说,主内存区域为4M---16M)
mem_map[i++]=0;
}
void calc_mem(void)//计算内存空闲页面数
{
int i,j,k,free=0;
long * pg_tbl;
for(i=0 ; i<PAGING_PAGES ; i++)//扫描内存映射字节数组
if (!mem_map[i]) free++;
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);
}
}
}
|