段页结合的实际内存管理(虚拟内存)--OS

段页结合的实际内存管理

如何让操作系统既支持段又支持页?

虚拟内存:我们让应用程序分为段,然后映射到一段虚拟内存中,再让虚拟内存映射到物理内存中的页中,这样就完成了段和页的结合

段、页同时存在时的重定位(地址翻译)

  • 根据段号找到偏移求出虚拟地址
  • 根据虚拟地址求出页号找到物理地址

一个实际的段、页式内存管理

内存管理核心:内存分配

  • 分配段、建段表:分配页、建页表
  • 进程带动内存使用的图谱
  • 从进程fork中的内存分配开始

在这里插入图片描述
使用内存分为五步

  • 分配段
  • 建段表
  • 分配页
  • 建页表
  • 重定位

1.分配段、建段表

fork()->sys_fork->copy_process

//在linux/kernel/fork.c中
int copy_process(int nr, long ebp, ...)
{
    ...
    copy_mem(nr, p);
    ...
}
int copy_mem(int nr, task_struct *p) //nr表示第几个进程,p为PCB
{
    unsigned long new_data_base;
    new_data_base = nr*0x4000000; // 64M*nr ,这一步完成了虚拟内存的分割
    set_base(p->ldt[1], new_data_base);
    set_base(p->ldt[2], new_data_base);
    /*上面两句完成了段表的初始化,这里将64M的内存既用作代码段又用作数据段,因此基址是相同的*/
}

在这里插入图片描述

在上面的程序中,每个进程占64M的虚拟地址空间,互不重叠,因此,他们可以共用一套页表,但是实际中页号是可能重叠的,因此需要有单独的页表

2.分配内存、建页表

int copy_mem(int nr, task_struct *p)
{
    unsigned long old_data_base;
    old_data_base = get_base(current->ldt[2]);
    copy_page_tables(old_data_base, new_data_base, data_limit); 
    /*子进程与父进程共用一套页表,子进程不需要分配内存,因为父进程已经分配好了,子进程只需要跟父进程拷贝一份相同的页表,传递两个虚拟地址,重新建一个页表,页表内容和父进程相同*/
}
int copy_page_tables(unsigned long from, unsigned long to, long size)
{
    from_dir = (unsigned long *)((from>>20) & 0xffc);
    to_dir = (unsigned long *)((to>>20) & 0xffc);
    size = (unsigned long)(size+0x3fffff)>>22;
    for(; size-->0; from_dir++,to_dir++)
    {
        from_page_table=(0xfffff000&from_dir);
        to_page_table = get_free_page();
    }
}

我们解释一下int copy_page_tables(...)函数
在这里插入图片描述

from_dir = (unsigned long *)((from>>20) & 0xffc);

from是32位虚拟地址,我们将它右移22位就得到页目录号了,再乘于4,因为一个页目录大小为4个字节,因此相当于右移20位

from_dir就是一个指向页目录号的指针,接下来,就是根据这个指针找到每一个页号和对应的页框号

for(; size-->0; from_dir++,to_dir++)
{
    to_page_table = get_free_page(); /
    *分配一个物理内存页来保存页表,就是在mem_map中找一段没有被用过的内存*/
    *to_dir = ((unsigned long)to_page_table) | 7;
}

接下来,就是将父进程的页表拷贝到子进程中

// nr = 1024;
for(; nr-->0; from_page_table++; to_page_table++)
{
    this_page = *from_page_table;
    this_page &=~2; //只读,父进程子进程共享一个页
    *to_page_table = this_page;
    *from_page_table = this_page;
    this_page -= LOW_MEM;
    this_page >> = 12;
    mem_map[this_page]++;  //这一页被共享了,当其中一个释放,还有其他的在使用,因此要+1
}

这里如果你想给子进程单独设置一个页,只要调用get_free_page()得到一个空闲的内存地址,再将其赋给子进程的页表就可以了。

3.重定位

  • 通过逻辑地址找到虚拟地址(MMU完成)
  • 通过虚拟地址找到物理地址(MMU完成)

在这里插入图片描述

在上面的程序中,如果我们对父进程指向p=0x300, *p=7,那么父进程就会在通过重定位找到物理地址,然后将7写入,然后父进程fork()一个子进程,因为公用的是一套页表,并且将页表置位只读,因此子进程指向p=0x300, *p=8时,就会重新申请一段内存,修改页表,然后MMU重新计算,然后执行*p = 8,这样就实现了进程之间的分离。

猜你喜欢

转载自blog.csdn.net/jump_into_zehe/article/details/106103433
今日推荐