《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 暂告一段落