• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Linux系统原理之内存管理

系统管理 彭东稳 9年前 (2015-08-31) 26403次浏览 已收录 0个评论

Linux内存架构

为了执行一个进程,Linux内核为请求的进程分配一部分内存区域。该进程使用该内存区域作为其工作区并执行请求的工作。它与你的申请一个办公桌,然后使用办公桌来摆放纸张、文档和备忘录来执行你的工作类似。不同之处是内核必须使用更动态的方式来分配内存空间。有时运行的进程数会达到数万个,但内存的数量是有限的。因此,Linux内核必须有效地处理内存。在本节,我们将会讲述Linux的内存结构、地址分布和Linux如何有效地管理内存空间。

内存地址相关概念

1)物理地址(physical address)

物理内存,真是存在的插在主板内存槽上的内存条的容量的大小。

内存是由若干个存储单元组成的,每个存储单元有一个编号,这种编号可唯一标识一个存储单元,称为内存地址,我们可以把内存看成一个从0字节一直到内存最大容量逐字节编号的存储单元数组,即每个存储单元与内存地址的编号相对应。

2)逻辑地址(logical address)

源程序经过汇编或编译后,形成目标代码,每个目标代码都是0为基址顺序进行编址的,原来用符号访问的单元用具体的数据—–单元号取代。这样生成的目标程序占据一定的地址空间,称为作业的逻辑地址空间,简称逻辑空间。

在逻辑空间中每条指令的地址和指令中要访问的操作数地址统称为逻辑地址,即应用程序中使用的地址。要经过寻址方式的计算或变换才得到内存中的物理地址。很简单,逻辑地址就是你源程序里使用的地址,或者源代码经过编译以后编译器将一些符号,变量转换成的地址,或者相对于当前段的偏移地址。

PS:逻辑地址与虚拟地址,两者并没有什么明确的界限。

3)线性地址(Linux也称虚拟地址)

线性地址是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。

跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。

CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量=),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。

Linux内存管理技术

Linux中对于内存的管理是一部分被静态地划分给了内核,用来存放内核代码和静态数据结构。其余部分称为动态内存,这不仅仅是运行用户进程所需要的资源,也是内核所需的资源。事实上,整个系统的性能取决于如何有效地管理动态内存。

页表(page tables):进程在读取指令和存取数据时都要访问内存,在一个虚拟内存系统中,所有的地址都是虚拟地址而非物理地址。操作系统维护虚拟地址和物理地址转的信息,处理器通过这组信息将虚拟地址转换为物理地址。虚拟内存和物理内存被分为适当编号和业内偏移量;然后,处理器根据虚拟地址和物理地址的映射关系将虚拟页编号转换为物理页;最后,根据偏移量访问物理页的确定偏移位置。每个物理页面都有一个struct page结构,位于include/linux/mm.h,该结构体包含了管理物理页面时的所有信息。

虚拟内存技术(virtual memory)

虚拟内存地址就是每个进程可以直接寻址的地址空间,不受其他进程干扰。每个指令或数据单元都在这个虚拟空间中拥有确定的地址。虚拟内存不考虑物理内存的大小和信息存放的实际位置,只规定进程中相互关联信息的相对位置。每个进程都拥有自己的虚拟内存,且虚拟内存的大小由处理机的地址结构和寻址方式决定。如:32位机器可以直接寻址4G空间,意思是每一个应用程序都有4G内存空间可用。这是一种计算机系统内存管理技术。很多人就很奇怪了,为什么Linux进程的起始地址都是一样的,他们互相不干扰吗?还有电脑的内存只有2G,可是每个进程都有4G的虚拟内存,而且有那么多进程一起跑,2G怎么够用呢?原因就是虚拟内存是“假”的,跟实际的物理内存不是一回事儿。

其实虚拟内存与操作系统本身并没有多大关系,虚拟内存是CPU的硬件特性,而操作系统要负责的是对它的管理。如果某个CPU不具备虚拟内存的特性,操作系统即便支持对虚拟内存的管理也是白搭,它管理谁呢?虚拟内存是能够开发出多任务操作系统的关键特性,所以对于一个不支持虚拟内存的CPU来说,是很难在它上面运行多任务操作系统的。对于一个支持虚拟内存的CPU来说,往往都是利用一个独立的模块来完成的,而提供虚拟内存支持的CPU模块叫内存管理单元,即MMU(memory management unit),那么MMU都做了什么事呢?我们拿最常见的x86 CPU做介绍。处于简单起见不介绍64位。

在历史上,实现虚拟内存技术主要有两种方案:段式内存管理和页式内存管理。这两种方案只有一个目的,就是让多任务操作系统能够将多个进程的地址空间保护起来,让它们相互隔离,使得它们不会互相“打架”。我们耳熟能详的“保护模式”这种叫法也就是来源于此。段式内存管理就是将整个内存划分成大小不同的段,每个进程的地址空间处于不同的独立的段中,这样就可以实现进程地址空间的相互隔离。页式内存管理则将整个内存划分成许多大小相等的页面,每个进程的地址空间可以由多个页面构成,同样可以实现进程地址空间的相互隔离。至于段式内存管理和页式内存管理孰好孰坏,这东西仁者见仁、智者见智。但目前主流的CPU和操作系统都不约而同的采用了页式内存管理。其余的你懂的

从大的方向上看,主流的操作系统支持的都是页式内存管理,那么CPU也就势必要支持页式内存管理。至于段式内存管理则是可选的。但是x86系列CPU在这方面一直很坑爹,因为它采用了一种非主流设计:页式内存管理是构筑在段式内存管理之上的。也就是先要对内存进行分段,然后在分段的基础上再分页。话说这两者完全是独立的方案,弄在一起唯一合理的解释就是x86系列的CPU需要向前兼容,但是想不明白谁会在i7上去跑DOS系统。

x86的CPU这么做,但是我们当前的主流操作系统都采用了一个小窍门,将它的段式内存管理给废掉了。方法就是将整个内存划分成一个段,然后再进行页式映射。这样该死的段式内存管理就是透明的了。当然,这也就有了“线性地址”这个新名词。前面已经解释过了:内存的真实地址叫物理地址,整个内存分成一个大段的段式内存管理所使用的地址则叫线性地址,虽然线性地址跟物理地址是对等的,但是为了加以区分,给它起一个新的名词还是有必要的。

线性地址是个中间概念,基本没谁使用,但是虚拟地址和物理地址则是最常用的。若要让一个程序能够成为进程,虚拟地址和物理地址必须有关联。下面我们看看在页式内存管理下他们是怎么关联的。一个32位的x86系列CPU的虚拟地址,在二进制层面被划分成三个不同的区域:页面目录、页面表和页内偏移。

Linux系统原理之内存管理

页面目录和页面表都是数组,页面目录的元素就是页面表,而页面表的元素就是页面了。由于页面目录和页面表都是使用10位二进制来表示的,那么也就代表数组的元素最多只有1024个。页面偏移使用12位二进制,则说明一个页面最多可以有4096个字节,二实际上一个页面就是固定的4096字节,也就是4k。

页面表是页面目录的元素,那么只要知道页面目录在哪儿就行了,可是页面目录再哪儿呢?答案是在CR3寄存器中。当然,作为32位的CPU,寄存器不可能太大,也是个32位的。所以CR3寄存器中保存的内容并不是真个页面目录,而是页面目录的指针,页面目录实际上是在内存中。页面目录在保存页面表的时候也不可能每项都保存一个完整的页面表,采用的也是指针,那么页面目录也就需要4096个自己来保存了,正好一个页面。

按照这种推理,页面表的元素是不是就是页面的指针了呢?答案是否定的,因为如果这样,页内偏移就有些多余了。实际上页面表中的元素是一个复杂的结构。当然,这个结构一共占有32位,整个页面表也是4096字节,正好是一个页面。更进一步说,对于页面目录,由于每个页表必须是页面对齐的,那么只需要20位的地址就能够确认页表地址了(低位都为0即可),那么也可以利用剩下的位来做点事情。intel真就这样干了,而且页面目录与页面表的结构基本完全相同。具体的可参考下图页面表结构图

Linux系统原理之内存管理

P位是存在(present)标志,用于致命页面是否在内存中。1则表示在内存中;0表示不再内存中。当这个标志位0的时候,要访问这个页面就会引发缺页异常,利用这个异常就可以将页面从磁盘中换入内存,然后置该位为1.这实际上就是页面交换的实现原理,mmap能够将文件映射到内存也是基于此。该位对于页面目录有类似的效果,当它为0时,则表明页面表不在内存中。通过页面交换,可以将这个页面表从磁盘中交换到内存中。

R/W位是读/写标志位,如果该位为1,则表示页面可以读、写或执行。如果为0,表示页面只读或可执行。当CPU运行于特权级(ring0、1、3)时,该位不起作用。页面目录中的R/w位对其所管辖的所有页面都起作用。

A位是已访问标志位,当处理器访问页表项映射的页面时,该标志位就会被置1.当处理器访问页面目录所管辖的任意页面时,对应的页面目录项的这个标志就会被置1。CPU只负责设置该标志,操作系统可通过定期地复位该标志来统计也没见的使用情况。

D位是脏页面标志位,所谓的脏页面就是被修改过的页面。当处理器对一个也页面执行写操作时,就会设置该标志位,CPU不会修改页面目录中的D位。对于缓存式I/O,操作系统可以利用该位将被修改过的内容同步回磁盘。

AVL字段则保留给专用程序使用,CPU不会修改这几个位,以后的CPU也不会。

那么CPU的MMU是如何将程序发出的虚拟地址,最终落实到物理地址上的呢?操作系统在组织进程的时候,就会为其准备好页面目录和页面表。在调度到这个进程的时候,会将它的页面目录地址装入CR3寄存器。这样,当程序给出一个虚拟地址,MMU就会从CR3中找到这个进程专有的页面目录,利用虚拟地址中的页面目录位找到页面表的地址。然后MMU继续利用虚拟地址中的页面表位,找到具体的一个页面。最后,MMU就按照虚拟地址中的页面偏移位,定位到具体物理内存的一个地址上了。是读、写还是执行,就看程序怎么要求了。

下图是虚拟地址到物理地址的转换过程:

Linux系统原理之内存管理

那么根据这种虚拟地址转换物理地址的方式,就可以玩出很多花样了。进程与进程之间可以让虚拟内存地址相同,但是物理地址不同而达到了空间上的真正分离;进程自己并不能看到自己的真实物理地址,而且即便物理地址不存在,也可以通过页面交换技术让它存在,那么操作系统就可以欺骗进程它拥有很多的内存可用;同样利用页面交换技术,可以将一个文件映射到内存中,使得mmap这样的系统调用得以实现;将相同的虚拟地址转换成相同的物理地址,就可以做到数据的共享,线程就是这么干的;将硬件设备的控制存储区域反映到虚拟内存上,就可以实现通过内存访问就达到控制硬件的目的;等等这些。

将物理地址伪装成虚拟地址的这个过程,或将虚拟内存转换成物理内存的这个过程,统称为成内存映射,也就是memory map,简写为mmap。所以mmap是一种机制,只是Linux有一个系统调用恰好与这种机制同名了。不过没关系,本身这个系统调用就可以代表整个Linux的内存管理过程了。其次虚拟内存还解决了内存不连续和碎片的问题(因为对程序来说线性地址都是连续的);每个进程都有各自的页表,虚拟地址空间都各自独立。

内存映射mmap()机制

实现内存共享是mmap()主要应用之一,它使得进程之间通过映射同一个普通文件实现共享内存。MAP_SHARED选项意味着允许与其他所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用文件才会被更新。MAP_PRIVATE是指建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式, 因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

先看看传统文件访问机制!

如果按照传统的方式来读写文件,首先必须使用open()系统调用打开这个文件是用open打开它们, 如果有多个进程访问同一个文件, 则每一个进程在自己的地址空间都包含有该文件的副本,这不必要地浪费了存储空间. 下图说明了两个进程同时读一个文件的同一页的情形. 系统要将该页从磁盘读到高速缓冲区中, 每个进程再执行一个存储器内的复制操作将数据从高速缓冲区读到自己的地址空间。因为每一次读写都要进行一次系统调用,毕竟浪费掉这么多无用的CPU时钟还是说不过去的。

Linux系统原理之内存管理

用mmap()函数读写文件就没有传统方法的那些毛病,利用mmap对一个文件做普通的读写,跟读写内存没什么两样,都是一些指针的操作,mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。自然就不浪费CPU的时钟了。另外,由于mmap玩的是虚拟内存,虽然不同的进程看到内存区域可能不同,但那毕竟是虚的,实际的物理内存区域可以是一个。这样一来,不但多个进程访问同一个文件可以不用维护多个副本,多个进程本身也不需要多个副本,自然就不浪费“宝贵”的内容了。此外,由于Linux一直都秉承着一切皆文件的方针来做事情,那么将设备文件做内存映射,就使得对设备的控制像访问内存那样简单,也避免了I/O操作,从而提高了系统的整体效率。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (1)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!