操作系统-四、内存管理(小林coding笔记)

文章详细阐述了虚拟内存的作用,包括内存分段和分页机制,以及它们如何解决内存碎片问题。介绍了Linux内存管理中malloc、brk和mmap的内存分配方式,以及内存回收的策略,如后台和直接回收,以及OOM杀手机制。此外,讨论了NUMA架构下的内存管理策略,以及内核如何处理匿名页和文件页的回收。文章还涵盖了预读失效、缓存污染的优化以及进程和线程的内存空间管理。
摘要由CSDN通过智能技术生成

虚拟内存

        防止内存运行多个程序时崩溃。

把进程所使用的地址隔离开,让操作系统为每个进程分配一套独立的虚拟地址。操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。

内存分段

        程序时由若干逻辑分段组成的,不同的段有不同的属性,所以用分段的形式把这些段分离出来。

        虚拟地址由段选择因子段内偏移量组成。

 带来的问题:内存碎片、内存交换的效率低。

内存碎片主要分为内部内存碎片和外部内存碎片,但一般不会出现内部内存碎片。用swap空间解决外部内存碎片,将内存和硬盘进行空间交换。但这样很慢导致效率低。

内存分页

        分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。

分页不会产生外部内存碎片,但页内会出现内存浪费,导致内部内存碎片的现象。

分页机制下,虚拟地址分为页号和页内偏移两部分。

多级页表 节约空间          TLB 节约时间,把最常访问的页表项存储到访问更快的硬件中,成为页表缓存。

段页式内存管理

        先把程序分为多个有逻辑意义的段,再把每个段分成固定大小的页。地址结构就由段号、段内页号和页内位移组成。

Linux内存主要采用页式内存管理,但同时不可避免的涉及了段机制。

内存分配

        malloc()是c库里的函数,用于动态分配内存。

方式一通过brk()系统调用从堆分配内存,方式二通过mmap()系统调用在文件映射区域分配内存。

             

 malloc分配的是虚拟内存,在分配内存时,会预分配更大的空间作为内存池。

malloc(1)实际分配132K字节的内存。

brk()方式申请的内存,free释放内存时,没有把内存归还给操作系统而是缓存在malloc内存池中了,待下次使用;而mmap方式申请的内存会归还给操作系统。

频繁使用mmap分配内存,每次都要进行运行态的切换,还会发生缺页中断,导致CPU消耗大。

频繁使用brk分配内存,堆内将产生越来越多不可用的碎片,导致内存泄漏。

free函数会对传进的内存地址向左偏移16字节,分析出内存块的大小。

内存回收

        后台内存回收和直接内存回收;

        后台内存回收过程是异步的,不会阻塞进程的执行;若后台异步回收跟不上就开始直接回收,这个过程是同步的,会阻塞进程的执行。若仍然无法满足内存需求,那么就会触发OOM机制。

        OOM Killer机制根据算法选择占内存高的进程将其杀死。

可以回收的内存

        文件页:内核缓存的磁盘数据和内核缓存的文件数据;回收干净页的方式是直接释放内存,回收脏页的方式是先写回磁盘在释放内存。

        匿名页:这部分内存没有实际载体。通过Swap机制先把内存写到磁盘,再释放内存。

回收都是基于LRU算法,也就是优先回收不常访问的内存。

调整文件页和匿名页的回收倾向:在Linux里把swappiness的数值设置为0,这样更倾向于文件页的回收,减少磁盘I/O的操作。

内核定义了三个内存阈值来衡量当前剩余内存的情况:页高阈值、页低阈值、页最小阈值。

 橙色部分:执行内存回收,直到剩余内存大于高阈值。异步回收

红色部分:触发直接内存回收。

通过调节内存最小阈值类设置页低阈值和页高阈值。

NUMA架构下的内存回收策略

SMP架构:多个CPU处理器共享资源的电脑硬件架构,每个CPU地位平等。也称为一致存储访问结构(UMA),但用同一根总线访问内存,总线压力大。

NUMA架构将每个CPU进行了分组,每组用Node表示,每个Node有自己独立的资源,包括内存、IO等。每组之间可以通过互联模块总线通信。

 在NUMA架构下,某个Node内存不足时,系统可以从其他Node寻找空闲内存。

保护进程

        omm_badness()根据进程消耗的内存打分,消耗的内存越大越容易被杀掉,可以通过调整omm_score_adj的值改变得分结果。

        将omm_score_adj设置为-1000,进程就不会被杀掉。

避免预读失效和缓存污染

        Linux操作系统的缓存:对读取的文件数据缓存到页缓存中。

        MySQL的缓存:Innodb存储引擎设计了一个缓冲池,其属于内存空间。

传统LRU算法:访问的页在内存中,把该页对应的LRU链表节点移动到链表头部;不在内存中,把该页放到LRU链表头部还要淘汰末尾的页。

预读机制:操作系统出于空间局部性原理,提前将磁盘块后续的数据都加载到内存中。好处是减少了磁盘I/O次数。

预读失效:被提前加载来的页没有被访问,反而占用链表头部位置,导致淘汰热点数据。

改进:

        Linux实现了两个LRU表:活跃LRU链表(active list)和非活跃LRU链表

        MySQL在LRU链表上划分了两个区域:young和old区域。

将预读页加入到inactive list的头部,当预读页被访问时,才将页插入active list的头部。

缓存污染

        热点数据都被挤到非活跃链表了,就导致活跃链表被污染了。

改进:

        提高进入活跃LRU链表的门槛,保证热点数据不会被轻易替换掉。

        Linux在内存也被第二次访问时,才把页升级到活跃链表中。

        MySQL在内存页被第二次访问同时时间间隔超过1秒时才会升级到young区。

深入理解虚拟内存管理

        进程虚拟内存空间

 存放进程程序二进制文件中的机器指令的代码段

定义全局变量和静态变量的数据段和BSS段

用于动态申请内存的堆

存放动态链接库以及内存映射区的文件映射与匿名映射

存放函数调用过程中的局部变量和函数参数的栈

Linux进程虚拟内存空间

 

 进程虚拟内存空间的管理

         进程在内核中的描述符task_struct结构,每个进程都有唯一的mm_struct结构体,保证虚拟地址空间都是独立互不干扰的。

        通过fork()创建的子进程,其虚拟内存空间和父进程的虚拟内存空间是一模一样的,直接拷贝过来的。

        通过vfork或clone系统调用创建的子进程,是将父进程的虚拟内存空间以及相关页表直接赋值给子进程,也就是父子的虚拟内存空间是共享的,一样的,并不是一份拷贝。这样子进程就变成了线程

        是否共享地址空间几乎是进程和线程的本质区别。

        内核线程和用户态线程的区别是内核线程没有相关的内存描述符mm_struct;

划分用户态和内核态虚拟内存空间

         进程的内存描述符mm_struct结构体中的task_size变量定义了用户态地址空间和内核态地址空间的分界线。

        内核中用mm_struct结构体中的属性来定义虚拟内存空间的不同内存区域。

 内核管理虚拟内存空间

结构体vm_area_struct描述了虚拟内存空间

 虚拟内存区域的访问权限和行为规范

        vm_page_prot偏向于定义页这一级别的访问控制权限,可直接应用在底层表中。

        vm_flags偏向于定义整个虚拟内存空间的访问权限和行为规范。

 内存映射中的映射关系

        属性anon_vma关联匿名映射区;属性vm_file关联被映射的文件;vm_pgoff表示映射进虚拟内存中的文件内容在文件中的偏移。

结构体 vm_area_struct结构中有一个vm_ops用来指向针对虚拟内存区域的相关操作的函数指针。

组织关系

内核中通过一个双向 vm_area_struct结构中的双向链表将虚拟内存空间中的虚拟内存区域串联起来的。头指针存储在内存描述符struct mm_struct结构体中的mmap中;每个虚拟内存区域又通过struct vm_arrea_struct中的vm_mm指针指向所属的虚拟内存空间mm_struct;

还有一种红黑树用于高效的查找,每个VMA都是树的一个结点,红黑树的根结点存储在mm_rb中。

 二进制文件映射到虚拟内库空间

        通过函数load_elf_binary将ELF格式的二进制文件中的Section加载并映射到虚拟内存空降中。

内核虚拟内存空间

        内核态虚拟内存空间是所有进程共享的。

32位体系内核虚拟内存空间布局

        直接映射区:范围为 3G -- 3G + 896m这块连续的虚拟内存地址会映射到0-896M这块连续的物理内存上。

        直接映射区存放的内容:前1M被系统占用,之后的存放内核代码段、数据段、BSS段。还有进程相关的数据结构、内核栈。

        物理内存896M以上的区域被内核划分为ZONE_HIGHMEM区域,称为高端内存。通过动态映射的方式映射到128M大小的内核虚拟内存空间中。

        动态映射区:动态的一部分一部分的分批映射。

         永久映射区:建立与物理高端内存的长期映射关系。

        固定映射区:虚拟地址是固定的,被映射的物理地址是可以改变的。相当于一个指针常量。

        临时映射区:临时映射,用完就释放。

 64位体系内核虚拟内存空间布局

        64位下内核虚拟内存空间很大,不用精细管理,直接映射就可以了。

 

 物理内存地址

         内存也叫随机访问存储器(RAM)

        静态RAM(SRAM):用于CPU高速缓存L1、L2、L3,访问速度快,容量小,造价高;

        动态RAM(DRAM):用于常说的主存,访问速度较慢,容量较大,相对便宜。

 主存由一个个存储器模块组成,多个存储器模块连接到存储控制器上,就聚合成了主存。

         DRAM芯片的存储结构是二维矩阵,二维矩阵中存储的元素成为超单元。都有坐标地址。

         包含两个地址引脚,八个数据引脚。

DRAM芯片的访问

 CPU读写主存

 CPU和主存之间的数据交互是通过总线完成的,数据在总线的传送过程称为总线事务。

总线上传输的信号有地址信号、数据信号、控制信号。总线传输的地址为物理内存地址

IO bridge负责转换不同总线上的电子信号。

CPU从内存读数据

 存储控制器将物理地址转换为二维坐标,并广播给所有存储器模块。并依次从DRAM0到DRAM7读取相应的supercell。

 物理内存地址实际上是不连续的,因为这连续的八个字节实际上存储与不同的DRAM芯片上。

Linux物理内存管理

        物理内存划分为内存页,在内核中用struct page结构体进行管理。每个结构体定义了一个索引编号PFN,与struct page 一 一 对应。

物理内存模型

        FLATMEN平坦内存模型

        假设内存是地址连续的内存空间,内核将内存空间分为一页一页的内存块struct page,用一个数组来组织连续的的物理内存页。

 内核使用mem_map的全局数组来组织所有划分出来的内存页,计算逻辑就是基于mem_map数组进行偏移操作。

        DISCONTIGMEM非连续内存模型

平坦模型管理非连续内存时会造成内存空洞。

        在DISCONTIGMEM非连续内存模型中,内核将物理内存从宏观上划分成了一个个的结点node,每个结点管理一块连续的物理内存。

 每个节点中还是采用FLATMEM平坦内存模型来组织管理物理内存页。

        SPARSEMEM稀疏内存模型

DISCONTIGMEN内存模型中的每个node中的物理内存也不一定都是连续的。若node数目多了,开销变大。

SPARSEMEM稀疏内存模型核心思想是对粒度更小的连续内存块进行精细管理。管理单元称为section。

 物理内存热插拔

热插拔分为两个阶段:物理热插拔阶段和逻辑热插拔阶段。

SPARSEMEM内存模型中的每个men_section都可以在系统运行时改变offline和online的状态,当offline时,内核把这部分内存隔离开,然后将内存页迁移到其他内存上。

 内核将物理内存根据物理页是否可前移,划分为不可迁移页、可回收页、可迁移页。热拔出时,操作的都是可迁移的内存页,从而使内存顺利拔出。

物理内存架构

        从CPU访问物理内存的角度观察物理内存的架构。

        一致性内存访问UMA架构

多个CPU访问内存都要过总线,且距离相同,访问速度都是一样的,这种模式叫SMP,即对称多处理器

 优点:结构简单,访问速度一致。

缺点:总线压力会变大,CPU可用带宽减少;总线长度增加,访问延迟。

非一致性内存访问NUMA架构

        内存被划分为一个个内存结点,每个CPU都有自己的本地内存节点,本地内存不足时,需要跨节点访问其他内存节点。

 NUMA内存分配策略

       

 内核管理NUMA节点

        内核统一组织NUMA节点

内核使用了一个类型为struct pglist_data的全局数据node_data[]来管理所有的NUMA节点。

         NUMA节点描述符pglist_data结构

node_id表示NUMA节点的id;

struct page 类型的数组node_men_map包含了NUMA节点内所有的物理内存页;

node_start_pfn指向NUMA节点内第一个物理页的PFN;

node_present_pages用于统计NUMA节点内所有真正可用的物理页面数量;

 NUMA节点物理内存区域的划分

        

 ZONE_MOVABLE 是一个虚拟内存区域,该区域的物理页都是可以迁移的,主要为了防止内存碎片和支持内存的热插拔。

NUMA节点之间所包含的物理内存区域个数是不一样的。

 NUMA节点中内存规整与回收

        kswapd进程用于回收不经常使用的页面;

        kcompactd进程用于内存的规整避免内存碎片。

NUMA节点的状态

        NUMA节点多余一个,内核会维护一个位图node_states,用于维护各个NUMA节点的状态信息。

内核管理NUMA节点中的物理内存区域

        内核中描述和管理NUMA节点中物理内存区域的结构体是struct zone;

 物理内存区域中的预留内存

        每个物理内存区域struct zone为操作系统预留了一部分内存,用于内核的核心操作。

        内核不允许高位内存对低位内存的无限制挤压,每个内存区域可以根据各自的lowmen_reserve_ratio计算各自区域中的预留内存大小;也可以进行动态调整。

物理内存区域中的水位线

        内存资源紧张时应对方法:产生OOM、内存回收、内存规整。

内核为每个NUMA结点中的每个物理内存区域定制了三条指示内存容量的水线:WMARK_MIN(页最小阈值)WMARK_LOW(页低阈值)WMARK_HIGH(页高阈值)

 水位线的数值是以min_free_kbytes为基准分别计算出来的,用户也可以通过sysctl来动态设置这个参数。

要保证kswapd进程活动范围大一些,这样就减少了直接回收内存的可能。调整watermark_scale_factor重新计算水位线之间的间距。

物理内存区域中的冷热页

        热页:已经加载进CPU高速缓存中的物理内存页;

        冷页:还未加载进CPU高速缓存中的物理内存页;

内核关于冷热页的管理全部封装在struct per_cpu_pageset结构中。两个pre_cpu_pages结构分别管理冷页和热页。

内核5.0版本直接使用struct pre_cpu_pages的链表来集中管理系统中所有CPU高速缓存冷热页。

每种迁移类型都对应一个冷热页链表。

内核描述物理内存页

        Linux采用4KB作为标准物理内存页的大小:必须是2的整数次幂;4KB是磁盘块大小的整数倍,同时传输小块数据会更高效

        struct page结构是内核中访问最频繁的结构体,其中包含了大量的union结构,被用于同一块内存根据不同的场景保存不同类型数据的一种方式。

匿名页的反向映射

        从物理内存映射到虚拟内存,用于某个物理内存页需要进行回收或迁移,此时就要去找到这个物理页被映射到了哪些进程的虚拟地址空间,并断开他们的映射。

        内核可以直接检出page结构中的mapping指针的最低位来判断该物理内存到底是匿名页还是文件页。

内存回收相关属性

        内核为了将页面的使用频率这个因素加入进来,就引入了active链表和inactive链表。

回收优先级:inactive 链表尾部 > inactive 链表头部 > active 链表尾部 > active 链表头部。

因为匿名页的换入换出代价大,所以要优先替换文件页。对active链表和inactive链表按照匿名页和文件页进行了归类。(匿名页的active链表,inactive链表和文件页的active链表,inactive链表)

swappiness设置为0时,内核只会置换文件页,不会置换匿名页。

物理内存页属性和状态的标志位flag

        struct page中的flag字段的高8位用来表示struct page的定位信息,剩余低位表示特定的标志位。

 复合页compound_page相关属性

        巨型大页:通过两个或者多个物理上连续的内存页page组装成一个比普通内存页page更大的页。

        可减少缺页中断提高性能;所需要的页表项比普通页要少,加速内存访问;拷贝父进程的页表时更快。

 复合页中所有尾页都会指向首页从而组合成一个完整的复合页。

 Slab对象池相关属性

        slab好比一个对象池,内核中的数据结构对象都对应于一个slab对象池,用于分配固定类型对象所需要的内存。从伙伴系统申请一整页内存,然后划分为多个大小相等的小块内存被slab管理。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值