续上篇:

  • [译] C程序员该知道的内存知识 (1)

  • [译] C程序员该知道的内存知识 (2)

  • [译] C程序员该知道的内存知识 (3)

这是本系列的第4篇,也是最后一篇,含泪填完这个坑不容易,感谢阅读~

这个系列太干了,阅读量一篇比一篇少,但我仍然认为这个系列非常有价值,在翻译的过程中我也借机进行系统性的梳理、并学习了很多新知识,收获满满。希望你也能有收获(但肯定没我多)。

那,开始吧。


理解内存消耗

工具箱:

  • vmtouch[2] - portable virtual memory toucher

(译注:vmtouch这个工具用来诊断和控制系统对文件系统的缓存,例如查看某个文件被缓存了多少页,清空某个文件的缓存页,或将某个文件的页面锁定在内存中;基于这些功能可以实现很多有意思的应用;详情参考该工具的文档。)

然而共享内存的概念导致传统方案 —— 测量对内存的占用 —— 变得无效了,因为没有一个公正的方法可以测量你进程的独占空间。这会引起困惑甚至恐惧,可能是两方面的:

用上了基于 mmap 的I/O操作后,我们的应用现在几乎不占用内存.

— CorporateGuy

求救!我这写入共享内存的进程有严重的内存泄漏!!!

— HeavyLifter666

页面有两种状态:清洁(clean)页和脏(dirty)页。区别是,脏页在被回收之前需要被写回到持久存储中(译注:写回文件实际存放的地方)。MADV_FREE 这个建议通过将脏标志位清零这种方式来实现更轻量的内存释放,而不是修改整个页表项(译注:page table entry,常缩写为PTE,记录页面的物理页号及若干标志位,如能否读写、是否脏页、是否在内存中等)。此外,每一页都可能是私有的或共享的,这正是导致困惑的源头。

前面引用的两个都是(部分)真实的,取决于视角。在系统缓冲区的页面需要计入进程的内存消耗里吗?如果进程修改了缓冲区里那些映射文件的那些页面呢?在这混乱中可以整出点有用的东西么?

假设有一个进程,索伦之眼(the_eye)会写入对魔都(mordor)的共享映射(译注:指环王的梗)。写入共享内存不计入 RSS(resident set size,常驻内存集)的,对吧?

$ ps -p $$ -o pid,rssPID  RSS
17906  1574944 # <-- 什么鬼? 占用1.5GB?

(译注:$$ 是 bash 变量,保存了在执行当前script的shell的PID;这里应该是用来指代the_eye的PID)

呃,让我们回到小黑板。

PSS(Proportional Set Size)

PSS(译注:Proportional 意思是 “比例的”) 计入了私有映射,以及按比例计入共享映射。这是我们能得到的最合理的内存计算方式了。关于“比例”,是指将共享内存除以共享它的进程数量。举个例子,有个应用需要读写某个共享内存映射:

$ cat /proc/$$/maps
00400000-00410000         r-xp 0000 08:03 1442958 /tmp/the_eye
00bda000-01a3a000         rw-p 0000 00:00 0       [heap]
7efd09d68000-7f0509d68000 rw-s 0000 08:03 4065561 /tmp/mordor.map
7f0509f69000-7f050a108000 r-xp 0000 08:03 2490410 libc-2.19.so
7fffdc9df000-7fffdca00000 rw-p 0000 00:00 0       [stack]
... 以下截断 ...

(译注:cat /proc/$PID/maps 是从内核中读取进程的所有内存映射)

这是个被简化并截断了的映射,第一列是地址范围,第二列是权限信息,其中 r 表示可读, w 表示可写,x 表示可执行 —— 这都是老知识点了 —— 然后 s 表示共享,p 表示私有。然后是映射文件的偏移量,设备号(OS分配的),inode号(文件系统上的),以及最后是文件的路径名。具体这个文档[3](译注:kernel.org 对 /proc 文件系统的说明文档),超级详细。

我得承认我删掉了一些输出中一些不太有意思的信息。如果你对被私有映射的库感兴趣的话可以读一下 FAQ-为什么“strict overcommit”是个蠢主意[4](译注:根据这个FAQ,strict overcommit应该是指允许overcommmit、但要为申请的每一个虚拟页分配一个真实页,不管是用物理页还是swap,确实听起来很蠢……)。不过这里我们感兴趣的是魔都(mordor)这个映射:

$ grep -A12 mordor.map /proc/$$/smaps
Size:           33554432 kB
Rss:             1557632 kB
Pss:             1557632 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:   1557632 kB
Private_Dirty:         0 kB
Referenced:      1557632 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr sh mr mw me ms sd

译注:这个文件大小 32GB,已加载了 1521MB 到内存中,因为只有这一个进程映射了它,所以在这个进程的PSS中占比是100%,也是 1521MB。

在共享映射里的私有页面 —— 搞得我像巫师一样?在Linux上,即使共享内存也会被认为是私有的,除非它真的被共享了(译注:不止一个进程创建共享映射)。让我们看看它是否在系统缓冲区里:

# 好像开头的那一块在内存中...
$ vmtouch -m 64G -v mordor.map
[OOo                    ] 389440/8388608Files: 1Directories: 0Resident Pages: 389440/8388608  1G/32G  4.64%Elapsed: 0.27624 seconds# 将它全都载入到Cache!
$ cat mordor.map > /dev/null
$ vmtouch -m 64G -v mordor.map
[ooooooooo      oooOOOOO] 2919606/8388608Files: 1Directories: 0Resident Pages: 2919606/8388608  11G/32G  34.8%Elapsed: 0.59845 seconds

译注:

  1. “-m 64G” 表示允许 vmtouch 将小于 64G 的文件加载到内存中,应当是用于需要加载一个目录下的文件、但排除其中过大的文件,似乎不适用于这里;至少忽略这个参数不影响阅读

  2. o 表示这一块部分被加载,O 表示全部被加载。因为物理内存有限,虽然全量读取了文件,但只有部分内容被缓存

嗬,只是简单地读取一个文件就会把它缓存起来?先不管这,我们的进程呢?

$ ps -p $$ -o pid,rssPID   RSS
17906 286584 # <-- 等了足足一分钟

常见的误解是,映射文件会消耗内存,而通过文件API读取不会。实际上,无论哪一种方式,包含文件内容的页面都会被放进系统缓冲区。但还有个小的区别是,使用mmap的方式需要在进程的页表中创建对应的页表项(PTE),而这些包含文件内容的页面是可以被共享的。有趣的是,我们这个进程的RSS缩小了,因为系统 需要 进程的页面了(译注:因为 mordor 太大,可用物理内存页不够,系统将 the_eye 的部分页面swap了;所以前述命令才会需要等一分钟,因为涉及到磁盘IO)。

有时我们的所有想法都是错的

映射文件的内存总是可被回收的,区别只在于该页是否脏页 —— 脏页在回收前需要被清理(译注:写回底层存储)。所以当你在 top 命令发现有一个进程占用了大量内存时是否需要恐慌?当这个进程有很多匿名的脏页的时候才需要恐慌——因为这些页面无法被回收。如果你发现有个匿名映射段在增长,你可能就有麻烦了(而且是双倍的麻烦)。但是不要盲目相信 RSS 甚至 PSS 。

另一个常见错误是认为进程的虚拟内存和实际消耗内存之间总有某种关系,甚至认为所有内存映射都一样。任何可回收的内存,实际上都可以认为是空闲的。简而言之,它不会导致你下次内存分配失败,但可能会增加分配的延迟 —— 这点我会解释:

内存管理器需要花很大功夫来决定哪些东西需要保存在物理内存里。它可能会决定将进程内存中的一部分调到swap,以便给系统缓存腾出空间,因此该进程下次访问这一块时需要再将这些页面调回到物理内存中。幸运的是这通常是可以配置的。例如,Linux 有一个叫做 swappiness[5] 的选项,用来指导内核何时开始将匿名映射的内存页调出到swap。当它取值为 0 是表示“直到绝对绝对有必要的时候”(译注:取值[0, 100],值越低,系统越倾向于先清理系统缓冲区的页面)。

终章,一劳永逸地

如果你看到这里,向你致敬!我在工作之余写的这篇文章,希望能用一种更方便的方式,不仅能解释这些说过上千遍的概念,还能帮我整理这些思维,以及帮助其他人。我花了比预期更长的时间。远超预期。

我对文章的作者们只有无尽的敬意,因为写作真是个冗长乏味、令人头秃的过程,需要永无止境的修改和重写。Jeff Atwood(译注:stack overflow的创始人) 曾说过,最好的学编程书籍是教你盖房子的那本。我不记得在哪儿了,所以无法引用它。我只能说,第二好的是教你写作的那本。说到底,编程本质上就是写故事,简明扼要。

EDIT:我修正了关于 alloca() 和 将 sizeof(char) 误写为 sizeof(char*) 的错误,多亏了 immibis 和 BonzaiThePenguin。感谢 sWvich 指出在 slab + sizeof(struct slab) 里漏了的类型转换。显然我应该用静态分析跑一下这篇文章,但并没有 —— 涨经验了。

开放问题 —— 有没有比 Markdown 代码块更好的实现?我希望能展示带注释的摘录,并且能下载整个代码块。

写于 2015 年 2 月 20 日。



读到这里都是真爱,喜欢的话请点个“在看”,感谢~

照例再贴下之前推送的几篇文章:

  • 《踩坑记:go服务内存暴涨》

  • 《TCP:学得越多越不懂》

  • 《TCP#2: 西厢记和西厢计划》

  • 《UTF-8:一些好像没什么用的冷知识》

  • 《关于RSA的一些趣事》

  • 《程序员面试指北:面试官视角》


参考链接:

[1] What a C programmer should know about memory

https://marek.vavrusa.com/memory/

[2] vmtouch - the Virtual Memory Toucher

https://hoytech.com/vmtouch/

[3] kernel.org - THE /proc FILESYSTEM

https://www.kernel.org/doc/Documentation/filesystems/proc.txt

[4] wikipedia - Paging - swapinness

https://en.wikipedia.org/wiki/Swappiness

[译] C程序员该知道的内存知识 (4)相关推荐

  1. C程序员必须知道的内存知识【英】

    C程序员必须知道的内存知识[英] 时间 2015-03-08 14:16:11 极客头条原文  http://marek.vavrusa.com/c/memory/2015/02/20/memory/ ...

  2. 程序员的灯下黑:重知识轻技术(转)

    为什么80%的码农都做不了架构师?>>>    程序员的灯下黑:重知识轻技术(转) 电视<雍正王朝>讲了这么一个故事:大将军年羹尧奉命到青海平叛,清军因路途遥远,军耗巨大 ...

  3. 后端程序员必备的 Linux 基础知识

    后端程序员必备的 Linux 基础知识 原文来自github stars>63k的项目JavaGuide,欢迎小伙伴去支持原作者 一 从认识操作系统开始 1.1 操作系统简介 1.2 操作系统简 ...

  4. 成为一名厉害的程序员,需要哪些必备知识

    程序员在入职时,大部分互联网公司都会进行基础知识的考察,基础知识的重要性不言而喻.计算机基础知识对程序员来说很重要.计算机核心基础知识方面,算法.数据结构.组成原理.网络等涉及到的基础知识一定要彻底掌 ...

  5. 一不小心就触碰红线...程序员必须知道的法律知识有哪些?

    很多程序员空有一身本领,却不注重法律意识的培养,于是造成了很多不可估量的后果. 话不多说,直接先上实例: 实例一 10 月 7 日,丰田汽车发现,296019 名客户的电子邮件地址和客户编号可能已被泄 ...

  6. Java数据结构与算法面试题,首发Java程序员人手必备的进阶知识体系,(1)

    在市场上很少能够看到一套不错的学习笔记,小编也是花了挺久的时间总结了这份**<Java程序员人手必备的进阶知识体系>**,帮助大家系统化高效的进阶学习,而不是零散低效的阅读. 2020全新 ...

  7. 程序员应该学习掌握哪些知识和技能?

    现在做为一名程序员,压力越来越大,各种开发工具越来越庞大.不断推陈出新,各种开发设计工程理念缤纷精彩.需要融入平常的开发当中,还有很多新的知识点在不断开拓中,相比以前,做为一名程序员尤其是合格程序员的 ...

  8. 程序员基本功04JAVA的内存回收

    读书笔记自己看的(O_O) 1.Java引用的功能和意义 程序员需要通过关键字new创建Java对象,即可视作为Java对象申请内存空间,JVM会在堆内存中为每个对象分配空间:当一个Java对象失去引 ...

  9. 【译】程序员都有的这 10 个坏习惯!

    一.休息不够 二.拒绝寻求帮助 三.停止学习 四.混乱的代码 五.工作和生活的不平衡 六.糟糕的办公室政治 七.不能从错误中吸取教训 八.太早放弃 九.做一个无所不知的人 十.不接受建设性批评 编者按 ...

最新文章

  1. Squid故障与解决方法汇总
  2. 在java下使用log4j2记录日志
  3. 有趣分享:国内产业图谱
  4. 【c++算法刷题笔记】——洛谷1
  5. 完整的Web应用程序Tomcat JSF Primefaces JPA Hibernate –第2部分
  6. 本行没有输入值结余隐藏_仓库库存管理系统,内含逻辑公式,自动结余库存!操作简单易上手...
  7. 10月24日学习内容整理:自增约束条件,外键,修改表,复制表
  8. JSTL 核心标签库 使用(C标签)
  9. 【博弈 —— NIM模型】
  10. Python简易图片批量压缩程序
  11. 软件测试使用linux做什么?
  12. Python学习 之 tenacity重试模块
  13. alert的确定和取消
  14. 浅谈Docker的安全性支持(上篇)
  15. 网页嵌入Twitter的推文
  16. 搭建网校系统,既能解决疫情下教育培训难题,也解决了企业办公问题
  17. market Dwon
  18. 五月该种下一株康乃馨
  19. linux uda1341驱动
  20. python爬虫下载小说_用PYTHON爬虫简单爬取网络小说

热门文章

  1. 树莓派——9、IO操控代码编程
  2. 户外顶级装备---安全套和卫生巾!(转载)
  3. Mybatis-Plus代码生成器CodeGenerator
  4. 学计算机但物理数学不太好,高考志愿指南:这4个专业难学到怀疑人生,数学不好的学生慎选...
  5. 离散数学 第二章命题逻辑
  6. InTouch与Kepware OPC server通讯配置
  7. 网络加速_神奇黑科技出现:双WiFi网络加速技术
  8. 手机卡不卡与处理器关系大,还是和内存关系大?
  9. 支付宝pc扫码支付简单实现
  10. 英语 | Day 35、36 x 句句真研每日一句(从句)