/*
* 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");
出错码含义是“资源暂时不可用”,使用了信号SIGSEGV
do_exit(SIGSEGV);
}
//刷新页变换高速缓冲宏函数
//重新加载页目录基址寄存器cr3的方法来进行刷新缓冲区
//eax=0是页目录基址
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))
/* these are not to be changed without changing head.s etc */
#define LOW_MEM 0x100000 //内存低端地址
#define PAGING_MEMORY (15*1024*1024) //分页内存15MB
#define PAGING_PAGES (PAGING_MEMORY>>12) //分页后的物理内存页数,右移12相当于除4K
#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;
//复制1页内存
#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")
//内存映射字节图(1字节代表1页内存),每个页面对应的字节用于标志页面当前被引用(占用)次数
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.
*/
//如果已经没有可用内存了,则返回0,否则返回空闲页面地址
//__asm__汇编程序:
//输入:%1(ax=0)-0;%2(LOW_MEM);%3(cx=PAGING_PAGES);%4(ed i=mem_map+PAGING_PAGES-1)
//输出:返回%0(ax=页面起始地址)
//其中%4指向mem_map[]内存字节图的最后一个字节。
//本函数从字节图末端开始向前扫描所有页面标志(页面总数为 PAGING_PAGES),若有页面空闲(值为0)则返回页面地址
//但只指出了空闲页面的起始地址,还没有映射到进程的线性地址中
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");
__asm__("std ; repne ; scasb\n\t" //寻找值为0的页面
"jne 1f\n\t" //没有的话,则结束,返回0
"movb $1,1(%%edi)\n\t" //将mem_map[]对应的字节置0
"sall $12,%%ecx\n\t" //左移12位
"addl %2,%%ecx\n\t" //加上主内存起始地址得最终地址
"movl %%ecx,%%edx\n\t" //edx=ecx
"movl $1024,%%ecx\n\t" //ecx放计数值1024
"leal 4092(%%edx),%%edi\n\t" //edi=4092+edx
"rep ; stosl\n\t" //从页面末端开始清零
"movl %%edx,%%eax\n" //eax=edx
"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()'
* 释放物理地址 addr 开始的一页面内存
*/
//1MB以下的内存空间用于内核程序和缓冲,不作为分配页面的内存空间
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 >>= 12; //物理地址减去低端内存位置,再除以4KB 得页面号
//如果对应内存页面映射字节不等于0,则减1返回
if (mem_map[addr]--) return;
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.
*/
//根据指定的线性地址from和限长size(页表个数),释放对应内存页表所指定的内存块并置表项空闲
int free_page_tables(unsigned long from,unsigned long size)
{
unsigned long *pg_table;
unsigned long * dir, nr;
if (from & 0x3fffff)
panic("free_page_tables called with wrong alignment");
//出错,试图释放内核和缓冲所占空间
if (!from)
panic("Trying to free up swapper memory space");
//所占页表数
size = (size + 0x3fffff) >> 22;
//from是线性地址,右移22位得到的是页目录号
//但是目录下每项占4字节,而CPU里按字节存,所以实际的目录项指针=目录项号<<2,即乘4,而且页目录的起始地址是0
//最后与上0xffc确保目录项指针范围有效,相当于是以4字节为一个块
dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
for ( ; size-->0 ; dir++) { //size 现在是需要被释放内存的目录项数
//如果该目录项无效(P位=0),则继续
//P位是表示对应页表是否存在
if (!(1 & *dir))
continue;
//取目录项中页表地址,前20位
pg_table = (unsigned long *) (0xfffff000 & *dir);
//每个页表有 1024 个页项,循环1024次
for (nr=0 ; nr<1024 ; nr++) {
if (1 & *pg_table) //同上P位
free_page(0xfffff000 & *pg_table); //释放对应内存页
*pg_table = 0; //页表项清零
pg_table++; //下一项
}
//释放该页表所占内存页面
free_page(0xfffff000 & *dir);
*dir = 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)
{
unsigned long * from_page_table;
unsigned long * to_page_table;
unsigned long this_page;
unsigned long * from_dir, * to_dir;
unsigned long nr;
//4Mb边界
if ((from&0x3fffff) || (to&0x3fffff))
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;
for( ; size-->0 ; from_dir++,to_dir++) {
if (1 & *to_dir) //目的地址P位判断
panic("copy_page_tables: already exist");
if (!(1 & *from_dir)) //源地址P位判断
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 */
//设置目的目录项信息。7是标志信息,表示(Usr,R/W,Present)
*to_dir = ((unsigned long) to_page_table) | 7;
//在内核空间,则仅需复制头160页,640KB
//否则复制一个页表的1024页
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
//取源页表项内容
this_page = *from_page_table;
//如果是空页面不用复制
if (!(1 & this_page))
continue;
//置目的页标志位为只读R/W标志
this_page &= ~2;
//将该页表项复制到目的页表中
*to_page_table = this_page;
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 */
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)
printk("mem_map disagrees with %p at %p\n",page,address);
//计算指定地址在页目录表中对应的目录项指针
page_table = (unsigned long *) ((address>>20) & 0xffc);
//如果该目录项有效(P=1)(也即指定的页表在内存中),则从中取得指定页表的地址
if ((*page_table)&1)
page_table = (unsigned long *) (0xfffff000 & *page_table);
//申请空闲页面给页表使用,并在对应目录项中置相应标志7
else {
if (!(tmp=get_free_page()))
return 0;
*page_table = tmp|7;
page_table = (unsigned long *) tmp;
}
//在页表中设置指定地址的物理内存页面的页表项内容
page_table[(address>>12) & 0x3ff] = page | 7;
/* no need for invalidate */
return page;
}
//取消写保护页面函数,用于写时复制,参数是页表项指针
void un_wp_page(unsigned long * table_entry)
{
unsigned long old_page,new_page;
//取指定页表项内物理页面位置(地址)
old_page = 0xfffff000 & *table_entry;
//若被引用 1 次,页面没有被共享,那么只要置R/W即可返回
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
*table_entry |= 2;
invalidate();
return;
}
//否则页面就是共享的,mem_map>1
if (!(new_page=get_free_page()))
oom();
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]--;
//指定页表项内容更新为新页面的地址,并置可读写等标志(U/S,R/W,P)
*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.
*/
//页异常中断处理调用的C函数,写共享页面时,需复制页面(写时复制)
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
//处理取消页面保护。参数指定页面在页表中的页表项指针
//((address>>10)&0xffc):计算指定地址的页面在页表中的偏移地址
//(0xfffff000&*((address>>20)&0xffc)):取目录项中页表的地址值
un_wp_page((unsigned long *)
(((address>>10) & 0xffc) + (0xfffff000 &
*((unsigned long *) ((address>>20) &0xffc)))));
}
//写页面验证,若不可写,则复制页面
void write_verify(unsigned long address)
{
unsigned long page;
//判断指定地址所对应页目录项的页表是否存在(P),若不存在(P=0)则返回
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); /* 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)
{
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);
//计算进程 p 的代码起始地址所对应的页目录项
from_page += ((p->start_code>>20) & 0xffc);
//计算当前进程中代码起始地址所对应的页目录项
to_page += ((current->start_code>>20) & 0xffc);
/* is there a page-directory at from? */
//取进程p的页目录项内容
from = *(unsigned long *) from_page;
if (!(from & 1))
return 0;
//取该目录项对应的页表地址
from &= 0xfffff000;
//计算地址对应的页表项指针值,并取出该页表项内容给phys_addr
from_page = from + ((address>>10) & 0xffc);
phys_addr = *(unsigned long *) from_page;
/* is the page clean and present? */
//0x41 对应页表项中的 Dirty 和 Present 标志
if ((phys_addr & 0x41) != 0x01)
return 0;
//取phys_addr页面的地址
phys_addr &= 0xfffff000;
if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM)
return 0;
to = *(unsigned long *) to_page;
if (!(to & 1))
if (to = get_free_page())
*(unsigned long *) to_page = to | 7;
else
oom();
to &= 0xfffff000;
to_page = to + ((address>>10) & 0xffc);
if (1 & *(unsigned long *) to_page)
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;
invalidate();
phys_addr -= LOW_MEM;
phys_addr >>= 12;
mem_map[phys_addr]++;
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;
//如果是不可执行的,则返回。
//excutable是执行进程的内存 i节点结构
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) //如果 executable 不等,也继续
continue;
if (try_to_share(address,*p))
return 1;
}
return 0;
}
//start_code是进程代码段地址,end_data是代码加数据长度。对于 Linux 内核,它的代码段和数据段是起始基址是相同的
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 = address - current->start_code;
//execuable=0,表示进程刚设置,需要内存
//指定的线性地址超出代码加数据长度,表明进程在申请新的内存空间
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_SIZE=1024B,因此一页内存需要4个块
block = 1 + tmp/BLOCK_SIZE;
//取数据块在设备上的对应的逻辑块号
for (i=0 ; i<4 ; block++,i++)
nr[i] = bmap(current->executable,block);
//读设备上一个页面的数据(4个逻辑块)到指定物理地址page处
bread_page(page,current->executable->i_dev,nr);
//在增加了一页内存后,该页内存的部分可能会超过进程的 end_data位置
//超出的部分进行清零处理
i = tmp + 4096 - current->end_data;
tmp = page + 4096;
while (i-- > 0) {
tmp--;
*(char *)tmp = 0;
}
if (put_page(page,address))
return;
free_page(page);
oom();
}
//start_mem 可用作分页处理的物理内存起始位置
//end_mem 实际物理内存最大地址
void mem_init(long start_mem, long end_mem)
{
int i;
HIGH_MEMORY = end_mem;
//首先置所有页面为已占用(USED=100)状态
for (i=0 ; i<PAGING_PAGES ; i++)
mem_map[i] = USED;
//计算可使用起始内存的页面号
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<PAGING_PAGES ; i++)
if (!mem_map[i]) free++;
printk("%d pages free (of %d)\n\r",free,PAGING_PAGES);
//扫描所有页目录项(除0,1项),如果页目录项有效,则统计对应页表中有效页面数
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);
}
}
}
linux内存管理memory.c 源码注释
最新推荐文章于 2023-04-18 14:21:41 发布