- 前言
- 一、虚拟内存的引入
- 1.基本概念
- 2.局部性原理
- 3.引入
- 二、实现虚拟内存
- 1.载入内存
- 2. 分配虚存、 建段表
- 3.分配内存、建页表
- 4.重定位
- 总结
提示:以下是本篇文章正文内容
一、虚拟内存的引入 1.基本概念虚拟内存:
用辅助存储器(一般指磁盘)作为内存的补充。虚拟内存允许进程执行时只将部分程序放入内存,因此程序可以比物理内存大。虚拟内存的大小受计算机寻址机制和可用辅助存储器容量大限制,而不受内存容量的限制。
特征:
①运行进程时只把现在要执行的页/段装入内存,其余页/段放在外存,需要时再利用请求调入页/段功能和置换功能将其调入内存。 ②在逻辑上扩充内存容量 ③访问速度接近于内存,没位(bit)成本接近于外存。
虚拟地址:即逻辑地址,虚拟内存中某个字节的地址,假设该字节在内存中(其实可能位于磁盘,但这对用户是透明的)
虚拟地址空间:分配给某个进程(程序)的虚拟地址范围
实地址:即物理地址, 物理内存中某个字节的地址
驻留集:进程运行时装入内存的部分
工作集:在 t 时刻,进程在过去的N个时间单位内访问的页面集合(活跃页面)
内存管理单元MMU:集成在CPU中,或作为一个协处理器 功能:分解逻辑地址;逻辑地址到物理地址的转换;查找更新快表TLB;进程切换时清空TLB;发出缺页中断或越界中断;设置和检查页表中各个特征位。
2.局部性原理上一篇笔记中提了一点关于局部性原理的特点,这里更详细的介绍。
局部性原理表现在以下两个方面:
(1)时间局部性
如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。 原因:是由于在程序中存在着大量的循环操作
(2)空间局部性
一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内 原因:指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
快表、 页高速缓存以及虚拟内存技术从广义上讲,都是属于高速缓存技术。这个技术所依赖的原理就是局部性原理。局部性原理既适用于程序结构,也适用于数据结构
3.引入从前面我们了解到:物理内存必须得是分页管理的;对用户来说是分段的。
但是用户程序最终能在内存上面跑,肯定需要某种机制或者转化使得以用户程序的视角看起来内存是分段的,以物理内存的视角看起来又是分页的,这种机制就是虚拟内存
时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。
空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。
虚拟内存是一种和物理内存一样的东西,每一个字节都有对应的地址。但是有一点与物理内存不同,从它的名字就能看出来,“虚拟”:即实际上并不存在,它只是一种机制,是用程序表示的。它的作用就是让上层程序看起来是内存是分段的,而实际上是分页的,如图
用户程序使用了一段内存,首先会在虚拟内存上面找到一段空的内存,然后将用户程序使用的内存映射到这段内存上,然后虚拟内存再将这段内存映射到物理内存上 用户程序使用的逻辑内存经过了两次映射才达到物理内存,第一次映射是段的映射,需要段表;第二次是页的映射,需要页表。
逻辑地址究竟是如何变成物理地址的呢?
逻辑地址是段号+偏移(CS:IP)组成的,首先根据段号在段表中找到虚拟内存的段基址,然后加上偏移得到虚拟地址(即在虚拟内存上面的地址),格式是:页号+偏移。然后根据页号在页表中找到对应的页框号,再加上偏移得到最后的物理地址。实现了逻辑地址与物理地址的对应。也就是重定位操作。
二、实现虚拟内存 1.载入内存内存管理的核心就是内存分配,所以从程序放入内存、使用内存开始 首先为程序分配虚拟内存,将程序中的各段分配到虚拟内存的闲置空间中,然后再将虚拟内存中的各段再分成若干页,映射到物理内存的页框中
创建进程使用的是fork()系统调用,从前面可以知道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)
{
unsigned long new_data_base;
new_data_base = nr*0x4000000; // nr * 64M
set_base(p_>ldt[1], new_data_base); // 代码段 ldt(段表)
set_base(p->ldt[2], new_data_base); // 数据段
………………
}
调用fork(),然后调用sys_fork,进入copy_process后,在copy_process中调用copy_men();
copy_men()函数就是给该进程在虚拟内存上分配内存空间的, 形参 nr:第nr个进程 p:该进程的pcb
new_data_base = nr*0x4000000; // nr * 64M
给该进程在虚拟内存上分配一块64M的内存块。可以看到第0个进程内存区域就是0 ~ 64M,第一个进程64~128M,依次类推,互不重叠。然后将p的ldt[1]和ldt[2]都指向这块内存
ldt[1]和ldt[2]指的是数据段和代码段,数据段和代码段现在在虚拟内存上分配内存、建立段表完成
3.分配内存、建页表分配内存、建立页表,还是copy_mem()函数完成的
int copy_mem(int nr, task_struct *p)
{
unsigned long old_data_base;
old_data_base = get_base(currnet->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();
*to_dir = ((unsigned long) to_page_table) | 7;
}
}
简单分析:
old_data_base = get_base(currnet->ldt[2]);
得到当前进程的虚拟内存地址赋给old_data_base,再调用copy_page_tables()函数,
参数from和to:都是32为虚拟内存地址 from_dir指向一个父进程的页目录项(章) to_dir指向一个子进程的页目录项(章) 32位虚拟地址格式:
from_dir = (unsigned long *) ((from>>20) & 0xffc);
//from右移22位得到的是页目录号 ffc00000 ->1111 1111 1100 0000
//这里右移20 并与上0xffc 就是去前10位,
size是页目录项数
for (; size-->0; from_dir++, to_dir++)
{
from_page_table=(0xfffff000 & *from_dir);
to_page_table = get_free_page();
//分配一个物理内存页来保存页表,就是在mem_map中找一段没有被用过的内存
*to_dir = ((unsigned long) to_page_table) | 7;
}
from_dir就是一个指向页目录号的指针,根据这个指针找到每一个页号和对应的页框号,get_free_page()新建一个子进程的页目录表,然后将这个页目录表赋给to_dir,但是to_dir指向的表的内容是空的, get_free_page()函数:得到一段空闲的空间
unsigned long get_free_page(void)
{ register unsigned
long _res asm(“ax”);
_asm_(“std; repne;
scasb\n\t”
“movl %%edx,%%eax\n”
“D”(mem_map+PAGIG_PA
GES-1));
return _res; }
接下来就是填表,将父进程的页表拷贝到子进程中
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_MEN;
this->page >>= 12;
mem_map[this_page]++; 这一页被共享了,当其中一个释放,还有其他的在使用,因此要+1
}
做完上面三步,内存情况:
通过逻辑地址找到虚拟地址,通过虚拟地址找到物理地址(MMU自动完成)
如:
对父进程指向p=0x300, *p=7,父进程就会在通过重定位找到物理地址,然后将7写入
然后父进程fork()一个子进程,因为公用的是一套页表,并且将页表置位只读,因此子进程指向p=0x300, p=8时,就会重新申请一段内存,修改页表,然后MMU重新计算,然后执行p = 8,这样就实现了进程之间的分离。
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?