Linux 系统死机后挽救措施

一、背景

在这里插入图片描述

  因我们日常使用Linux系统过程中,会不时遇到系统崩溃的事,但这时系统界面除了呈现一片告警字符外,无发执行任何其他操作,留给我们的要不重启,要不就是尴尬等待指令。那面对会这种情况,还到底有没有可操作的空间呢?未必,也许可绝处逢源。

在这里插入图片描述
资源链接:Linux内存管理子系统centos7调优

二、Linux系统崩溃

2.1、OOM

OOM是Out of Memory的简写,也就是内存不足,它是Linux内核在系统内存严重不足时,强行释放进程内存的一种机制。可能原因有系统bug,内存泄露。有时候服务过载,比如短时间内连接数飙升,数据量大导致内存飙升,OOM宕机 ;另外还有就是Java类程序导致,最常见的是四类:堆溢出(Heap), 栈溢出(Stack), 永久代溢出(常量池/方法区), 直接内存溢出。

对于云主机,inux操作系统在实例全局内存或实例内cgroup的内存不足时,会先触发内存回收机制释放内存,并将这部分被释放的内存分配给其他进程。如果内存回收机制不能处理系统内存不足的情况,则系统会触发OOM Killer强制释放进程占用的内存。

2.2、内存结构

在这里插入图片描述

  Linux内核中,如何管理系统中所有的内存资源的分配,回收,异常触发和处理等相关逻辑实现呢,这就要提到内存管理子系统,它使用节点(node)、**区域(zone)页(page)**三级结构描述物理内存。内存节点 ( Node ) 是 " 内存管理 " 的 最顶层的结构 , 下层分别是 区域 和 页 ; 内存管理子系统会按照一定的算法管理内存,并且对用户提供调用接口,处理用户的内存分配、重分配、释放和内存分区(对象)的创建、初始化、删除,还包括了虚拟内存与请求 分页的实现,内核内部结构和用户空间程序的内存分配、将文件映射到进程地址空间等。用户无需费心考虑内存的管理,而只需要调用内存管理机制提供的调用接口。它的大多数配置都可以通 过 /proc 文件系统获得,并且可以使用 sysctl 进行查询和调整。内存管理子系统支持 3 种内存模型:
在这里插入图片描述

(1)平滑内存(Flat Memory):内存的物理地址空间是连续的,没有空洞。

(2)非连续内存(Discontiguous Memory):内存的物理地址空间存在空洞,这种模型可以高效地处理空洞。系统包含多块物理内存,两块内存的物理地址空间之间存在空洞。当然一块内存的物理地址空间也可能存在空洞。如果内存的物理地址空间是连续的,就不会像不连续内存模型那样产生额外的开销,降低性能,因此平滑内存模型性能更好。

(3)稀疏内存(Sparse Memory):内存的物理地址空间存在空洞。如果要支持内存热插拔,只能选择稀疏内存模型。

在这里插入图片描述

☛ 内存管理模型图:

在这里插入图片描述
在这里插入图片描述

在 NUMA 非一致内存访问架构 中, 将 CPU 划分为多个节点 , 每个节点都有自己的 " 内存控制器 " 和 " 内存插槽 " , CPU 访问自己的节点上的 内存 很快 , 但是访问其它 CPU 的内存 很慢 ; UMA(Uniform Memory Access) 统一内存访问架构 / SMP 对称多处理器架构 , 就是当做 1 个节点的 NUMA 架构的系统 ;NUMA 非一致内存访问结构 中 的 " 内存节点 " , 根据 " 处理器与内存的距离 " 划分 " 内存节点 " ;在 不连续内存 的 NUMA 架构中 , 根据 " 物理地址是否连续 " 划分 " 内存节点 " , 每个 物理地址连续 的内存块 是一个 " 内存节点 " ;在代码结构中,node_zones 是 内存区域数组 ;nr_zones 是 该 " 内存节点 " 包含 多少个 " 内存区域 " ;node_id 是 节点标识符 ;node_start_pfn 是 该 " 内存节点 " 的 起始物理页 编号 ;node_present_pages 是 物理页 的总数 ;node_spanned_pages 是 物理页 的区间范围 总大小 , 该大小包括 " 内存空洞 " 大小 ;

1、:有时也成为页帧。在内核中,内存管理单元MMU(负责虚拟地址和物理地址转换的硬件)是把物理页Page作为内存管理的基本单位。体系结构不同,支持的页大小也不尽相同。每个页都会对应有一个struct page的实例。一般位4KB。通常来说32位体系结构支持4KB的页,64位体系结构一般会支持8KB的页大小。我当前使用的MIPS64架构的主机,支持的页大小为16KB。如果你使用的主机系统是Linux。我们可以使用如下命令查看当前主机的内存页大小:getconf PAGE_SIZE;如果与硬盘上的数据相比,页的内容已经改变,则置为 PG_dirty ,设置该标志的页称为脏页。

2、区:是比页更大的一个概念。内核中使用区(zone)对具有相似特性的页进行分组。Linux主要使用了四种区:
ZONE_DMA :这个区的页面可以执行DMA操作
ZONE_DMA32:功能同ZONE_DMA,区别在于在此区域的页只能被32位设备访问。
ZONE_NORMAL:正常可寻址的页。一般用途的内存都可以从此区分配需要的页
ZONE_HIGHEM:一般是为了解决物理内存大于虚拟内存数量时的地址映射。例如在IA-32系统上,可以直接管理的物理内存数量不超过896MiB。超过该值的内存只能通过高端内存寻址。目前多数大芯片为64位体系结构,所能管理的地址空间巨大,所以ZONE_HEGHEM基本不用。

在NUMA模型中,一个存储节点Node结点关联到系统中的一个处理器,在内核中表示为 pg_data_t 的实例。从系统体系架构来说,主机可以分为如下种类:
UMA(一致内存访问)结构:各CPU共享相同的物理内存,每个 CPU访问内存中的任何地址所需时间是相同的。因此此类结构也称:对称多处理器结构(SMP)。
NUMA(非一致内存访问):各CPU都有本地内存,可支持特别快速的访问。各个CPU之间通过总线连接起来,以支持对其他CPU的本地内存访问,当然比访问本地内存慢些。在NUMA架构的内核中,内存被划分为节点Node。可简单理解:每个节点关联到系统中的一个处理器上;一个CPU或多个CPU对应一个节点Node, 一个节点可以划分为多个区Zone,一个区管理多个页Page、页为内核管理的最小单位。

3、页表(pgtable):用来将虚拟地址空间映射到物理地址空间的数据结构称为页表。

4、slab分配器:通常我们会面临的问题的某个数据结构(或者对象)会频繁的分配和释放,这会带来的两个问题是内存碎片和效率低下。要解决这个问题的办法就是使用缓存机制(高速缓存)。就是预先分配一个比较大的空间,后面每次的 内存申请和释放都是对这块内存空间的操作。这个高速缓存可以由多个slab组成。而slab又由一个或者多个物理上连续的页组成(通常一个slab仅仅由一页组成),里面存放多个有着相同数据结构的对象。

5、内存节点:Linux将内存分为节点的主要原因是为了支持NUMA(Non-Uniform Memory Access)体系结构。在NUMA体系结构中,处理器访问本地内存的速度比访问远程内存的速度要快得多。因此,将内存划分为多个节点,每个节点都有一定数量的本地内存和一些本地处理器,可以提高内存访问的效率和性能。在具有不连续内存的 UMA 系统中,node表示比区域的级别更高的内存区域,根据物理地址是否连续划分,每块物理地址连续的内存是一个内存节点,它使用一个 pglist_data 结构体描述内存布局;一般多少核的cpu就有多少个节点node。Node由挂在同一个CPU下的一片连续的物理内存组成,在内核中使用pg_data_t进行抽象。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在NUMA存储模式下,可运行cat /proc/buddyinfo或numactl -H命令查看内存节点相关资源信息。
在这里插入图片描述

☛ 相关概念:

1)虚拟内存:是一种内存管理技术,允许程序使用比实际物理内存更多的内存。当程序需要使用内存时,虚拟内存会将一部分数据从硬盘中复制到内存中,这样程序就可以继续执行。Linux内核使用一种称为页表的机制来管理虚拟内存。每个进程都有自己的页表,其中包含虚拟地址和对应的物理地址。当进程需要访问某个虚拟地址时,Linux内核会将其转换为对应的物理地址,并将其加载到内存中。
在这里插入图片描述

2)页面置换算法:是用于虚拟内存管理的一种算法。当内存不足时,页面置换算法会将某些页面从内存中移除,并将新的页面加载到内存中。常见的页面置换算法有FIFO、LRU、LFU等。Linux内核中使用了一个称为**“页面调度器”**的模块来选择页面置换算法。页面调度器会根据系统负载、内存压力等因素来选择最适合的页面置换算法。

3)内存碎片:是指内存中存在很多小的空闲块,但这些空闲块无法组合成足够大的连续块,从而无法满足大内存请求。Linux内核使用了一种称为“伙伴系统”的技术来解决内存碎片问题。

4)共享内存:是一种允许进程共享同一块物理内存的机制。在Linux内核享内存由内核维护,可以通过系统调用shmget()和shmat()来创建和访问共享内存。

5)交换空间:是一种虚拟内存技术,它允许将不常用的内存数据保存到硬盘中,以释放物理内存。Linux内核使用了一个称为“交换分区”的技术来管理交换空间。

6)Linux内核中如何分配内存?Linux内核中使用了一种称为“伙伴系统”的技术来分配内存。伙伴系统将可用内存块划分成不同的大小类别,并使用二叉树结构来管理这些内存块。当需要分配内存时,伙伴系统会选择最适合的大小类别,并将其分配给进程。

7)SLAB分配器:是Linux内核中的一种内存管理机制,用于高效地分配和释放小型对象。SLAB分配器使用了三个缓存池,分别用于保存空闲对象、部分被占用的对象和完全被占用的对象。这种机制可以提高内存分配和释放的效率。

8)调试Linux内核中的内存问题时,我们可借助Linux内核中自带的一些工具来调试内存问题,例如:

memtest86+:一个用于检测内存错误的工具。
kmemleak:一个用于检测内存泄漏的工具。
slabtop:一个用于查看SLAB分配器状态的工具。
vmstat:一个用于监控系统内存使用情况的工具。
此外,还可以使用gdb等调试器来跟踪内存分配和释放的过程,以及使用内核代码中的打印语句进行调试。

9)内存映射:是一种将文件映射到进程地址空间的技术。在Linux内核中,可以使用mmap()系统调用来创建内存映射区域。这样做可以让程序直接读写文件内容,而无需进行复制或者使用缓冲区。

10)大页是指大小超过4KB的页面。在Linux内核中,可以使用HugePages技术来管理大页。HugePages技术允许用户创建一个由大页组成的内存池,并将其分配给进程使用。这样可以提高内存分配和访问的效率。

11)NUMA架构?NUMA(Non-Uniform Memory Access)架构是一种多处理器体系结构,其中每个处理器都有自己的本地内存,而远程内存则需要通过网络连接来访问。在Linux内核中,可以使用NUMA技术来优化内存访问性能,以便更好地适应NUMA架构。

12)Linux内核使用了一些机制来保护内存安全,例如:

地址空间布局随机化(ASLR):随机化地址空间布局,使攻击者难以猜测系统中各个部分的位置。
堆栈保护:使用堆栈保护技术来防止缓冲区溢出攻击。
内核地址空间隔离(KASLR):将内核代码和数据分配到不同的地址空间中,从而提高系统的安全性。

13)内存压缩:是一种在内存不足时,将部分内存数据压缩并保存到内存中的技术。在Linux内核中,可以使用zswap技术来实现内存压缩。这样可以避免频繁地进行页面置换,从而提高系统的性能。

14)内存泄漏:是指程序中存在一些内存资源没有被正确释放的情况。如果这种情况发生得越来越频繁,系统可能会耗尽所有可用的内存,导致系统崩溃或者变得非常缓慢。在Linux内核中,可以使用kmemleak等工具来检测和修复内存泄漏问题。

15)DMA(Direct Memory Access)内存是一种直接访问物理内存的机制,它允许外部设备(如网络适配器、硬盘控制器等)直接读写系统内存。那Linux内核中如何管理DMA内存?在Linux内核中,可以使用DMA映射技术来管理DMA内存。DMA映射技术可以将系统内存映射到DMA地址空间,从而允许外部设备直接访问系统内存。

16)页面回收:在Linux内核中,页面回收是一种重要的机制,它可以帮助系统有效地管理内存资源。Linux内核使用了一个称为“页回收器”的机制来处理页面回收。页回收器会定期扫描系统中的页面,并将不再使用的页面移动到空闲列表中,以便后续的内存分配。

17)内存锁定:是一种将内存页锁定在物理内存中的机制。在Linux内核中,可以使用mlock()和mlockall()系统调用来实现内存锁定。这样做可以防止页面置换或者交换操作对这些内存页的影响。

18)内存热插拔:是指在系统运行过程中,动态添加或删除内存设备。在Linux内核中,可以使用ACPI(Advanced Configuration and Power Interface)技术来处理内存热插拔。ACPI技术可以通过通知内核来处理内存设备的添加和删除,并自动更新内存映射关系。

19)Linux内核中如何处理内存保护?在Linux内核中,可以使用一些机制来保护内存安全,例如地址空间布局随机化(ASLR)、堆栈保护、内核地址空间隔离(KASLR)等。此外,还可以使用SECCOMP(Secure Computing Mode)模式来限制进程的系统调用权限,从而提高系统的安全性。

20)内存共享:是指多个进程共享同一块物理内存的机制。在Linux内核中,可以使用共享内存(Shared Memory)技术来实现内存共享。共享内存由内核维护,可以通过系统调用shmget()和shmat()来创建和访问共享内存。

21)内存映射?在Linux内核中,可以使用mmap()系统调用来创建内存映射区域。内存映射区域可以将文件映射到进程地址空间,使程序可以直接读写文件内容,而无需进行复制或者使用缓冲区。

22)NUMA架构?在Linux内核中,可以使用NUMA(Non-Uniform Memory Access)技术来优化内存访问性能,以便更好地适应NUMA架构。NUMA技术可以通过调整内存分配策略和页面置换算法来优化内存访问效率。

23)内存隔离:内存隔离是一种将不同进程或者用户的内存资源隔离开来的机制。在Linux内核中,可以使用cgroups(Control Groups)技术来实现内存隔离。cgroups可以将进程或者用户的内存资源限制在一定范围内,从而避免出现资源竞争或者滥用问题。

24)内存缓存:在Linux内核中,可以使用页缓存(Page Cache)技术来处理内存缓存。页缓存是一种将文件内容缓存到内存中的机制,以便程序可以更快地读取和写入文件内容。页缓存可以通过调整缓存大小和页面置换算法来优化系统性能。

25)内存保留是指将一些内存资源保留在系统中,不允许其他进程或者用户使用。在Linux内核中,可以使用mmap()系统调用的MAP_LOCKED标志来实现内存保留。这样做可以防止页面置换或者交换操作对这些内存页的影响。

26)大量小对象分配:在Linux内核中,可以使用SLAB分配器和SLUB分配器等技术来处理大量小对象分配。SLAB分配器是一种专门用于高效分配小型对象的机制,而SLUB分配器则是一种更加简单和高效的分配器,适用于大部分情况下的小对象分配。

27)内存监控是指实时监控系统中的内存使用情况,并提供相应的统计信息和警报功能。在Linux内核中,可以使用vmstat、sar、top等工具来进行内存监控。此外,还可以使用一些第三方工具来实现更加高级的内存监控功能。

28)大页处理:在Linux内核中,可以使用HugePages技术来管理大页。HugePages技术允许用户创建一个由大页组成的内存池,并将其分配给进程使用。这样可以提高内存分配和访问的效率。

☛ 内存管理软件架构:

在这里插入图片描述

内核内存管理的核心工作就是内存的分配回收管理,其内部分为2个体系:页管理对象管理。页管理体系是一个两级的层次结构,对象管理体系是一个三级的层次结构,分配成本和操作对CPU cache和TLB的负面影响,从上而下逐渐升高。

1、页管理层次结构:由冷热缓存、伙伴系统组成的两级结构。负责内存页的缓存、分配、回收。

2、对象管理层次结构:由per-cpu高速缓存、slab缓存、伙伴系统(UME)组成的三级结构。负责对象的缓存、分配、回收。这里的对象指小于一页大小的内存块。

在这里插入图片描述

除了内存分配,内存释放也是按照此层次结构操作。如释放对象,先释放到per-cpu缓存,再释放到slab缓存,最后再释放到伙伴系统。

2.3、OOM参数

1、panic_on_oom

当kernel遇到OOM的时候,可以有两种选择:

(1)产生kernel panic(直接死机)。
(2)积极面对,选择一个或者几个最“适合”的进程,启动OOM killer,干掉那些选中的进程以释放内存缓解,让系统继续活下去,等待进一步处理。

panic_on_oom参数是控制当遇到OOM的时候,系统如何响应的。当该参数等于0的时候,表示选择积极面对人生,启动OOM killer。当该参数等于2的时候,表示无论是哪一种情况,都强制进入kernel panic。panic_on_oom等于其他值的时候,表示要区分具体的情况,对于某些情况可以panic,有些情况启动OOM killer。k

对于UMA而言, oom_constraint永远都是CONSTRAINT_NONE,表示系统并没有什么约束就出现了OOM。在NUMA的情况下,有可能附加了其他的约束导致了系统遇到OOM状态,实际上,系统中还有充足的内存。这些约束包括:

(1)CONSTRAINT_CPUSET:cpusets是kernel中的一种机制,通过该机制可以把一组cpu和memory node资源分配给特定的一组进程。这时候,如果出现OOM,仅仅说明该进程能分配memory的那个node出现状况了,整个系统有很多的memory node,其他的node可能有充足的memory资源。

(2)CONSTRAINT_MEMORY_POLICY:memory policy是NUMA系统中如何控制分配各个memory node资源的策略模块。用户空间程序(NUMA-aware的程序)可以通过memory policy的API,针对整个系统、针对一个特定的进程,针对一个特定进程的特定的VMA来制定策略。产生了OOM也有可能是因为附加了memory policy的约束导致的,在这种情况下,如果导致整个系统panic,就需要及时调整策略。

(3)CONSTRAINT_MEMCG:MEMCG就是memory control group,Cgroup中的memory子系统就是控制系统memory资源分配的控制器,通俗的将就是把一组进程的内存使用限定在一个范围内。当这一组的内存使用超过上限就会OOM。

其中,内核中sysctl_panic_on_oom变量是和/proc/sys/vm/panic_on_oom对应的,默认值为0。

2、oom_kill_allocating_task

当系统选择了启动OOM killer,试图杀死某些进程的时候,又会遇到这样的问题:干掉哪个,哪一个才是“合适”的哪那个进程?系统可以有下面的选择:

(1)谁触发了OOM就干掉谁

(2)谁最“坏”就干掉谁

oom_kill_allocating_task这个参数就是控制这个选择路径的,当该参数等于0的时候选择(2),否则选择(1)。
当然也不能说杀就杀,还是要考虑是否用户空间进程(不能杀内核线程)、是否unkillable task(例如init进程就不能杀),用户空间是否通过设定参数(oom_score_adj)阻止kill该task。排除这些安全限制后,那么就调用oom_kill_process干掉当前进程,对应的文件/proc/sys/vm/oom_kill_allocating_task ,默认值为0。

3、oom_dump_tasks

当系统的内存出现OOM状况,无论是panic还是启动OOM killer,做为系统管理员,都想保留下线索,找到OOM的root cause,例如:dump系统中所有的用户空间进程关于内存方面的一些信息,包括:进程标识信息、该进程使用的total virtual memory信息、该进程实际使用物理内存(我们又称之为RSS,Resident Set Size,不仅仅是自己程序使用的物理内存,也包含共享库占用的内存),该进程的页表信息等等。拿到这些信息后,才能帮助我们后续了解现象(出现OOM)之后的真相,以便彻底处理。该参数对应的映射文件为/proc/sys/vm/oom_dump_tasks,默认值为1。

当设定为0的时候,上一段描述的各种进程们的内存信息都不会打印出来。在大型的系统中,有几千个进程,逐一打印每一个task的内存信息有可能会导致性能问题(要知道当时已经是OOM了)。当设定为非0值的时候,在下面三种情况会调用dump_tasks来打印系统中所有task的内存状况:

(1)由于OOM导致kernel panic

(2)没有找到适合的“bad”process

(3)找适合的并将其干掉的时候

4、oom_adj、oom_score_adj和oom_score

这几个参数都是和具体进程相关的,因此它们位于/proc/xxx/目录下(xxx是进程ID)。假设我们选择在出现OOM状况的时候杀死进程,那么一个很自然的问题就浮现出来:到底干掉哪一个呢?内核的算法倒是非常简单,那就是打分(oom_score,注意,该参数是read only的),找到分数最高的就OK了。那么怎么来算分数呢?

unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
const nodemask_t *nodemask, unsigned long totalpages)
{......
adj= (long)p->signal->oom_score_adj;
if (adj == 0OM_SCORE_ADJ_MIN){-------------------------------------(1)
  task_unlock(p);
  retur 0;---------------------------------------------------------(2)
}
points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS)+ atomic_long_read(&p->mm->nr_ptes) + mm_nr_pmds(p->mm);-------------------------------------(3)
task_unlock(p);
if (has_capability_noaudit(p, CAP_SYS_ADMIN))----------------------(4)
   points -= (points * 3)/ 100;
   adj *= totalpages /1000;-----------------------------------------(5)
   points += adj;
   return points > 0 ? points:1;
}

(1)对某一个task进行打分(oom_score)主要有两部分组成,一部分是系统打分,主要是根据该task的内存使用情况。另外一部分是用户打分,也就是oom_score_adj了,该task的实际得分需要综合考虑两方面的打分。如果用户将该task的 oom_score_adj设定成OOM_SCORE_ADJ_MIN(-1000)的话,那么实际上就是禁止了OOM killer杀死该进程。

(2)第2步骤中返回了0也就是告知OOM killer,该进程是“good process”,不要干掉它,实际计算分数的时候最低分是1分。

(3)系统打分是看物理内存消耗量的,主要是三部分,RSS部分,swap file或者swap device上占用的内存情况以及页表占用的内存情况。

(4)root进程有3%的内存使用特权,因此这里要减去那些内存使用量。

(5)用户可以调整oom_score,具体如何操作呢?oom_score_adj的取值范围是**-1000~1000**,0表示用户不调整oom_score,负值表示要在实际打分值上减去一个折扣,正值表示要惩罚该task,也就是增加该进程的oom_score。在实际操作中,需要根据本次内存分配时候可分配内存来计算(如果没有内存分配约束,那么就是系统中的所有可用内存,如果系统支持cpuset,那么这里的可分配内存就是该cpuset的实际额度值)。oom_badness函数有一个传入参数total pages,该参数就是当时的可分配的内存上限值。实际的分数值(points)要根据oom_score_adj进行调整,例如:如果oom_score_adj设定-500,那么表示实际分数要打五折(基数是totalpages),也就是说该任务实际使用的内存要减去可分配的内存上限值的一半。

2.4、内存映射空间划分

内核把内存映射地址空间划分成多个部分,每个划分空间都有自己的起止地址、分配接口和使用场景。下图是一个常见的32位地址空间划分结构:
在这里插入图片描述
在DMA zone和highmem zone中间的区域即normal zone,主要用于内核的动态内存分配。高端内存动态分配地址空间:高端内存分配的内存是虚拟地址连续而物理地址不连续的内存,一般用于内核动态加载的模块和驱动,因为内核可能运行了很久,内存页面碎片情况严重,如果要申请大的连续地址的内存页会比较困难,容易导致分配失败。

kmap与vmap的区别是:vmap可以映射一组page,即page不连续,但虚拟地址连续,而kmap只能映射一个page到虚拟地址空间。kmap主要用于fs、net等对高端内存访问有较高性能要求的模块中。 固定映射地址空间:持久映射的问题是可能会休眠,在中断上下文、自旋锁临界区等不能阻塞的场景中不可用。为了解决这个问题,内核又划分出固定映射,其接口不会休眠。固定映射空间通过kmap_atomic接口来映射。kmap_atomic的使用场景与kmap较为相似,主要用于mm、fs、net等对高端内存访问有较高性能要求而且不能休眠的模块中。

不同的CPU体系架构在地址空间划分上不尽相同,但为了保证CPU体系差异对外部模块不可见,内存地址空间的分配接口的语义是一致的。因为64位CPU一般都不需要高端内存(当然也可以支持),在地址空间划分上与32位CPU的差异较大,下图是一个MIPS64 CPU的内核地址空间划分图:
在这里插入图片描述

2.5、其他

检查发现:内存占用前三的进程
在这里插入图片描述
在这里插入图片描述
对于Java程序,查找java.lang.OutOfMemoryError,确认报错原因后,尝试调整JVM参数,比如-XX:MaxDirectMemorySize=numG;

对于cgroup导致的,日志看到类似:as a result of limit of 的说明;当实例的全局内存出现了不足触发OOM killer时,日志中会看到类似as a result of limit of host的提示;如果通过cpuset.mems参数指定cgroup只能使用特定内存节点的内存,则可能导致实例在具备充足的空闲内存的情况下,仍出现OOM Killer的情况。其中,cpuset 的作用是为进程的执行指定绑定的cpu 以及 对应的内存节点的。cpuset.mems 文件之中填内存节点号 或者是 内存节点范围指定允许访问的内存节点列表;注意:需要同时设置 cpuset.cpus和cpuset.mems 文件,位置/sys/fs/cgroup/cpuset/。调整cgroup内存上限的命令示例:echo value > /sys/fs/cgroup/memory/test/memory.limit_in_bytes,其中value为您为cgroup设置的内存上限。

另外,操作系统的内存在进行内存分配的过程中,如果伙伴系统的内存不足,则系统会通过OOM Killer释放内存,并将内存提供至伙伴系统。

另外这里要提一个概念:非一致内存访问(Non-Uniform Memory Access,NUMA):指内存被划分成多个内存节点的多处理器系统,访问一个内存节点花费的时间取决于处理器和内存节点的距离。每个处理器有一个本地内存节点,处理器访问本地内存节点的速度比访问其他内存节点的速度快。NUMA 是中高端服务器的主流体系结构。在实际应用中多采用混合体系结构,在 NUMA 节点内部使用 SMP (Symmetric Multi-Processor,SMP,又称一致内存访问(Uniform Memory Access,UMA,这种架构中,所有处理器访问内存花费的时间是相同的,“0 号处理器作为引导处理器负责初始化内核,其他处理器等待内核初始化完成。)体系结构。

三、紧急措施

linux死机后按电源建强制关机,非常容易进一步损坏系统或者硬件,导致重启后二次故障;实际除了这种果断措施,还能尝试以下挽救措施:

1.一种方式是进入终端界面进行操作,按ctrl+alt+f1(或者f2~f6)进入tty,使用top命令查看一下进程,把一直占用资源很大的进程kill掉,如果是在图形界面下死机的话,不要再依赖任何图形界面的工具,切换到非图形界面尝试 Alt+F7;

2.如果进入不了tty或者进入tty后不管用,可以采取终极大杀器–reisub方法,这个方法可以在系统死机的情况下安全的重启计算机,数据还不会丢失;具体操作是:按住alt+SysRq,再依次按下reisub几个键,按完b键就会重启

其实:Sys Rq 是一种叫做系统请求的东西,按住 Alt-Print/PrtSc 的时候就相当于按住了 Sys Rq 键,这个时候输入的一切都会直接由 Linux 内核来处理,它可以进行许多低级操作。这个时候 reisub 中的每一个字母都是一个独立操作,他们分别表示:
  nRaw:将键盘控制从 X Server 那里抢回来
  tErminate:给所有进程发送 SIGTERM 信号,让他们自己解决善后
  kIll:给所有进程发送 SIGKILL 信号,强制他们马上关闭
  Sync:将所有数据同步至磁盘
  Unmount:将所有分区挂载为只读模式
  reBoot:重启

  另外需要注意下,不要快速连续地按下这几个键,他们执行需要时间,因此可大概每个间隔10秒左右执行下一个,尤其s键之后因为同步数据比较慢,可以停20秒或更长一点。

在这里插入图片描述

登录后,检查实例内是否存在内存泄漏的情况。重点检查:slab_unreclaimable内存使用情况,执行:cat /proc/meminfo | grep "SUnreclaim";slab_unreclaimable内存为系统不可回收的内存,当其占用总内存的比例过高时,将会影响可用内存与系统性能。当占用总内存的10%以上时,表示系统可能存在slab内存泄漏。查看systemd内存使用情况:cat /proc/1/status | grep "RssAnon";内核发生OOM Killer时,会自动跳过系统的1号进程。此时查看systemd内存使用情况时,一般不会超过200 MB。如果出现异常,您可以尝试自行更新systemd工具的版本。查看透明大页THP(Transparent Huge Pages)的性能,开启THP会出现内存膨胀(memory bloating),从而导致OOM Killer的出现。如果异常,可以对THP进行调优。经验表明:slab内存是内核组件(或驱动)通过调用kmalloc类接口向伙伴系统申请内存,如果该内核组件(或驱动)没有正确释放内存,即实例一旦出现slab内存泄漏,slab内存泄漏会导致实例上运行的业务可用内存变少、内存碎片化,还会引起系统OOM Killer以及系统性能抖动,且无法通过kill进程的方式回收内存,则只能通过重启实例解决。

#查看内存最多的slab内存信息
slabtop -s -a   #输出如下,查看并记录OBJ/SLAB列数值较高的slab内存对应的名称
Active / Total Objects (% used)    : 4887183 / 5091415 (96.0%)
 Active / Total Slabs (% used)      : 162257 / 162257 (100.0%)
 Active / Total Caches (% used)     : 76 / 98 (77.6%)
 Active / Total Size (% used)       : 847535.38K / 872548.29K (97.1%)
 Minimum / Average / Maximum Object : 0.01K / 0.17K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
3190707 3011949  94%    0.10K  81813	   39    327252K buffer_head
1234065 1233944  99%    0.19K  58765	   21    235060K dentry

#确认slab内存是否为不可回收;命令中的<slab NAME>变量需要手动修改为上一步中获取到的OBJ/SLAB列数值较高的slab内存对应的名称,例如,查看名称为kmalloc-8的slab内存是否为不可回收,cat /sys/kernel/slab/<slab NAME>/reclaim_account
cat /sys/kernel/slab/kmalloc-8/reclaim_account #显示为0,表示slab内存不可回收;查询结果为1时,表示slab内存可回收
#crash工具进行静态分析,也可以使用perf工具进行动态分析,排查造成slab内存泄漏的原因

#crash工具静态分析
yum install crash kernel-debuginfo -y
sudo crash
>kmem -S kmalloc-8  #输出类似如下,主要关注空闲内存较少(FREE列),已分配的内存较多(ALLOCATED列)

 SLAB              MEMORY            NODE  TOTAL  ALLOCATED  FREE
  ffffea004c94e780  ffff88132539e000     0     42         29    13
  ffffea004cbef900  ffff88132fbe4000     0     42         40     2
  ffffea000a0e6280  ffff88028398a000     0     42         40     2
>rd ffffea004c94e780 512 -S   

#perf分析
yum install perf -y
#动态获取kmalloc-8中没有释放的内存,动态获取数据的时间间隔为200秒
perf record -a -e kmem:kmalloc --filter 'bytes_alloc == 8' -e kmem:kfree --filter ' ptr != 0' sleep 200
perf script > testperf.txt  #将动态获取的数据打印至临时文件中,之后需要手动排查没有空闲内存(free)的内存信息,然后在Linux内核源代码中手动查询产生slab内存泄漏的函数。

#查询透明页开启
less /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never    #开启透明大页THP的功能,always 系统全局开启透明大页THP功能;never:系统全局关闭透明大页THP功能;madvise:仅在通过madvise()系统调用,并且设置了MADV_HUGEPAGE标记的内存区域中开启透明大页THP功能。

#透明大页THP碎片整理;发生缺页异常(Page Fault)时,该功能可控制内存分别进行直接回收(Direct Reclaim)、后台回收(Background Reclaim)、直接整理(Direct Compaction)、后台整理(Background Compaction)的行为。开启或关闭该功能如下:
cat /sys/kernel/mm/transparent_hugepage/defrag #输出结果同上;always 系统分配不出透明大页时,暂停内存分配行为,总是等待系统进行内存的直接回收和内存的直接整理。内存回收和整理结束后,如果存在足够的连续空闲内存,则继续分配透明大页。defer:当系统分配不出透明大页时,转为分配普通的4KB页。同时唤醒kswapd内核守护进程以进行内存的后台回收,唤醒kcompactd内核守护进程以进行内存的后台整理。一段时间后,如果存在足够的连续空闲内存,khugepaged内核守护进程将此前分配的4KB页合并为2MB的透明大页。madvise:仅在通过madvise()系统调用,并且设置了MADV_HUGEPAGE标记的内存区域中,内存分配行为等同于always。其余部分的内存分配行为保持为:发生缺页异常时,转为分配普通的4 KB页。defer+madvise(试验性开关):仅在通过madvise()系统调用,并且设置了MADV_HUGEPAGE标记的内存区域中,内存分配行为等同于always。其余部分的内存分配行为保持为defer

#khugepaged碎片整理功能
cat /sys/kernel/mm/transparent_hugepage/khugepaged/defrag #0表关闭khugepaged碎片整理功能。1表khugepaged内核守护进程会在系统空闲时周期性唤醒,尝试将连续的4 KB页合并成2 MB的透明大页;该操作会在内存路径中加锁,并且khugepaged内核守护进程可能会在错误的时间启动扫描和转换大页,因此存在影响应用性能的可能性。

#大页分配重试间隔:当透明大页THP分配失败时,khugepaged内核守护进程进行下一次大页分配前需要等待的时间。避免短时间内连续发生大页分配失败。默认值为60000,单位为毫秒,即默认等待60秒。
cat /sys/kernel/mm/transparent_hugepage/khugepaged/alloc_sleep_millisecs

#khugepaged内核守护进程每次唤醒的时间间隔。默认值为10000,单位为毫秒,即默认每10秒唤醒一次。
cat /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs

#扫描页数:khugepaged内核守护进程每次唤醒后扫描的页数。默认值为4096个页。配置文件路径如下。
cat /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan

透明页配置推荐:透明大页THP能增加转译后备缓冲器TLB(Translation Lookaside Buffer)命中的几率,减少内存访问页表项PTE(Page Table Entries)时的开销,使系统获得性能提升。透明大页THP试图进一步释放运维压力,使用户在无感知的情况下享受到性能的提升。然而,透明大页THP的资源是有限的,当系统达到透明大页THP分配的瓶颈时,需要使用一系列机制来保证系统的正常运行。在系统运维人员不够了解透明大页THP的前提下,使用系统的默认配置对于许多应用来说是存在风险的。主要的风险可能有:

1、如果透明大页THP的碎片整理开关设置为always,内存紧张时会和普通4 KB页一样,出现内存的直接回收或内存的直接整理,这两个操作均是同步等待的操作,会造成系统性能下降。
2、如果khugepaged碎片整理的开关设置为1,在khugepaged内核守护进程进行内存合并操作时,会在内存路径中加锁。如果khugepaged碎片整理在错误的时间被触发,会对内存敏感型应用造成性能影响。
3、如果保持开启透明大页THP,同时关闭上述两个碎片整理的开关,则内存分配过程相较于4 KB页可能会更快地消耗完空闲页资源,然后系统开始进入内存回收和内存整理的过程,反而更早的出现系统性能下降。

#查看透明大页THP的使用情况,如果系统返回非零值,则说明系统中使用了一定数量的透明大页THP。
cat /proc/meminfo | grep AnonHugePages

#查看某个进程使用的透明大页THP。
cat /proc/[$PID]/smaps | grep AnonHugePages

echo 'defer+madvise' > /sys/kernel/mm/transparent_hugepage/defrag  #开启,使内核的内存后台回收(kswapd内核守护进程)、内存的后台整理(kcompactd内核守护进程)与khugepaged内核守护进程尽可能协同工作,在内存整理和性能平稳之间找到平衡点。

#如果发现系统中的khugepaged内核守护进程达到或接近100%的CPU使用率时,可以考虑增加khugepaged内核守护进程唤醒的间隔时间,比如修改为30秒,示例如下:
echo 30000 > /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs
#或者直接关闭khugepaged内核守护进程
echo 0 > /sys/kernel/mm/transparent_hugepage/khugepaged/defrag

#对于数据库应用有大量访问请求的场景、大量延迟敏感型应用场景或大量短生命周期的内存分配(Short-lived Allocation)场景,如果系统的稳定性比性能更重要,建议关闭透明大页THP功能
echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled  #仅在本次系统运行期间有效,系统重启后透明大页THP功能仍会开启

#永久关闭:在内核启动参数中添加关闭透明大页THP功能的参数
grubby --args="transparent_hugepage=never" --update-kernel="/boot/vmlinuz-$(uname -r)"
#完成后
reboot

如果是内存节点(Node)的内存不足导致的OOM Killer,就需要重新配置cpuset.mems接口的值,使cgroup能够合理使用内存节点的内存。Node节点数量查询:cat /proc/buddyinfo;配置cpuset.mems:echo value > /sys/fs/cgroup/cpuset/进程名/cpuset.mems;其中,value为对应的内存节点号。比如,系统中存在三个Node,分别为Node 0、Node 1、Node 2。您需要让cgroup使用Node 0和Node 2两个节点的内存。则value取值为0,2。

原因3,如怀疑是内存碎片化导致的OOM Killer,可在业务空闲时间段,进行内存整理,开启内存整理功能执行:echo 1 > /proc/sys/vm/compact_memory,建议定期执行进行内存整理。查看当前内存条数:dmidecode | grep -A16 "Memory Device$";先sync一下,防止丢数据,执行后会将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件。然后可清理内存:echo 1 > /proc/sys/vm/drop_caches;cache释放:echo 1 > /proc/sys/vm/drop_caches释放pagecache;echo 2 > /proc/sys/vm/drop_caches释放dentries and inodes;echo 3 > /proc/sys/vm/drop_caches释放前面所有的cache;内存里:shared——应用程序共享内存
buffers——缓存,主要用于目录方面,inode值等(ls大目录可看到这个值增加);cached——缓存,用于已打开的文件;used=buffers+cached。-buffers/cache——应用程序使用的内存大小,used减去缓存值;+buffers/cache——所有可供应用程序使用的内存大小,free加上缓存值;

四、附录:

1、怀疑本机对外大量tcp外访扫描:有大量syn_sent请求

#抓SYN, ACK:
#TCP标记值:tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-push, tcp-ack, tcp-urg
#-s 0是抓取完整数据包,否则默认只抓68字节;-c参数指定抓多少个包
tcpdump -i eth1 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack != 0'
#计算抓10000个SYN包花费多少时间,可以判断访问量大概是多少
time tcpdump -nn -i eth0 'tcp[tcpflags] = tcp-syn' -c 10000 > /dev/null

2、系统故障辅助工具

Memtest86:用于检测系统内存是否存在问题。可以使用Live CD或USB启动Memtest86并运行测试。
Systemtap:用于跟踪系统的执行过程。可以使用Systemtap来查找并修复可能导致系统死机的问题。
GDB(GNU调试器):用于调试系统中的程序。可以使用GDB来分析系统死机时的堆栈信息,并找出引发故障的代码。
Jprofiler:分析Dump文件

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羌俊恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值