关键字:内存分配机制
在 [[linux是怎样工作的-内存管理]]中怀疑过malloc返回值的意义,感觉它从来不会失败。下面来分析一下应用层的内存分配机制。
应用层机制 从系统层面看,进程分配内存有两种方式,brk和mmap(暂不考虑共享内存)。1、brk是将数据段(.data)的最高地址指针_edata往高地址推; 2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存 。这两种方式分配的都是虚拟内存,没有分配物理内存 。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
一般程序分配内存使用的标准c库提供的malloc/free。c库本身使用上面的两种方式向内核申请一块内存,然后自己管理。默认情况下,当malloc分配的内存小于128KB, 使用brk,分配的内存大于128KB时使用mmap
情况1 - 使用brk 因为小内存分配才调用brk, brk只是推数据段的指针,而数据段的虚拟地址限制是很长的(多长以后再来填坑)。所以brk调用不会失败,即malloc不会失败。
系统调用的实现函数:SYSCALL_DEFINE1(brk, unsigned long, brk)
情况2 - 使用mmap mmap的内核实现很复杂,梳理出检查内存的大致路线为:
1 do_mmap --> mmap_region --> security_vm_enough_memory_mm --> __vm_enough_memory
__vm_enough_memory只是检查”剩余可用内存”,而mmap次数限制,地址空间限制在前面的流程就检查了。
那么函数__vm_enough_memory何时返回ENOMEM呢?
内核机制 应用层分配的虚拟内存大于系统的物理内存这种情况叫overcommit。 overcommit有三种策略,可通过/proc/sys/vm/overcommit_memory调整,默认值为0
1 2 3 #define OVERCOMMIT_GUESS 0 #define OVERCOMMIT_ALWAYS 1 #define OVERCOMMIT_NEVER 2
当配置为策略为OVERCOMMIT_NEVER时,如下两个参数起作用:
overcommit_kbytes:当不允许overcommit时,设置vm允许申请值的上限
overcommit_ratio:当不允许overcommit时,设置vm允许申请的百分比,默认50%
查看内存分配够不够的关键函数为__vm_enough_memory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #define OVERCOMMIT_GUESS 0 #define OVERCOMMIT_ALWAYS 1 #define OVERCOMMIT_NEVER 2 int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin) { long free , allowed, reserve; vm_acct_memory(pages); if (sysctl_overcommit_memory == OVERCOMMIT_ALWAYS) return 0 ; if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) { free = global_page_state(NR_FREE_PAGES); free += global_page_state(NR_FILE_PAGES); free -= global_page_state(NR_SHMEM); free += get_nr_swap_pages(); free += global_page_state(NR_SLAB_RECLAIMABLE); if (free <= totalreserve_pages) goto error; else free -= totalreserve_pages; if (!cap_sys_admin) free -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10 ); if (free > pages) return 0 ; goto error; } allowed = vm_commit_limit(); if (!cap_sys_admin) allowed -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10 ); if (mm) { reserve = sysctl_user_reserve_kbytes >> (PAGE_SHIFT - 10 ); allowed -= min_t (long , mm->total_vm / 32 , reserve); } if (percpu_counter_read_positive(&vm_committed_as) < allowed) return 0 ; error: vm_unacct_memory(pages); return -ENOMEM; }unsigned long vm_commit_limit (void ) { unsigned long allowed; if (sysctl_overcommit_kbytes) allowed = sysctl_overcommit_kbytes >> (PAGE_SHIFT - 10 ); else allowed = ((totalram_pages - hugetlb_total_pages()) * sysctl_overcommit_ratio / 100 ); allowed += total_swap_pages; return allowed; }
malloc失败发生过吗 在嵌入式编程里面,很少遇到malloc失败的情况。因为内存不足时,还有缓存回收机制。 当程序的代码段都需要释放掉,用时才从flash上读出来时,系统此时已经很卡了。
所以大多数情况下检查malloc返回值都没有意义。 真正的大内存(至少大于128KB)分配检查才是有意义的:比如升级时。
但上面的描述仅限于linux,可能随着内核得更新实现也有区别。所以编写可移植,长久可靠的还是检查一下比较好。
参考 vm内核参数之虚拟内存申请overcommit malloc原理和内存碎片 知其然知其所以然,/PROC/MEMINFO之谜 [[meminfo详解]]