资讯专栏INFORMATION COLUMN

拨开由问题《Linux下malloc最大可申请的内存》带来的重重疑云

trilever / 726人阅读

摘要:本文测试环境如下一首先需要考虑的几个问题我们使用申请到的是物理内存吗使用能申请到的只有的物理内存吗申请到的内存大小全都可以被用来吗以上三个问题,正是本次所要讨论的内容。

今天阅读相关书籍的时候看到 "进程中堆的最大申请数量" 这一问题,我们知道使用malloc分配内存是在堆Heap里面分配的,如果一台机器一共有8GB物理内存,空闲5GB,那么我们使用malloc( )就一定能够申请到这5GB内存吗?理论上来说确实如此,因为这些内存未被其它进程使用。但实际测试出来结果却可能令人疑惑。

本文测试环境如下:

1 qi@qi:~$ uname -a2 Linux qi 5.4.0-89-generic #100~18.04.1-Ubuntu SMP Wed Sep 29 10:59:42 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux 

 


 一、首先需要考虑的几个问题

  1. 我们使用malloc( )申请到的是物理内存吗?
  2. 使用malloc( )能申请到的只有8GB的物理内存吗?
  3. malloc( )申请到的内存大小全都可以被用来memset( )吗?

以上三个问题,正是本次所要讨论的内容。现在假定认为以上三个陈述均正确,那么我们可以用以下程序测试malloc( )可以申请的内存大小:

 1 #include  2 #include  3 #include <string.h> 4  5 unsigned long long int maximum = 0; 6  7 int main (int argc, char* argv[]) 8 { 9     unsigned int block_size[] = {1024*1024, 1024, 1};10     int i, count;11 12     for( int i=0; i<3; i++) {13         for(count=1;; count++) {14             void *block = malloc(maximum + block_size[i]*count);15             if( block ) {16                 //memset(block, 0, maximum + block_size[i]*count);17                 free(block);18                 maximum = maximum + block_size[i]*count;19             } else {20                 break;21             }22         }23     }24 25     printf("maximum malloc size is %llu bytes /n", maximum);26 27     return 0;28 }

 运行以上程序,得到输出为:

1 root@qi:/home/qi/test_park/elf_load# ./main 2 maximum malloc size is 24587279333 bytes

 可以看到,以上测试程序最大申请到了 22.9GB 的内存,但是我的机器上实际内存有多少呢?如下:

1 qi@qi:~/test_park/elf_load$ free2               total        used        free      shared  buff/cache   available3 Mem:        8011016     2373760     3517884      719508     2119372     46546404 Swap:      15999996           0    15999996

很明显,机器上最大的物理内存也没到8GB,如果你了解swap 交换空间,可能会说Mem项和Swap项的total加起来似乎正好是22.9GB,但是另外一个问题有来了,那就是这些内存或者交换空间并不是全部空闲,包括系统内核和系统界面等等也要占用一部分物理内存,所以我们看到Mem项的 "available"的可用内存只有大约4.5GB,所以结果就是,malloc( )申请到的内存数量是远远大于我们实际的物理内存的。既然malloc( )函数的实际输出和我们的预期不相符,那是不是我们哪里用错了呢?不妨使用"man malloc"查看对其的官方解释:

1 NOTES2        By default, Linux follows an optimistic memory allocation strategy.  This means that when malloc() returns non-NULL there is no guarantee that the mem‐3        ory really is available.  In case it turns out that the system is out of memory, one or more processes will be killed by  the  OOM  killer.   For  more4        information,  see  the  description  of /proc/sys/vm/overcommit_memory and /proc/sys/vm/oom_adj in proc(5), and the Linux kernel source file Documenta‐5        tion/vm/overcommit-accounting.

 果不其然,Note中说明了就算malloc( )返回非NULL指针也不能保证该指针指向的内存区域全都可以被该进程使用。那么为什么会这样呢?后面有提示,首先涉及到的最重要的一个设置就是 "/proc/sys/vm/overcommit_memory" 这一个文件,使用 "man proc" 找到有关其的说明:

 1        /proc/sys/vm/overcommit_memory 2               This file contains the kernel virtual memory accounting mode.  Values are: 3  4                      0: heuristic overcommit (this is the default) 5                      1: always overcommit, never check 6                      2: always check, never overcommit 7  8               In mode 0, calls of mmap(2) with MAP_NORESERVE are not  checked,  and  the  default 9               check is very weak, leading to the risk of getting a process "OOM-killed".10 11               In mode 1, the kernel pretends there is always enough memory, until memory actually12               runs out.  One use case for this mode is  scientific  computing  applications  that13               employ  large  sparse  arrays.   In Linux kernel versions before 2.6.0, any nonzero14               value implies mode 1.15 16               In mode 2 (available since Linux 2.6), the total virtual address space that can  be17               allocated (CommitLimit in /proc/meminfo) is calculated as18 19                   CommitLimit = (total_RAM - total_huge_TLB) *20                                 overcommit_ratio / 100 + total_swap

可以看到,如果该文件内容为0,mmap(malloc的内部调用)将不检查,有导致使用不存在内存的风险,如果文件内容为1,则malloc( )可以申请的内存可以非常大,我的机器上经过测试可以达到90T,如果该文件内容为2,那么所有可以申请的内存为 "CommitLimit",具体可以通过公式或者 "cat /proc/meminfo | grep Limit"查看大小。那么这就能说通为什么上面的程序可以malloc( )出22GB多的内存了,查看 "/proc/sys/vm/overcommit_memory" 果不其然,内容为0:

1 root@qi:/home/qi/test_park/elf_load# cat /proc/sys/vm/overcommit_memory 2 0

以上回答了第2个问题中的一部分,那就是某些设置下,malloc( )可以申请到超出机器物理内存的大小,为什么说是一部分呢,因为可申请的内存不仅和上述设定相关,还和机器的swap space相关,如果你不了解或者没听过( 事实上在你给你机器装Linux系统的时候应该碰到过,那就是磁盘分区的时候会有一个swap设定)swap空间,只需要知道它是一种挂载在物理硬盘上,用来存放一些不太频繁使用的内存,是一种低速的物理内存的扩展,当物理内存不够用时,原先一些物理内存中不常访问的内容会被转移到这里以让出空间给其它进程。所以swap空间也可以被malloc( )申请到。

由此,第2个问题得到了全部的解答。这个时候你可能会说,第1个问题应该也有答案了,因为malloc( )不仅申请了8GB的物理内存,还申请了15GB的swap硬盘空间作为扩展内存,甚至还可以申请大约90TB的不存在的内存,所以第一个问题就解决了吗?

其实对,但也不全对,因为malloc( )这个时候申请了内存,但没有完全申请,这就涉及到一个叫做 "Lazy Allocation" 的东东,类似于fork的写时复制机制,当你使用malloc( )时,系统并没有真正从物理内存中分配,而是等到进程要操作时才提供allocation,这也就解释了我们刚开头申请了22.9GB的内存都还没有报段错误的原因。只有当你access这个内存区域的时候才会真正分配,所以我们可以大胆的在程序里面加上memset,把上面贴出的代码的memset那一行取消掉注释,然后再运行。如果你不想等太久,可以像我这样:

1 root@qi:/home/qi/test_park/elf_load# echo 0 >  /proc/sys/vm/overcommit_memory 2 root@qi:/home/qi/test_park/elf_load# swapoff -a3 root@qi:/home/qi/test_park/elf_load# echo 2 >  /proc/sys/vm/overcommit_memory

以上命令是把交换空间禁用,这样就可以减少可使用的内存了,关闭交换空间后,如果/proc/sys/vm/overcommit_memory内容为0,那么你可以malloc( )的内存大小应该为8GB左右,但是不是每一个字节都可以memset,大可以测试一下,会发现memset了6~7GB的内存空间后程序报错异常退出,这是因为这个时候可使用的内存也就这么大,这种情况下随意使用malloc( )申请到的内存是不安全的。如果/proc/sys/vm/overcommit_memory内容为2,那么这个时候可申请的内存就得看 "CommitLimit" 了,在我的机器上测试是只能申请1.5GB左右,这种情况下无论如何也不会访问非法内存区域了,但是一个缺点是不能使用全部的空闲内存,只能修改相应的设置。

那么该如何知道实际可用的内存大小呢?一种解决方案是查看 "/proc/meminfo" 中的available memory,乘个安全系数再来申请。

以上,三个问题全都被解决,离专业的linuxer又近了一步~

 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/124812.html

相关文章

  • Linux内存管理深入分析(上)

    摘要:鄙人以为主要是堆溢出漏洞的门槛较高,需要先吃透相应操作系统的堆内存管理机制,而这部分内容一直是一个难点。对的理解在中将整个堆内存空间分成了连续的大小不一的,即对于堆内存管理而言就是最小操作单位。众所周知,无论是何种堆内存管理器,其完成的核 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘、利用的分析文章层出不穷。从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种。国内关...

    Steven 评论0 收藏0
  • Linux内存管理深入分析(上)

    摘要:鄙人以为主要是堆溢出漏洞的门槛较高,需要先吃透相应操作系统的堆内存管理机制,而这部分内容一直是一个难点。对的理解在中将整个堆内存空间分成了连续的大小不一的,即对于堆内存管理而言就是最小操作单位。众所周知,无论是何种堆内存管理器,其完成的核 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘、利用的分析文章层出不穷。从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种。国内关...

    lylwyy2016 评论0 收藏0
  • 秋招——语言篇(C++)

    摘要:语言不支持函数重载,编译后的代码只包含函数名。发布程序无需提供静态库,移植方便。全局和静态变量当且仅当对象首次用到时才进行构造。静态全局变量全局作用域文件作用域,无法在其他文件中使用。求数组数组名数据类型。 ...

    LuDongWei 评论0 收藏0
  • iOS微信内存监控

    摘要:微信急需一个有效的内存监控工具来发现问题。一实现原理微信内存监控最初版本是使用的工具监控对象分配,用工具等接口监控堆内存分配,每隔秒,把当前所有对象个数最大堆内存及其分配堆栈,用文本输出到本地。 欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者:杨津,腾讯移动客户端开发 高级工程师由 WeTest质量开放平台团队 发布在云+社区商业转载请联系腾讯WeTest获得授权,非商业...

    fredshare 评论0 收藏0
  • iOS微信内存监控

    摘要:微信急需一个有效的内存监控工具来发现问题。一实现原理微信内存监控最初版本是使用的工具监控对象分配,用工具等接口监控堆内存分配,每隔秒,把当前所有对象个数最大堆内存及其分配堆栈,用文本输出到本地。 作者:杨津,腾讯移动客户端开发 高级工程师商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。 原文链接:http://wetest.qq.com/lab/view/367.html...

    ziwenxie 评论0 收藏0

发表评论

0条评论

trilever

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<