[转帖]运维必读:Linux 的内存分页管理

运维必读:Linux 的内存分页管理

内存是计算机的主存储器。内存为进程开辟出进程空间,让进程在其中保存数据。我将从内存的物理特性出发,深刻到内存管理的细节,特别是了解虚拟内存和内存分页的概念。安全

▉内存markdown

简单地说,内存就是一个数据货架。内存有一个最小的存储单位,大多数都是一个字节。内存用内存地址(memory address)来为每一个字节的数据顺序编号。所以,内存地址说明了数据在内存中的位置。内存地址从0开始,每次增长1。这种线性增长的存储器地址称为线性地址(linear address)。为了方便,咱们用十六进制数来表示内存地址,好比0x0000000三、0x1A010CB0。这里的“0x”用来表示十六进制。“0x”后面跟着的,就是做为内存地址的十六进制数。网络

内存地址的编号有上限。地址空间的范围和地址总线(address bus)的位数直接相关。CPU经过地址总线来向内存说明想要存取数据的地址。以英特尔32位的80386型CPU为例,这款CPU有32个针脚能够传输地址信息。每一个针脚对应了一位。若是针脚上是高电压,那么这一位是1。若是是低电压,那么这一位是0。32位的电压高低信息经过地址总线传到内存的32个针脚,内存就能把电压高低信息转换成32位的二进制数,从而知道CPU想要的是哪一个位置的数据。用十六进制表示,32位地址空间就是从0x00000000 到0xFFFFFFFF。数据结构

内存的存储单元采用了随机读取存储器(RAM, Random Access Memory)。所谓的“随机读取”,是指存储器的读取时间和数据所在位置无关。与之相对,不少存储器的读取时间和数据所在位置有关。就拿磁带来讲,咱们想听其中的一首歌,必须转动带子。若是那首歌是第一首,那么当即就能够播放。若是那首歌恰巧是最后一首,咱们快进到能够播放的位置就须要花很长时间。咱们已经知道,进程须要调用内存中不一样位置的数据。若是数据读取时间和位置相关的话,计算机就很难把控进程的运行时间。所以,随机读取的特性是内存成为主存储器的关键因素。运维

内存提供的存储空间,除了能知足内核的运行需求,还一般能支持运行中的进程。即便进程所需空间超过内存空间,内存空间也能够经过少许拓展来弥补。换句话说,内存的存储能力,和计算机运行状态的数据总量至关。内存的缺点是不能持久地保存数据。一旦断电,内存中的数据就会消失。所以,计算机即便有了内存这样一个主存储器,仍是须要硬盘这样的外部存储器来提供持久的储存空间。dom

▉虚拟内存spa

内存的一项主要任务,就是存储进程的相关数据。咱们以前已经看到过进程空间的程序段、全局数据、栈和堆,以及这些这些存储结构在进程运行中所起到的关键做用。有趣的是,尽管进程和内存的关系如此紧密,但进程并不能直接访问内存。在Linux下,进程不能直接读写内存中地址为0x1位置的数据。进程中能访问的地址,只能是虚拟内存地址(virtual memory address)。操做系统会把虚拟内存地址翻译成真实的内存地址。这种内存管理方式,称为虚拟内存(virtual memory)。操作系统

每一个进程都有本身的一套虚拟内存地址,用来给本身的进程空间编号。进程空间的数据一样以字节为单位,依次增长。从功能上说,虚拟内存地址和物理内存地址相似,都是为数据提供位置索引。进程的虚拟内存地址相互独立。所以,两个进程空间能够有相同的虚拟内存地址,如0x10001000。虚拟内存地址和物理内存地址又有必定的对应关系,如图1所示。对进程某个虚拟内存地址的操做,会被CPU翻译成对某个具体内存地址的操做。翻译

图1 虚拟内存地址和物理内存地址的对应设计

应用程序来讲对物理内存地址一无所知。它只可能经过虚拟内存地址来进行数据读写。程序中表达的内存地址,也都是虚拟内存地址。进程对虚拟内存地址的操做,会被操做系统翻译成对某个物理内存地址的操做。因为翻译的过程由操做系统全权负责,因此应用程序能够在全过程当中对物理内存地址一无所知。所以,C程序中表达的内存地址,都是虚拟内存地址。好比在C语言中,能够用下面指令来打印变量地址:

int v = 0; printf("%p", (void*)&v);

本质上说,虚拟内存地址剥夺了应用程序自由访问物理内存地址的权利。进程对物理内存的访问,必须通过操做系统的审查。所以,掌握着内存对应关系的操做系统,也掌握了应用程序访问内存的闸门。借助虚拟内存地址,操做系统能够保障进程空间的独立性。只要操做系统把两个进程的进程空间对应到不一样的内存区域,就让两个进程空间成为“老死不相往来”的两个小王国。两个进程就不可能相互篡改对方的数据,进程出错的可能性就大为减小。

另外一方面,有了虚拟内存地址,内存共享也变得简单。操做系统能够把同一物理内存区域对应到多个进程空间。这样,不须要任何的数据复制,多个进程就能够看到相同的数据。内核和共享库的映射,就是经过这种方式进行的。每一个进程空间中,最初一部分的虚拟内存地址,都对应到物理内存中预留给内核的空间。这样,全部的进程就能够共享同一套内核数据。共享库的状况也是相似。对于任何一个共享库,计算机只须要往物理内存中加载一次,就能够经过操纵对应关系,来让多个进程共同使用。IPO中的共享内存,也有赖于虚拟内存地址。

▉内存分页

虚拟内存地址和物理内存地址的分离,给进程带来便利性和安全性。但虚拟内存地址和物理内存地址的翻译,又会额外耗费计算机资源。在多任务的现代计算机中,虚拟内存地址已经成为必备的设计。那么,操做系统必需要考虑清楚,如何能高效地翻译虚拟内存地址。

记录对应关系最简单的办法,就是把对应关系记录在一张表中。为了让翻译速度足够地快,这个表必须加载在内存中。不过,这种记录方式惊人地浪费。若是树莓派1GB物理内存的每一个字节都有一个对应记录的话,那么光是对应关系就要远远超过内存的空间。因为对应关系的条目众多,搜索到一个对应关系所需的时间也很长。这样的话,会让树莓派陷入瘫痪。

所以,Linux采用了分页(paging)的方式来记录对应关系。所谓的分页,就是以更大尺寸的单位页(page)来管理内存。在Linux中,一般每页大小为4KB。若是想要获取当前树莓派的内存页大小,可使用命令:

$getconf PAGE_SIZE

获得结果,即内存分页的字节数:

4096

返回的4096表明每一个内存页能够存放4096个字节,即4KB。Linux把物理内存和进程空间都分割成页。

内存分页,能够极大地减小所要记录的内存对应关系。咱们已经看到,以字节为单位的对应记录实在太多。若是把物理内存和进程空间的地址都分红页,内核只须要记录页的对应关系,相关的工做量就会大为减小。因为每页的大小是每一个字节的4000倍。所以,内存中的总页数只是总字节数的四千分之一。对应关系也缩减为原始策略的四千分之一。分页让虚拟内存地址的设计有了实现的可能。

不管是虚拟页,仍是物理页,一页以内的地址都是连续的。这样的话,一个虚拟页和一个物理页对应起来,页内的数据就能够按顺序一一对应。这意味着,虚拟内存地址和物理内存地址的末尾部分应该彻底相同。大多数状况下,每一页有4096个字节。因为4096是2的12次方,因此地址最后12位的对应关系自然成立。咱们把地址的这一部分称为偏移量(offset)。偏移量实际上表达了该字节在页内的位置。地址的前一部分则是页编号。操做系统只须要记录页编号的对应关系。

图2 地址翻译过程

▉多级分页表

内存分页制度的关键,在于管理进程空间页和物理页的对应关系。操做系统把对应关系记录在分页表(page table)中。这种对应关系让上层的抽象内存和下层的物理内存分离,从而让Linux能灵活地进行内存管理。因为每一个进程会有一套虚拟内存地址,那么每一个进程都会有一个分页表。为了保证查询速度,分页表也会保存在内存中。分页表有不少种实现方式,最简单的一种分页表就是把全部的对应关系记录到同一个线性列表中,即如图2中的“对应关系”部分所示。

这种单一的连续分页表,须要给每个虚拟页预留一条记录的位置。但对于任何一个应用进程,其进程空间真正用到的地址都至关有限。咱们还记得,进程空间会有栈和堆。进程空间为栈和堆的增加预留了地址,但栈和堆不多会占满进程空间。这意味着,若是使用连续分页表,不少条目都没有真正用到。所以,Linux中的分页表,采用了多层的数据结构。多层的分页表可以减小所需的空间。

咱们来看一个简化的分页设计,用以说明Linux的多层分页表。咱们把地址分为了页编号和偏移量两部分,用单层的分页表记录页编号部分的对应关系。对于多层分页表来讲,会进一步分割页编号为两个或更多的部分,而后用两层或更多层的分页表来记录其对应关系,如图3所示。

图3 多层分页表

在图3的例子中,页编号分红了两级。第一级对应了前8位页编号,用2个十六进制数字表示。第二级对应了后12位页编号,用3个十六进制编号。二级表记录有对应的物理页,即保存了真正的分页记录。二级表有不少张,每一个二级表分页记录对应的虚拟地址前8位都相同。好比二级表0x00,里面记录的前8位都是0x00。翻译地址的过程要跨越两级。咱们先取地址的前8位,在一级表中找到对应记录。该记录会告诉咱们,目标二级表在内存中的位置。咱们再在二级表中,经过虚拟地址的后12位,找到分页记录,从而最终找到物理地址。

多层分页表就好像把完整的电话号码分红区号。咱们把同一地区的电话号码以及对应的人名记录同通一个小本子上。再用一个上级本子记录区号和各个小本子的对应关系。若是某个区号没有使用,那么咱们只须要在上级本子上把该区号标记为空。一样,一级分页表中0x01记录为空,说明了以0x01开头的虚拟地址段没有使用,相应的二级表就不须要存在。正是经过这一手段,多层分页表占据的空间要比单层分页表少了不少。

多层分页表还有另外一个优点。单层分页表必须存在于连续的内存空间。而多层分页表的二级表,能够散步于内存的不一样位置。这样的话,操做系统就能够利用零碎空间来存储分页表。还须要注意的是,这里简化了多层分页表的不少细节。最新Linux系统中的分页表多达3层,管理的内存地址也比本章介绍的长不少。不过,多层分页表的基本原理都是相同。

 

*声明:推送内容及图片来源于网络,部份内容会有所改动,版权归原做者全部,如来源信息有误或侵犯权益,请联系咱们删除或受权事宜。

- END -

相关文章
相关标签/搜索