首页 > 代码库 > 《linux 内核完全剖析》chapter 13 内存管理 (不含swap.c)

《linux 内核完全剖析》chapter 13 内存管理 (不含swap.c)

内存管理(memory.c 和swap.s 部分)



      “倒着看” 先看memory management,很明显,前面各种阻力,都是因为涉及内存管理。不先看这个,我估计前面看了也是白看

        我估算着理论打基础砸了差不多一个星期的时间在memory management上面了。。。感觉很有收获,是时候用实践(code)印证理论了!


《modern operating system》讲内存管理那一章

http://blog.csdn.net/cinmyheart/article/details/24888847

free_page

http://blog.csdn.net/cinmyheart/article/details/24940731

get_free_page

http://blog.csdn.net/cinmyheart/article/details/24967455

           上面两个函数单独拿出来笔记了,几乎这章memory.c 里后面的函数都会用到get_free_page,所以很重要


          由于swap page部分和块设备有关系。。。偶还木有看,swap牵扯的比较多,这章暂且不做印证,待以后更新吧



free_page_table

int free_page_tables(unsigned long from,unsigned long size)//size是页表的数目
{
	unsigned long *pg_table;
	unsigned long * dir, nr;

	if (from & 0x3fffff)//检测开始释放页的地址是否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 */ //戳这个link http://blog.csdn.net/cinmyheart/article/details/24964363
        // 下面这个循环是对页目录page  directory的循环
	for ( ; size-->0 ; dir++) {
		if (!(1 & *dir))//检测该目录项是否已经被使用,对应<注释> 743页 页目录和页表项结构最低位P的检测
			continue;//如果没有使用,就不用释放,直接跳过,进行下一个页目录的检索
		pg_table = (unsigned long *) (0xfffff000 & *dir);//对dir进行解引用,得到页表项的首地址,并且强制转换成指针,赋值给pg_table,
                                                                 //此时pg_table指向该表项的首地址
                //下面这个循环是对页表 page table的循环
		for (nr=0 ; nr<1024 ; nr++) {
			if (*pg_table) { //对表项地址解引用,得到物理地址,如果得到的物理地址不是RAM首地址0x00,那么则可以进行下一步,
                                         //否则pg_table++移动指针,进行下一个页的释放检测
				if (1 & *pg_table)//检测该页表项指向的物理内存页是否已经被使用,如果是,释放,否则swap 释放交换设备中的对应页
					free_page(0xfffff000 & *pg_table);
 				else
 					swap_free(*pg_table >> 1);
				*pg_table = 0;
			}
			pg_table++;
		}
		free_page(0xfffff000 & *dir);//释放掉页表本身占用的内存
		*dir = 0;//所有位置0,移除该页目录项!换而言之,*dir 不指向任何页表
	}
	invalidate();//刷新BTL
	return 0;
}
大笑理论和实践得到了很好的印证






       

 

         Linus说下面的copy_page_tables,这是他认为内存管理里面最难的代码,其实。。。纠结完上面的两个函数之后,暂且抛开swap不说(但是心里要明白,理论上要清楚是个什么过程),这个最难的代码也会思路很清晰的

        这段代码的主要思想就是把页目录项里面从某一个page(from)起始到某一个page(to)结束的之间所有的memory page释放掉

 

抛开《注释》 自己写注释写理解,这样我想,对于代码的理解会更好

 

copy_page_tables

intcopy_page_tables(unsigned long from,unsigned long to,long size)//size是要释放页的数目
{

    unsigned long * from_page_table;//from 这个page所在的page table

    unsigned long * to_page_table;// to这个page所在的page table

    unsigned long this_page;

    unsigned long * from_dir, * to_dir;//from所在的页目录项和to所在的页目录项

    unsigned long new_page;

    unsigned long nr;
 

    if ((from&0x3fffff) ||(to&0x3fffff))// 4M对齐检测,老生常谈了。。。。

        panic("copy_page_tables calledwith wrong alignment");

    from_dir = (unsigned long *)((from>>20) & 0xffc); /* _pg_dir = 0 */ //看不懂就戳上面前面代码注释里面埋的link

    to_dir = (unsigned long *) ((to>>20)& 0xffc); //看不懂就戳上面前面代码注释里面埋的link

    size = ((unsigned) (size+0x3fffff))>> 22; //页面数取整

    //下面有两层for循环,表慌hold住,很简单
    // 第一层for循环是对页目录项page directory进行检索
    for( ; size-->0 ;from_dir++,to_dir++) {

        if (1 & *to_dir) //我看到的第一反应就是 ——漂亮。 非常严谨的错误检查。检测即将被写入的页是否处于被占用状态

            panic("copy_page_tables:already exist");
          //panic的水有点深,涉及块设备和fs,暂且把这个当作一个错误检查就行了,以后再补充

        if (!(1 & *from_dir))//检测要写入的数据源内存页是否是空页(即没有被使用)

            continue;//如果没有被使用,就continue,下一个页目录

        from_page_table = (unsigned long *)(0xfffff000 & *from_dir);
        //对from_dir解引用并且将低的12位置0,得到页表项首地址

        if (!(to_page_table = (unsigned long *)get_free_page()))//get_free_page得到空闲页,to_page_table指向该页

            return -1;    /* Out of memory, see freeing */

        *to_dir = ((unsigned long)to_page_table) | 7;
       //低三位权限设置,111 任何页面可以被读写,运行在任何权限级上的程序可以访问,占用该页

       nr = (from==0)?0xA0:1024;
       //如果要copy的是内核段,页面数置0xa0 == 160 页,普通内存段,则nr置1024 1K ,即一个页表的所有项

        //第二层for循环,对页表项 page table进行检索
        for ( ; nr-- > 0 ;from_page_table++,to_page_table++) {

            this_page = *from_page_table;//对from_page_table 解引用得到页表项地址,赋值给this_page

            if (!this_page)//检测当前页表是否使用,如果没有使用,则下一个

                continue;

            if (!(1 & this_page)) { //如果没有被占用,跳进if

                if (!(new_page =get_free_page()))//申请一个空闲页

                    return -1;

                read_swap_page(this_page>>1,(char *) new_page);
               //把RAM区域的空闲内存页对应的swap区域页内容写入到new_page

                *to_page_table = this_page;
               // 我布吉岛肿么表达,但是就是这样么回事。。
              //this_page 赋值给*to_page_table 实现this_page指向的物理地址传递给*to_page_table
                *from_page_table = new_page |(PAGE_DIRTY | 7);//设置数据源内存页的各种权限
                continue;
            }
            // 如果this_page指向的物理页被占用了
            this_page &= ~2; //更改读写权限为只读

            *to_page_table =this_page;
           //this_page 赋值给*to_page_table 实现this_page指向的物理地址传递给*to_page_table

            if (this_page > LOW_MEM) {//this_page 指向的物理地址在内核段以外

                *from_page_table = this_page;//重新修改过的this_page 赋值给*from_page_table

                this_page -= LOW_MEM;

                this_page >>= 12;//这两部实现把this_page转换成memory_page的数,就是当前this_page指向的地址是内存页的第多少页

                mem_map[this_page]++;//内存页占用+1
            }
        }
    }

    invalidate();//刷新TLB(translation lookaside buffer )

    return 0;

}

 

 

 put_page

static unsigned long put_page(unsigned longpage,unsigned long address)
{
       unsigned long tmp, *page_table;

/* NOTE !!! Thisuses the fact that _pg_dir=0 */
 

       if (page < LOW_MEM || page >=HIGH_MEMORY)//错误检查,保证page在0x1000和最高地址之间

              printk("Trying to put page %pat %p\n",page,address);

       if (mem_map[(page-LOW_MEM)>>12] !=1)//检测当前被映射的page是否被引用,空页

              printk("mem_map disagreeswith %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 页目录项 指向该页

              page_table = (unsigned long *)tmp;

       }

       page_table[(address>>12) &0x3ff] = page | 7; //我始终没明白为嘛一个局部变量改变它的值之后,立马return返回了,这个局部变量还有啥用

/* no need forinvalidate */

       return page;//我不知道返回page有啥意义

}

 

 put_dirty_page

unsigned long put_dirty_page(unsigned long page, unsigned long address)
{
       unsigned long tmp, *page_table;

/* NOTE !!! Thisuses the fact that _pg_dir=0 */

       if (page < LOW_MEM || page >=HIGH_MEMORY)

              printk("Trying to put page %pat %p\n",page,address);

       if (mem_map[(page-LOW_MEM)>>12] !=1)

              printk("mem_map disagreeswith %p at %p\n",page,address);

       page_table = (unsigned long *)((address>>20) & 0xffc);

       if ((*page_table)&1)

              page_table = (unsigned long *)(0xfffff000 & *page_table);

       else {

              if (!(tmp=get_free_page()))

                     return 0;

              *page_table = tmp|7;

              page_table = (unsigned long *)tmp;

       }

       page_table[(address>>12) &0x3ff] = page | (PAGE_DIRTY | 7);//和put_page唯一的区别就是这个置位不同,这里置位了dirty page bit

/* no need forinvalidate */

       return page;
}

 un_wp_page

//取消写保护页面,具体做法就是改变R/W 读写权限

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) {//如果该页被引用了mem_map对应值为1,仅被一个进程使用,并且在0x1000以上

              *table_entry |= 2;//将*table_entry指向的页设为可读写

              invalidate();//刷新TLB

              return;//成功返回

       }

       if (!(new_page=get_free_page()))//申请一个新的内存页

              oom();

       if (old_page >= LOW_MEM)//如果内存页在0x1000之上

              mem_map[MAP_NR(old_page)]--;//引用数-1

       copy_page(old_page,new_page);//将old_page的内容复制到new_page中

       *table_entry = new_page | 7;//更改新页的权限,开的很大的说。。。让*table_entry 指向新的memory page

       invalidate();//刷新TLB

}      

 do_wp_page

void do_wp_page(unsigned long error_code,unsigned long address)

{

     if (address < TASK_SIZE)//TASK_SIZE0x4000000 == 64M 是内核的虚拟地址范围,address不能在这个范围内,内核是写保护的

              printk("\n\rBAD! KERNELMEMORY WP-ERR!\n\r");

       if (address - current->start_code >TASK_SIZE) {//如果当前虚拟地址所在进程的代码段大于64M(一个进程可以拥有的最大内存大小),报错

              printk("Bad things happen:page error in do_wp_page\n\r");

              do_exit(SIGSEGV);

       }

#if 0

/* we cannot dothis 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 & //((address>>10) & 0xffc) 这个是页表项在page table 里面的偏移量,即第几个页表项

              *((unsigned long *)((address>>20) &0xffc)))));//取消写保护页面,并复制相应偏移的页表项


}

 write_verify

void write_verify(unsigned long address)
{

       unsigned long page;

     if (!( (page = *((unsigned long *)((address>>20) & 0xffc)) )&1)) //获取page  table 的入口信息,赋值给page,并验证这个页目录项是否是非空

              return;

       page &= 0xfffff000;//获取page table 的入口地址

       page += ((address>>10) &0xffc);//得到address对应page table中的偏移量

       if ((3 & *(unsigned long *) page) ==1)  /* non-writeable, present */ //验证该页表项非空并且对应的物理内存页是可读写的

              un_wp_page((unsigned long *)page);//取消写保护页面,并复制相应偏移的页表项

       return;
}

 

 

 

 get_empty_page

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

static int try_to_share(unsigned long address, struct task_struct * p)//成功返回1,错误返回0

{

       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的代码段起始地址所在页目录首地址,并且加上偏移量from_page,于是得到相应的页目录地址

       //逆向算地址,很精彩!有木有

       to_page += ((current->start_code>>20)& 0xffc);//同理算出当前进程current的页目录项的地址

/* is there apage-directory at from? */

       from = *(unsigned long *) from_page;//解引用为页表项信息

       if (!(from & 1))//该页表是否被空闲,非空就不进入

              return 0;

       from &= 0xfffff000;//得到首个页表项地址

       from_page = from + ((address>>10)& 0xffc);//首歌页表项加上页表偏移量得到对应的from页表项

       phys_addr = *(unsigned long *)from_page;//对页表项进行解引用,得到相应的页表项内容

/* is the pageclean and present? */

       if ((phys_addr & 0x41) != 0x01)//物理内存页不是dirty,并且存在,那么不进入if

              return 0;

       phys_addr &= 0xfffff000;//取页表项偏移量

       if (phys_addr >= HIGH_MEMORY ||phys_addr < LOW_MEM)//物理地址范围检查

              return 0;

       to = *(unsigned long *) to_page;//对to_page 解引用,得到页目录项内容,赋值给to

       if (!(to & 1))//页目录项指向的页表项是否存在非空

              if (to = get_free_page())//如果不存在,那么申请一个空内存页,让to指向它

                     *(unsigned long *) to_page= to | 7;//更新*to_page 的权限

              else

                     oom();

       to &= 0xfffff000;//得到首页表项的地址

       to_page = to + ((address>>10) &0xffc);//加上偏移量,得到页表项地址

       if (1 & *(unsigned long *) to_page)//检测to_page 页表项指向的物理内存页是否存在

              panic("try_to_share: to_pagealready exists");

/* share them:write-protect */

       *(unsigned long *) from_page &= ~2;//将p进程对应的页表项*from_page 置为只读!我擦,这才是最核心的一句,前面都是铺垫

       *(unsigned long *) to_page = *(unsignedlong *) from_page;//p,current 两个进程共用一个目录项

       invalidate();//刷新TLB

       phys_addr -= LOW_MEM;//

       phys_addr >>= 12;//算出对应的页数

       mem_map[phys_addr]++;//引用加1

       return 1;//成功分享

}

 

 

share_page

static int share_page(struct m_inode * inode, unsigned long address)
{
       struct task_struct ** p;

       if (inode->i_count < 2 || !inode)

       //如果有两个及以上的进程运行同一个inode指向的文件,那么不进入。一个进程涉及一个inode标记的内存段还分享什么。。。。

              return 0;
       for (p = &LAST_TASK ; p >&FIRST_TASK ; --p) {//把所有进程通通扫描一遍

              if (!*p)//如果*p任务项空闲,下一个
                     continue;
              if (current == *p)//如果*p任务项是当前进程,下一个
                     continue;
              if (address < LIBRARY_OFFSET){
              // 这个检测我真不知道,应该和文件系统的inode有关系,以后update吧。。。。      
                     if (inode !=(*p)->executable)

                            continue;
              } else {

                     if (inode !=(*p)->library)

                            continue;

              }
              if (try_to_share(address,*p))

                     return 1;//分享搞定之后跳出for循环,直接返回
       }
       return 0;
}

 


do_no_page

//执行缺页处理
void do_no_page(unsigned long error_code,unsigned long address)
{

       int nr[4];

       unsigned long tmp;

       unsigned long page;

       int block,i;

       struct m_inode * inode;
 

       if (address < TASK_SIZE)//TASK_SIZE 是64M,刚好在内核线性地址内,内核范围被写保护,不能执行

              printk("\n\rBAD!! KERNEL PAGEMISSING\n\r");

       if (address - current->start_code >TASK_SIZE) {//进程大小超过了64M 线性,不符合规定,address不在当前进程的线性地址内

              printk("Bad things happen:nonexistent page error in do_no_page\n\r");

              do_exit(SIGSEGV);

       }
       page = *(unsigned long *) ((address>> 20) & 0xffc);//找到address所在页目录的首页目录项地址,并解引用

       if (page & 1) {//如果页目录项指向的页表存在非空

              page &= 0xfffff000;

              page += (address >> 10)& 0xffc;//这两步得到页表项

              tmp = *(unsigned long *) page;//得到页表项的内容

              if (tmp && !(1 & tmp)){//页表项指向的物理地址存在,并且不是内核段地址,交换page指向的内存页
                     swap_in((unsigned long *)page);
                     return;
              }
       }//如果页目录项指向的页表为空
       address &= 0xfffff000;

       tmp = address - current->start_code;//计算出address在当前进程中的相对偏移量

       if (tmp >= LIBRARY_OFFSET ) {//尼玛。。又和fs有关系
              inode = current->library;
              block = 1 + (tmp-LIBRARY_OFFSET) /BLOCK_SIZE;
       } else if (tmp < current->end_data){//如果address在代码段之内
              inode = current->executable;
              block = 1 + tmp / BLOCK_SIZE;
       } else {
              inode = NULL;//利用后面的get_empty_page获得空页

              block = 0;
       }

       if (!inode) {

              get_empty_page(address);

              return;

       }
       if (share_page(inode,tmp))//尝试分享逻辑地址tmp处的memory page

              return;
       if (!(page = get_free_page()))//共享失败就get_free_page申请一个空页

              oom();
/* remember that 1block is used for header */

       for (i=0 ; i<4 ; block++,i++)//和fs有关系。。。。

              nr[i] = bmap(inode,block);
       bread_page(page,inode->i_dev,nr);//和块设备有关系
       i = tmp + 4096 - current->end_data;//和块设备有关系
       if (i>4095)//和块设备有关系

              i = 0;
       tmp = page + 4096;//和块设备有关系
       while (i-- > 0) {//和块设备有关系

              tmp--;
              *(char *)tmp = 0;
       }
       if (put_page(page,address))//把引起异常的addresss地址,映射到page

              return;
       free_page(page);//映射失败就释放刚申请的memory page

       oom();
}

 

 mem_init

void mem_init(longstart_mem, long end_mem)
{
       int i;
 
       HIGH_MEMORY = end_mem;//把物理内存的最高地址赋值给HIGH_MEMORY 16M

       for (i=0 ; i<PAGING_PAGES ; i++)
              mem_map[i] = USED;//统统初始化为USED

       i = MAP_NR(start_mem);//start_mem对应的是主内存取其实地址

       end_mem -= start_mem;

       end_mem >>= 12;//除以4M,得到主内存区的memory page的页数

       while (end_mem-->0)

              mem_map[i++]=0;//主内存区的mem_map 统统置0,4M以内的是USED
}


page.s

/*
 *  linux/mm/page.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * page.s contains the low-level page-exception code.
 * the real work is done in mm.c
 */

.globl _page_fault

_page_fault:
    xchgl %eax,(%esp)
    pushl %ecx
    pushl %edx
    push %ds
    push %es
    push %fs
    movl $0x10,%edx
    mov %dx,%ds
    mov %dx,%es
    mov %dx,%fs
    movl %cr2,%edx
    pushl %edx
    pushl %eax
    testl $1,%eax//检测是否是由缺页引起的page fault,是就执行_do_no_page,否则执行_do_wp_page
    jne 1f
    call _do_no_page
    jmp 2f
1:    call _do_wp_page
2:    addl $8,%esp
    pop %fs
    pop %es
    pop %ds
    popl %edx
    popl %ecx
    popl %eax
    iret




以上都是自己写的注释,肯定没有《注释》那本书权威,仅供参考交流讨论,欢迎指正,thank you







memory management 暂告一段落