Linux内存管理

242 阅读12分钟

转载自:happyseeker.github.io/kernel/2016…

参考资料:知乎:CPU,内核,寄存器,缓存,RAM,ROM的作用和他们之间的联系?

基本概念简要
内核内核是操作系统最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。内核的分类可分为单内核和双内核以及微内核。严格地说, 内核并不是计算机系统中必要的组成部分 内核并不是计算机系统中必要的组成部分 内核并不是计算机系统中必要的组成部分 重要的事情要说三遍
CPU中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。 中央处理器主要包括运算器(算术逻辑运算单元,ALU,Arithmetic Logic Unit)和高速缓冲存储器(Cache)及实现它们之间联系的数据(Data)、控制及状态的总线(Bus)。CPU 与内部存储器(Memory)和输入/输出(I/O)设备合称为电子计算机三大核心部件
寄存器寄存器是中央处理器内的组成部份。它跟CPU有关。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。- 在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。
  • 在中央处理器的算术及逻辑部件中,包含的寄存器有累加器(ACC)。 | | 存储器 | 存储器范围最大,它几乎涵盖了所有关于存储的范畴。你所说的寄存器,内存,都是存储器里面的一种。凡是有存储能力的硬件,都可以称之为存储器,这是自然,硬盘更加明显了,它归入外存储器行列。 | | 内存 | 内存既专业名上的内存储器,它不是个什么神秘的东西,它也只是存储器中的沧海一粟,它包涵的范围也很大,一般分为:- 只读存储器(ROM:Read Only Memory): 只读存储器应用广泛,它通常是一块在硬件上集成的可读芯片,作用是识别与控制硬件,它的特点是只可读取,不能写入。

  • 随机存储器(RAM:Random Access Memory): 随机存储器的特点是可读可写,断电后一切数据都消失,我们所说的内存条就是指它了。

  • 最强悍的**高速缓冲存储器(CACHE)******ROM、RAM的区别:****ROM和RAM指的都是半导体存储器.ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是在掉电之后就丢失数据,典型的RAM就是计算机的内存 | | 缓存CACHE | 缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。因为缓存往往使用的是RAM(断电即掉的非永久储存),所以在用完后还是会把文件送到硬盘等存储器里永久存储。电脑里最大的缓存就是内存条了,最快的是CPU上镶的L1和L2缓存,显卡的显存是给显卡运算芯片用的缓存,硬盘上也有16M或者32M的缓存。CACHE是在CPU中速度非常块,而容量却很小的一种存储器,它是计算机存储器中最强悍的存储器。由于技术限制,容量很难提升。 | | FLASH存储器 | FLASH存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦出可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据 (NVRAM的优势),U盘和MP3里用的就是这种存储器。在过去的20年里,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,然而近年来 Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘)。目前Flash主要有两种NOR Flash和NADN Flash。- NOR Flash的读取和我们常见的SDRAM的读取是一样,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本。

  • NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一快的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户 不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还作上了一块小的NOR Flash来运行启动代码。一般小容量的用NOR Flash,因为其读取速度快,多用来存储操作系统等重要信息,而大容量的用NAND FLASH,最常见的NAND FLASH应用是嵌入式系统采用的DOC(Disk On Chip)和我们通常用的“闪盘”,可以在线擦除。 |

从内存分配开始

大家写代码时,应该都会分配内存,不同语言,层次不同,使用的接口不同,不管使用哪种方式,在Linux系统中,基本上都会调用到C库的malloc接口,那就从malloc分配内存开始。

malloc就是用于分配一段内存,但这里分配到的内存并非物理内存,而是虚拟内存,这里没有严格区分虚拟地址、线性地址之类的概念,只会给大家添负担,也不深入讲述物理内存和虚拟内存的概念,书上通常有大量的篇幅介绍,大家可以简单这样理解:

  • 虚拟内存就是从进程的角度看,逻辑上的概念,并不实际存在;
  • 物理内存就对应物理上内存条上的内存;
  • 虚拟内存和物理内存有对应关系;
  • 虚拟内存分配时,相应的物理内存还没有分配;

既然虚拟内存并不实际存在,那么分配来有何用处呢,如果要向其中写数据,数据如何写入呢?会写到哪里去呢?

当然,内存分配后就是拿来用的,如果不能用(比如写数据),那就没有意义了。前面说的虚拟内存和物理内存有对应关系,当分配虚拟内存时,相应的物理内存还没有分配,这里有几个关键问题:

  1. 虚拟内存和物理内存的对应关系由谁来负责维护,如何对应?
  2. 物理内存何时分配?

虚拟内存到物理内存的映射

先来解答问题1。

页表来建立虚拟内存到物理内存之间的映射关系。 页表是啥?

简单看,页表就是在内存中的一张表(不详细介绍页表的具体格式啥的,看书就可以了),可以简单看做一张hash表,记录的是虚拟地址和物理地址的对应关系,每个虚拟地址对应一个表项,通过这张表,就能将虚拟地址转换为物理地址,也就能建立虚拟内存到物理内存的映射关系了。

谁用页表?

页表有了,那谁来用呢?不可能是应用程序自己用吧,我写代码时好像从来都没见过页表

当然,用户看到的只是虚拟地址(虚拟内存),其他的对用户都是透明的~

CPU中有个硬件单元,叫MMU(内存管理单元),页表就是给MMU硬件用的,MMU使用页表进行虚拟地址到物理地址的映射。也就是说,地址映射是由硬件完成的,软件(包括操作系统内核自身)都不管关心。

都说软件不用关心了,那我们为嘛还需要讲页表?

软件只是不用页表而且,但页表的创建和维护都是由软件(操作系统内核)负责的,也就是说我们(软件)创建虚拟内存和物理内存的映射关系,然后由硬件来自动进行地址映射(转换),我们不需要关心具体的转换过程。

前面说了,页表是在内存中,而页表是由软件创建的,那MMU如果知道页表到底在哪儿呢?

简单说,需要我们(软件)告诉它在哪儿,如何告诉?当然,写寄存器。CPU上有特别的寄存器(CR3),向其中写入页表的地址,MMU就知道了,然后硬件自己使用即可,我们就不管了。

有多少张页表?

看似一张页表就能完成所有的地址映射了?

当然不行,如果是这样,虚拟内存就没有什么必要存在了。

这里又涉及新概念了:进程,这是操作系统中最基础的概念,其实不“新”。

系统中,所有任务都是以进程方式运行的,每个进程都有自己的独立的虚拟地址空间,好像又说复杂了,简单说,就是每个进程都有自己的虚拟内存,独立代表,其他进程看不到自己的虚拟内存,那么就意味着,每个进程都需要独立的虚拟内存到物理内存的映射,就是说,每个进程都需要自己的页表

所以,系统中有多少进程,就有多少张页表。

页表创建

前面说了,页表的创建和维护是由操作系统内核完成的,那何时创建页表呢?

如前面所说,每个进程都需要自己的页表,显然,页表需要在进程创建时建立。具体的创建过程和原理就不讲了。

页表的维护

如果维护页表?

当然,进程自己维护,如果维护?

拿前面的例子说,malloc分配虚拟内存后,并没有分配物理内存,也没有建立映射关系,没有修改页表,那到底什么时候,由谁来做映射?

答案是:“缺页异常”。

缺页异常

缺页异常(page fault)又是另一个专业术语,表面上看好像不好理解。

简单看,就是一个异常。什么是异常?这又是硬件上的概念了,具体看看书

简单说,就是硬件上的一种机制,当硬件检测到某种“不对”时,主动触发,然后会自动跳转到异常处理程序处理。异常跟中断类似,区别在于,中断是异步的,由外设触发;异常是同步的,由CPU自己触发;好像又扯远了。

缺页异常就是一种特定的异常,触发条件是:MMU检测到页表项(页表中的项~)不存在。

何时触发缺页异常?

MMU何时会去检测页表项?

当CPU需要访问某个虚拟地址时,比如向某个虚拟地址写数据(memset),需要将虚拟地址转换成物理地址,此时MMU就会自动去页表中找相应的页表项,如果发现此时相应的页表项(也就是虚拟地址到物理地址的映射关系)还不存在,那么就会自动从硬件层面触发缺页异常。

也就说,缺页异常是硬件自己触发的,条件是当需要访问某个虚拟地址,而该虚拟地址还没有相应的页表项时。

典型的场景就是,当malloc之后,对相应的虚拟地址执行memset操作。

缺页异常中干嘛?

缺页异常后,会跳转到相应的异常处理程序处理,该异常处理程序是内核中提前注册好的,其中要做的主要操作,就是:分配物理内存,然后修改相应进程的页表,建立该物理内存和虚拟内存的映射关系(页表项)。

当然,实际实现要复杂得多,这里不详细描述。

回头看看

再来重头捋一下内存分配过程:

  1. 用户态程序使用malloc接口,分配虚拟地址。
  2. 用户程序访问该虚拟地址,比如memset。
  3. 硬件(MMU)需要将虚拟地址转换为物理地址。
  4. 硬件读取页表。
  5. 硬件发现相应的页表项不存在,硬件自动触发缺页异常。
  6. 硬件自动跳转到page fault的处理程序(内核实现注册好)
  7. 内核中的page fault处理程序执行,在其中分配物理内存,然后修改页表(创建页表项)
  8. 异常处理完毕,返回程序用户态,继续执行memset相应的操作。

至此,虚拟内存和物理内存都分配完成,并完成映射。

另一个角度看,如果malloc分配内存后,一直不使用,那就一直不会分配物理内存,这种内存分配策略叫延迟分配,有其相应的好处,自己理解一下~

接下来?

本章,从内存分配的角度看了Linux内核中内存管理的关键原理,已经以尽量简单的方式描述了,希望没给大家带来负担~

Linux内存管理还涉及其他很多方面,如:

  • 内核自身使用的内存(slab、vmalloc)
  • 伙伴系统
  • 进程地址空间管理

后面抽空慢慢讲,但都希望能尽量简单,希望大家一看就明白。

c参考资料

happyseeker.github.io/kernel/2016…