record-5.内存

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
}
根因

未适配前置补丁,判断逻辑出现问题

https://github.com/torvalds/linux/commit/9066e5cfb73cdbcdbb49e87999482ab615e9fc76#diff-268fe084429e2dda106503d80d590ac28f341bcf5969eaed6c09891eea0ca466

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)

img

排查到内存占用多的是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驱动

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值