Linux 内核解读之内存管理memory.c

volatile void do_exit(long code); //退出处理函数

static inline volatile void oom(void) //没有内存空间了
{

printk("out of memory\n\r");
do_exit(SIGSEGV);

}

//刷新页变换高速缓冲区
//为了提高地址转换效率,CPU将最近使用的页表数据存放在芯片中高速缓冲区,
//修改过信息后,就需要刷新缓冲区,这里是通过加载页目录基址寄存器cr3的方法来进行刷新的,eax = 0为物理地址的0,即页目录的基地址

define invalidate() \

asm(“movl %%eax,%%cr3”::“a” (0))

/ these are not to be changed without changing head.s etc /

define LOW_MEM 0x100000 //定义内存低端(1MB)

define PAGING_MEMORY (1510241024) //定义 15MB作为主内存即分页内存

define PAGING_PAGES (PAGING_MEMORY»12) //分页后的主内存页数(15MB/4KB)

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”)

//内存映射字节图,可以知道每个页表被同时使用的次数
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”

"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开始的一页面内存开始释放,
    {
    if (addr < LOW_MEM) return; //如果地址小于最低内存低端,则返回
    if (addr >= HIGH_MEMORY) //如果地址大于高端,着提示错误
    panic("trying to free nonexistent page");
    
    addr -= LOW_MEM;//减去低端的地址,右移12位即除以4*1024,4KB,得到页面号
    addr »= 12;
    if (mem_map[addr]–) return;//如果内存页面字节不等于0,则减1,并返回
    mem_map[addr]=0; //否则设置页面映射字节为0,提示释放重复经释放掉的错误信息
    panic(“trying to free free page”);
    }

int free_page_tables(unsigned long from, unsigned long size)
{

unsigned long *pg_table;
unsigned long *dir, nr;

if(from & 0x3fffff)    //0x4fffff是4MB,这里需要是4M的地址为界,否则报错
    panic("free_page_tables called with wrong alignment");

if(!from)
    panic("Trying to free upswapper memory space");

size = (size + 0x3fffff) >> 22;//即把size+(4M - 1)/ 4M得出所占页表数,记得每个页表有1024项,每项对于一页的内存4KB
//注意:上面的size是得到的目录号,我们知道每一项目录号占4个字节,即每个地址是以4增量间隔递增,由于页目录在head.s里面
//载入内存时从物理地址的0开始,所以实际的页目录号地址指针为:目录项号<<2,所以也是from>>20,与0xffc确保目录项指针范围有效
//0xffc = 0b111111111100
dir = (unsigned long *) ((from>>20) & 0xffc);
for(; size-- > 0; dir++){ // size 为被释放目录数,dir为目录项指针
    if(!(1 & *dir))       //*dir值与1,判断如果目录项无效即P位为0则继续,P位表示是否存在
        continue;
    pa_table = (unsigned long *) (0xfffff000 & *dir);//计算出目录的页表地址
    for(nr = 0; nr<1024; nr++){ //每个页表有1024个页       
        if(1&*pg_table)         //如果页表项P位为1,则释放内存
            free_page(0xfffff000 & *pg_table);

        *pa_table = 0;          //该页表项内容清零
        pg_table++;             //下一页
    }
    free_page(0xfffff000 & *dir);//这里是想是否页表所在的内存,这里的内存是1024*4B<1M;所以这里什么也没有做
    *dir = 0;   //清零页表目录项
}
invalidate();   //刷新页变换高速缓冲区
return 0;

}

int copy_page_tables(unsigned long from, unsigned long to, long size)
{

        unsigned long *from_page_tables;
        unsigned long *to_page_tables;
        unsigned long this_page;
        unsigned long *from_dir, *to_dir;
        unsigned long nr;

        //源地址和目的地址都需要在4M内存边界上。否则出错
        if((from&0x3fffff) || (to&0x3fffff))
            panic("copy_page_tables called with wrong alignment");
        //计算出源地址和目标地址的目录指针,都是内存的物理地址
        from_dir = (unsigned long *) ((from>>20) & 0xffc);
        to_dir   = (unsigned long *) ((to>>20) & 0xffc);

        //计算要复制的内存块占用的页表数
        size = ((unsigned)(size + 0x3fffff)) >> 22;

        for(; size-- > 0; from_dir++, to_dir++) {
                if(1 & *to_dir)
                        panic("copy_page_tables: already exist");   
                from_page_table = (unsigned long *)(0xfffff000 & *from_dir);
                //拿到一块空闲页内存,get_free_page()返回0时表示失败,失败返回-1
                if(!(to_page_table = (unsigned long *) get_free_page()))
                    return -1;
                //页目录表置7,二进制为0b111表示Usr,R/W,Present
                *to_dir = ((unsigned long) to_page_table) | 7;
                //from =0 则仅需要复制头160页
                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置0,表示用户层只能读不能写页面
                        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;   //除以1024*4,得到内存所在页目录数
                                    mem_map[this_page]++;   //内存页映射值+1                          
                        }   
                }
        }

        invalidate();           //刷新页变换高速缓冲
        return 0;

}

unsigned long put_page(unsigned long page, unsigned long address)
{

        unsigned long tmp, *page_table;
        /*page地址在合法地址主存范围内*/    
        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);
        if ((*page_table) & 1)// 如果页目录值标志P=1,表明该页面有效,则取出该页指定的页表地址
            page_table = (unsigned long *) (0xfffff000 & *page_table);
        else
        {
            //否则申请一块新的页面
            if(!(tmp=get_free_page()))
                return 0;
            //标志对应的页目录项为(Use,U/S,R/W)
            *page_table = tmp|7;
            page_table = (unsigned long*) tmp;
        }
        //在页表中设置指定地址的页表
        page_table[(address>>12) & 0x3ff] = page | 7;

        return page;

}

//取消页面写保护
//table_entry 为页表项的指针
void un_wp_page(unsigned long *table_entry)
{

    unsigned long old_page, new_page;

    old_page =0xfffff000 & *table_entry; //取页的物理地址
    //如果地址大于1M而且这个页面还没有被共享,则设置该页面的R/W为可写,刷新变换高速缓冲 
    if(old_page >= LOW_MEM && mem_map[MAP_NR(old_page)] == 1){
            *table_entry |= 2;
            invalidate();
            return;
    }

    //申请空闲页失败,则提示out of memory 
    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);

}

/
当用户试图在一个已经共享的页面上写是,需要复制到一个新的地址,并递减原页面
error_code 是由CPU产生,address作为页面地址
/
void do_wp_page(unsigned long error_code, unsigned long address)
{

// ((address»10) & 0xffc)address»10 = address/1024 获得以4K个字节为递增的页表项偏移地址
// ((unsigned long)((address»20) & 0xffc )) 获得目录项,
// (0xfffff000 & ((unsigned long)((address»20) & 0xffc )))并由页目录项得到页表中地址
// 2个相加得到页表项指针

un_wp_page((unsigned long *) (((address>>10) & 0xffc) + (0xfffff000 & *((unsigned long*)((address>>20) & 0xffc )))));

}

//写页面验证函数
/
address 为传入的地址
*/
void write_verify(unsigned long address)
{

    unsigned long page;
    if(!((page = *((unsigned long *)((address>>20) & 0xffc)))&1))//获取地址所在的目录项,查看P位是不是存在
        return ;

    page &= 0xfffff000;//获取页表地址,
    page += ((address>>10) & 0xffc);//并且获取页表中的偏移量

    //如果页面不可写,则执行共享检验,复制页面操作
    if((3 & *(unsigned long *) page) == 1)
        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);
        oom();
    }

}
/
try_to_share 在任务"p"中检查地址address的页面,看是否存在,是否干净
如果干净,这分享
注意:我们已经假设p != 当前任务,并且它们共同享有一个执行程序

/
static int try_to_shart(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);

    //检查from地址处是否存在有效的页目录
    from = *(unsigned long*) from_page;
    if(!(from & 1))
        return 0;
    //获取页目录处的页表起始地址
    from &= 0xfffff000;
    //2项相加计算from_page的页表项地址
    from_page = from + ((address>>10) & 0xffc);
    //得出物理地址
    phys_addr = *(unsigned long *) from_page;
    //判断页表项的Dirty和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))
        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");

        //对应的页,至上写保护标志
        *(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]++;//标志该页的内存被引用加一次
        return 1;

}

/
分享页
*/
static int share_page(unsigned long address)
{

        struct task_struct **p;
        //如果是不可以执行的,则返回。excutable是执行进程节点    
        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;
                //如果2个executable不相等,继续
                if((*p)->executable != current->executable)
                    continue;
                //尝试共享页
                if(try_to_share(address, *p))
                    return 1;           
        }
        return 0;

}

//页异常中断处理调用函数,在页缺失的情况下处理
//error_code是由于CPU产生的错误码,address是页面线性地址
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;//计算出线性地址与进程空间地址的偏移长度
        //假如当前进程的executable为空,或者超出代码与数据长度,则申请一页物理内存
        //start_code 是进程代码段地址, end_data是代码加数据长度
        if(!current->executable || tmp >= current->end_data){
                get_empty_page(address);
                return;     
        }

        //共享成功,退出   
        if(share_page(tmp))
            return;
        //申请空闲页
        if(!(page = get_free_page()))
            oom();
        //计算出缺页所在的数据块,一页内存有4块,4KB=4*block
        block = 1 + tmp/BLOCK_SIZE;
        //根据i节点信息,取数据块在设备上对应的逻辑块
        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;
        }

        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 *) (oxfffff000 & 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)