本节介绍常见内存故障的排查与定位。以下先对内存中常见的内存相关错误进行分析。
内存相关的错误,按错误来源可划分为以几类:
内存泄漏
内存泄漏是指系统中没有引用的“孤儿”内存对象。
持续的内存泄漏会表现为系统内存不断减少,而偶发的少量泄漏则可能难以觉察,并不会有破坏性后果
错误的内存使用方式
应用程序未按预期使用内存,占用了大量内存以致于影响到系统的正常运行。
这类错误可归结设计时内存分析不到位所致。 例如,分片的DOS功击,就是IP的分片组装程序,如果不对组装失败的报文及时清理, 则这些报文会占用大量系统内存,使得系统无法再处理新报文,从而造成网络服务中断。
从外部观察来看,“内存泄漏”错误与这类错误比较相近,通常最终都表现为系统内存不足。 两者的区别在于: 前者可以通过内存对象的引用来分析,在不知晓程序逻辑就可以分析出来,因此有许多工具可以用来排查此类错误,如mtrace; 而后者就需要理解应用才能确定是否是故障;
如果能够确切分析每一块内存的使用状况,则能直接区别2种类型的缺陷。
内存的非法访问
与指针相关的
这类特性与C语言的特性紧密相关:指针可指向任意内存位置,没有附加的合法检查手段。
与内存边界相关的
这类特性也与C语言的特性相关,包括:
数组类(包括缓冲区)没有边界检查,如memcpy(3)、strcpy(3);
允许不同尺寸的数据类型(指针)相互转换,如可以内存变量int32_t转换为int64_t,此时实际上变量已经到下一个内存位置了
堆栈溢出
在Linux系统中,在用户空间通常会使进程接收到SIGSEGV或SIGBUS而中止运行,在内核空间则是产生Kernel Oops;
有时产生的现象与问题根源之间的关联不是很明显。 例如,一个内存复制覆盖了邻近的结构,可能就表现为邻近的应用首先有故障,使得故障排查有时无从入手。
C语言的这种“特性”成为困扰整个业界的问题,这类故障要以“预防为主”。
Linux系统中,系统的空间划分为内核空间与用户空间,内核空间与用户空间相互隔离, 分别使用不同的内存管理方法,因此故障的表现不同,排查方法也有所不同, 以下的各类分析通常分别按内核空间与用户空间分别分析。
Linux运行于“虚拟内存映射”机制上,程序运行虚拟空间上,程序的内存访问由CPU提供的映射器件转换为对物理内存的访问。 在转换时,通常要进行访问的合法性检查。
用户空间的程序,在内存映射基础上,可以工作在“请求页”机制上。 当用户空间申请内存时,系统并非给其分配内存,而只是分配“空间”,当程序访问这段空间时,就会触发“缺页”中断, 在“缺页”中断处理程序中,再分配内存物理页。
内核空间的内存管理,除负责内核自身需要使用的内存对象外,还负责整个系统的内存管理。 内核自身通常采用固定映射方式,除vmalloc()分配的页帧外,不会产生缺页。
要理解Linux的内存管理,需理解“空间”与“内存”的差异。
内核负责整个系统内存的管理。 系统中的物理内存是按页为单位的,除了内核固定使用的内存外, 这些页面统一管理,内核内部的模块自身需要使用,还需要提供给用户空间。
文件/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级:
Zones
Zone的划分是由于物理内存的属性不同,如是否支持DMA操作。 zone的类型包括ZONE_DMA,ZONE_NORMAL和ZONE_HIGHMEM
当物理内存不连续时(称为nodes),它们按属性划分为对应类型zone下。
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管理的内存是以以页帧为单位管理的,后文将详细页帧的管理方法。
10.4.2.2.1. 页帧的分析
系统的页帧,可划分为以下几类:
文件映射(file mapping)
但一个文件被打开时,FS为存放文件内容分配物理页面。 例如,可执行程序的代码段、数据段所占内存属于这种类型,应用程序打开操作的文件内容,也属于这种类型。
匿名映射(anonymous mapping)
这部分页面用于进程的虚拟内存区域,如进程的堆栈、堆所占页面属于这种类型。
交换缓冲(swap cache)
存在交换分区的系统中,这些页面用于与外存之间交换物理页面。
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
其中
显而易见,专用缓冲池的使用状况,较容易关联其所属模块; 而对于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
用户空间的全局分析
本节主要分析用户空间的物理内存占用情况。
用户空间划分一个个独立的进程空间,各个进程在运行时,就好象在专属空间中运行一样。 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列,分别是:
address
这是虚拟空间分布,而非物理内存的分布。
perms
r = read
w = write
x = execute
s = shared
p = private (copy on write)
offset
dev
pathname
共享库的空间虽然标记为私有的,但它们是COW(copy on write)的,因此只要程序运行过程中不修改它们,实际上就是共享的,只占一份内存。
如果要查看进程的页面映射情况,可以察看文件/proc/PID/statm。如下例:
#cat /proc/8560/statm 3726 302 213 14 0 123 0
输出共有6列,含义分别是:
total内存总数)
resident set size(RSS,驻留内存数量)
shared pages(共享页)
text(代码段)
library(?):总是为0
data/stack(数据段/堆栈)
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()调用以前的状态。
内存使用状况的分析,通常采用“自顶向下”的分析方法,首先将排查范围缩小在内核空间或用户空间。
内存泄漏,是指内存中没有引用者的内存对象。这类故障可以采用2种方法定位:
较严重的内存泄漏,会表现为系统的内存持续减少,通过观察系统的内存使用状况,经常可以推断出内存泄漏的程序
根据内存对象的引用情况,来判断是否存在内存泄漏。这种方法可以定位少量的内存泄漏,而用内存使用状况通常分析不出来
本节介绍利用内存引用情况来定位内存泄漏的方法,内存使用状况的分析,可参见上一节。
如果内存对象的管理算法中已经记录了每个内存对象的引用情况,则定位内存泄漏就是一件很容易的事情。 但通常出于效率及复杂度的方面的考虑,通常内存管理算法不提供这样的跟踪,或者仅作为一个调试选项。
在采用C语言编写的程序中,除静态对象外,内存对象都是采用指针形式引用的, 通过指针的指向,整个系统的动态内存对象就构成了一张有向图(对象称为“可达的”),而最终没有加入有向图的动态内存对象(对象称为“孤儿”),就是泄漏。
一个对象通过2种方式可达:
通过调用栈或全局变量
通过其它可达的对象
构造有向图的过程,就是找出泄漏对象的过程。 [todo todo]
从以上方法的分析可以看见,这种方式主要有以下局限:
不是“在线”的监控方式,需要外部事件触发,如手工输入命令、定时器等
需要自行构造有向图来跟踪对象的引用,因此效率较低
基于以上原因,这种类型的定位功能通常都是作为调试手段提供的。
Kernel模块使用的动态内存,绝大多数是通过slab来分配的。 Kernel的调试开关CONFIG_DEBUG_KMEMLEAK,用于启动kernel中的slab泄漏的跟踪。 当启动这个功能后,Kernel每10分钟检查一次内存引用情况,打印出未引用的内存对象。
kmemleck的基础算法如下:
通过kamlloc、vmalloc、keme_cache_alloc和类似函数分配的内存被跟踪, 指针和其它如尺寸、栈回溯附加信息,存储于prio search tree。 相应的释放函数也被跟踪,此时从kmemleak数所结构中移除指针。
当没有指针指向它的起始地址,或者在内存扫描(包括saved registers)过程中发现了块的内部地址, 这个已分配的内存块被视为孤儿。 这表示kernel没有办法通过传递块的地址给释放函数来释放它,因此这个块被视为内存泄漏。
遍历算法的步骤:
标记所有对象为白色 (最后仍是白色的对象将视为孤儿);
从数据段和堆栈开始遍历内存,检查否有值与prio serach tree中存储的地址相同的值. 如果有发现指向白色对象的值,对象加入灰色链表中;
在灰色对象中扫描匹配的地址(一个白色对象会变为灰色,加入灰色链表的后面), 只到检查完灰色集合。
剩下的白色对象视为孤儿,通过/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=
scan - 触发一次内存扫描
clear - 清除当前的内存泄漏检查,将当前已报告的未引用对象都标记为灰色
dump=
系统启动时,可以通过命令行参数"kmemleak=off"来禁用这一检查。
进程中的内存泄漏分析,可以使用mtrace工具。 mtrace通过malloc库提供的hooks,在hooks上挂接自己的跟踪函数,记录每一次的内存申请及释放。
典型使用过程如下: todo todo todo todo
这种方法的特性:
需要在代码中插入代码以触发嗖跟踪的启动及停止。
跟踪过程通常对程序效率的影响较明显,可能达到数倍。
与mtrace类似的工具还包括dmalloc、memwatch、mpatrol和dbgmem,等等。 它们的工作原理都比较类型。
RGOS系统中常见的内存非法访问,按其来源有以下几类:
指针未赋值
内存缓冲区的填充未落在有效范围内
例如:memcpy()调用未有效的限定复制的长度、字符串类的off-by-one错误,等
这类缺陷通常有一个共性:从合法区域越界至区域外,多数预防手段也是在这个特性上相办法。 例如,在合法区域两端设置标志字段(标志字段本身不占使用者的空间), 如果标志字段的值被修改了,则可判断出现了内存越界。这种方法的明显不足是只能是在事后(如free或主动触发)探测到, 无法在出错的第一时间捕获到,因此可能出现还未来得及检查,系统就已崩溃,或者,发现有错误,但又查不到调用者等。
为帮助定位一些常见编程错误,slab提供了一些调试机制用于检查未初化、越界错误,以下分别说明。
本节介绍的功能需要打开调试开关DEBUG_SLAB。
打开DEBUG_SLAB开关后,能够捕获到的缺陷包括:
未初始化或释放后仍然引用
这个调试功能在缓冲区分配及释放时,将缓冲区内内部填充为特定字符,当出现缓冲区未初始化,或者释放后仍然使用的情况时, 通常会触发错误(如错误的内存访问)。
越过边界
这个调试功能在缓冲区边界两端填充特征字符,当缓冲区释放时,检查特征字符是否存在, 如果不存在,则说明使用过程中出现越过边界的情况
重复释放
缓冲区释放后,在对象的flags上设置一个标志位,通过释入时检查这个标志位,就可以捕获重复释放。