0%

《linux 0.12内核完全剖析》--内存管理分析

笔记重点:
在琢磨linux源代码的过程,一直有一些疑惑,比如我明明只有4G的内存,计算机却可以运行很多进程,并且为每个进程都分配4G的内存,显然内存是不够用的;还有就是操作系统是怎么知道哪些内存可用,哪些内存不可用,以及怎样有效的分配他们,在从早期操作系统的平坦式管理,到粗糙的段式管理到段页式管理,可谓对内存的管理是不断的抽象,以至于页面管理对现代操作系统的重要性,不言而喻。

  • 内存的大小以及可用段是通过BIOS传给操作系统初始化的
  • 内存的段页式管理需要硬件的支持!!!
  • 最初分配给进程的内存大概只有一页,其他的内存按需分配,通过缺页中断申请(进程虚拟页面和物理页面并不是一一对应)

1. 管理的内存从哪里来?(初始化)

744 图13.5

memory.c第443行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void mem_init(long start_mem, long end_mem)
{
int i;

HIGH_MEMORY = end_mem;
for (i=0 ; i<PAGING_PAGES ; i++)
mem_map[i] = USED;//内核使用的那些页初始化被使用了
//算出开始页的索引号
i = MAP_NR(start_mem);
end_mem -= start_mem;
end_mem >>= 12;//除以4096(4K)一页的大小
while (end_mem-->0)
mem_map[i++]=0;
}

2. 进程的页目录项和页表项的复制

742 图13-2

memory.c第118行,这段代码是相当有意思的,在创建进程时调用,分配一页内存存放页表项,并复制源进程的页表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 new_page;
unsigned long nr;

//没有对齐就是异常了
if ((from&0x3fffff) || (to&0x3fffff))
panic("copy_page_tables called with wrong alignment");
//页目录表的偏移位置 (from >> 22) << 2
from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
to_dir = (unsigned long *) ((to>>20) & 0xffc);
//2^22 = 4M 一张页表管理4M的内容,右移22计算出需要几张页表
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 */
// 7 代表 U/S W/R P第0,1,2位置1
*to_dir = ((unsigned long) to_page_table) | 7;
//页表每项4字节,一共1024项
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
this_page = *from_page_table;
if (!this_page)
continue;
if (!(1 & this_page)) {
if (!(new_page = get_free_page()))
return -1;
read_swap_page(this_page>>1, (char *) new_page);
*to_page_table = this_page;
*from_page_table = new_page | (PAGE_DIRTY | 7);
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]++;
}
}
}
invalidate();
return 0;
}

3. 物理内存时如何分配给线性地址的

743 图13-4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static unsigned long put_page(unsigned long page,unsigned long address)
{
unsigned long tmp, *page_table;

/*
page是索引值
address是物理地址
*/

/* NOTE !!! This uses the fact that _pg_dir=0 */

if (page < LOW_MEM || page >= HIGH_MEMORY)
printk("Trying to put page %p at %p\n",page,address);
if (mem_map[(page-LOW_MEM)>>12] != 1)
printk("mem_map disagrees with %p at %p\n",page,address);
//从物理地址得到页表项
page_table = (unsigned long *) ((address>>20) & 0xffc);
//*page_table 是页表项的内容,&1是表示页表项已经存在,没有就重新分配一页
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 | 7;
/* no need for invalidate */
return page;
}

4. 页面出错异常处理机制(写时复制和需求加载的01基础)

4.1 引起异常的条件
  • 地址变换过程中页目录项或页表项存在位P=0

  • 没有足够的特权访问指定的页面

4.2 异常的结果

cr2寄存器存放出错的线形地址
栈中出错码信息

  • 位0 p=0 页面不存在 p=1 违反页面保护权限
  • 位1 w/r =0 读引起 w/r =1 写引起
  • 位2 u/s =0 执行超级用户代码
4.3 异常处理过程

trap.c 第203行

1
2
3
4
5
6
void trap_init(void)
{
......
set_trap_gate(14,&page_fault);//设置页面异常处理函数
......
}

page.s详细分析

  • 写时复制 : 页面不共享时,设置可写则返回,共享时分配一页并复制
  • 缺页处理: 是否在交换设备中,存在则交换回来,不存在则分配一页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.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 #测试页存在位
jne 1f
call _do_no_page #缺页处理函数 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

5. 到底怎么分配物理内存

get_free_page()函数的分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");

repeat:
__asm__("std ; repne ; scasb\n\t"
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t"
"sall $12,%%ecx\n\t"
"addl %2,%%ecx\n\t"
"movl %%ecx,%%edx\n\t"
"movl $1024,%%ecx\n\t"
"leal 4092(%%edx),%%edi\n\t"
"rep ; stosl\n\t"
"movl %%edx,%%eax\n"
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1)
:"di","cx","dx");
if (__res >= HIGH_MEMORY)
goto repeat;
if (!__res && swap_out())
goto repeat;
return __res;
}