Chinaunix首页 | 论坛 | 博客
  • 博客访问: 253524
  • 博文数量: 44
  • 博客积分: 1052
  • 博客等级: 少尉
  • 技术积分: 742
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-17 16:51
文章分类

全部博文(44)

文章存档

2013年(7)

2012年(14)

2011年(23)

分类: LINUX

2013-03-17 22:03:58

描述linux 0.11的内存管理主要内容。


1:内存初始化
linux 0.11最大支持16MB的物理内存。
main函数和mem_init函数对内存进行了初始化。
主要使用数组mem_map[]来标记相应的内存页是否被占用。

memory_end是用BIOS中断调用得到的实际内存大小。
if (memory_end > 16 * 1024 * 1024)
    memory_end = 16 * 1024 * 1024;           # 因此最大只支持16MB内存
if (memory_end > 12*1024*1024)            
    buffer_memory_end = 4 * 1024 * 1024;   # buffer_memory_end为高速缓存末端地址,其大小于机器总内存大小相关
else if (memory_end > 6 * 1024 * 1024)
    buffer_memory_end = 2 * 1024 * 1024
else
    buffer_memory_end = 1*1024*1024

main_memory_start = buffer_memory_end;
mem_init(main_memory_start, memory_end); 

在mem_init中会对mem_map[]数组进行初始化。
在1MB~16MB之间,共有(15 * 1024 * 1024) >> 12 = 3840页。
定义数组mem_map[3840]对应于这段内存的每一页,在main_memory_start和memory_end之间的页,相应的mem_map[i]的值
初始化为0,表示未使用,其余的项初始化为100,表示被占用。 


2:基本页面分配函数
有几个基本的页面分配和释放的函数。
get_free_page():返回一个空闲页面的物理地址, 该函数就是在mem_map数组中查找值为0的项,然后转换成页面物理地址返回。
free_page(phy_addr):释放phy_addr指向的页面, 释放操作就是将phy_addr在mem_map中对应项的值进行减1。
get_empty_page(line_addr):该函数的参数指定的是线性地址,要求获得一页物理内存,并用line_addr指向这页内存。

void get_empty_page(unsigned long address)
{
    unsigned long tmp;
    
    if (!(tmp=get_free_page()) || !put_page(tmp, address)) {
        free_page(tmp);
        oom();        # 内存不够
    }
}
明显该函数先调用get_free_page获得一页物理内存, 然后用put_page对这页物理内存的物理地址和线性地址之间建立映射。

建立映射的过程:

对于32位的线性地址, 高10位表示页目录索引, 中间10位为页表索引, 低12位为页内偏移。
因此给定一个线性地址,我们就能通过页目录基地址和线性地址高10位来确定它的页目录项。
在Linux 0.11中,页目录基地址就是0。

put_page:
    page_table = (unsigned long *) ((line_addr >> 20) & 0xffc)      #  获得页目录项的指针
    如果该页目录项所指向的页表是存在的, 则利用线性地址的中间10位,定位到页表中的相应页表项,
并将物理地址保存进去即可建立映射。  若页表不存在,则用get_free_page首先分配一页作为页表,再去建立映射。


不管是建立映射还是进行页表拷贝,都是先考虑页目录,再考虑页表。

3:sys_fork与copy_page_tables函数
在sys_fork时会调用copy_process,copy_process中会调用copy_mem函数, 该函数会将父进程的页表拷贝给子进程,
这是父进程和子进程会共享相应的代码和数据段,只有当父进程或子进程对共享的内存进行写操作时,才会为子进程分配内存,即为写时复制。

copy_mem是调用copy_page_table进行页表拷贝的。

copy_page_table(old_data_base, new_data_base, data_limit)
将父进程的线性地址old_data_base ~ old_data_base+data_limit对应的页表,拷贝给子进程。
在拷贝过程中,将每个页表项设定为只读, 并且执行相应的mem_map[i]++(相当于添加引用计数)

4:页出错异常处理
有两种不同的页出错:
i)页表项指向的页不存在,即页表项的存在位的值为0.
ii)页保护机制。 写只读页面时出错。

但出现页错误时,会发生int 14中断。系统会执行_page_fault:异常处理代码。
该代码会根据两种不同的页错误,分别执行do_no_page和do_wp_page

do_wp_page(error_code, address)
该函数主要是实现了写时复制功能, 在copy_page_table时,将父进程和子进程的页表都设定成了只读,当访问了其中一个页面后,
会触发中断并执行do_wp_page函数。
此函数会分配一页新的物理内存, 并将相应的页表项设成可读写。


do_no_page(error_code, address)
该函数可处理两种情况:
i)在应用分配内存时,内核并不会实际分配物理内存, 只有在访问相应内存时,才会分配。
ii)在执行exec系列函数时,同样只有在访问相应内存时,才会去读文件,
对于第一种情况, do_no_page直接调用get_empty_page函数,获取一页内存。
对于第二种情况,有两步过程。
i)调用share_page函数。 该函数的主要目的是共享代码和数据段。 如果一个可执行文件,已经有一个进程实例在执行,那么
新进程可以和其他进程共享该可执行文件的代码和数据段。
ii)如果只执行过一次该文件, 那就先调用get_free_page函数,将文件内容读入内存,然后用put_page函数建立映射。
阅读(3907) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~