浅谈Solaris中的malloc()和free()

来源:互联网 发布:榕基软件股份有限公司 编辑:程序博客网 时间:2024/06/18 15:17

 

很多Solaris开发人员可能注意到一个现象,当我们在Solaris程序中用malloc()分配一块内存空间,然后使用这个内存空间,最后利用free()释放这个空间,可从系统来观察,程序并没有释放这块内存空间,还是占用了这个内存空间,这和Linux中的表现是不同的。本文就这个问题并结合一个测试程序来说明Solaris中对于malloc()free()的处理方式。。

 

首先,对于上述的问题,我们从Solaris的帮助文件中可以得到直接的答案,在C库函数free()manpage中有以下的描述:

Afterfree() is executed, this space is made available for furtherallocation by the application, though not returned to the system.Memory is returned to the system only upon termination of the application.

 

由此我们就可以知道,当free()被调用后,所释放的空间并没有还给操作系统,只有当程序退出时,malloc()出来的内存空间才会被真正释放。这里忽略了一种情况,在Solaris中,当系统内存紧缺时,Solaris操作系统会启动系统内存页面扫描进程,从而检测出当前系统中哪些内存页面是不用的或暂时不用的,然后将这些页面释放或者转换到缓存中,从而解决内存紧缺问题。具体的Solaris内存管理机制可以参考SolarisInternals中相关的章节,这里不做详述。

 

那在实际的程序运行时,具体会如何呢?这里我们用一个程序样例来做个更直观的说明和分析。

 

这个程序样例是这样分阶段运行的,

 

1,在程序中用malloc()分配200m空间的内存

2,用memset()来对分配的200M内存进行引用

3,用free()释放分配的内存块

4,再次利用malloc()来分配200m空间的内存并用memset()进行初始化。

 

 

在每个阶段运行完成后,我们都会利用Solaris的系统监测工具和一些Dtrace脚本来观测这个程序的所占用内存变化以及malloc()的一些表现,下面是具体的分析数据。

 

阶段一:当程序利用malloc()第一次分配了200m空间后

prstat的输出可以看出,目前程序已经占用了近200M的内存,但真正使用的内存只有1M,这是因为系统对于malloc()出来的内存,在没有进行引用之前,只是将200M的内存进行了预定,而没有在实际物理内存中进行分配,所以我们看到RSS段的数值还是1028K

#prstat

PIDUSERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923wenlong 202M 1208K sleep 59 0 0:00:00 0.0% mymalloctest/1

而通过pmap我们可以再次证实,在程序的heap段,已经预留了200M的空间,但RSS值表示出还没有去引用这200M内存。

#pmap -ax 13923

13923: ./mymalloctest

Address Kbytes RSS Anon Locked Mode Mapped File

00010000 8 8 - - r-x-- mymalloctest

00020000 8 8 8 - rwx-- mymalloctest

00022000 204808 16 16 - rwx-- [ heap ]

...

FFBFC000 16 16 16 - rwx-- [ stack ]

--------------- ------- ------- -------

totalKb 206504 1704 128 -

那从kernel的角度(mdb-k)来说,目前的内存分配没有表现出程序已经预约的200M空间(程序运行前已经有200M的匿名内存分配了)。

#mdb -k

>::memstat

PageSummary Pages MB %Tot

------------ ---------------- ---------------- ----

Kernel 35779 279 28%

Anon 27826 217 21%

Execand libs 5447 42 4%

Pagecache 6241 48 5%

Free(cachelist) 12198 95 9%

Free(freelist) 42003 328 32%

 

Total 129494 1011

Physical 127269 994

 

阶段二,memset()来对分配的200M内存进行引用

 

Solaris中,当对已经malloc()后的内存进行引用时,系统将会在物理内存中真正分配这块内存,如下,

#prstat

PIDUSERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923wenlong 202M 199M sleep 59 0 0:00:00 0.2% mymalloctest/1

可以看到,RSS的值已经为近200M了,从pmap的输出也可以证实这一点。

#pmap -ax 13923

13923: ./mymalloctest

Address Kbytes RSS Anon Locked Mode Mapped File

00010000 8 8 - - r-x-- mymalloctest

00020000 8 8 8 - rwx-- mymalloctest

00022000 204808 204808 203072 - rwx-- [ heap ]

...

FFBFC000 16 16 16 - rwx-- [ stack ]

--------------- ------- ------- -------

totalKb 206504 206496 203184 -

 

Solaris系统Kernel的角度来看,系统中内存的分配中,对于Anon中,多了200M的分配空间,而相应空闲的内存少了200M.

>::memstat

PageSummary Pages MB %Tot

------------ ---------------- ---------------- ----

Kernel 36296 283 28%

Anon 53424 417 41%

Execand libs 5419 42 4%

Pagecache 6240 48 5%

Free(cachelist) 12070 94 9%

Free(freelist) 16045 125 12%

 

Total 129494 1011

Physical 127269 994

 

阶段三,用free()来释放这块200M的内存

 

在这个过程中,我们调用了free()来释放刚才分配的200M内存空间,根据文章开始时介绍的,在Solaris中,调用free()并不真正释放内存,只是把这段内存标记为程序内部的可用空间,而对操作系统来说,程序还是占用了200M的匿名内存空间。同样的,这个就反映在了以上的命令输出中,可以看到,输出和阶段二的基本一样。

 

#prstat

PIDUSERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923wenlong 202M 199M sleep 59 0 0:00:00 0.0% mymalloctest/1

 

#pmap -ax 13923

13923: ./mymalloctest

Address Kbytes RSS Anon Locked Mode Mapped File

00010000 8 8 - - r-x-- mymalloctest

00020000 8 8 8 - rwx-- mymalloctest

00022000 204808 204808 203072 - rwx-- [ heap ]

...

FFBFC000 16 16 16 - rwx-- [ stack ]

--------------- ------- ------- -------

totalKb 206504 206496 203184 -

 

从系统Kernel的角度来说,样例程序始终占用了这200M内存空间而没有释放到系统中去。

#mdb -k

>::memstat

PageSummary Pages MB %Tot

------------ ---------------- ---------------- ----

Kernel 36281 283 28%

Anon 53429 417 41%

Execand libs 5415 42 4%

Pagecache 6244 48 5%

Free(cachelist) 12077 94 9%

Free(freelist) 16048 125 12%

 

Total 129494 1011

Physical 127269 994

 

阶段四:再次利用malloc()来分配200m空间的内存并用memset()进行初始化

 

在第四个阶段,我们再次利用malloc()来分配200M的内存,并直接初始化这段内存,看看程序和系统在内存分配上有什么变化。

 

从程序的表现来说,RSS相比阶段三没有变化,说明新分配的内存直接利用了第一次被分配的内存空间,这个可以从以下的命令输出得到更直观的表现。

#prstat

PIDUSERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923wenlong 202M 201M sleep 59 0 0:00:00 0.0% mymalloctest/1

 

#pmap -ax 13923

13923: ./mymalloctest

Address Kbytes RSS Anon Locked Mode Mapped File

00010000 8 8 - - r-x-- mymalloctest

00020000 8 8 8 - rwx-- mymalloctest

00022000 204808 204808 204808 - rwx-- [ heap ]

...

FFBFC000 16 16 16 - rwx-- [ stack ]

--------------- ------- ------- -------

totalKb 206504 206496 204920 -

 

从系统kernel的角度来说,系统中的Anon段的数值还是一样。

#mdb -k

>::memstat

PageSummary Pages MB %Tot

------------ ---------------- ---------------- ----

Kernel 35146 274 27%

Anon 52380 409 40%

Execand libs 5324 41 4%

Pagecache 6227 48 5%

Free(cachelist) 12095 94 9%

Free(freelist) 18322 143 14%

 

Total 129494 1011

Physical 127269 994

 

 

二,malloc()行为的具体分析

 

那对于两次调用的malloc(),有什么不同呢?以下我们利用Dtrace来分析两次malloc()的调用有什么不同,见如下具体的Dtrace脚本,

#pragmaD option flowindent

 

pid$1:libc:malloc:entry

{

self->traceme= 1;

printf("fd:%d", arg0);

}

 

fbt:::

/self->traceme/

{}

 

pid$1:libc:malloc:return

/self->traceme/

{

self->traceme= 0;

exit(0);

}

 

这个Dtrace脚本能跟踪当程序调用malloc()时,其具体执行的kernel函数有哪些,以下的输出是跟踪第一次malloc()调用的输出结果。我们可以看出,第一次调用malloc()时,程序进入kernel层并进行了一系列的kernel函数的执行,输出结果很长,这里只列出很少的一部分,有兴趣的读者可以根据以上的Dtrace脚本自己来验证,

dtrace:script 'malloc.d' matched 48549 probes

CPUFUNCTION

0 -> malloc fd: 209715200

0 -> syscall_mstate

0 <- syscall_mstate

0 -> brk

0 -> as_rangelock

0 <- as_rangelock

0 -> brk_internal

0 -> rctl_enforced_value

0 -> rctl_set_find

0 <- rctl_set_find

...

0 -> brk

0 -> as_rangelock

0 <- as_rangelock

0 -> brk_internal

0 -> rctl_enforced_value

0 -> rctl_set_find

0 <- rctl_set_find

0 -> rctl_model_value

0 -> rctl_model_maximum

0 <- rctl_model_maximum

...

0 <- as_fault

0 <- pagefault

0 -> trap_rtt

0 <- trap_rtt

0 -> new_mstate

0 -> cpu_update_pct

0 -> cpu_grow

0 -> cpu_decay

0 -> exp_x

0 <- exp_x

0 <- cpu_decay

0 <- cpu_grow

0 <- cpu_update_pct

0 -> new_cpu_mstate

0 <- new_cpu_mstate

0 <- new_mstate

0 <- trap

0 <- malloc

 

而在进行第二次malloc()调用时,程序根本不进入kernel层,在用户层,也就是在libc中就完成了对malloc的操作,同样执行这个脚本得到的结果,

dtrace:script 'malloc.d' matched 48549 probes

CPUFUNCTION

0 -> malloc fd: 209715200

0 <- malloc

我们可以看到,第二次malloc()调用在用户层就直接完成返回了。

 

那这两次malloc()在各自花费的时间上有什么不同呢,我们还可以利用Dtrace来得到相关的时间,以下是用来统计malloc()调用所花费时间的Dtrace脚本,

 

pid$1:libc:malloc:entry

{

self->ts= timestamp;

}

 

pid$1:libc:malloc:return

/self->ts/

{

printf("themalloc running time is %d (us)/n", (timestamp - self->ts)/1000);

self->ts= 0;

}

 

在这个例子中,我们得到的结果为:

#dtrace -s malloctime.d 13990

dtrace:script 'malloctime.d' matched 2 probes

CPU ID FUNCTION:NAME

0 51687 malloc:return the malloc running time is169 (us)

 

0 51687 malloc:return the malloc running time is 20(us)

 

 

我们可以看到,第二次执行分配200M空间的malloc()操作消耗的时间远小于第一次,这也验证了我们对于第一个Dtrace脚本运行结果的验证,即第一次malloc()时进行的指令操作远多于第二次。

 

以上的样例程序实验中,第二次malloc()的参数大小也为200M,如果第二次分配的内存大小小于或大于200M时,会有什么不同的结果呢?下面做个简要的概述,不列出具体的数据,

1,如果是第二次malloc()的参数小于200M时,从系统角度来看,这个程序中还是占用了200M的匿名空间,应用Dtrace脚本得到的结果和样例程序一样。

2,如果是第二次malloc()的参数大于200M时,这时调用malloc()就要进入kernel来进行更多的内存空间分配了,这时候malloc()的行为和第一次malloc类似,调用完成后,系统占用的匿名空间就变成了新的大小了。

 

三,Linux中两次调用malloc()的结果

为了做比较,我们在LinuxRHELAS5)上做了相同的实验,可惜在Linux上没有Dtrace这样的工具来跟踪LinuxKernel的内部调用,这里我们采用了Strace来跟踪程序执行时系统调用的执行时间,主要是针对两次malloc()来说,注意,这次是malloc的内存大小为50M,Strace-T的输出如下,

execve("./mymalloc",["./mymalloc"], [/* 37 vars */]) = 0 <0.001515>

...

mmap2(NULL,52432896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0xb4d12000 <0.000137>

...

munmap(0xb4d12000,52432896) = 0 <0.009671>

...

mmap2(NULL,52432896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0xb4d12000 <0.000381>

 

从以上的输出我们可以直观的看到,两次调用malloc()都要实际的去执行系统调用来分配内存,而在Linux中,调用free()也确实释放了malloc得到的内存空间,这个可以用toppmap这样的工具来具体观察,这里不做祥述。

 

四,总结

 

通过以上的分析,我们可以看到,在Solaris中,当程序调用malloc()时,程序首先是看看是否自己的进程空间有可用的空间,如果有,就直接分配了,而不用去Kernel中申请了,这样处理的好处是当一个程序频繁的进行mallocfree的操作时,程序就不用每次malloc都要调用系统调用,从而增加运行时间(malloc是个很消耗系统资源的函数)。好在在实际部署的系统当中,往往只运行一个或几个主要的应用,系统中大部分的内存申请和释放是这几个程序发生的,从而在内存的处理上得到了更高的效率。但我们需要注意以下的问题,

 

1,在Solaris中,当一个程序运行时间长久以后,由于程序分配的内存总数不断增加,而利用free()想释放内存时,并不能真正的释放内存到系统中,这样我们很可能看到程序的占用内存数有所持续上升,这个时候就不能马上下结论说程序中有内存泄露的问题,很可能是我们程序中持续分配所造成的。

2,针对上述的特点,那我们在调用malloc()分配大量内存时要多注意一些,分配过大的内存有可能造成内存使用的浪费。

 

 

 

 

 

原创粉丝点击