Linux虚拟内存


前言


提示:以下是本篇文章正文内容

一、虚拟内存的引入

1.基本概念

虚拟内存:

用辅助存储器(一般指磁盘)作为内存的补充。虚拟内存允许进程执行时只将部分程序放入内存,因此程序可以比物理内存大。虚拟内存的大小受计算机寻址机制和可用辅助存储器容量大限制,而不受内存容量的限制。

特征:

①运行进程时只把现在要执行的页/段装入内存,其余页/段放在外存,需要时再利用请求调入页/段功能和置换功能将其调入内存。
②在逻辑上扩充内存容量
③访问速度接近于内存,没位(bit)成本接近于外存。

虚拟地址:即逻辑地址,虚拟内存中某个字节的地址,假设该字节在内存中(其实可能位于磁盘,但这对用户是透明的)

虚拟地址空间:分配给某个进程(程序)的虚拟地址范围

实地址:即物理地址, 物理内存中某个字节的地址

驻留集:进程运行时装入内存的部分

工作集:在 t 时刻,进程在过去的N个时间单位内访问的页面集合(活跃页面)

内存管理单元MMU:集成在CPU中,或作为一个协处理器
功能:分解逻辑地址;逻辑地址到物理地址的转换;查找更新快表TLB;进程切换时清空TLB;发出缺页中断或越界中断;设置和检查页表中各个特征位。

2.局部性原理

上一篇笔记中提了一点关于局部性原理的特点,这里更详细的介绍。

局部性原理表现在以下两个方面:

(1)时间局部性

如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。
原因:是由于在程序中存在着大量的循环操作

(2)空间局部性

一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内
原因:指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。

快表、 页高速缓存以及虚拟内存技术从广义上讲,都是属于高速缓存技术。这个技术所依赖的原理就是局部性原理。局部性原理既适用于程序结构,也适用于数据结构

3.引入

从前面我们了解到:物理内存必须得是分页管理的;对用户来说是分段的。

但是用户程序最终能在内存上面跑,肯定需要某种机制或者转化使得以用户程序的视角看起来内存是分段的,以物理内存的视角看起来又是分页的,这种机制就是虚拟内存

时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。

空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。

虚拟内存是一种和物理内存一样的东西,每一个字节都有对应的地址。但是有一点与物理内存不同,从它的名字就能看出来,“虚拟”:即实际上并不存在,它只是一种机制,是用程序表示的。它的作用就是让上层程序看起来是内存是分段的,而实际上是分页的,如图

在这里插入图片描述

用户程序使用了一段内存,首先会在虚拟内存上面找到一段空的内存,然后将用户程序使用的内存映射到这段内存上,然后虚拟内存再将这段内存映射到物理内存上
在这里插入图片描述
用户程序使用的逻辑内存经过了两次映射才达到物理内存,第一次映射是段的映射,需要段表;第二次是页的映射,需要页表。

逻辑地址究竟是如何变成物理地址的呢?

逻辑地址是段号+偏移(CS:IP)组成的,首先根据段号在段表中找到虚拟内存的段基址,然后加上偏移得到虚拟地址(即在虚拟内存上面的地址),格式是:页号+偏移。然后根据页号在页表中找到对应的页框号,再加上偏移得到最后的物理地址。实现了逻辑地址与物理地址的对应。也就是重定位操作。

二、实现虚拟内存

1.载入内存

内存管理的核心就是内存分配,所以从程序放入内存、使用内存开始
在这里插入图片描述
首先为程序分配虚拟内存,将程序中的各段分配到虚拟内存的闲置空间中,然后再将虚拟内存中的各段再分成若干页,映射到物理内存的页框中

2. 分配虚存、 建段表

创建进程使用的是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
}

做完上面三步,内存情况:
在这里插入图片描述

4.重定位

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

如:
在这里插入图片描述

对父进程指向p=0x300, *p=7,父进程就会在通过重定位找到物理地址,然后将7写入

然后父进程fork()一个子进程,因为公用的是一套页表,并且将页表置位只读,因此子进程指向p=0x300, p=8时,就会重新申请一段内存,修改页表,然后MMU重新计算,然后执行p = 8,这样就实现了进程之间的分离。


总结

提示:这里对文章进行总结:

虚拟内存的实现
1.分配段
2.建段表
3.分配页
4.建页表
5.重定位

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Super.Bear

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值