前不久,群里又有一个分享让我很期待:《Linux虚拟内存》。一天晚上我们加班的时候,在讨论虚拟内存的概念时,领导发现有几个同事对虚拟内存理解的不是很清楚,所以特地给这个学生选了题目(笑)。之前学习了一些操作系统的概念,主要是毕业后四年对大学的浪费感到恼火。有点心疼自己的计算机专业,就抽空在网易云课堂看了哈工大的操作系统开放课。还看了一本比较浅显的操作系统的书《Linux内核设计与实现》,去年用C写了一个简单的服务器,也对系统的底层知识有了更多的了解。得益于这些知识,让我对应用层的知识有了更多的掌控感,对我上次排查问题的时候也有帮助。前几天又有同事来问另外一个虚拟内存相关的问题,才发现自己对虚拟内存的理解还不够深入,有些概念有些矛盾。于是翻了翻资料,重新整理知识,希望下次能用的更顺手。虚拟内存的由来毋庸置疑,虚拟内存绝对是操作系统中最重要的概念之一。我想主要是因为内存的重要“战略地位”。CPU太快了,但是又小又功能单一,其他I/O硬件支持各种花哨的功能,但是相对于CPU来说,就太慢了。所以它们之间需要润滑剂作为缓冲,这就是记忆发挥作用的地方。在现代操作系统中,多任务处理是标准的。多任务并行极大地提高了CPU利用率,但是会导致多个进程之间在内存操作上发生冲突。虚拟内存的概念就是为了解决这个问题而提出的。上图是对虚拟内存最简单直观的解释。操作系统有一块物理内存(中间部分),有两个进程(实际上更多)P1和P2。操作系统分别偷偷告诉P1和P2,我的全部内存都是你的,你想怎么用就怎么用。.但实际上,操作系统只是为他们划了一块大蛋糕。这些内存说是给了P1和P2,其实只是给了一个序号。只有当P1和P2真正开始使用这些内存时,系统才开始使用tumbling和shifting来拼凑进程的各个块。P2自认为是在使用A内存,但实际上已经被系统悄悄重定向到了真正的B。甚至,P1和P2共享C内存的时候,他们自己都不知道。这种欺骗操作系统进程的手段就是虚拟内存。对于P1和P2这样的进程,它们都认为自己占用了整块内存,它们并不知道也不需要关心它们使用的是物理内存的哪一段。分页和页表虚拟内存是操作系统中的一个概念。对于操作系统来说,虚拟内存就是一个对照表。当P1获取A内存中的数据时,应该去物理内存的A地址寻找B内存。in的数据应该到物理内存的C地址。我们知道系统中的基本单位是Byte字节。如果虚拟内存的每个字节对应物理内存的地址,则每个表项至少需要8个字节(32位虚拟地址->32位物理地址),4G内存的情况下,需要32GB的空间来存储对比表,那么这个表太大放不下真实的物理地址,所以操作系统引入了页(Page)的概念。系统启动时,操作系统将整个物理内存以4K为单位划分为页。之后在分配内存的时候,都是以页为单位的,所以虚拟内存页对应物理内存页的映射表就大大减少了。4G内存只需要一个8M的映射表。一些没有被进程使用的虚拟内存,不需要保存映射关系,而且Linux还为大内存设计了多级页表,可以进一步减少一页的内存消耗。操作系统的虚拟内存到物理内存的映射表称为页表。内存寻址与分配我们知道,通过虚拟内存机制,每个进程都认为自己占据了所有的内存。当进程访问内存时,操作系统会将进程提供的虚拟内存地址转换成物理地址,然后再去对应的物理地址。检索数据。CPU中有一种硬件,内存管理单元MMU(MemoryManagementUnit)专门用来翻译虚拟内存地址。CPU还为页表寻址设置了缓存策略,由于程序局部性,其缓存命中率可达98%。上面的情况是页表中存在虚拟地址到物理地址的映射,如果进程访问的物理地址还没有分配,系统就会产生缺页中断。当中断处理完成后,系统切换到内核态,成为进程的虚拟地址。分配物理地址。函数式虚拟内存不仅通过内存地址转换解决了多进程访问内存冲突的问题,还带来了更多的好处。进程内存管理帮助进程管理内存,主要体现在:内存完整性:由于虚拟内存对进程的“欺骗”,每个进程都认为自己获取的内存是一个连续的地址。我们在编写应用程序时,不需要考虑大块地址的分配。我们总是认为系统有足够大的内存块。安全性:进程访问内存时,必须通过页表来寻址。操作系统可以为页表中的每一项添加各种访问权限标志,实现内存权限控制。数据共享通过虚拟内存更容易共享内存和数据。进程加载系统库时,总是先分配一块内存,将磁盘中的库文件加载到这块内存中。直接使用物理内存时,由于物理内存地址是唯一的,即使系统发现同一个库在系统中加载了两次,但是每个进程指定的加载内存都不一样,系统也无能为力关于它。使用虚拟内存时,系统只需要将进程的虚拟内存地址指向库文件所在的物理内存地址即可。如上图所示,进程P1和P2的B地址都指向物理地址C。通过使用虚拟内存来使用共享内存也很简单。系统只需要将每个进程的虚拟内存地址指向系统分配的共享内存地址即可。SWAP虚拟内存允许辅助进程“扩展”内存。前面我们提到,虚拟内存通过缺页中断为进程分配物理内存。记忆总是有限的。如果所有的物理内存都被占用了怎么办?Linux提出了SWAP的概念。在Linux中,可以使用SWAP分区。当分配了物理内存,但可用内存不足时,会将暂时不用的内存数据先放到磁盘上,让有需要的进程先使用,需要的进程再使用。当加载数据时,数据被加载到内存中。通过这种“交换”技术,Linux可以让进程使用更多的内存。常见问题我也有很多关于虚拟内存的问题。32位和64位最常见的问题是32位和64位。CPU通过物理总线访问内存,因此访问地址的范围受到机器总线数量的限制。在32位机器上,有32条总线,每条总线有高低两个电位,分别代表bit1和bit0。访问的最大地址是2^32bit=4GB,所以在32位机器上插入超过4G的内存是无效的,CPU不能访问超过4G的内存。但是,64位机器没有64位总线,它们的最大内存受操作系统限制。Linux目前最大支持256G内存。按照虚拟内存的概念,在32位系统上运行64位软件也不是不可能,但是由于系统对虚拟内存地址的结构设计,64位虚拟地址不能在32位系统中使用.直接操作物理内存操作系统使用虚拟内存。想直接操作内存怎么办?Linux会将每个设备映射到/dev/目录中的文件。我们可以通过这些设备文件直接操作硬件,内存也不例外。在Linux中,内存设置被映射为/dev/mem,root用户可以通过读写这个文件直接操作内存。JVM进程占用虚拟内存过多。在使用TOP查看系统性能时,我们会发现在VIRT这一列,Java进程会占用大量的虚拟内存。出现这个问题的原因是Java使用了Glibc的Arena内存池分配了大量的虚拟内存而没有使用。另外,Java读取的文件也会被映射为虚拟内存。在虚拟机的默认配置下,每个Java线程栈都会占用1M的虚拟内存。具体可以查看为什么多线程程序在linux下如此消耗虚拟内存。实际占用的物理内存取决于RES(驻留)列。该列的值是实际映射到物理内存的大小。常用的管理命令我们也可以自己管理Linux虚拟内存。有很多方法可以检查系统内存状态。free、vmstat等命令可以输出当前系统内存状态。需要注意的是,可用内存不仅仅是空闲列。由于操作系统的惰性特性,大量的buffer/cache在进程不再使用后不会立即清除。如果之前使用它们的进程再次运行,它可以继续使用。必要时也可以使用它们。另外,通过cat/proc/meminfo可以查看系统内存使用的详细信息,包括脏页的状态。有关详细信息,请参阅:/PROC/MEMINFO之谜。如果pmap想单独查看某个进程的虚拟内存分布情况,可以使用pmappid命令,该命令会从低地址到高地址列出各段虚拟内存的占用情况。可以加上-XX参数输出更详细的信息。修改内存配置,我们也可以修改Linux系统配置,使用sysctlvm[-options]CONFIG或者直接读写/proc/sys/vm/目录下的文件来查看和修改配置。SWAP操作虚拟内存的SWAP特性并不总是有益的。让进程不停地在内存和磁盘之间交换大量的数据,会极大地占用CPU,降低系统的运行效率,所以有时候我们并不想使用swap。我们可以修改vm.swappiness=0,设置内存尽量少使用swap,或者干脆用swapoff命令禁用SWAP。总结虚拟内存的概念非常容易理解,但是会衍生出一系列非常复杂的知识。这篇文章只讲了一些基本原理,跳过了很多细节,比如段寄存器在虚拟内存寻址中的使用,操作系统中使用虚拟内存增强缓存,缓冲区的应用等。
