极客时间——Linux内核技术实战课:内存泄漏问题

时间:Oct. 27, 2020 分类:

目录:

内存泄漏问题

进程的那些内存类型容易引起内存泄漏

应用程序会直接或者间接调用系统调用,例如brk(2)和sbrk(2)修改的是heap(堆),mmap(2)和munmap(2)修改的是Memory Mapping Region(内存映射区)

应用程序针对的都是虚拟地址,虚拟内存地址被转换为物理地址进行操作

内存可以分为四类

  • 私有匿名内存:进程的堆,栈以及mmap(MAP_ANON | MAP_PRIVATE)申请的内存,栈是由操作系统管理,堆和私有匿名映射是由程序管理,容易产生内存泄漏
  • 共享匿名内存:mmap(MAP_ANON | MAP_SHARED)申请的内存,例如tmpfs和shm
  • 私有文件映射:mmap(MAP_FILE | MAP_PRIVATE)申请的内存,例如共享库和可执行的代码段映射到内存空间
  • 共享文件映射:mmap(MAP_FILE | MAP_SHARED)申请的内存,page cache就是这种内存

请求内存的过程是CPU将虚拟内存地址传给内存管理单元MMU,MMU在高速缓存TLB中查找转换关系,找到了对应内存地址直接访问,如果找不到则在地址转换表中查找

进程内存可以top->g->3查看,可以看到

VIRT    RES   CODE    DATA    SHR nMaj nDRT

这些信息来自/proc/[pid]/statm/proc/[pid]/stat

  • VIRT是进程申请的虚拟内存大小
  • RES是映射到进程地址空间的物理内存大小
  • SHR是MAP_SHARED方式存储的内存,可以共享
  • CODE是代码段大小
  • DATA是数据段大小
  • nDRT是脏页大小,2.6内核之后废弃了

RES中的一些内存是很多进程共享的

查看一个ssh进程的详细内存使用

$  pmap -x `pidof sshd`
Address           Kbytes     RSS   Dirty Mode  Mapping 
000055e798e1d000     768     652       0 r-x-- sshd
000055e7990dc000      16      16      16 r---- sshd
000055e7990e0000       4       4       4 rw--- sshd
000055e7990e1000      40      40      40 rw---   [ anon ]
...
00007f189613a000    1800    1624       0 r-x-- libc-2.17.so
00007f18962fc000    2048       0       0 ----- libc-2.17.so
00007f18964fc000      16      16      16 r---- libc-2.17.so
00007f1896500000       8       8       8 rw--- libc-2.17.so
...
00007ffd9d30f000     132      40      40 rw---   [ stack ]
...
  • Address是起始地址
  • Kbytes是虚拟内存大小
  • RSS是虚拟内存中已经分配的大小
  • Dirty是内存数据未同步到磁盘的大小
  • Mode是权限,r-x为可读可执行,一般是代码段,rw-为可读写,一般为数据段,r--为只读,一般为只读数据段
  • Mapping是文件占用内存的文件,heap为堆,stask为栈

原理也是解析的/proc/[pid]/maps/proc/[pid]/smaps

$ cat /proc/meminfo
...
Cached:          3799380 kB
...
AnonPages:       1060684 kB
...
Shmem:              8724 kB
...

AnonPages为私有内存,Cached为共享内存,匿名共享内存为Shmem

如何预防内存泄漏导致的问题

内存泄漏之后,操作系统进行OOM的时候调用printk会触发多个操作

  • 写入内核缓冲区,通过dmesg命令查看
  • 写入rsyslog,由内核缓冲区转储到/var/log/messages
  • 发往服务器的console,例如串口

console的速度非常慢,输出过多的日志,导致OOM时间过长,没有回收内存,虽然OOM会有全局的锁,但是会导致其他申请内存的时候重试。如果有很多程序申请重试,就会被阻塞在这里

可以做的vm.oom_dump_tasks调整为0,但是OOM的日志没有打印,无法排查问题

或者避免打印到console,将console的日志级别调整为小于默认的4

# 初始配置(为7):所有信息都会输出到console
$ cat /proc/sys/kernel/printk
7  4  1  7

# 调整console_loglevel级别,不让OOM信息打印到console
$ echo "3 4 1 7" > /proc/sys/kernel/printk

# 查看调整后的配置
$ cat /proc/sys/kernel/printk
3  4  1  7

不过这样默认级别为4的内核服务也无法打印到console

进程没有消耗内存,内存到哪里去了

OOM的时候kill时候按照/proc/[pid]/oom_score的排序进行kill,系统一般为-1000,而kubernetes的为-998

tmpfs的大小调整

$ mount -o remount,size=2G /run

进程退出malloc()在用户态申请的内存直接释放了,kmalloc()/kfree()和vmalloc()/vfree()在内核态申请,生命周期和内核一致,而不是内核模块,只有重启才会释放

/proc/meminfo中内核内存(比如VmallocUsed和SUnreclaim)过大或者一直增长就可能是内存泄漏

kmemleak可以用来分析内存泄漏,但是影响性能

内存泄漏如何查到根因

$ pidstat -r -p 31108  1


04:47:00 PM     31108    353.00      0.00 299029776 4182152  12.73  app_server
...
04:47:59 PM     31108    149.00      0.00 299029776 4181052  12.73  app_server
04:48:00 PM     31108    191.00      0.00 299040020 4181188  12.73  app_server
...
04:48:59 PM     31108    179.00      0.00 299040020 4181400  12.73  app_server
04:49:00 PM     31108    183.00      0.00 299050264 4181524  12.73  app_server
...
04:49:59 PM     31108    157.00      0.00 299050264 4181456  12.73  app_server
04:50:00 PM     31108    207.00      0.00 299060508 4181560  12.73  app_server
...
04:50:59 PM     31108    127.00      0.00 299060508 4180816  12.73  app_server
04:51:00 PM     31108    172.00      0.00 299070752 4180956  12.73  app_server

内存稳定有一分钟,VSZ会增大10244KB


$ cat /proc/31108/smaps 
...
7faae0e49000-7faae1849000 rw-p 00000000 00:00 0 
Size:              10240 kB
Rss:                  80 kB
Pss:                  80 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        80 kB
Referenced:           60 kB
Anonymous:            80 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
7faae1849000-7faae184a000 ---p 00000000 00:00 0 
Size:                  4 kB
Rss:                   0 kB
Pss:                   0 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:            0 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB

私有空间为rw-p,一个保护页——p进程无法访问,应该是线程栈有关系了

$ strace -t -f -p 31108 -o 31108.strace

-f跟踪线程