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

 

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

 

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

After free() is executed, this space is made available for further allocation 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 内存管理机制可以参考 Solaris Internals 中相关的章节,这里不做详述。

 

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

 

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

 

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

PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923 wenlong 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 ]

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

total Kb 206504 1704 128 -

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

# mdb -k

> ::memstat

Page Summary Pages MB %Tot

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

Kernel 35779 279 28%

Anon 27826 217 21%

Exec and libs 5447 42 4%

Page cache 6241 48 5%

Free (cachelist) 12198 95 9%

Free (freelist) 42003 328 32%

 

Total 129494 1011

Physical 127269 994

 

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

 

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

#prstat

PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923 wenlong 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 ]

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

total Kb 206504 206496 203184 -

 

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

> ::memstat

Page Summary Pages MB %Tot

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

Kernel 36296 283 28%

Anon 53424 417 41%

Exec and libs 5419 42 4%

Page cache 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

PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923 wenlong 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 ]

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

total Kb 206504 206496 203184 -

 

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

# mdb -k

> ::memstat

Page Summary Pages MB %Tot

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

Kernel 36281 283 28%

Anon 53429 417 41%

Exec and libs 5415 42 4%

Page cache 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

PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/NLWP

13923 wenlong 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 ]

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

total Kb 206504 206496 204920 -

 

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

# mdb -k

> ::memstat

Page Summary Pages MB %Tot

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

Kernel 35146 274 27%

Anon 52380 409 40%

Exec and libs 5324 41 4%

Page cache 6227 48 5%

Free (cachelist) 12095 94 9%

Free (freelist) 18322 143 14%

 

Total 129494 1011

Physical 127269 994

 

 

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

 

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

#pragma D 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

CPU FUNCTION

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

CPU FUNCTION

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("the malloc 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 is 169 (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() 的结果

为了做比较,我们在 Linux RHEL AS5 )上做了相同的实验,可惜在 Linux 上没有 Dtrace 这样的工具来跟踪 Linux Kernel 的内部调用,这里我们采用了 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 得到的内存空间,这个可以用 top pmap 这样的工具来具体观察,这里不做祥述。

 

四,总结

 

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

 

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

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

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值