/* * 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); } } }
|