内存故障排查

本节介绍常见内存故障的排查与定位。以下先对内存中常见的内存相关错误进行分析。

内存相关的错误,按错误来源可划分为以几类:

  • 内存泄漏

    内存泄漏是指系统中没有引用的“孤儿”内存对象。

    持续的内存泄漏会表现为系统内存不断减少,而偶发的少量泄漏则可能难以觉察,并不会有破坏性后果

  • 错误的内存使用方式

    应用程序未按预期使用内存,占用了大量内存以致于影响到系统的正常运行。

    这类错误可归结设计时内存分析不到位所致。 例如,分片的DOS功击,就是IP的分片组装程序,如果不对组装失败的报文及时清理, 则这些报文会占用大量系统内存,使得系统无法再处理新报文,从而造成网络服务中断。

    注意

    从外部观察来看,“内存泄漏”错误与这类错误比较相近,通常最终都表现为系统内存不足。 两者的区别在于: 前者可以通过内存对象的引用来分析,在不知晓程序逻辑就可以分析出来,因此有许多工具可以用来排查此类错误,如mtrace; 而后者就需要理解应用才能确定是否是故障;

    如果能够确切分析每一块内存的使用状况,则能直接区别2种类型的缺陷。

  • 内存的非法访问

    • 与指针相关的

      这类特性与C语言的特性紧密相关:指针可指向任意内存位置,没有附加的合法检查手段。

    • 与内存边界相关的

      这类特性也与C语言的特性相关,包括:

      • 数组类(包括缓冲区)没有边界检查,如memcpy(3)、strcpy(3);

      • 允许不同尺寸的数据类型(指针)相互转换,如可以内存变量int32_t转换为int64_t,此时实际上变量已经到下一个内存位置了

    • 堆栈溢出

    重要

    在Linux系统中,在用户空间通常会使进程接收到SIGSEGV或SIGBUS而中止运行,在内核空间则是产生Kernel Oops;

    有时产生的现象与问题根源之间的关联不是很明显。 例如,一个内存复制覆盖了邻近的结构,可能就表现为邻近的应用首先有故障,使得故障排查有时无从入手。

    C语言的这种“特性”成为困扰整个业界的问题,这类故障要以“预防为主”。

Linux系统中,系统的空间划分为内核空间与用户空间,内核空间与用户空间相互隔离, 分别使用不同的内存管理方法,因此故障的表现不同,排查方法也有所不同, 以下的各类分析通常分别按内核空间与用户空间分别分析。

10.4.2. 系统的内存分析

10.4.2.1. 概述

Linux运行于“虚拟内存映射”机制上,程序运行虚拟空间上,程序的内存访问由CPU提供的映射器件转换为对物理内存的访问。 在转换时,通常要进行访问的合法性检查。

用户空间的程序,在内存映射基础上,可以工作在“请求页”机制上。 当用户空间申请内存时,系统并非给其分配内存,而只是分配“空间”,当程序访问这段空间时,就会触发“缺页”中断, 在“缺页”中断处理程序中,再分配内存物理页。

内核空间的内存管理,除负责内核自身需要使用的内存对象外,还负责整个系统的内存管理。 内核自身通常采用固定映射方式,除vmalloc()分配的页帧外,不会产生缺页。

注意

要理解Linux的内存管理,需理解“空间”与“内存”的差异。

10.4.2.2. 内核空间中的内存管理

内核负责整个系统内存的管理。 系统中的物理内存是按页为单位的,除了内核固定使用的内存外, 这些页面统一管理,内核内部的模块自身需要使用,还需要提供给用户空间。

文件/proc/meminfo提供了系统内存的概况:

#cat /proc/meminfo
MemTotal:        2058688 kB
MemFree:          848840 kB
Buffers:          279392 kB
Cached:           408544 kB
SwapCached:            0 kB
Active:           494532 kB
Inactive:         376516 kB
Active(anon):     186468 kB
Inactive(anon):      156 kB
Active(file):     308064 kB
Inactive(file):   376360 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        131064 kB
SwapFree:         131064 kB
...

提示

文件meminfo并未提供所有物理页的使用情况跟踪,因此上面的数量存在计算不“平衡”的情况。

但这些数值已经包括绝大多数物理面的分配信息,足以分析系统的内存使用。

物理内存的管理分为2级:

  1. Zones

    Zone的划分是由于物理内存的属性不同,如是否支持DMA操作。 zone的类型包括ZONE_DMA,ZONE_NORMAL和ZONE_HIGHMEM

    当物理内存不连续时(称为nodes),它们按属性划分为对应类型zone下。

  2. Pages

    各程序以页帧为单位从zone中获取物理内存。

Zones提供是系统内动态内存的管理。在kernel启动过程中,kernel在预留自身使用的部分物理内存后,剩余的物理内存区域划分为zone进行管理。

由于kenrel预留内存只占系统总内存数量的一小部分(典型值在10M以内),通常我们只关心zone管理的物理内存。 在/proc/zoneinfo中提供了zone的基本信息。如下例:

# cat /proc/zoneinfo
Node 0, zone      DMA
  pages free     3938
        min      10
        low      12
        high     15
        scanned  0
        spanned  4080
        present  3827
    nr_free_pages 3938
...    
Node 0, zone    DMA32
  pages free     208272
        min      1426
        low      1782
        high     2139
        scanned  0
        spanned  520192
        present  513064
    nr_free_pages 208272
    nr_inactive_anon 39
...

其中的值是zone中所含的页帧数量。

Zone管理的内存是以以页帧为单位管理的,后文将详细页帧的管理方法。

10.4.2.2.1. 页帧的分析

系统的页帧,可划分为以下几类:

  1. 文件映射(file mapping)

    但一个文件被打开时,FS为存放文件内容分配物理页面。 例如,可执行程序的代码段、数据段所占内存属于这种类型,应用程序打开操作的文件内容,也属于这种类型。

  2. 匿名映射(anonymous mapping)

    这部分页面用于进程的虚拟内存区域,如进程的堆栈、堆所占页面属于这种类型。

  3. 交换缓冲(swap cache)

    存在交换分区的系统中,这些页面用于与外存之间交换物理页面。

  4. SLAB管理

    这是kernel自身动态对象所使用内存区域。

其它还有一些区域,如kernel stack等,这些区域通常只占内存总量的一小部分,因此暂不进行分析。

10.4.2.2.2. SLAB的分析

在kernel中,多数程序使用的动态内存都是从slab管理中分配的,slab以对象为单位来管理内存。 系统中slab的分配情况,可以通过文件/proc/slabinfo来观察。如下例:

# more /proc/slabinfo
slabinfo - version: 2.1
# name                 : tunables    :
 slabdata   
fuse_request           7      7    584    7    1 : tunables    0    0    0 : slabdata      1      1      0
fuse_inode             6      6    640    6    1 : tunables    0    0    0 : slabdata      1      1      0
rpc_inode_cache        9      9    832    9    2 : tunables    0    0    0 : slabdata      1      1      0
nf_conntrack_expect    0      0    240   17    1 : tunables    0    0    0 : slabdata      0      0      0
VMBlockInodeCache      2      2   4736    1    2 : tunables    0    0    0 : slabdata      2      2      0
blockInfoCache         1      1   4160    1    2 : tunables    0    0    0 : slabdata      1      1      0
hgfsInodeCache      2844   2844    640    6    1 : tunables    0    0    0 : slabdata    474    474      0
hgfsReqCache           1      1   6208    1    2 : tunables    0    0    0 : slabdata      1      1      0
UDPv6                  8      8    960    4    1 : tunables    0    0    0 : slabdata      2      2      0
TCPv6                  4      4   1792    4    2 : tunables    0    0    0 : slabdata      1      1      0
kmalloc_dma-512        8      8    512    8    1 : tunables    0    0    0 : slabdata      1      1      0
dm_uevent              0      0   2608    3    2 : tunables    0    0    0 : slabdata      0      0      0
...
shared_policy_node     0      0     48   85    1 : tunables    0    0    0 : slabdata      0      0      0
numa_policy          682    850     24  170    1 : tunables    0    0    0 : slabdata      5      5      0
kmalloc-4096          63     64   4096    8    8 : tunables    0    0    0 : slabdata      8      8      0
kmalloc-2048         626    656   2048   16    8 : tunables    0    0    0 : slabdata     41     41      0
kmalloc-1024         661    664   1024    4    1 : tunables    0    0    0 : slabdata    166    166      0
kmalloc-512          228    240    512    8    1 : tunables    0    0    0 : slabdata     30     30      0
kmalloc-256          343    368    256   16    1 : tunables    0    0    0 : slabdata     23     23      0
kmalloc-128          662    864    128   32    1 : tunables    0    0    0 : slabdata     27     27      0
kmalloc-64          7558   7808     64   64    1 : tunables    0    0    0 : slabdata    122    122      0
kmalloc-32          4089   4096     32  128    1 : tunables    0    0    0 : slabdata     32     32      0
kmalloc-16          2448   2816     16  256    1 : tunables    0    0    0 : slabdata     11     11      0
kmalloc-8           4094   4096      8  512    1 : tunables    0    0    0 : slabdata      8      8      0
kmalloc-192         4833   5019    192   21    1 : tunables    0    0    0 : slabdata    239    239      0
kmalloc-96           843    924     96   42    1 : tunables    0    0    0 : slabdata     22     22      0
kmem_cache_node        0      0     56   73    1 : tunables    0    0    0 : slabdata      0      0      0

其中是指当前正在使用的内存对象的数量,由于slab内部存在缓冲机制,实际对象数量是

重要

显而易见,专用缓冲池的使用状况,较容易关联其所属模块; 而对于kmlloc-nnn这种通用缓冲池,由于在kernel中多处使用,要确定是哪个模块使用的,则有一定的困难。 为减少内存分析困难,RGOS系统中约定自行开发的内核功能,需要使用专用缓冲池, 详细约定可参见第 8.3.3 节 “内存使用”。

如果要持续跟踪slab的状况,可以使用命令slabtop。如下例:

ctive / Total Objects (% used)    : 1342614 / 1347193 (99.7%)
 Active / Total Slabs (% used)      : 30794 / 30794 (100.0%)
 Active / Total Caches (% used)     : 71 / 91 (78.0%)
 Active / Total Size (% used)       : 265362.10K / 266022.52K (99.8%)
 Minimum / Average / Maximum Object : 0.01K / 0.20K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
321300 320943  99%    0.02K   1890      170      7560K fsnotify_event_holder
190208 189875  99%    0.02K    743      256      2972K kmalloc-16
154773 154725  99%    0.55K   5337       29     85392K radix_tree_node
122528 122486  99%    0.07K   2188       56      8752K selinux_inode_security
117696 117649  99%    0.12K   3678       32     14712K iint_cache
113505 113497  99%    0.19K   5405       21     21620K dentry
101772 101772 100%    0.87K   5654       18     90464K ext4_inode_cache
 98460  97272  98%    0.11K   2735       36     10940K buffer_head
 35072  34315  97%    0.03K    274      128      1096K kmalloc-32
 14467  14202  98%    0.17K    629       23      2516K vm_area_struct
 12036  12035  99%    0.08K    236       51       944K sysfs_dir_cache
 10458  10018  95%    0.19K    498       21      1992K kmalloc-192
  9216   9212  99%    0.01K     18      512        72K kmalloc-8
  6475   6475 100%    0.62K    259       25      4144K hgfsInodeCache
  6272   6042  96%    0.06K     98       64       392K kmalloc-64
  6006   5904  98%    0.09K    143       42       572K kmalloc-96
  5211   5210  99%    0.58K    193       27      3088K inode_cache
  5248   4987  95%    0.03K     41      128       164K anon_vma
  3025   2988  98%    0.62K    121       25      1936K proc_inode_cache
  1420   1420 100%    0.78K     71       20      1136K shmem_inode_cache
  1088   1038  95%    0.25K     68       16       272K kmalloc-256
   960    802  83%    0.12K     30       32       120K kmalloc-128
   713    693  97%    0.69K     31       23       496K sock_inode_cache
   704    691  98%    0.50K     44       16       352K kmalloc-512
   680    680 100%    0.02K      4      170        16K journal_handle
   630    615  97%    0.75K     30       21       480K RAW
...   

10.4.2.2.3. vmalloc的分析

slab算法分配的物理页面是连续的,在系统运行一段时间后,可能由于碎片问题而使较大的slab分配请求失败。 对于一些程序而言,它们并不一定需要连续的物理页面,vmalloc可以通过虚拟内存映身机制,将多个非连续 的物理页面映射为连续的物理页面,降低了由于碎片而使大区域内存申请失败的可能性。

vmalloc分析的页面属于匿名映射,可以通过/proc/vmallocinfo来查看系统内vmalloc的使用状况。

10.4.2.2.4. Page Frame的回收

todo todo

todo todo

10.4.2.3. 用户空间的内存管理

用户空间的全局分析

本节主要分析用户空间的物理内存占用情况。

用户空间划分一个个独立的进程空间,各个进程在运行时,就好象在专属空间中运行一样。 Linux系统采用“请求页”调度方式,因此空间占用不等同于内存占用,进程的物理内存的占用需要另外跟踪,这就是进程的过RSS(Resident Set Size)来进行统计。

利用shell下提供的工具ps可以直接获取系统中所有进程的内存使用概况, 为了便于分析,还可以利用ps的sort选项,将ps的输出按RSS排序。如下例:

# ps -eF --sort -rss
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
maxj      2273  2258  0 196346 72652  2 Apr17 ?        00:01:45 /usr/lib64/firefox-3.5.4/firefox
root      1726  1720  0 38463 41456   0 Apr17 tty1     00:02:18 /usr/bin/Xorg :0 -nr -verbose -auth /var/run/gdm/auth-for-gdm-oebTRZ/database -nol
maxj      1994  1854  0 208241 29144  0 Apr17 ?        00:00:46 nautilus
maxj      2001     1  0 75950 20248   0 Apr17 ?        00:00:18 /usr/lib/vmware-tools/bin64/vmware-user-loader --blockFd 3
maxj      2017  1854  0 86158 19828   2 Apr17 ?        00:00:00 python /usr/share/system-config-printer/applet.py
maxj      2232     1  0 121802 18836  0 Apr17 ?        00:00:13 gnome-terminal
maxj      2185     1  0 92599 17440   1 Apr17 ?        00:00:00 /usr/bin/gnote --panel-applet --oaf-activate-iid=OAFIID:GnoteApplet_Factory --oaf-
maxj      1978  1854  0 112584 16188  0 Apr17 ?        00:00:04 metacity
maxj      2171     1  0 126162 15740  0 Apr17 ?        00:00:05 /usr/libexec/wnck-applet --oaf-activate-iid=OAFIID:GNOME_Wncklet_Factory --oaf-ior
maxj      2192     1  0 117421 15464  0 Apr17 ?        00:00:02 /usr/libexec/clock-applet --oaf-activate-iid=OAFIID:GNOME_ClockApplet_Factory --oa
maxj      1983  1854  0 122486 15104  0 Apr17 ?        00:00:02 gnome-panel
maxj      2021  1854  0 75069 13120   1 Apr17 ?        00:00:00 nm-applet --sm-disable
maxj      2014  1854  0 115086 11908  3 Apr17 ?        00:00:01 gpk-update-icon
maxj      2173     1  0 116893 11740  3 Apr17 ?        00:00:01 /usr/libexec/trashapplet --oaf-activate-iid=OAFIID:GNOME_Panel_TrashApplet_Factory
maxj      2086     1  0 69938 11028   0 Apr17 ?        00:00:00 /usr/libexec/notification-daemon
maxj      2190     1  0 76766 10920   3 Apr17 ?        00:00:00 /usr/libexec/gdm-user-switch-applet --oaf-activate-iid=OAFIID:GNOME_FastUserSwitch
maxj      2022  1854  0 85375  9616   2 Apr17 ?        00:00:00 gnome-volume-control-applet
maxj      2025  1854  0 68755  8592   1 Apr17 ?        00:00:00 gnome-power-manager
maxj      1958     1  0 74187  8456   3 Apr17 ?        00:00:00 seahorse-daemon
maxj      2194     1  0 71500  7580   1 Apr17 ?        00:00:00 /usr/libexec/notification-area-applet --oaf-activate-iid=OAFIID:GNOME_Notification
maxj      2064  1854  0 57528  6404   0 Apr17 ?        00:00:00 bluetooth-applet
maxj      1854  1833  0 61707  6228   1 Apr17 ?        00:00:00 gnome-session
maxj      2012  1854  0 60072  6164   2 Apr17 ?        00:00:00 /usr/libexec/gdu-notification-daemon
maxj      2003  1854  0 58220  6160   3 Apr17 ?        00:00:00 abrt-applet
maxj      1944     1  0 33772  5684   1 Apr17 ?        00:00:00 /usr/libexec/gconfd-2
maxj      2009  1854  0 56306  5656   2 Apr17 ?        00:00:00 /usr/bin/seapplet
root      1616     1  0 64945  5560   1 Apr17 ?        00:00:05 /usr/sbin/abrtd
maxj      2024  1854  0 56360  5284   3 Apr17 ?        00:00:00 /usr/libexec/polkit-gnome-authentication-agent-1
maxj      1982     1  0 109234 4904   2 Apr17 ?        00:00:00 /usr/bin/pulseaudio --start --log-target=syslog
  ...
root       654     2  0     0     0   1 Apr17 ?        00:00:00 [kmpathd/1]
root       655     2  0     0     0   2 Apr17 ?        00:00:00 [kmpathd/2]
root       656     2  0     0     0   3 Apr17 ?        00:00:00 [kmpathd/3]
root       657     2  0     0     0   2 Apr17 ?        00:00:00 [kmpath_handlerd]
root       688     2  0     0     0   2 Apr17 ?        00:00:00 [kjournald2]
root      1011     2  0     0     0   1 Apr17 ?        00:00:00 [vmmemctl]
root      1557     2  0     0     0   0 Apr17 ?        00:00:00 [rpciod/0]
root      1558     2  0     0     0   1 Apr17 ?        00:00:00 [rpciod/1]
root      1559     2  0     0     0   2 Apr17 ?        00:00:00 [rpciod/2]
root      1560     2  0     0     0   3 Apr17 ?        00:00:00 [rpciod/3]

注:

  • RSS值包含了共享库的内存,这部分重复计算入各个使用共享库的进程

  • 内核线程的RSS值为0,这是因为所有内核线程是共用内存空间的

用户空间所使用物理内存,对应于kernel中页帧管理的“文件映射”和“匿名映射”。

单个进程的内存

一个进程的空间分布,可以在文件/proc/PID/maps中观察到。如下例:

# cat /proc/8560/maps
00400000-0040e000 r-xp 00000000 fd:00 90192                              /usr/bin/top
0060e000-0060f000 rw-p 0000e000 fd:00 90192                              /usr/bin/top
0060f000-00612000 rw-p 00000000 00:00 0 
0080e000-00810000 rw-p 0000e000 fd:00 90192                              /usr/bin/top
00d5e000-00da0000 rw-p 00000000 00:00 0                                  [heap]
33dea00000-33dea1e000 r-xp 00000000 fd:00 214080                         /lib64/ld-2.11.so
33dec1d000-33dec1e000 r--p 0001d000 fd:00 214080                         /lib64/ld-2.11.so
...
33ea41d000-33ea421000 rw-p 0001d000 fd:00 214184                         /lib64/libtinfo.so.5.7
7ffa3948c000-7ffa39498000 r-xp 00000000 fd:00 7521                       /lib64/libnss_files-2.11.so
7ffa39498000-7ffa39697000 ---p 0000c000 fd:00 7521                       /lib64/libnss_files-2.11.so
7ffa39697000-7ffa39698000 r--p 0000b000 fd:00 7521                       /lib64/libnss_files-2.11.so
7ffa39698000-7ffa39699000 rw-p 0000c000 fd:00 7521                       /lib64/libnss_files-2.11.so
7ffa39699000-7ffa3969d000 rw-p 00000000 00:00 0 
7ffa396b2000-7ffa396b4000 rw-p 00000000 00:00 0 
7fff4dfa3000-7fff4dfb8000 rw-p 00000000 00:00 0                          [stack]
7fff4dfff000-7fff4e000000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

它的输出共分为5列,分别是:

  1. address

    这是虚拟空间分布,而非物理内存的分布。

  2. perms

    r = read

    w = write

    x = execute

    s = shared

    p = private (copy on write)

  3. offset

  4. dev

  5. pathname

提示

共享库的空间虽然标记为私有的,但它们是COW(copy on write)的,因此只要程序运行过程中不修改它们,实际上就是共享的,只占一份内存。

如果要查看进程的页面映射情况,可以察看文件/proc/PID/statm。如下例:

#cat /proc/8560/statm
3726 302 213 14 0 123 0

输出共有6列,含义分别是:

  1. total内存总数)

  2. resident set size(RSS,驻留内存数量)

  3. shared pages(共享页)

  4. text(代码段)

  5. library(?):总是为0

  6. data/stack(数据段/堆栈)

  7. dirty pages(脏页):总是为0

其中RSS的值,就是进程驻留物理内存的页帧的数理。这个值未计入还未分配的虚拟页以及换出的页,因此RSS的值要小于后几个值的总和。

如果要分析计算每个区间所占的RSS数量,可以察看文件/proc/PID/smaps。如下例:

# cat /proc/8560/statm /proc/8560/smaps
3726 302 213 14 0 123 0
00400000-0040e000 r-xp 00000000 fd:00 90192                              /usr/bin/top
Size:                 56 kB
Rss:                  52 kB
Pss:                  52 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:        52 kB
Private_Dirty:         0 kB
Referenced:           52 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
0060e000-0060f000 rw-p 0000e000 fd:00 90192                              /usr/bin/top
Size:                  4 kB
Rss:                   4 kB
Pss:                   4 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         4 kB
Referenced:            4 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
0060f000-00612000 rw-p 00000000 00:00 0 
Size:                 12 kB
Rss:                  12 kB
Pss:                  12 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        12 kB
Referenced:           12 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
0080e000-00810000 rw-p 0000e000 fd:00 90192                              /usr/bin/top
Size:                  8 kB
Rss:                   8 kB
Pss:                   8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         8 kB
Private_Dirty:         0 kB
Referenced:            8 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
00d5e000-00da0000 rw-p 00000000 00:00 0                                  [heap]
Size:                264 kB
Rss:                 216 kB
Pss:                 216 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:       216 kB
Referenced:          216 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
33dea00000-33dea1e000 r-xp 00000000 fd:00 214080                         /lib64/ld-2.11.so
Size:                120 kB
Rss:                 104 kB
Pss:                   1 kB
Shared_Clean:        104 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
...

以上显示省去部分内容。通过计算可以发现各区间的RSS值相加等于302。

注意

从这个例子可以看出,进程RSS的统计包含了so库的所占用的内存。

对应用程序进行内存分析时,需要注意malloc库内部的缓冲机制,其内存空间是按block管理的, 当block内的部分区域释放时,block所占的区域通常不立即释放。 因此,在调用free()后,系统内存不一定能恢复至malloc()调用以前的状态。

10.4.3. 故障定位:内存使用状况

内存使用状况的分析,通常采用“自顶向下”的分析方法,首先将排查范围缩小在内核空间或用户空间。

10.4.4. 故障定位:内存泄漏

10.4.4.1. 内存泄漏的判断方法

内存泄漏,是指内存中没有引用者的内存对象。这类故障可以采用2种方法定位:

  • 较严重的内存泄漏,会表现为系统的内存持续减少,通过观察系统的内存使用状况,经常可以推断出内存泄漏的程序

  • 根据内存对象的引用情况,来判断是否存在内存泄漏。这种方法可以定位少量的内存泄漏,而用内存使用状况通常分析不出来

本节介绍利用内存引用情况来定位内存泄漏的方法,内存使用状况的分析,可参见上一节。

如果内存对象的管理算法中已经记录了每个内存对象的引用情况,则定位内存泄漏就是一件很容易的事情。 但通常出于效率及复杂度的方面的考虑,通常内存管理算法不提供这样的跟踪,或者仅作为一个调试选项。

在采用C语言编写的程序中,除静态对象外,内存对象都是采用指针形式引用的, 通过指针的指向,整个系统的动态内存对象就构成了一张有向图(对象称为“可达的”),而最终没有加入有向图的动态内存对象(对象称为“孤儿”),就是泄漏。

一个对象通过2种方式可达:

  1. 通过调用栈或全局变量

  2. 通过其它可达的对象

构造有向图的过程,就是找出泄漏对象的过程。 [todo todo]

从以上方法的分析可以看见,这种方式主要有以下局限:

  • 不是“在线”的监控方式,需要外部事件触发,如手工输入命令、定时器等

  • 需要自行构造有向图来跟踪对象的引用,因此效率较低

基于以上原因,这种类型的定位功能通常都是作为调试手段提供的。

10.4.4.2. kernel中内存泄漏的定位

Kernel模块使用的动态内存,绝大多数是通过slab来分配的。 Kernel的调试开关CONFIG_DEBUG_KMEMLEAK,用于启动kernel中的slab泄漏的跟踪。 当启动这个功能后,Kernel每10分钟检查一次内存引用情况,打印出未引用的内存对象。

kmemleck的基础算法如下:

通过kamlloc、vmalloc、keme_cache_alloc和类似函数分配的内存被跟踪, 指针和其它如尺寸、栈回溯附加信息,存储于prio search tree。 相应的释放函数也被跟踪,此时从kmemleak数所结构中移除指针。

当没有指针指向它的起始地址,或者在内存扫描(包括saved registers)过程中发现了块的内部地址, 这个已分配的内存块被视为孤儿。 这表示kernel没有办法通过传递块的地址给释放函数来释放它,因此这个块被视为内存泄漏。

遍历算法的步骤:

  1. 标记所有对象为白色 (最后仍是白色的对象将视为孤儿);

  2. 从数据段和堆栈开始遍历内存,检查否有值与prio serach tree中存储的地址相同的值. 如果有发现指向白色对象的值,对象加入灰色链表中;

  3. 在灰色对象中扫描匹配的地址(一个白色对象会变为灰色,加入灰色链表的后面), 只到检查完灰色集合。

  4. 剩下的白色对象视为孤儿,通过/sys/kernel/debug/kmemleak报告。

一些已分配的内存块由kernel内部的数据结构指向,它们不应该视当作孤儿。 为了避免这种情况,kemeleak也存储了需要发现的、指向这些块的内部地址的值, 如此这些块就不会被视为泄漏。例如,__vmalloc()

典型使用过程如下:

# mount -t debugfs nodev /sys/kernel/debug/
# cat /sys/kernel/debug/kmemleak

如果要手工触发一次检查:

# echo scan > /sys/kernel/debug/kmemleak

如果要清除检查的结果

# echo clear > /sys/kernel/debug/kmemleak

当读取文件/sys/kernel/debug/kmemleak时,会再次触发泄漏检查。

写入文件/sys/kernel/debug/kmemleak,可以对kemeleak进行配置:

  • off - 关闭kmemleak (不可取消)

  • stack=on - 启用task的堆栈扫描 (缺省)

  • stack=off - 禁用tasks的堆栈扫描

  • scan=on - 启用自动内存扫描线程 (缺省)

  • scan=off - 禁用自动内存扫描线程

  • scan= - 设置自动扫描周期(秒) (缺省600, 0代表禁用自动扫描)

  • scan - 触发一次内存扫描

  • clear - 清除当前的内存泄漏检查,将当前已报告的未引用对象都标记为灰色

  • dump= - dump在位置发现的内存对象的信息;

系统启动时,可以通过命令行参数"kmemleak=off"来禁用这一检查。

10.4.4.3. 进程的内存泄漏

进程中的内存泄漏分析,可以使用mtrace工具。 mtrace通过malloc库提供的hooks,在hooks上挂接自己的跟踪函数,记录每一次的内存申请及释放。

典型使用过程如下: todo todo todo todo

这种方法的特性:

  • 需要在代码中插入代码以触发嗖跟踪的启动及停止。

  • 跟踪过程通常对程序效率的影响较明显,可能达到数倍。

提示

与mtrace类似的工具还包括dmalloc、memwatch、mpatrol和dbgmem,等等。 它们的工作原理都比较类型。

10.4.5. 故障定位:内存的非法访问

10.4.5.1. 问题分析

RGOS系统中常见的内存非法访问,按其来源有以下几类:

  1. 指针未赋值

  2. 内存缓冲区的填充未落在有效范围内

    例如:memcpy()调用未有效的限定复制的长度、字符串类的off-by-one错误,等

    这类缺陷通常有一个共性:从合法区域越界至区域外,多数预防手段也是在这个特性上相办法。 例如,在合法区域两端设置标志字段(标志字段本身不占使用者的空间), 如果标志字段的值被修改了,则可判断出现了内存越界。这种方法的明显不足是只能是在事后(如free或主动触发)探测到, 无法在出错的第一时间捕获到,因此可能出现还未来得及检查,系统就已崩溃,或者,发现有错误,但又查不到调用者等。

10.4.5.2. slab的调试机制

为帮助定位一些常见编程错误,slab提供了一些调试机制用于检查未初化、越界错误,以下分别说明。

注意

本节介绍的功能需要打开调试开关DEBUG_SLAB。

打开DEBUG_SLAB开关后,能够捕获到的缺陷包括:

  • 未初始化或释放后仍然引用

    这个调试功能在缓冲区分配及释放时,将缓冲区内内部填充为特定字符,当出现缓冲区未初始化,或者释放后仍然使用的情况时, 通常会触发错误(如错误的内存访问)。

  • 越过边界

    这个调试功能在缓冲区边界两端填充特征字符,当缓冲区释放时,检查特征字符是否存在, 如果不存在,则说明使用过程中出现越过边界的情况

  • 重复释放

    缓冲区释放后,在对象的flags上设置一个标志位,通过释入时检查这个标志位,就可以捕获重复释放。

你可能感兴趣的:(网络随笔)