一 linux虚拟内存、MMU、分页的基本原理

整体目录

一 linux虚拟内存、MMU、分页的基本原理
二 OOM打分因子、oom_adj以及oom_score
三 页的alloc与free、Buddy算法以及CMA
四 page_fault、内存IO交互、VSS、LRU
五 DMA及Cache一致性

=================================================================================

最近学习了内存管理的相关知识点,主要参考了三者:
1:宋宝华老师的内存讲解视频
2:蜗窝科技:蜗窝科技之内存管理
3:三个有意思的博客:
Memory Management分类50篇
Linux 内存管理

Linux底层内存管理、分配器、内存回收、用户态内存分配等内容的学习笔记,该博客与宋宝华老师讲解的视频有很多相同之处。

=============================================================

虚拟内存

《深入理解计算机系统》对虚拟内存的解释:

虚拟内存是一种操作系统对主存的抽象概念,提供了三个重要能力:
1: 它将主存看作是一个存储在磁盘上地址空间的高速缓存,并根据需要在主存和磁盘之间来回切换数据。
2:它为每个进程提供了一致的地址空间,从而简化了内存管理。
3: 它保护了每个进程的地址空间不被其他进程破坏。
个人理解:
虚拟内存:从逻辑上对内存容量加以扩充,它为每个进程提供了4G的独享空间,由操作系统通过地址映射的方式,转换为对物理内存的访问。在32位Linux机器上,每个进程的虚拟内存都是4G。(这里的虚拟内存与操作系统使用中过程常见的虚拟内存概念不同,不要混淆了,如Linux中swap)
在这里插入图片描述
具体链接可以查看:
《深入理解计算机系统》笔记—(3)虚拟内存
linux阅码场链接
廖威雄: 学习Linux必备的硬件基础一网打尽中对于虚拟内存的解释是“从逻辑上对内存容量加以扩充”。
我们把APP访问到的4G虚拟内存地址叫做:虚拟地址
我们把内核实际管理的2G物理内存地址叫做:物理地址

个人理解

1:CPU 通过物理总线访问内存,那么访问地址的范围就受限于机器总线的数量,在32位机器上,有32条总线,每条总线有高低两种电位分别代表 bit 的 1 和 0,那么可访问的最大地址就是 2^32bit = 4GB,所以说 32 位机器上插入大于 4G 的内存是无效的,CPU 访问不到多于 4G 的内存。
2:下图为4G 虚拟地址空间分布:
其中的 3G~4G 的空间中,是内核的地址空间,每个进程的这部分都不可用于应用程序,针对 0 ~ 3GB 的用户空间来说,用户的程序分为 .data,.text,.bss,stack,heap 这几个区域,上图为这些区域在 3G 的空间分布

程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。
初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。
未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。
栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。
堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。
具体细节可以参考Linux 内存管理窥探(1):内存规划与分布

进程的虚拟内存空间会被分成不同的若干区域,每个区域都有其相关的属性和用途,一个合法的地址总是落在某个区域当中的,这些区域也不会重叠。在linux内核中,这样的区域被称之为虚拟内存区域(virtual memory areas),简称 VMA。一个vma就是一块连续的线性地址空间的抽象,它拥有自身的权限(可读,可写,可执行等等) ,每一个虚拟内存区域都由一个相关的 struct vm_area_struct 结构来描述。
从进程的角度来讲,VMA 其实是虚拟空间的内存块,一个进程的所有资源由多个内存块组成,所以,一个进程的描述结构 task_struct 中首先包含Linux的内存描述符 mm_struct 结构。
mm_struct详解Linux之内存描述符mm_struct

=====================================================

页和页表 MMU

页和页表 MMU参考资料

页表和MMU网上的资料相对比较多,本人比较推荐linux阅码场链接
廖威雄: 学习Linux必备的硬件基础一网打尽;但是有关多级页表他描述的不是太到位,可以查看宋宝华: CPU是如何访问到内存的?–MMU最基本原理

本人理解:

1:把APP的虚拟地址空间切分为若干个大小相等的片,每一片,就是我们说的页(Page),在Linux上,页大小通常为4KB,页是内存管理的基本单位。
2:把物理内存也按"页"的大小切分为若干大小相等的片,每一片,就是我们说的页框(frame),大小通常为4KB,页框也叫页帧、物理块。
3:页表就是一个存放页表条目(Page Table Entry,PTE)的数组,里面存放了若干个页表条目。页表的每一行是32bit,所以如一个32位CPU的一级页表所占内存空间计算:寻址空间是4GB,每一页大小是4KB,那么总共需要4GB/4KB=1024*1024行;每一行的大小是32bit也就是4B,所以总共占用内存是4MB,且一级页表在内存中必须是连续的地址,所以,这个页表的大小是4MB,覆盖了整个0-4GB的虚拟地址空间,任何一个虚拟地址,都可以用地址的高20位(由于一页是4KB,4KB=2^12,低12位就是叶内偏移了),作为页表这个表的行号去读对应的页表项。

=============================================================

分类知识点

多级页表

一级页表的缺陷:一个进程,真的会需要一整个虚拟地址空间去存放吗?.
二级页表可以查看该博客:内存管理之二级页表详解
我们用地址的高10位作为一级页表的索引,中间10位作为2级页表的索引。CPU访问虚拟地址16,这个地址如果分解为10/10/12位的话,就是这个样子:
在这里插入图片描述
那么MMU会用0这个下标去访问一级页表(一级页表的地址填入MMU的页表地址寄存器)的第0行,第0行的内容写的是2MB(此处不再是最终的物理地址,而是二级页表的物理地址),证明二级页表的地址在2MB,于是MMU自动去以中间的10位作为下标,去查询位置在2MB的二级页表,在2级页表里面,最终查到第0页(地址范围0x00000000~0x00000FFF)这个虚拟地址的物理地址是1GB,于是MMU去访问内存条的1GB+16这个物理地址。
在这里插入图片描述
据以上分析,1级页表占据的内存是2的10次方,再乘以4,即4KB。而每个二级页表,也是2的10次方,再乘以4,即4KB。分级机制的主要好处是,二级页表不是一定存在了,比如一级页表的第2行不命中,也即如下地址都无效的话:
在这里插入图片描述
那么这一行对应的二级页表,就整个都不需要了,于是就省掉了这段区间4KB二级页表的内存占用。页表当然还有是三级甚至更多。
至于有多级页表的时候,其实MMU也只需要知道一级页表的基地址即可。每次切换进程的时候,把一级页表的地址重新填入MMU,把新的进程的页表激活即可。

进程的内存申请详解

内存分配的原理__进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap系统调用
Linux进程所能直接操作的地址都为虚拟地址。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存,获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请求页机制”产生“缺页”异常,从而进入分配实际页面的例程。在int p = (int)malloc(100M)时返回的p地址指向的是内存中的zero_page,此时若打印p指针开始的100m内存区域,结果都为0,在VMA中对于该100M区域的权限是R+W。但是在内存的页表分配中却只是R权限。当应用层对p开始的10个字节写值时,发现页表中只拥有R权限,此时会发生page_fault,此时在VMA的vm_area_struct 结构体中发现其拥有W权限,于是建立内存和虚拟内存的映射,再为其分配一页4KB大小的空间。(注:此为第一次申请的情况,因为一般来说都是通过libc或者slab来进行二次内存分配)
内核中分配空闲页面的基本函数是get_free_page/get_free_pages,它们或是分配单页或是分配指定的页面(2、4、8…512页)。
注意:get_free_page是在内核中分配内存,不同于malloc在用户空间中分配,malloc利用堆动态分配,实际上是调用brk()系统调用,该调用的作用是扩大或缩小进程堆空间(它会修改进程的brk域)。如果现有的内存区域不够容纳堆空间,则会以页面大小的倍数为单位,扩张或收缩对应的内存区域,但brk值并非以页面大小为倍数修改,而是按实际请求修改。因此Malloc在用户空间分配内存可以以字节为单位分配,但内核在内部仍然会是以页为单位分配的。
另外,需要提及的是,物理页在系统中由页结构struct page描述,系统中所有的页面都存储在数组mem_map[]中,可以通过该数组找到系统中的每一页(空闲或非空闲)。而其中的空闲页面则可由上述提到的以伙伴关系组织的空闲页链表(free_area[MAX_ORDER])来索引。

内存映射(mmap)

  • 首先普及kmalloc和vmalloc的区别:
    kmalloc是物理和逻辑都连续的物理内存映射虚拟地址;vmalloc是逻辑连续但非物理连续的vmalloc分配的内存虚拟地址。
    Linux 字符设备驱动—— ioremap() 函数解析
    在这里插入图片描述
    其次普及内存描述符
    参考网址:Linux驱动mmap内存映射在这里插入图片描述
    内存映射是把设备地址映射到进程空间地址(注意:并不是所有内存映射都是映射到进程地址空间的,ioremap是映射到内核虚拟空间的,mmap是映射到进程虚拟地址的),实质上是分配了一个vm_area_struct结构体加入到进程的地址空间,也就是说,把设备地址映射到这个结构体,映射过程就是驱动程序要做的事了。
    接下里是正餐,mmap详解:
    Linux mmap
    该博客从内核角度解释了Linux下的mmap。

在这里插入图片描述

局部性原理

时间局部性:如果一个数据/指令正在被访问,那么在近期它很可能还会被再次访问。
空间局部性:在最近的将来将用到的信息很可能与现在正在使用的信息在空间地址上是临近的。
顺序局部性:在典型程序中,除转移类指令外,大部分指令是顺序进行的。 简单来说,对同一个进程而言,CPU访问了某个逻辑地址的数据/指令,则CPU在将来很有可能再次访问这个虚拟地址或相邻的一小片连续地址,这就是局部性原理。
学习一下什么是页面调入和页面置换?如何置换页?以及何为LRU置换算法? 都可以查看该博客:第四章

CPU寻找页面的过程

CPU寻址则建议查看廖威雄: 学习Linux必备的硬件基础一网打尽的第六章,讲解的很详细。

进程切换和花销

同上。

猜你喜欢

转载自blog.csdn.net/baidu_38410526/article/details/104099150