背景
Linux下访问匿名页发生的神奇“化学反应”,我看完这篇文章感觉对linux的内存分配又多了一点点理解,因此也想推荐给你读下。
原文提出了一个问题:向mmap
系统调用申请的内存”读时”没有导致物理可用内存减少,但是”随后写时”发现物理可用内存减少。
原作者借助”分析内核源码”来剖析问题原因,最终得到结论:第一次读匿名页后,然后写匿名页,先只读方式映射到0页,然后发生写时复制,分配物理页,虚拟页以可读可写的方式映射到此物理页。
我自己总结一下,按照时间顺序来看,当对”可读可写的vma”先读后写时,”对应的页表项”应该是:
-
在第一次读之前不存在 -
在第一次读之后,第一次写之前,映射到固定位置,此时页表属性是只读 -
在第一次写之后,映射到实际分配的物理内存,此时页表属性是可读可写
可能这个结论你听着还是有点绕,并且我想你可能和我一样,也不想一下子就去研究”内核源码”来理解到底是怎么回事。
那怎么能搞清楚原文的问题和结论呢?我发现一个神器crash
,实际操作一番后,我感觉文章结论其实很好理解。下面就和我一起玩一玩crash
,理解mmap
分配的内存属性到底是怎么回事,也验证一下原文的结论是否正确。
crash是一个可以用来调试linux内核崩溃的工具,也可以调试正在运行中的内核。它的安装和使用都很简单,理解本篇文章也不需要你之前接触过这个工具。
过程
准备源码
我们在”读内存之前”、”读内存后写内存前”、”写内存之后”这三个时刻分别调用getchar()
停顿一下,方便我们用crash
工具观察”页表项”
[root@instance-fj5pftdp tmp]# cat e.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
int main(){
char* addr = mmap (NULL, 1024, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS , - 1 , 0);
printf ("addr: %lu pid:%dn", addr, getpid()); // 在读内存之前:页表项不存在
getchar();
printf ("1:%s n", addr); // 在读内存之后:映射了页表,页表项属性为"只读"
getchar();
strcpy(addr, "AAAABBBB"); // 在写内存之后:页表项属性为"可读可写"
printf ("2:%s n", addr);
getchar();
return 0;
}
上面的c程序编译运行后,我们就可以来分析”读内存之前”、”读内存后写内存前”、”写内存之后”这三个时刻的”页表项”是什么了。
使用crash
分析”页表项”的变化
首先简单说一下crash的安装和使用。
如果你是centos系统,就可以用以下命令来安装
yum install crash // 安装crash工具
debuginfo-install kernel-debuginfo-`uname -r` // 安装crash工具需要的内核文件
安装好后,使用下面的命令就可以调试”运行中的内核了”。vmlinux是带调试符号的内核文件。
[root@instance-fj5pftdp ~]# crash /usr/lib/debug/usr/lib/modules/3.10.0-1160.11.1.el7.x86_64/vmlinux /dev/mem
下面来看一下”页表项”的变化:我在程序三次getchar()
时,使用crash
的vtop
命令查看”虚拟地址”对应的页表
crash> set 31880 // 31880是进程号
PID: 31880
COMMAND: "a.out"
TASK: ffff9396f6a7a100 [THREAD_INFO: ffff93983cbe4000]
CPU: 1
STATE: TASK_INTERRUPTIBLE
crash> px 139812061335552 // 十进制转成十六进制。139812061335552是mmap的返回值
$1 = 0x7f2888405000
crash> vtop 0x7f2888405000 // vtop指令用来查看"虚拟地址"对应的"物理地址"
VIRTUAL PHYSICAL
7f2888405000 (not mapped) // 这里可以看出来,此时没有映射到对应的"物理地址"
PGD: 16abd67f0 => 16b7d7067
PUD: 16b7d7510 => 170f95067
PMD: 170f95210 => 160fd5067
PTE: 160fd5028 => 0 // "页表项"为0,其中的P标志位为0,表示"页表项"不存在,没有映射到对应的"物理地址"
...
crash> vtop 0x7f2888405000
VIRTUAL PHYSICAL
7f2888405000 117d65000
....
PTE: 160fd5028 => 8000000117d65225
PAGE: 117d65000
PTE PHYSICAL FLAGS
8000000117d65225 117d65000 (PRESENT|USER|ACCESSED|NX) // 此时"页表项"有了映射关系,说明此时只能读不能写
crash> vtop 0x7f2888405000
...
PTE PHYSICAL FLAGS
80000003b0cf5867 3b0cf5000 (PRESENT|RW|USER|ACCESSED|DIRTY|NX) // RW标志表明此时可以写
...
如上面指令结果中的注释:执行三次vtop指令时,页表项(PTE)中的”FLAGS”都不断变化,读写权限确实像”问题背景”中那样变化。可以看出来,结论是没有问题的
怎么样,crash验证这个结论是不是很简单?
总结
crash很好用,如果你想了解更多crash工具的应用场景,可以参考 解决Linux内核问题实用技巧之 – Crash工具结合/dev/mem任意修改内存
原文始发于微信公众号(leveryd):借助crash工具理解linux系统的内存分配