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

内存管理(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

<span style="font-size:14px;">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();//刷新BTLreturn 0;}</span>

理论和实践得到了很好的印证

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 2f1:    call _do_wp_page2:    addl $8,%esp    pop %fs    pop %es    pop %ds    popl %edx    popl %ecx    popl %eax    iret

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

memory management 暂告一段落

人生难免遇风雨,天空晴朗有阴云,别因雨水湿透衣衫而难过,

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

相关文章:

你感兴趣的文章:

标签云: