您的位置:首页 > 运维架构 > Linux

linux 0.11 源码学习(十一)

2013-05-09 15:29 148 查看
memory.c

在X86的保护模式中,线性地址由页目录表(10位)+页表(10位)+ 偏移(12位)组成,因此对线性地址而言可以寻址4G的地址空间。而实际中linux支持16M的内存,因此在memory.c或者说linux的内存管理模块中,维护了线性地址和实际物理地址的映射。本篇博客主要记录内存管理的几个主要函数学习。

下面几个宏定义可以看出物理页面的分配数:

#define USED 100    //mem_map中的映射值,UNUSED是初始化值0,在mem_init中完成
#define PAGING_MEMORY (15*1024*1024) //实际的主内存15M,最低端的1M分配给内核
#define PAGING_PAGES (PAGING_MEMORY>>12) //实际的物理页,每页表项是12位偏移地址,因此长度为4096


get_free_page(void)代码,完成功能是获取一个可用的页:

/*
* 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"); //返回值是寄存器ax

__asm__("std ; repne ; scasb\n\t"//edi指向的值与al(0)比较
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t" //edi+1的值=1
"sall $12,%%ecx\n\t" //ecx的值左移12位,即ecx * 4KB
"addl %2,%%ecx\n\t" //ecx的值加2
"movl %%ecx,%%edx\n\t" //赋值给edx
"movl $1024,%%ecx\n\t" //ecx = 1024
"leal 4092(%%edx),%%edi\n\t" //edx + 4092的值读进edi
"rep ; stosl\n\t" //将eax的值拷贝到edi中,重复ecx次,完成页表项清零
"movl %%edx,%%eax\n" //edx赋值给eax,返回eax
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES), //ax = 0, ecx = PAGIN_PAGES 物理内存页数
"D" (mem_map+PAGING_PAGES-1)//edi = mem_map + PAGING_PAGES - 1
);
return __res;
}


free_page完成释放指定物理地址对应的物理页,代码如下:

addr -= LOW_MEM;
addr >>= 12; //上述两行代码,将addr由实际物理地址转换为mem_map中的索引
if (mem_map[addr]--) return;
mem_map[addr]=0;//对应的addr的mem_map设置为未使用


copy_page_tables在进程fork时被调用,如下:

if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
printk("free_page_tables: from copy_mem\n");
free_page_tables(new_data_base,data_limit);
return -ENOMEM;
}


注: 此处的from/to都是线性地址,如上述描述由10+10+12表示,但要注意的是这里的10\10\都是在页目录表和页表中的索引,因此对于实际的物理地址要*4B。

int copy_page_tables(unsigned long from,unsigned long to,long size)
{
unsigned long * from_page_table;
unsigned long * to_page_table;
unsigned long this_page;
unsigned long * from_dir, * to_dir;
unsigned long nr;

if ((from&0x3fffff) || (to&0x3fffff))
panic("copy_page_tables called with wrong alignment");
from_dir = (unsigned long *) ((from>>20) & 0xffc); //左移24位 * 4B,即from线性地址对应的目录表实际地址
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");
if (!(1 & *from_dir))
continue;
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
if (!(to_page_table = (unsigned long *) get_free_page()))//获取一个物理页,存放其页目录表
return -1;    /* Out of memory, see freeing */
*to_dir = ((unsigned long) to_page_table) | 7;
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {//每个页目录表有1024个页表项
this_page = *from_page_table;
if (!(1 & this_page))
continue;
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;
mem_map[this_page]++;//该物理页(即是1024*4大小)
}
}
}
invalidate();
return 0;
}


在linux中采用了写时复制技术,也就是当某个线性地址被写时,触发相应的缺页错误。该缺页错误会导致分配物理页面,实现的代码是page.s,

具体的业务逻辑在函数do_no_page中,如下:

void do_no_page(unsigned long error_code,unsigned long address)//address是产生异常页面的线性地址
{
int nr[4];
unsigned long tmp;
unsigned long page;
int block,i;

address &= 0xfffff000;//低12位是页面内的偏移,此处要复制的是整个页的起始地址,因此低12位取0
tmp = address - current->start_code;//current->start_code是进程线性地址的起始地址
if (!current->executable || tmp >= current->end_data) {
get_empty_page(address);
return;
}
if (share_page(tmp))//申请共享内存,如果有其他进程已执行了一样的文件
return;
if (!(page = get_free_page()))//获取一个page
oom();
/* remember that 1 block is used for header */
block = 1 + tmp/BLOCK_SIZE;
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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: