前不久组内又有一次我比较期待的分享:”Linux 的虚拟内存”。是某天晚上加班时,咱们讨论虚拟内存的概念时,leader 发现几位同事对虚拟内存认识不清后,特地给这位同窗挑选的主题(笑)。html
我以前了解一些操做系统的概念,主要是毕业后对本身大学四年的荒废比较懊恼,以为本身有些对不起计算机专业出身,因而在工做之余抽出时间看了哈工大在网易云课堂的操做系统公开课,本身也读了一本讲操做系统比较浅的书 《Linux内核设计与实现》,并且去年本身用 C 写简单的服务器时,也追根究底了解了更多的系统底层知识。多亏了这些知识,让我对应用层的知识更有掌控感,也在上次排查问题时(从应用到内核查接口超时)助了我一臂之力。linux
前几天另外一位同事来问另外一个虚拟内存相关的问题,我才发现对于虚拟内存,个人理解还不够深入,一些概念还有些矛盾。因而翻一下资料从新整理一下这些知识,但愿下次在用到它们时能更顺畅。git
转载随意,文章会持续修订,请注明来源地址:https://zhenbianshu.github.io 。github
毋庸置疑,虚拟内存绝对是操做系统中最重要的概念之一。我想主要是因为内存的重要”战略地位”。CPU太快,但容量小且功能单一,其余 I/O 硬件支持各类花式功能,但是相对于 CPU,它们又太慢。因而它们之间就须要一种润滑剂来做为缓冲,这就是内存大显身手的地方。api
而在现代操做系统中,多任务已经是标配。多任务并行,大大提高了 CPU 利用率,但却引出了多个进程对内存操做的冲突问题,虚拟内存概念的提出就是为了解决这个问题。缓存
上图是虚拟内存最简单也是最直观的解释。安全
操做系统有一块物理内存(中间的部分),有两个进程(实际会更多)P1 和 P2,操做系统偷偷地分别告诉 P1 和 P2,个人整个内存都是你的,随便用,管够。可事实上呢,操做系统只是给它们画了个大饼,这些内存说是都给了 P1 和 P2,实际上只给了它们一个序号而已。只有当 P1 和 P2 真正开始使用这些内存时,系统才开始使用展转挪移,拼凑出各个块给进程用,P2 觉得本身在用 A 内存,实际上已经被系统悄悄重定向到真正的 B 去了,甚至,当 P1 和 P2 共用了 C 内存,他们也不知道。服务器
操做系统的这种欺骗进程的手段,就是虚拟内存。对 P1 和 P2 等进程来讲,它们都觉得本身占用了整个内存,而本身使用的物理内存的哪段地址,它们并不知道也无需关心。多线程
虚拟内存是操做系统里的概念,对操做系统来讲,虚拟内存就是一张张的对照表,P1 获取 A 内存里的数据时应该去物理内存的 A 地址找,而找 B 内存里的数据应该去物理内存的 C 地址。app
咱们知道系统里的基本单位都是 Byte 字节,若是将每个虚拟内存的 Byte 都对应到物理内存的地址,每一个条目最少须要 8字节(32位虚拟地址->32位物理地址),在 4G 内存的状况下,就须要 32GB 的空间来存放对照表,那么这张表就大得真正的物理地址也放不下了,因而操做系统引入了 页(Page)
的概念。
在系统启动时,操做系统将整个物理内存以 4K 为单位,划分为各个页。以后进行内存分配时,都以页为单位,那么虚拟内存页对应物理内存页的映射表就大大减少了,4G 内存,只须要 8M 的映射表便可,一些进程没有使用到的虚拟内存,也并不须要保存映射关系,并且Linux 还为大内存设计了多级页表,能够进一页减小了内存消耗。操做系统虚拟内存到物理内存的映射表,就被称为页表
。
咱们知道经过虚拟内存机制,每一个进程都觉得本身占用了所有内存,进程访问内存时,操做系统都会把进程提供的虚拟内存地址转换为物理地址,再去对应的物理地址上获取数据。CPU 中有一种硬件,内存管理单元 MMU(Memory Management Unit)
专门用来将翻译虚拟内存地址。CPU 还为页表寻址设置了缓存策略,因为程序的局部性,其缓存命中率能达到 98%。
以上状况是页表内存在虚拟地址到物理地址的映射,而若是进程访问的物理地址尚未被分配,系统则会产生一个缺页中断
,在中断处理时,系统切到内核态为进程虚拟地址分配物理地址。
虚拟内存不只经过内存地址转换解决了多个进程访问内存冲突的问题,还带来更多的益处。
它有助于进程进行内存管理,主要体如今:
经过虚拟内存更容易实现内存和数据的共享。
在进程加载系统库时,老是先分配一块内存,将磁盘中的库文件加载到这块内存中,在直接使用物理内存时,因为物理内存地址惟一,即便系统发现同一个库在系统内加载了两次,但每一个进程指定的加载内存不同,系统也无能为力。
而在使用虚拟内存时,系统只须要将进程的虚拟内存地址指向库文件所在的物理内存地址便可。如上文图中所示,进程 P1 和 P2 的 B 地址都指向了物理地址 C。
而经过使用虚拟内存使用共享内存也很简单,系统只须要将各个进程的虚拟内存地址指向系统分配的共享内存地址便可。
虚拟内存可让帮进程”扩充”内存。
咱们前文提到了虚拟内存经过缺页中断为进程分配物理内存,内存老是有限的,若是全部的物理内存都被占用了怎么办呢?
Linux 提出 SWAP 的概念,Linux 中可使用 SWAP 分区,在分配物理内存,但可用内存不足时,将暂时不用的内存数据先放到磁盘上,让有须要的进程先使用,等进程再须要使用这些数据时,再将这些数据加载到内存中,经过这种”交换”技术,Linux 可让进程使用更多的内存。
在了解虚拟内存时,我也有过不少的问题。
最多见的就是 32位和64位的问题了。
CPU 经过物理总线访问内存,那么访问地址的范围就受限于机器总线的数量,在32位机器上,有32条总线,每条总线有高低两种电位分别表明 bit 的 1 和 0,那么可访问的最大地址就是 2^32bit = 4GB,因此说 32 位机器上插入大于 4G 的内存是无效的,CPU 访问不到多于 4G 的内存。
但 64位机器并无 64位总线,并且其最大内存还要受限于操做系统,Linux 目前支持最大 256G 内存。
根据虚拟内存的概念,在 32 位系统上运行 64 位软件也并没有不可,但因为系统对虚拟内存地址的结构设计,64位的虚拟地址在32位系统内并不能使用。
操做系统使用了虚拟内存,咱们想要直接操做内存该怎么办呢?
Linux 会将各个设备都映射到 /dev/
目录下的文件,咱们能够经过这些设备文件直接操做硬件,内存也不例外。 在 Linux 中,内存设置被映射为 /dev/mem
,root 用户经过对这个文件读写,能够直接操做内存。
使用 TOP 查看系统性能时,咱们会发如今 VIRT 这一列,Java 进程会占用大量的虚拟内存。
致使这种问题的缘由是 Java 使用 Glibc 的 Arena 内存池分配了大量的虚拟内存并无使用。此外,Java 读取的文件也会被映射为虚拟内存,在虚拟机默认配置下 Java 每一个线程栈会占用 1M 的虚拟内存。具体能够查看 为何linux下多线程程序如此消耗虚拟内存。
而真实占用的物理内存要看 RES
(resident) 列,这一列的值才是真正被映射到物理内存的大小。
咱们也能够本身来管理 Linux 的虚拟内存。
查看系统内存状况的方式有不少,free
、 vmstat
等命令均可输出当前系统的内存状态,须要注意的是可用内存并不仅是 free 这一列,因为操做系统的 lazy 特性,大量的 buffer/cache 在进程再也不使用后,不会被当即清理,若是以前使用它们的进程再次运行还能够继续使用,它们在必要时也是能够被利用的。
此外,经过 cat /proc/meminfo
能够查看系统内存被使用的详细状况,包括脏页状态等。详情可参见:/PROC/MEMINFO之谜。
若是想单独查看某一进程的虚拟内存分布状况,可使用 pmap pid
命令,它会把虚拟内存各段的占用状况从低地址到高地址都列出来。
能够添加 -XX
参数来输出更详细的信息。
咱们也能够修改 Linux 的系统配置,使用 sysctl vm [-options] CONFIG
或 直接读写 /proc/sys/vm/
目录下的文件来查看和修改配置。
虚拟内存的 SWAP 特性并不老是有益,听任进程不停地将数据在内存与磁盘之间大量交换会极大地占用 CPU,下降系统运行效率,因此有时候咱们并不但愿使用 swap。
咱们能够修改 vm.swappiness=0
来设置内存尽可能少使用 swap,或者干脆使用 swapoff
命令禁用掉 SWAP。
虚拟内存的概念很是容易理解,可是它会衍生出来的一系列很是复杂的知识。本文只讲了些基本原理,略过了不少细节,好比虚拟内存寻址中段寄存器的使用,操做系统使用虚拟内存加强缓存、缓冲区的应用等,有机会单独拿出来讲。
关于本文有什么疑问能够在下面留言交流,若是您以为本文对您有帮助,欢迎关注个人 微博 或 GitHub 。您也能够在个人 博客REPO 右上角点击 Watch
并选择 Releases only
项来 订阅
个人博客,有新文章发布会第一时间通知您。