漫谈虚拟内存

虚拟内存是什么?它是对主存和I/O设备的抽象,这一点在漫谈进程和线程中已经提及过,也就是说,虚拟内存是将内存看做硬盘的高速缓存,内存中只保存程序的活动区域,根据需要在硬盘和内存之间传输数据;同时,虚拟内存为每个进程提供一个一致的地址空间,比如说32位计算机,每个进程的地址范围是0,4G。此外,虚拟内存保护每个进程的地址空间不被其他进程破坏,那么,通过何种方法使得每个进程的地址空间是独立的?看完本文,这个问题你自然就知道答案了。

内存管理的要求

  • 针对批处理系统,程序顺序执行,程序依次装入内存运行,一个新装入的程序会完全覆盖老的程序。
  • 针对分时系统,多个程序并发执行,要把尽可能多的程序装入内存,但是物理内存的空间是有限的,经常需要将程序换入换出,这样一来,系统性能就变差了;程序在内存中要区分开。
    内存分配

如上图,程序1、程序2、程序3装入到内存,而程序2运行完成被换出,内存空闲出20k,然后进来程序4,大小为25K,此时,只有两处空闲块,10K和20K,没有一处是符合条件的,应该怎么办?一个明显的办法就是将两块空闲区域进行合并,形成一个大小为30K的空闲块满足程序4。

注意:此时描述的内存的物理地址,物理内存使用的是连续地址,下面详细介绍物理地址。

物理地址

使用物理地址的缺点:当多个程序同时对某个地址进行操作时就会引起冲突,如下图所示,程序1和程序2中都有指令movl eax,(100)。针对这个问题怎么解决呢?

在装载程序的时候,修改指令的地址。例如程序2中的(100)+1000,其中1000代表这个程序的开始地址,而程序1中的(100)+0。这样做是很困难的,因为需要我们理解所有的指令。既然这个方法不好,那么有没有其他方法呢?答案就是逻辑地址

程序1、2地址冲突

逻辑地址

CPU有一个内存管理单元(MMU),它有一个基址寄存器,它保存着每个程序的开始地址,比如说程序1的基址寄存器是1000,偏移量是200,转换成物理地址是1200。

逻辑地址

分页

假设一个程序很大,需要占据所有内存,而内存管理的一个要求就是把尽可能多的程序装入内存,两者相互矛盾。应对方法就是分页,就是说每个程序开始运行时只会加载部分数据到内存中操作系统会为每个进程维护一个页表,页表是维护虚拟页和物理页的映射关系,当页表中的虚拟页对应的物理页是空白时,操作系统会发生缺页中断。分页的理论依据是局部性原理(空间局部性+时间局部性),也正是这个原因,程序在大部分时间内不需要进行页面置换。如果发生缺页中断,缺页中断处理程序读取磁盘,选择一个空闲物理页面,修改页表,重新执行程序。

  • 注意事项
  • 每个进程都要有一个页表,进程PCB有指向页表的指针
  • 页表访问要非常快(硬件缓存来拯救:转换缓冲区--TLB)
  • 页表可能非常大(2^32 的内存空间,每个页大小2^12 ,页表中需要2^20个页表条目,假设每个条目4Byte,需要4M空间来存放表,而且每个进程都需要4M,这是非常占用空间的。可以采用多级页表,反向页表等技术来解决)。
分页
  • 分页具体流程 以CPU执行MOV (0x560) EAX为例,CPU内部会将逻辑地址进行拆分成页号和偏移量,然后将逻辑地址转换成物理地址。
    分页流程

页面置换算法

  • 内存是有限的,不可能把所有的页面都装进来,缺页时需要进行页面置换。
  • 页面置换背后是个通用的问题(Web服务器的缓存、Redis、Memcached的缓存等等)。FIFO(先进先出)先进先出算法思想很简单,当内存满了,优先置换出最先进入内存的页面。但是它存在一个问题:经常被访问的数据有可能被换入换出,下面我就举个简单的栗子。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4
FIFO

LRU(最近最少使用)

LRU算法就是所有页用栈组成,当栈满了,且新增加元素没有命中,则将栈底元素淘汰,新增元素放到栈顶;当栈满了,且新增元素命中,则只需要将新增元素移动到栈顶位置即可。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4

LRU

Clock算法

Clock算法是LRU算法的近似实现,它为每个页加一个引用位,默认值为0,无论读还是写,都置为1,它把所有的页组成一个循环队列,选择淘汰页的时候,扫描引用位,如果是1则改成0(相当于再给该页面一次存活的机会),并扫描下一个;如果该引用位是0,则淘汰该页,换入新的页面。假设只有3个物理页面,逻辑页面的访问次序是: 3 4 2 6

Clock

分段

上述介绍的页表机制是面向机器的,而为了程序员更好的理解程序,我们的先辈们又提出了分段概念,就是将程序划分为若干段部分,每一段都有独立的功能,例如:代码段,数据段,栈,堆等。通过分段技术,我们把内存空间分成一个个可以自治的段,而且把内存从一维空间变成了一个二维空间。

段表结构

段页结合

段页结合流程:首先根据段表信息,将逻辑地址转换成另一个逻辑地址,在转换的过程中会判断偏移量是否超过指定长度,如果没有超过,则,则根据页表将逻辑地址转换成物理地址。

段页结合

虚拟内存具体实现

这里介绍Linux中的虚拟内存的具体实现,如下图,task_struct结构体是进程描述符,属于进程管理(PCB),其中,mm(memory manage)表示内存管理,它指向mm_struct结构体,它描述linux下进程的虚拟地址空间,它又包含两个重要字段:pgd、mmap,其中,pgd指向第一级页表的基址,而mmap指向一个vm_area_struct(区域结构)的链表,vm_start,vm_end分别表示数据的起始地址,vm_prot描述的是这个区域包含的所有页的读写许可权限;vm_flags描述这个区域是和别的进行共享的,还是该进程私有的

Linux是如何组织虚拟存储器

Linux缺页中断:MMU(内存管理单元)试图翻译一个虚拟地址A,当这个虚拟地址对应的物理地址不在内存中是,触发一个缺页中断。虚拟地址A是合法的吗?地址A在某个区域地址内吗(vm_start,vm_end),如果不存在,segement fault!段错误。如果存在,则接着判断进程是否可以读,写,执行这个区域内页面的权限?如果没有权限,触发保护异常。经过上诉两项判断,如果都是正常的,最后才开始真正的缺页处理,从硬盘装载数据,修改页表。


image

欢迎关注微信公众号:木可大大,所有文章都将同步在公众号上。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏鹅厂少年的奇妙之旅

【春节红包系列】一次"内存泄漏"引发的血案

2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写。重编发布一段时间后,结果发现新发布的Svr的...

46810
来自专栏微服务生态

性能分析系列-小命令保证大性能

最近在工作中经常和性能压测工作打交道,积累了一些性能分析经验,我觉得这些经验对每一个开发者都有帮助的,能开发出性能高的代码也是我们的最终目标。

1105
来自专栏智能大石头

NewLife.Net——构建可靠的网络服务

1593
来自专栏代码世界

Python中的logger和handler到底是个什么鬼

最近的任务经常涉及到日志的记录,特意去又学了一遍logging的记录方法。跟java一样,python的日志记录也是比较繁琐的一件事,在写一条记录之前,要写好多...

3349
来自专栏Golang语言社区

【Go 语言社区】用Go实现的简易TCP通信框架--转

接触到GO之后,GO的网络支持非常令人喜欢。GO实现了在语法层面上可以保持同步语义,但是却又没有牺牲太多性能,底层一样使用了IO路径复用,比如在LINUX下用了...

41310
来自专栏智能大石头

NewLife.Net——构建可靠的网络服务

老规矩,先上源码:https://github.com/nnhy/NewLife.Net.Tests

1240
来自专栏xingoo, 一个梦想做发明家的程序员

AngularJS 技术总结

学习AngularJS,并且能在工作中使用到,算是很幸运了。因此本篇也会搜集各种资料,进行分享。 书籍分享 AngularJS权威指南 常用链接 Angula...

20510
来自专栏james大数据架构

SQL SERVER 内存分配及常见内存问题 简介

一、问题: 1、SQL Server 所占用内存数量从启动以后就不断地增加:       首先,作为成熟的产品,内存溢出的机会微乎其微。对此要了解SQL SER...

35410
来自专栏Python中文社区

Python爬虫抓取收集考试大纲

專 欄 ❈ Garfield_Liang,Python中文社区专栏作者。 博客地址:http://www.jianshu.com/u/cac1d39abfa9 ...

23710
来自专栏学习有记

AlwaysOn 进阶 Level 1:What is "SQL Server AlwaysOn"?

1493

扫码关注云+社区

领取腾讯云代金券