Chinaunix首页 | 论坛 | 博客
  • 博客访问: 443508
  • 博文数量: 184
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 594
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-17 16:24
个人简介

我是一只小小鸟

文章分类

全部博文(184)

文章存档

2016年(1)

2015年(55)

2014年(127)

2013年(1)

分类: LINUX

2014-05-21 10:25:34

原文地址:memory.c 作者:jhluroom

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

阅读(615) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~