文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>给linux0.11的内存管理函数memory.c作了个注释

给linux0.11的内存管理函数memory.c作了个注释

时间:2007-05-23  来源:tthacker

给linux0.11的内存管理函数memory.c作了个注释,现在还没时间和能力分析2。6内核下的,不过linux早期版本的东西,对于想写自己内核的来说,真的是个宝藏。

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

/*  刷新页变换高速缓冲,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<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);
        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);
                }
        }
}
相关阅读 更多 +
排行榜 更多 +
rento大富翁手游

rento大富翁手游

休闲益智 下载
冲撞赛车3无限金币版

冲撞赛车3无限金币版

赛车竞速 下载
电动火车模拟器内置菜单

电动火车模拟器内置菜单

赛车竞速 下载