5、内存
技术栈:
1、伙伴系统
linux内存管理笔记(二十三)----伙伴系统Linux概述_memmap_init_zone_奇小葩的博客-CSDN博客
设计之初:
首先linux是采用4kb大小的页框作为标准内存分配单元。在实际应用中,经常会去分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已
原理:
(1)把所有空闲页分组为11个块链表,从1-1024个连续页块,对应4KB到4MB大小的连续内存;
(2)申请策略:假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。
如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。
(2)释放策略:页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块
优缺点:
(1)可以很好解决内存碎片;但是小块会阻塞大块内存合并
(2)若所需内存不是2的幂次方,会有部分页面浪费的情况,如1024个块,申请了16个块,被拆成了512,那么如果再申请600个块就申请不到了
内存管理框图:
struct zone { /* free areas of differents sizes */ struct free_area free_area[MAX_ORDER]; }; struct free_area { struct list_head free_list[MIGRATE_TYPES]; unsigned long nr_free; }; //初始化伙伴系统 static void __meminit zone_init_free_lists(struct zone *zone) { unsigned int order, t; for_each_migratetype_order(order, t) { INIT_LIST_HEAD(&zone->free_area[order].free_list[t]); zone->free_area[order].nr_free = 0; } } #define for_each_migratetype_order(order, type) \ for (order = 0; order < MAX_ORDER; order++) \ for (type = 0; type < MIGRATE_TYPES; type++)
2、sl[a|u|o]b分配器
linux内存管理笔记(二十七)----slub分配器概述_slab size_index_奇小葩的博客-CSDN博客
设计之初:
1、内核运行需要动态分配内存时有2种分配方案:以页为单位分配内存(内存大小是页的整数倍);按需分配
第一种通过伙伴系统实现,以页为单位管理和分配内存;
第二种若采用伙伴系统,分配4kb或者更大页面导致大量内存碎片,显然将页拆成更小单位是最佳方案,从而推出slab分配器,它并没有脱离伙伴系统,只是基于伙伴系统进一步细化分成小内存分配
2、slob是被改进的slab,针对嵌入式系统进行了特别优化,以便减小代码量。围绕一个简单的内存块链表展开,在分配内存时,使用同样简单的最新适配算法。slob分配器只有大约600行代码,总的代码量很小。从速度来说,它不是最高效的分配器,页肯定不是为大型系统设计的;
3、slub是在slab上进行的改进简化,在大型机上表现出色,并且能更好的使用NUMA系统,slub相对于slab有5%-10%的性能提升和减小50%的内存占用
数据结构:
//slub_def.h struct kmem_cache { //一个per cpu变量,对于每个cpu来说,相当于一个本地内存缓存池。当分配内存的时候优先从本地cpu分配内存以保证cache的命中率。 struct kmem_cache_cpu __percpu *cpu_slab; /* Used for retriving partial slabs etc */ unsigned long flags; unsigned long min_partial; int size; /* The size of an object including meta data */ int object_size; /* The size of an object without meta data */ int offset; /* Free pointer offset. */ int cpu_partial; /* Number of per cpu partial objects to keep around */ struct kmem_cache_order_objects oo; /* Allocation and freeing of slabs */ struct kmem_cache_order_objects max; struct kmem_cache_order_objects min; gfp_t allocflags; /* gfp flags to use on each alloc */ int refcount; /* Refcount for slab cache destroy */ void (*ctor)(void *); int inuse; /* Offset to metadata */ int align; /* Alignment */ int reserved; /* Reserved bytes at the end of slabs */ const char *name; /* Name (only for display!) */ struct list_head list; /* List of slab caches */ int red_left_pad; /* Left redzone padding size */ //slab节点。在NUMA系统中,每个node都有一个struct kmem_cache_node数据结构 struct kmem_cache_node *node[MAX_NUMNODES]; }
分配缓存:
在分配缓存的时候,需要分两种路径,快速通道(kmem_cache_cpu)和普通通道(kmem_cache_node)
每次分配的时候,要先从kmem_cache_cpu分配;如果kmem_cache_cpu里面没有空闲块,那就从kmem_cache_node中进行分配;
如果还是没有空闲块,最后从伙伴系统中分配新的页。
cpu_cache对于每个CPU来说,相当于一个本地内存缓冲池,当分配内存的时候,优先从本地CPU分配内存以及保证cache的命中率,struct kmem_cache_cpu用于管理slub缓存
struct kmem_cache_cpu { void **freelist; /* Pointer to next available object */ unsigned long tid; /* Globally unique transaction id */ struct page *page; /* The slab from which we are allocating */ #ifdef CONFIG_SLUB_CPU_PARTIAL struct page *partial; /* Partially allocated frozen slabs */ #endif #ifdef CONFIG_SLUB_STATS unsigned stat[NR_SLUB_STAT_ITEMS]; #endif };
struct kmem_cache_node
:用于管理每个Node的slub页面
,由于每个Node的访问速度不一致,slub
页面由Node来管理;
struct kmem_cache_node { spinlock_t list_lock; #ifdef CONFIG_SLUB unsigned long nr_partial; /* partial slab链表中slab的数量 */ struct list_head partial; /* partial slab链表表头 */ #ifdef CONFIG_SLUB_DEBUG atomic_long_t nr_slabs; /* 节点中的slab数 */ atomic_long_t total_objects; /* 节点中的对象数 */ struct list_head full; /* full slab链表表头 */ #endif #endi }
初始化
start_kernel mm_init kmem_cache_init static __initdata struct kmem_cache boot_kmem_cache, boot_kmem_cache_node; //创建2个cache create_boot_cache(kmem_cache_node) create_boot_cache(kmem_cache) //分配内存空间并将静态变量boot_kmem_cache和boot_kmem_cache_node中内容复制到分配的内存空间 kmem_cache = bootstrap(&boot_kmem_cache); kmem_cache_node = bootstrap(&boot_kmem_cache_node); //初始化kmalloc_caches表,其最终创建的kmalloc_caches是以{0,96,192,8,16,32,64,128,256,512,1024,2046,4096,8196}为大小的slab表 create_kmalloc_caches(0) bootstrap kmem_cache_zalloc kmem_cache_alloc slab_alloc
size_index与kmalloc_caches的对应关系如下:
1、size_index[0-23]数组根据对象大小映射到不同的kmalloc_caches[0-13]保存的cache
2、slub使用kmalloc_caches[1]保存96字节大小的对象,kmalloc_caches[2] 保存192字节大小的对象
疑问:为啥单独支持96和192字节的对象?
在内核中,对于内存块的申请需求大部分情况下都在 96 字节或者 192 字节附近,如果内核不单独支持这两个尺寸的通用 slab cache。那么当内核申请一个尺寸在 64 字节到 96 字节之间的内存块时,内核会直接从 kmalloc-128 中分配一个 128 字节大小的内存块,这样就导致了内存块内部碎片比较大,浪费宝贵的内存资源。
核心思想:
1、将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态,比如进程描述符,内核中会频繁对此数据进行申请和释放 2、当一个新进程创建时,内核就会直接从slab分配器的高速缓存中获取一个已经初始化的对象 3、当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中,如果没有基于对象的slab分配器,内核将花费更多的时间去分配、初始化、已经释放对象。
优点:
1、减小伙伴算法在分配小块连续内存时所产生的内部碎片问题,因为每个内核数据结构都有关联的cache,每个 cache都由一个或多个slab组成,而slab按所表示对象的大小来分块。因此,当内核请求对象内存时,slab 分配器可以返回刚好表示对象的所需内存。 2、将频繁使用的对象缓存起来,减小分配、初始化和释放的时间开销 ,当对象频繁地被分配和释放时,如来自内核请求的情况,slab 分配方案在管理内存时特别有效。分配和释放内存的动作可能是一个耗时过程。然而,由于对象已预先创建,因此可以从cache 中快速分配。再者,当内核用完对象并释放它时,它被标记为空闲并返回到cache,从而立即可用于后续的内核请求。
问题定位思路:
1、抓内核申请连续内存超32K的命令
(1)清理缓存信息
sudo echo 0 > /sys/kernel/debug/tracing/tracing_on sudo echo 0 > /sys/kernel/debug/tracing/events/kprobes/enable sudo echo > /sys/kernel/debug/tracing/kprobe_events sudo echo > /sys/kernel/debug/tracing/trace
(2)打开调用栈信息
sudo echo 1 > /sys/kernel/debug/tracing/options/stacktrace
(3)配置需要抓取的信息(size >= 0x8000,order >= 0x3:表示申请的大小大于等于32k。如果不需要过滤,可以将后面三行删除)
sudo echo 'p:__get_free_pages_entry __get_free_pages order=%x1' >> /sys/kernel/debug/tracing/kprobe_events sudo echo 'p:__kmalloc_entry __kmalloc size=%x0' >> /sys/kernel/debug/tracing/kprobe_events sudo echo 'p:dma_alloc_from_dev_coherent_entry dma_alloc_from_dev_coherent size=%x1' >> /sys/kernel/debug/tracing/kprobe_events echo 'size >= 0x8000' > /sys/kernel/debug/tracing/events/kprobes/dma_alloc_from_dev_coherent_entry/filter echo 'size >= 0x8000' > /sys/kernel/debug/tracing/events/kprobes/__kmalloc_entry/filter echo 'order >= 0x3' > /sys/kernel/debug/tracing/events/kprobes/__get_free_pages_entry/filter
(4)使能抓取
sudo echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable sudo echo 1 > /sys/kernel/debug/tracing/tracing_on
(5)触发申请内存的操作(kill进程,卡上下电等,由业务决定)
(6)查看抓取到的信息
cat /sys/kernel/debug/tracing/trace
2、kmalloc-128 slab内存泄漏定位过程
[内存泄漏]kmalloc-128 slab内存泄漏定位过程_kmalloc泄漏_浮沉飘摇的博客-CSDN博客
手动生成vmcore,解core分析泄漏文件
kernel内核slab内存泄露debug经验
linux kernel内核slab内存泄露debug经验_slab debug_os从业人员的博客-CSDN博客
打开slab trace来输出kmalloc-128的栈
echo 1 > /sys/kernel/slab/kmalloc-128/trace
需要手动修改printk级别:
echo "8 4 1 7" > /proc/sys/kernel/printk
3、内核mmap_sem锁调测工具(doing)
https://maintain.euleros.huawei.com/mtinfo/awesomeTools/tooldetail?id=5
案例分析:
1、4.18内核 oom后高概率oops
调用栈
crash> bt ... [exception RIP: oom_evaluate_task+291] RIP: ffffffffb6025f03 RSP: ffffa27b164efb30 RFLAGS: 00010246 RAX: 8000000000000000 RBX: ffff94d6ed7ebc00 RCX: 00000000000ef7ba RDX: ffff94dfdce38910 RSI: 00000000000ee6b2 RDI: 0000000000000000 RBP: ffffa27b164efc40 R8: 0000000000000000 R9: 0000000000000000 R10: 00000000000ed47c R11: 000000000000a364 R12: 8000000000000000 R13: ffff94d2461cc040 R14: 00000000006000c0 R15: 0000000000000001 ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0000 #6 [ffffa27b164efb50] mem_cgroup_scan_tasks at ffffffffb60c0926 #7 [ffffa27b164efbf8] out_of_memory at ffffffffb60266ef #8 [ffffa27b164efc38] mem_cgroup_out_of_memory at ffffffffb60bb005 #9 [ffffa27b164efc98] try_charge at ffffffffb60beb6d #10 [ffffa27b164efd38] mem_cgroup_try_charge at ffffffffb60c0fd6 #11 [ffffa27b164efd70] mem_cgroup_try_charge_delay at ffffffffb60c10ec #12 [ffffa27b164efd98] do_anonymous_page at ffffffffb605f4f5 #13 [ffffa27b164efdf0] __handle_mm_fault at ffffffffb60634b0 #14 [ffffa27b164efe90] handle_mm_fault at ffffffffb60635c4 #15 [ffffa27b164efec0] __do_page_fault at ffffffffb5e6f09b #16 [ffffa27b164eff20] do_page_fault at ffffffffb5e6f341 #17 [ffffa27b164eff50] async_page_fault at ffffffffb680125e
解core
crash> dis -l oom_evaluate_task+291 /usr/src/debug/*/mm/oom_kill.c: 393 0xffffffffb6025f03 <oom_evaluate_task+291>: mov 0x8a4(%rdi),%edx /usr/src/debug/*/mm/oom_kill.c: 393 0xffffffffb6025ef6 <oom_evaluate_task+278>: cmp %rax,%r12 0xffffffffb6025ef9 <oom_evaluate_task+281>: mov 0x28(%rbp),%rdi 0xffffffffb6025efd <oom_evaluate_task+285>: jne 0xffffffffb6025e6f <oom_evaluate_task+143> 0xffffffffb6025f03 <oom_evaluate_task+291>: mov 0x8a4(%rdi),%edx
对应代码
oom_evaluate_task 392 /* Prefer thread group leaders for display purposes */ 393 if (points == oc->chosen_points && thread_group_leader(oc->chosen)) static inline bool thread_group_leader(struct task_struct *p) { return p->exit_signal >= 0; }
结构体偏移
crash> struct task_struct -xo | grep 8a4 [0x8a4] int exit_signal; crash> struct oom_control -xo struct oom_control { [0x0] struct zonelist *zonelist; [0x8] nodemask_t *nodemask; [0x10] struct mem_cgroup *memcg; [0x18] const gfp_t gfp_mask; [0x1c] const int order; [0x20] unsigned long totalpages; [0x28] struct task_struct *chosen; [0x30] long chosen_points; [0x38] enum oom_constraint constraint; } SIZE: 0x40
确认空指针位置
if (points == oc->chosen_points && thread_group_leader(oc->chosen)) //oc->chosen为NULL
由于RBP为ffffa27b164efc40,查看oc确认chosen为0x0
crash> struct oom_control ffffa27b164efc40 struct oom_control { zonelist = 0x0, nodemask = 0x0, memcg = 0xffff94d2461cc040, gfp_mask = 6291648, order = 0, totalpages = 976562, chosen = 0x0, chosen_points = -9223372036854775808, constraint = CONSTRAINT_MEMCG }
根因
未适配前置补丁,判断逻辑出现问题
oom_badness返回值存在2种情况:
1、若points == LONG_MIN,2个判断都不满足,会走到thread_group_leader(oc->chosen)导致空指针访问;
2、若points > LONG_MIN,2个判断都不满足,走到第一判断也不会满足就会走到select流程
2、firewalld内存泄漏
背景:
现网出现firewalld内存占用高的情况,最高9G+
定位过程:
1、前期思路
(1)通过gcore生成core文件定位
(2)firewalld参数配置debug gc(此方案会重启firewalld,现网重启后会影响业务,网络会断掉)
2、pyrasite内存泄漏工具介绍
(1)工具介绍:
pyrasite是一个库和一组工具,用于将任意代码通过GDB注入到运行的Python进程中
(2)工具依赖:gdb > 7.3 gcc python3-devel
(3)python包:urwid meliae mem-top guppy3
(4)工具使用
A. 关闭selinux:setenforce 0
https://github.com/lmacken/pyrasite/blob/develop/docs/Installing.rst
B. pyrasite-shell + 进程号
B.1 使用mem_top打印进程的内存占用情况
from mem_top import mem_top #mem_top(limit=X,width=Y) #X:表示显示的条数 #Y:表示显示的数据长度 print(mem_top(limit=10, width=100, sep='\n',refs_format='{num}\t{type} {obj}',bytes_format='{num}\t{type} {obj}')) #refs回显信息: 对象中的引用其他对象的个数最多的topN(列表元素,字典的键值等) #bytes回显信息: 占用内存最大的topN对象其所占用的bytes #types回显信息: 经过垃圾回收后, 内存中topN类型的对象个数
B.2 使用guppy查看内存占用情况
import sys sys.path.append('/usr/local/lib64/python3.7/site-packages') from guppy import hpy #创建session上下文 h=hpy() #显示堆里面的Object信息,每增加一个more会打印 h.heap().more.more #显示单个最大object信息 h.heap().byid[0].sp
3、pyrasite定位过程
mem_top查看内存占用最大的Object
//进入交互式环境 pyrasite-shell pid from mem_top import mem_top #mem_top(limit=X,width=Y) #X:表示显示的条数 #Y:表示显示的数据长度 print(mem_top(limit=10, width=1000, sep='\n',refs_format='{num}\t{type} {obj}',bytes_format='{num}\t{type} {obj}'))
使用mem_top工具发现内存最大的对象为dbus.SystemBus相关,例如:{(‘:1.3930028’,<dbus._dbus_SystemBus(system) at 0xffff7de17570>)} 前一个数字表示进程的name,可以通过busctl查看(当前name已不存在)
当前明确是有进程创建bus object连接dbus与firewalld通信,但是没有释放bus object导致泄漏
查看单个最大object的类型
from guppy import hpy h = hpy() h.heap().byid[0].sp import slip.dbus.service as sds #通过heap命令查看内存占用大的class类型,即slip.dbus.service #查看已建立连接的object len(list(sds.Object.connections_senders.values())[0]) len(sds.Object.senders) len(sds.Object.connections_senders)
排查到内存占用多的是slip.dbus.service.Object这个对象,现网有将近200W个Object残留,经代码分析分析定界为Object由python3-slip包中的sender_seen函数创建,但没有删除操作
4、sender_seen调用栈信息
在sender_seen函数内增加以下打印来查看调用栈
import traceback traceback.print_stack()
3、数据库产品存在时延波动
根因:
(1)cpu比较空闲时任务集中于单个numa node,cpu比较忙时任务分布会比较均衡,业务压力波动引起任务切换node,导致numa balance
(2)numa balance会引起内存碎片化
(3)mlx5驱动需要申请连续内存,由于内存碎片化而申请不到,就会导致网络时延
解决方法:
监控buddyinfo;升级mlx5驱动