系列更新:C程序员应该知道的内存知识(一)C程序员应该知道的内存知识(二)C程序员应该知道的内存知识(三)内存C程序员应该知道的知识(四)这是本系列的第四篇,也是最后一篇。用泪水填满这个坑,实属不易。感谢阅读~这个系列太干了,阅读量一篇还不到一篇,不过我还是觉得这个系列很有价值。在翻译过程中,我也借此机会系统地梳理和学习了很多新知识,收获颇丰。希望你也能有所收获(但肯定不如我多)。好吧,让我们开始吧。了解内存消耗工具箱:vmtouch[2]-便携式虚拟内存触摸页,或内存中文件的锁定页;基于这些功能可以实现很多有趣的应用;有关详细信息,请参阅该工具的文档。)但是,共享内存的概念使传统的解决方案-测量内存使用情况-变得改变。无效,因为没有公平的方法来衡量您进程的独占空间。这可能会导致混淆甚至恐惧,这可能是双重的:在使用基于mmap的I/O操作后,我们的应用程序现在几乎不使用内存。-CorporateGuy帮助!我写共享内存的进程内存泄漏严重!!!—HeavyLifter666页面有两种状态:干净(clean)页面和脏(dirty)页面。不同的是,脏页在被回收之前需要写回持久化存储。MADV_FREE这个提议通过清除dirtyflag位来实现更轻量的内存释放,而不是修改整个页表项(译注:页表项,常简写为PTE,记录了该页的物理页号和几个标志位,比如read并写入,页面是否脏,是否在内存中等)。此外,每个页面可能是私有的或共享的,这就是混淆的来源。前面的两个引述(部分)是正确的,具体取决于观点。系统缓冲区中的页面是否计入进程的内存消耗?如果进程修改了缓冲区中那些映射文件的那些页面怎么办?能从这个烂摊子中找出有用的东西吗?假设有一个进程,索伦之眼(the_eye)写入共享的魔多地图。写入共享内存不计入RSS(residentsetsize,常驻内存集),对吧?$ps-p$$-opid,rssPIDRSS179061574944#<--到底是什么?占用1.5GB?(注解:$$是一个bash变量,保存的是执行当前脚本的shell的PID;这里应该指的是the_eye的PID)好了,我们回到小黑板。PSS(ProportionalSetSize)PSS(译注:Proportional的意思是“成比例”)在私有映射中占,与共享映射成比例。这是我们可以获得的最合理的内存计算。对于“比例”,您的意思是将共享内存除以共享它的进程数。例如,应用程序需要读写共享内存映射:$cat/proc/$$/maps00400000-00410000r-xp000008:031442958/tmp/the_eye00bda000-01a3a000rw-p000000:000[heap]7efd09d68000-7f0509d68000rw-s000008:034065561/tmp/mordor.map7f0509f69000-7f050a108000r-xp000008:032490410libc-2.19.so7fffdc9df000-7fffdca00000rw-p000000:000[stack]...The以下truncates...信息,其中r表示可读,w表示可写,x表示可执行——这是一个老知识点了——然后s表示共享,p表示私有。然后是映射文件的偏移量、设备号(由操作系统分配)、索引节点号(在文件系统上),最后是文件的路径名。详见这篇文档[3](译注:kernel.org关于/proc文件系统的文档),超级详细。我不得不承认我在输出中删除了一些不太有趣的信息。如果你对私有映射的库感兴趣,你可以阅读FAQ-Why"strictovercommit"isastupididea[4]Allocatingarealpage,whetherusingphysicalpagesorswap,doessoundstupid...)。但是这里我们对魔多的地图感兴趣:$grep-A12mordor.map/proc/$$/smapsSize:33554432kBRss:1557632kBPss:1557632kBShared_Clean:0kBShared_Dirty:0kBPrivate_Clean:1557632kBPrivate0_Dirty7onym:6BAnonym:2k05kBAnonHugePages:0kBSwap:0kBKernelPageSize:4kBMMUPageSize:4kBLocked:0kBVmFlags:rdwrshmrmwmemssd是mapped的,所以占了这个进程PSS的100%,也就是1521MB。共享地图中的私人页面-让我看起来像个巫师?在Linux上,即使共享内存也被认为是私有的,除非它实际上是共享的。让我们看看它是否在系统缓冲区中:#似乎开头的块在内存中...$vmtouch-m64G-vmordor.map[OOo]389440/8388608Files:1Directories:0ResidentPages:389440/83886081G/32G4.64%已用:0.27624秒#将其全部加载到缓存中!$catmordor.map>/dev/null$vmtouch-m64G-vmordor.map[oooooooooooooOOOOO]2919606/8388608文件:1目录:0ResidentPages:2919606/838860811G/32G34.8%Elapsed:0.59845secondsAnnotation:“-m64G”表示允许vmtouch加载小于64G的文件到内存中。它应该用于加载目录中的文件,但是排除太大的文件在这里似乎不适用;至少忽略这个参数不影响读取o表示这部分加载,O表示全部加载。由于物理内存有限,虽然读取了全量的文件,但只缓存了部分内容。咦,光是读一个文件就缓存了?不管怎样,我们的流程呢?$ps-p$$-opid,rssPIDRSS17906286584#<--等了整整一分钟一个常见的误解是映射文件会消耗内存,而通过文件API读取文件则不会。事实上,无论哪种方式,包含文件内容的页面都会被放入系统缓冲区。但是有个小区别就是使用mmap需要在进程的页表中创建对应的页表项(PTE),这些包含文件内容的页是可以共享的。有趣的是,我们进程的RSS缩小了,因为系统_需要_进程的页面。稍等一下,因为涉及磁盘IO)。有时我们所有的想法都是错误的。映射文件的内存始终是可回收的,唯一的区别是页是否脏——脏页需要在回收前清理。那么当你在top命令中发现一个进程占用大量内存时,是否需要恐慌呢?唯一会恐慌的时候是当进程有很多匿名脏页时——因为这些页无法回收。如果您发现匿名映射段在增长,您可能有麻烦了(而且是双重麻烦)。但不要盲目相信RSS甚至PSS。另一个常见的错误是认为进程的虚拟内存和它消耗的实际内存之间总是存在某种关系,甚至认为所有的内存映射都是相同的。任何可回收的内存实际上都可以认为是免费的。简而言之,它不会导致您的下一次内存分配失败,但_可能_会增加分配的延迟——我将对此进行解释:内存管理器需要花费大量精力来决定哪些内容需要保留在物理内存内部。它可能会决定交换部分进程内存以为系统缓存腾出空间,因此下次进程访问该块时,它需要将这些页面传输回物理内存。幸运的是,这通常是可配置的。例如,Linux有一个名为swappiness[5]的选项,它指示内核何时开始调出匿名映射的内存页面以进行交换。当它取值为0时,表示“直到绝对绝对必要”最后一章,一劳永逸如果你看到这个,我向你致敬!闲暇之余写下这篇文章,希望能用一种更便捷的方式,既能解释这些说了千遍的概念,又能帮我整理这些思路,帮助别人。我花了比预期更长的时间。远远超出预期。我对文章的作者只有无限的敬意,因为写作是一个如此乏味、无休止的修改和重写的秃头过程。JeffAtwood(译注:stackoverflow创始人)曾经说过,学习编程最好的书就是教你盖房子的那本。我不记得在哪里所以不能引用它。我只能说,下一个最好的书是教你写作的书。归根结底,编程本质上就是讲故事,简明扼要。编辑:感谢immibis和BonzaiThePenguin,我修复了关于alloca()和将sizeof(char)误写为sizeof(char*)的错误。感谢sWvich指出slab+sizeof(structslab)中缺失的转换。显然我应该用静态分析运行这篇文章,但我没有——我获得了经验。开放性问题——是否有比Markdown代码块更好的实现方式?我希望能够显示带注释的摘录并能够下载整个代码块。写于2015年2月20日,读到这里是真爱,喜欢请点个赞,谢谢~照例发上几篇:《踩坑记:go服务内存暴涨》《TCP:学得越多越不懂》《TCP#2: 西厢记和西厢计划》《UTF-8:一些好像没什么用的冷知识》《关于RSA的一些趣事》《程序员面试指北:面试官视角》欢迎关注参考链接:[1]C程序员应该了解的有关内存的知识[2]vmtouch-虚拟内存触摸器[3]kernel.org-/proc文件系统[4]常见问题解答(为什么“严格过度使用”是一个愚蠢的想法?)[5]维基百科-分页-交换性
