众所周知,内存管理是Linux内核中最基础,也是相当重要的部分。理解相关原理,不管是对内存的理解,还是对大家写用户态代码都很有帮助。很多书上、很多文章都写了相关内容,但个人总觉得内容太复杂,不是太容易理解,这里想用我自己理解的简单的方式来描述,希望能有所帮助。本篇文章由圆柱模板博主原创,转载需注明!
内存的分配
大家写代码时,应该都会分配内存,不同语言,层次不同,使用的接口不同,不管使用哪种方式,在Linux系统中,基本上都会调用到C库的malloc接口,那就从malloc分配内存开始。
malloc就是用于分配一段内存,但这里分配到的内存并非物理内存,而是虚拟内存,这里没有严格区分虚拟地址、线性地址之类的概念,只会给大家添负担,也不深入讲述物理内存和虚拟内存的概念,书上通常有大量的篇幅介绍,大家可以简单这样理解:
-
虚拟内存就是从进程的角度看,逻辑上的概念,并不实际存在;
-
物理内存就对应物理上内存条上的内存;
- 虚拟内存和物理内存有对应关系;
- 虚拟内存分配时,相应的物理内存还没有分配;
虚拟内存到物理内存的映射
由页表来建立虚拟内存到物理内存之间的映射关系。 页表就是在内存中的一张表,可以简单看做一张hash表,记录的是虚拟地址和物理地址的对应关系,每个虚拟地址对应一个表项,通过这张表,就能将虚拟地址转换为物理地址,也就能建立虚拟内存到物理内存的映射关系了。
页表的使用
页表有了,那谁来用呢?不可能是应用程序自己用吧,我写代码时好像从来都没见过页表? 当然,用户看到的只是虚拟地址(虚拟内存),其他的对用户都是透明的~ CPU中有个硬件单元,叫MMU(内存管理单元),页表就是给MMU硬件用的,MMU使用页表进行虚拟地址到物理地址的映射。也就是说,地址映射是由硬件完成的,软件(包括操作系统内核自身)都不管关心。 都说软件不用关心了,那我们为嘛还需要讲页表? 软件只是不用页表而且,但页表的创建和维护都是由软件(操作系统内核)负责的,也就是说我们(软件)创建虚拟内存和物理内存的映射关系,然后由硬件来自动进行地址映射(转换),我们不需要关心具体的转换过程。 前面说了,页表是在内存中,而页表是由软件创建的,那MMU如果知道页表到底在哪儿呢? 简单说,需要我们(软件)告诉它在哪儿,如何告诉?当然,写寄存器。CPU上有特别的寄存器(CR3),向其中写入页表的地址,MMU就知道了,然后硬件自己使用即可,我们就不管了。
页表的数量问题
看似一张页表就能完成所有的地址映射了? 当然不行,如果是这样,虚拟内存就没有什么必要存在了。 这里又涉及新概念了:进程,这是操作系统中最基础的概念,其实不“新”。 系统中,所有任务都是以进程方式运行的,每个进程都有自己的独立的虚拟地址空间,好像又说复杂了,简单说,就是每个进程都有自己的虚拟内存,独立代表,其他进程看不到自己的虚拟内存,那么就意味着,每个进程都需要独立的虚拟内存到物理内存的映射,就是说,每个进程都需要自己的页表。 所以,系统中有多少进程,就有多少张页表。
页表创建
简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的页表均置为写保护(即写地址的时候会产生缺页异常等)。那么父子进程谁向地址空间写数据时,产生缺页异常,分配新的页,并将两个页均置为可写,按照这种方式父子进程的地址空间渐渐变得不同。 按照上面的分析, 只需要讨论第一个进程页表初始化,进程创建时页表的拷贝,以及缺页异常时页表的更新即可。
什么是缺页异常
简单说,就是硬件上的一种机制,当硬件检测到某种“不对”时,主动触发,然后会自动跳转到异常处理程序处理。异常跟中断类似,区别在于,中断是异步的,由外设触发;异常是同步的,由CPU自己触发。
好了,时间也比较晚了,就写到这,该睡觉了,明天又得早期了!明天晚上继续写写这些理论的东西!学习需要坚持!