内存管理(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();//刷新BTL
return 0;
}</span>
![大笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
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 暂告一段落