linux 0.11 源码学习(十一)

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();

}

你可能感兴趣的:(linux)