惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
爱范儿
爱范儿
Blog — PlanetScale
Blog — PlanetScale
The GitHub Blog
The GitHub Blog
C
Check Point Blog
小众软件
小众软件
I
InfoQ
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Engineering at Meta
Engineering at Meta
酷 壳 – CoolShell
酷 壳 – CoolShell
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
腾讯CDC
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
B
Blog
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
A
About on SuperTechFans
D
Docker
博客园 - 司徒正美
博客园_首页
Recent Announcements
Recent Announcements
D
DataBreaches.Net
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
Stack Overflow Blog
Stack Overflow Blog
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
GbyAI
GbyAI
Jina AI
Jina AI
V
V2EX
Vercel News
Vercel News
IT之家
IT之家
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
V
Visual Studio Blog
C
Cybersecurity and Infrastructure Security Agency CISA

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
使用mmap来学习/proc/pid/smaps
重归混沌 · 2024-02-29 · via 重归混沌的BLOG

在很多年前我就断断续续使用过/proc/pid/smaps观测过进程的状态。

当时主要用于观测栈大小和一些文件映射的信息。

当需要特定信息时,会现查文档,因此虽然对于/proc/pid/smaps有模糊的认识,但是对于具体字段的含义认识并不深刻。

纸上得来终觉浅,最终我还是打算花一些时间来写几段代码来印证/proc/pid/smaps中文档的描述。

针对某映射地址范围08048000-080bc000(例如 /bin/bash),/proc/pid/smaps的一个典型条目如下:

08048000-080bc000 r-xp 00000000 03:02 13130      /bin/bash
Size:               1084 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                 892 kB
Pss:                 374 kB
Pss_Dirty:             0 kB
Shared_Clean:        892 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:          892 kB
Anonymous:             0 kB
KSM:                   0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
THPeligible:           0
VmFlags: rd ex mr mw me dw

根据文档,这些字段的含义如下:

  • Size: 映射区域的大小(即虚拟内存大小)。
  • KernelPageSize: 每个VMA分配的页面大小,通常与页表条目中的大小相同。
  • MMUPageSize: MMU使用的页面大小(在大多数情况下与KernelPageSize相同)。
  • Resident Set Size (RSS): 进程驻留内存大小,指的是进程使用的物理内存大小
  • Proportional Set Size (PSS): 是指进程在内存中的页面数量,其中每个页面都按其共享的进程数进行划分。因此,如果一个进程有独占的1000页,和另一个进程共享1000页,它的PSS将为1500。
  • PSS_Dirty: 是由脏页组成的PSS的部分, Pss_Clean不包括在内,但可以通过从Pss中减去Pss_Dirty来计算
  • Shared_Clean: 共享干净页大小,指的是共享内存中未被修改的页大小
  • Shared_Dirty: 共享脏页大小,指的是共享内存中被修改的页大小
  • Private_Clean: 私有干净页大小,指的是私有内存中未被修改的页大小
  • Private_Dirty: 私有脏页大小,指的是私有内存中被修改的页大小
  • Referenced: 当前标记为已引用或访问的内存量
  • Anonymous: 不属于任何文件的内存量。即使与文件关联的映射也可能包含匿名页:当使用 MAP_PRIVATE 并且修改页面时,文件页面将被替换为私有的匿名副本。
  • LazyFree: 使用 madvise(MADV_FREE) 标记的内存量。内存不会立即使用 madvise() 释放。如果内存是干净的,它将在内存压力下释放。请注意,由于当前实现中使用的优化,打印的值可能低于实际值。如果不希望这样,请提交错误报告。
  • KSM: 其中有多少页面是 KSM 页面。请注意,KSM 放置的零页面不包括在内,只包括实际的 KSM 页面。
  • AnonHugePages: 由透明大页面支持的内存数量。
  • ShmemPmdMapped: 由大页面支持的共享 (shmem/tmpfs) 内存数量。
  • Shared_Hugetlb: 和 PrivateHugetlb: 显示历史上原因未计入 "RSS" 或 "PSS" 字段的由 hugetlbfs 页面支持的内存量。它们也不包含在 {Shared,Private}{Clean,Dirty} 字段中。
  • Swap: 显示有多少非匿名内存也使用,但已交换出去。
  • SwapPss: 此映射的比例交换份额。与 "Swap" 不同,这不考虑底层 shmem 对象的交换出页面。
  • Locked: 指示映射是否锁定在内存中。
  • THPeligible: 指示映射是否有资格分配任何当前启用的尺寸的自然对齐 THP 页面。如果是 1,否则为 0。
  • VmFlags: 该成员以两位字母编码方式表示与特定虚拟内存区域关联的内核标志。

文档还给了两点提示:

  • 读取 /proc/pid/maps or /proc/pid/smaps 是存在竞争的。在内存映射修改的同时做部分读取可能会遇到不一致性,但是Linux内核保证了部分输出的正确性。
  • 由于KERNEL配置和内核版本迭代,/proc/pid/smaps的输出以及字段可能会发生变化。各项内存配置可能会是依赖特定版本。

smaps中的字段过多,而且有一些内核和透明大页相关字段。

这里仅测试常归内存分配和非透明大页应用程序对smaps中字段的影响。

现代Linux系统都会使用ASLR技术来随机化进程空间的地址空间布局,这会使得恶意攻击者难以准确预测代码和数据在内存中的位置,从而增加了系统的安全性。

从另一个角度看,ASLR会干扰我们对smaps文件的分析。

让我们先使用sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'来临时禁用掉ASLR


先来编写一段简单分配8M内存的代码,但并不对内存进行赋值,代码如下:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
void main(int argc, char *argv[])
{
        char *x = (char *)mmap(0, 8*1024*1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        printf("%p\n", x);
        for (;;) {
                usleep(1000);
        }
        munmap(x, 1000);
}

需要说明的是,这段代码并没有使用malloc,是因为malloc会重复使用分配过但是又释放的内存。

有可能使用malloc分配出来的内存,是之前代码已经使用过的,这会干扰我们的判断(比如虽然从本次分配开始,我们一直没有访问过这版内存,但是有可能在上一次分配时已经被进程访问过了)。

来看一下/proc/pid/smaps的输出(这里仅列出与上述代码相关的字段,无关字段已经省略):

...
7ffff75d1000-7ffff7dd4000 rw-p 00000000 00:00 0
Size:               8204 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   8 kB
Pss:                   8 kB
Pss_Dirty:             8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
FilePmdMapped:         0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
THPeligible:    1
VmFlags: rd wr mr mw me ac
...

smaps的输出可以看出Size字段正是我们分配的8M虚拟内存。

由于我们并没有对分配出来的内存有任何访问,所以RssPss都应该为0,这里却显示为8K

这可能是因为我这里的测试环境并不是标准的Linux系统,而是WSL

我在标准的Linux系统中,RssPss都会为0KB,这并不会影响我们接下来的分析。


接下来,我们读取一下使用mmap出来的内存, 再来观察smaps的输出:

diff --git a/a.c b/a.c
index 555efe5..bffd055 100644
--- a/a.c
+++ b/a.c
@@ -7,6 +7,7 @@ void main(int argc, char *argv[])
 {
         char *x = (char *)mmap(0, 8*1024*1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
         printf("%p\n", x);
+        printf("%d\n", x[8*1024*1024-1]);
         for (;;) {
                 usleep(1000);
         }
...
7ffff75d1000-7ffff7dd4000 rw-p 00000000 00:00 0
Size:               8204 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   8 kB
Pss:                   8 kB
Pss_Dirty:             8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
...

smaps的输出可以看出,所有的值都没有增加。

这并不符合我们学习的操作系统知识,从MMU的角度来看,只要我们访问了一个不存的的虚拟内存地址,就必然会产生一个minor page fault, 进而造成操作系统为这个虚拟内存地址所在的虚拟页分配物理内存。

带着疑惑,我们去查看一下/proc/pid/pagemap, 这个虚拟文件可以给出某个进程的详细内存映射信息,由于信息量过大,这个文件内容是以二进制提供的。

GitHub随便找了个解析器, 输出结果如下:

...
0x7ffff7dd0000     : pfn 0                soft-dirty 0 file/shared 0 swapped 0 present 1 library
...

/proc/pid/pagemap输出上看,操作系统已经为我们访问的内存分配的物理内存,但是/proc/pid/smaps中却并没有体现。

其根本原因就是,当我们使用mmap分配一个匿名页(MAP_ANONYMOUS)时,操作系统会保证分配出来的内存全部由0填充。

为了优化填充效率,Linux中会维护一个ZeroPage, 这个内存页是由操作系统管理的一个由0填充的只读内存页。

当我们试图对这个ZeroPage进行写操作时,就会触发copy-on-write机制,这时操作系统才会真正为当前进程分配物理内存。

为了验证这一点,让我们将代码修改如下:

diff --git a/a.c b/a.c
index 4f7b123..f1cf401 100644
--- a/a.c
+++ b/a.c
@@ -8,6 +8,7 @@ void main(int argc, char *argv[])
         char *x = (char *)mmap(0, 8*1024*1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        printf("%p\n", x);
        printf("%d\n", x[8*1024*1024-1]);
+        x[8*1024*1024-1] = 1;
         for (;;) {
                 usleep(1000);
         }

smaps的输出如下:

...
7ffff75d1000-7ffff7dd4000 rw-p 00000000 00:00 0
Size:               8204 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  12 kB
Pss:                  12 kB
Pss_Dirty:            12 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        12 kB
Referenced:           12 kB
Anonymous:            12 kB
...

果然RssPss都如预期增加了一个PageSize


到目前为止RssPss总是一样的,让我们来尝试一下两个进程共享内存,看看有什么不同。

我们先使用一个mmapMAP_PRIVATE的方式将一个8m.bin的文件映射到进程的虚拟空间,并启动一个进程, 代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void main(int argc, char *argv[])
{
    int fd;
    char *x;
    struct stat file_info;
    // 打开文件
    fd = open("8m.bin", O_RDWR);
    if (fd == -1) {
        perror("open");
        return ;
    }
    // 获取文件信息
    if (fstat(fd, &file_info) == -1) {
        perror("fstat");
        close(fd);
        return ;
    }
    // 映射文件至内存
    x = mmap(0, file_info.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (x == NULL) {
        perror("mmap");
        close(fd);
        return ;
    }
    printf("%p\n", x);
    for (;;) {
                usleep(1000);
    }
    munmap(x, 1000);
    }

这时smaps的输出如下:

7ffff7400000-7ffff7c00000 rw-p 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   0 kB
Pss:                   0 kB
Pss_Dirty:             0 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:            0 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
FilePmdMapped:         0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
THPeligible:    0
VmFlags: rd wr mr mw me ac

由于我们并没有读写过mmap后的内存,并没有触发minor page fault,因此RssPss都为0KB


我们来读取一下文件的内容,将代码修改如下:

diff --git a/a.c b/a.c
index d356103..b3bbbe6 100644
--- a/a.c
+++ b/a.c
@@ -29,6 +29,7 @@ void main(int argc, char *argv[])
         return ;
     }
     printf("%p\n", x);
+    printf("%d\n", x[8*1024*1024-1]);
     for (;;) {
                 usleep(1000);
     }

这时smaps的输出如下:

...
$ cat /proc/59838/smaps  | grep -A30 7ffff74
7ffff7400000-7ffff7c00000 rw-p 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  64 kB
Pss:                  64 kB
Pss_Dirty:             0 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:        64 kB
Private_Dirty:         0 kB
Referenced:           64 kB
Anonymous:             0 kB
...

Rss的字段可以看出,操作系统已经为这块虚拟内存分配了64kB物理内存。

由于我们只启动了一个进程来打开文件,所以PssRss的值是一样的。

由于我们只有一个进程来独占这块内存并且没有对文件内容进行过修改,所以分配的物理内存页被统计入了Private_Clean,而Private_Dirty为0。

我们来启动2个相同进程,smaps的输出如下:

7ffff7400000-7ffff7c00000 rw-p 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  64 kB
Pss:                  32 kB
Pss_Dirty:             0 kB
Shared_Clean:         64 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:           64 kB
Anonymous:             0 kB

由于我们启动了2个相同的进程并执行了相同的逻辑,因此Pss的值为Rss除以2

需要说明的是,RssPss并不总是存在比例关系

我们将上面的程序称为A, 然后A的基础上做出如下修改,得到程序B:

diff --git a/a.c b/a.c
index b3bbbe6..73315f5 100644
--- a/b.c
+++ b/b.c
@@ -29,7 +29,7 @@ void main(int argc, char *argv[])
         return ;
     }
     printf("%p\n", x);
-    printf("%d\n", x[8*1024*1024-1]);
+    printf("%d\n", x[8*1024*1024-1 - 128*1024]);
     for (;;) {
                 usleep(1000);
     }

同时运行程序AB, 再来看进程Asmaps文件内容:

...
7ffff7400000-7ffff7c00000 rw-p 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  64 kB
Pss:                  64 kB
Pss_Dirty:             0 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:        64 kB
Private_Dirty:         0 kB
Referenced:           64 kB
Anonymous:             0 kB
...

可以看到虽然进程AB共享了同一个文件,但是此时Pss竟然等于Rss

这是因为Pss的统计粒度是Page

比如进程A被分配了3个物理页A,B,C。这三个物理页的共享进程数分别为A:1,B:2,C:3, 那么Pss的最终值就是PageSize*(1/1 +1/2+1/3)。

而当前进程AB并没有共享任何内存页。


让我们为程序A增加文件代码,并启动两个相同的程序,修改如下:

diff --git a/a.c b/a.c
index b3bbbe6..34c8a42 100644
--- a/a.c
+++ b/a.c
@@ -30,6 +30,7 @@ void main(int argc, char *argv[])
     }
     printf("%p\n", x);
     printf("%d\n", x[8*1024*1024-1]);
+    x[8*1024*1024-1] = 1;
     for (;;) {
                 usleep(1000);
     }

smaps的输出如下:

...
7ffff7400000-7ffff7c00000 rw-p 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  64 kB
Pss:                  34 kB
Pss_Dirty:             4 kB
Shared_Clean:         60 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         4 kB
Referenced:           64 kB
Anonymous:             4 kB
...

这次的输出是目前为止信息量最大的一次。

由于我们对内存的写入,导致Anonymous的内存增加了4kB,这是由于MAP_PRIVATE参数导致的。

当我们使用MAP_PRIVATE来打开文件时,我们所有对文件的修改都会触发copy-on-write, 为当前进程分配独占物理内存。

可想而知,以MAP_PRIVATE打开的文件,也不可能真的被修改。

下面让我们来手工模拟如何计算出上述参数的值。

Rss的值代表操作系统为我们的虚拟内存映射了64kB物理内存,Anonymous的值操作系统为我们的虚拟内存映射了4kB独占物理内存。

也就是说当前两个进程的共享内存一共为60kB,字段Shared_Clean也可以印证这一点。

由于60kB内存是由两个进程共享, 并且还有4kB的独占内存,因此Pss等于60/2+4=34kB

由于修改过的脏页是私有页(自己独占), 因此Private_Dirty的值为4kB, Pss_Dirty的值为4kB/1=4kB


让我们将MAP_PRIVATE换为MAP_SHARED再来启动两个相同进程,看看有什么不同。

diff --git a/a.c b/a.c
index 34c8a42..1493c60 100644
--- a/a.c
+++ b/a.c
@@ -22,7 +22,7 @@ void main(int argc, char *argv[])
         return ;
     }
     // 映射文件至内存
-    x = mmap(0, file_info.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+    x = mmap(0, file_info.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
     if (x == NULL) {
         perror("mmap");
         close(fd);

smaps的输出如下:

....
7ffff7400000-7ffff7c00000 rw-s 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  60 kB
Pss:                  30 kB
Pss_Dirty:             2 kB
Shared_Clean:         56 kB
Shared_Dirty:          4 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:           60 kB
Anonymous:             0 kB
...

这次操作系统已经不再为我们分配匿名内存了,整个文件映射内存全部是两进程共享,就连Pss_Dirty都变为了2kB

为了难证Pss_Dirty的值是怎么计算的,我们不再启动两个相同的进程,而是分别让两个进程写入两个不同的内存位置。

在上面代码的基础上,做出如下修改:

diff --git a/a.c b/a.c
index 1493c60..c8ebe28 100644
--- a/a.c
+++ b/a.c
@@ -29,8 +29,8 @@ void main(int argc, char *argv[])
         return ;
     }
     printf("%p\n", x);
-    printf("%d\n", x[8*1024*1024-1]);
-    x[8*1024*1024-1] = 1;
+    printf("%d\n", x[8*1024*1024-1 - 128*1024]);
+    x[8*1024*1024-1 - 128*1024] = 1;
     for (;;) {
                 usleep(1000);
     }

smaps中的输出如下:

7ffff7400000-7ffff7c00000 rw-s 00000000 08:20 65306                      /home/dev/foo/8m.bin
Size:               8192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  60 kB
Pss:                  60 kB
Pss_Dirty:             4 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:        56 kB
Private_Dirty:         4 kB
Referenced:           60 kB
Anonymous:             0 kB

可以看到Pss_Dirty的值和和Pss的值没有直接比例关系,但是其计算方式很像。

Pss_Dirty只统计当前的共享脏页, 其中每个页面都按其共享的进程数按比例时计算。

需要说明的是,这种共享并不是说别的进程也修改了这个内存页,而是指这块内存被映射到了几个进程中去。