通过前两篇文章(系统调用mmap的内核实现分析,Linux下Page Fault的处理流程)我们可以知道,虚拟内存是在我们向操作系统申请内存(比如malloc或mmap)时分配的,而物理内存是在我们使用(比如读或写)虚拟内存时通过page fault分配的。
不管是虚拟内存的分配还是物理内存的分配,都是以page为单位的,page的默认大小为4096。
之前的两篇文章理论和代码部分比较多,所以,现在我们用示例的形式,展示下虚拟内存和物理内存的分配。
先看下面的这个小程序:
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char *p;
printf("1\n");
sleep(10);
p = mmap(NULL, 4096 + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) {
perror("mmap");
return -1;
}
printf("2: %p\n", p);
sleep(10);
*p = 1;
printf("3: %p\n", p);
sleep(10);
p += 4096;
*p = 2;
printf("4: %p\n", p);
sleep(10);
return 0;
}
编译并执行,结果为:
$ gcc main.c && ./a.out
1
2: 0x7efea597d000
3: 0x7efea597d000
4: 0x7efea597e000
在输出1、2、3、4时,分别用pmap命令查看当时的内存分配情况,对应到下面的四次输出:
$ pmap -x $(pgrep a.out)
7018: ./a.out
Address Kbytes RSS Dirty Mode Mapping
0000564744227000 4 4 0 r---- a.out
0000564744228000 4 4 0 r-x-- a.out
0000564744229000 4 4 0 r---- a.out
000056474422a000 4 4 4 r---- a.out
000056474422b000 4 4 4 rw--- a.out
0000564745373000 132 4 4 rw--- [ anon ]
00007efea579e000 148 140 0 r---- libc-2.29.so
00007efea57c3000 1320 748 0 r-x-- libc-2.29.so
00007efea590d000 292 64 0 r---- libc-2.29.so
00007efea5956000 4 0 0 ----- libc-2.29.so
00007efea5957000 12 12 12 r---- libc-2.29.so
00007efea595a000 12 12 12 rw--- libc-2.29.so
00007efea595d000 24 16 16 rw--- [ anon ]
00007efea597f000 8 8 0 r---- ld-2.29.so
00007efea5981000 124 124 0 r-x-- ld-2.29.so
00007efea59a0000 32 32 0 r---- ld-2.29.so
00007efea59a9000 4 4 4 r---- ld-2.29.so
00007efea59aa000 4 4 4 rw--- ld-2.29.so
00007efea59ab000 4 4 4 rw--- [ anon ]
00007ffff006d000 132 12 12 rw--- [ stack ]
00007ffff00ee000 12 0 0 r---- [ anon ]
00007ffff00f1000 4 4 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 2288 1208 76
$ pmap -x $(pgrep a.out)
7018: ./a.out
Address Kbytes RSS Dirty Mode Mapping
0000564744227000 4 4 0 r---- a.out
0000564744228000 4 4 0 r-x-- a.out
0000564744229000 4 4 0 r---- a.out
000056474422a000 4 4 4 r---- a.out
000056474422b000 4 4 4 rw--- a.out
0000564745373000 132 4 4 rw--- [ anon ]
00007efea579e000 148 140 0 r---- libc-2.29.so
00007efea57c3000 1320 940 0 r-x-- libc-2.29.so
00007efea590d000 292 128 0 r---- libc-2.29.so
00007efea5956000 4 0 0 ----- libc-2.29.so
00007efea5957000 12 12 12 r---- libc-2.29.so
00007efea595a000 12 12 12 rw--- libc-2.29.so
00007efea595d000 24 16 16 rw--- [ anon ]
00007efea597d000 8 0 0 rw--- [ anon ]
00007efea597f000 8 8 0 r---- ld-2.29.so
00007efea5981000 124 124 0 r-x-- ld-2.29.so
00007efea59a0000 32 32 0 r---- ld-2.29.so
00007efea59a9000 4 4 4 r---- ld-2.29.so
00007efea59aa000 4 4 4 rw--- ld-2.29.so
00007efea59ab000 4 4 4 rw--- [ anon ]
00007ffff006d000 132 12 12 rw--- [ stack ]
00007ffff00ee000 12 0 0 r---- [ anon ]
00007ffff00f1000 4 4 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 2296 1464 76
$ pmap -x $(pgrep a.out)
7018: ./a.out
Address Kbytes RSS Dirty Mode Mapping
0000564744227000 4 4 0 r---- a.out
0000564744228000 4 4 0 r-x-- a.out
0000564744229000 4 4 0 r---- a.out
000056474422a000 4 4 4 r---- a.out
000056474422b000 4 4 4 rw--- a.out
0000564745373000 132 4 4 rw--- [ anon ]
00007efea579e000 148 140 0 r---- libc-2.29.so
00007efea57c3000 1320 940 0 r-x-- libc-2.29.so
00007efea590d000 292 128 0 r---- libc-2.29.so
00007efea5956000 4 0 0 ----- libc-2.29.so
00007efea5957000 12 12 12 r---- libc-2.29.so
00007efea595a000 12 12 12 rw--- libc-2.29.so
00007efea595d000 24 16 16 rw--- [ anon ]
00007efea597d000 8 4 4 rw--- [ anon ]
00007efea597f000 8 8 0 r---- ld-2.29.so
00007efea5981000 124 124 0 r-x-- ld-2.29.so
00007efea59a0000 32 32 0 r---- ld-2.29.so
00007efea59a9000 4 4 4 r---- ld-2.29.so
00007efea59aa000 4 4 4 rw--- ld-2.29.so
00007efea59ab000 4 4 4 rw--- [ anon ]
00007ffff006d000 132 12 12 rw--- [ stack ]
00007ffff00ee000 12 0 0 r---- [ anon ]
00007ffff00f1000 4 4 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 2296 1468 80
$ pmap -x $(pgrep a.out)
7018: ./a.out
Address Kbytes RSS Dirty Mode Mapping
0000564744227000 4 4 0 r---- a.out
0000564744228000 4 4 0 r-x-- a.out
0000564744229000 4 4 0 r---- a.out
000056474422a000 4 4 4 r---- a.out
000056474422b000 4 4 4 rw--- a.out
0000564745373000 132 4 4 rw--- [ anon ]
00007efea579e000 148 140 0 r---- libc-2.29.so
00007efea57c3000 1320 940 0 r-x-- libc-2.29.so
00007efea590d000 292 128 0 r---- libc-2.29.so
00007efea5956000 4 0 0 ----- libc-2.29.so
00007efea5957000 12 12 12 r---- libc-2.29.so
00007efea595a000 12 12 12 rw--- libc-2.29.so
00007efea595d000 24 16 16 rw--- [ anon ]
00007efea597d000 8 8 8 rw--- [ anon ]
00007efea597f000 8 8 0 r---- ld-2.29.so
00007efea5981000 124 124 0 r-x-- ld-2.29.so
00007efea59a0000 32 32 0 r---- ld-2.29.so
00007efea59a9000 4 4 4 r---- ld-2.29.so
00007efea59aa000 4 4 4 rw--- ld-2.29.so
00007efea59ab000 4 4 4 rw--- [ anon ]
00007ffff006d000 132 12 12 rw--- [ stack ]
00007ffff00ee000 12 0 0 r---- [ anon ]
00007ffff00f1000 4 4 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 2296 1472 84
当程序输出1时,此时第一次pmap命令的输出就是该程序的原始内存情况。
当程序输出2时,此时已经执行过mmap操作,对应的第二次pmap命令的输出比第一次多了一个[ anon ]区域(第45行),该区域的起始地址也正好和程序输出2时输出的地址相同,说明该区域就是我们用mmap分配的内存区域。
该区域的虚拟内存大小是8k,因为我们在调用mmap时指定的内存大小是4097,page对齐后正好是8k。
该区域的物理内存大小是0,因为我们还没使用过该区域。
当程序输出3时,此时我们已经对p对应的地址空间赋值,也就是使用了虚拟内存的第一个page,对应看pmap命令的第三次输出,此时的[ anon ]区域(第74行)显示物理内存已使用4k。
当程序输出4时,此时我们已经对虚拟内存的第二个page进行了写操作,对应看pmap命令的第四次输出,此时的[ anon ]区域(第103行)显示已使用的物理内存是8k。
通过上面的示例程序和pmap命令,我们可以清楚的看到,进程的虚拟内存和物理内存是何时分配的。
那如何确定物理内存的分配是page fault触发的呢?
这里我们介绍一个非常强大的工具bpftrace,它使用了内核的ebpf特性,使得我们可以动态监控内核里的方法调用。
工具的详细描述请看下面的地址:
https://github.com/iovisor/bpftrace
下面结合上面的例子,展示下该工具的使用:
sudo bpftrace -e 'kprobe:handle_mm_fault /comm == "a.out"/ { printf("addr: 0x%lx\n", arg1); }'
该命令的意思是,监控内核的handle_mm_fault方法,如果调用该方法的程序是a.out,则输出该方法第二个参数的值。
由上一篇文章我们可以知道,handle_mm_fault方法就是被do_page_fault调用的方法,方法内容大致如下:
// mm/memory.c
vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
unsigned int flags)
{
...
}
由此可见,我们输出的第二个参数,就是触发page fault的虚拟内存地址。
好,看下bpftrace命令的输出:
$ sudo bpftrace -e 'kprobe:handle_mm_fault /comm == "a.out"/ { printf("addr: 0x%lx\n", arg1); }'
...
addr: 0x7efea597d000
addr: 0x7efea597e000
...
由上可见,bpftrace命令输出的page fault触发地址,正是我们的程序在输出3、4时输出的地址。
由此可见,示例程序中的那两次赋值操作,触发了page fault,进而分配了物理内存。
再推荐下我们之前推荐过的一篇文章,讲的也是linux内核对进程内存的分配、管理等,相信这次你会更加理解这篇文章。
https://manybutfinite.com/post/how-the-kernel-manages-your-memory/
完。
本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!