Linux下访问匿名页发生的神奇“化学反应”
时间:2021-09-29 15:22:35
手机看文章
扫描二维码
随时随地手机看文章
[导读]首先祝大家中秋节快乐,阖家欢乐,节日之余记得学习哟!Linux中有后备文件支持的页称为文件页,如属于进程的代码段、数据段的页,内存回收的时候这些页面只需要做脏页的同步即可(干净的页面可以直接丢弃掉)。反之为匿名页,如进程的堆栈使用的页,内存回收的时候这些页面不能简单的丢弃掉,需要交换到交换分区或交换文件。本文中,主要分析匿名页的访问将发生哪些可能颠覆我们认知的"化学反应"。1.实例代码首先以一个简单的示例代码来说明:#include #include #include #include #include #define MAP_SIZE (100 * 1024 * 1024)int main(...
首先祝大家中秋节快乐,阖家欢乐,节日之余记得学习哟!Linux中有后备文件支持的页称为文件页,如属于进程的代码段、数据段的页,内存回收的时候这些页面只需要做脏页的同步即可(干净的页面可以直接丢弃掉)。反之为匿名页,如进程的堆栈使用的页,内存回收的时候这些页面不能简单的丢弃掉,需要交换到交换分区或交换文件。本文中,主要分析匿名页的访问将发生哪些可能颠覆我们认知的"化学反应"。
1.实例代码
首先以一个简单的示例代码来说明:#include
#include
#include
#include
#include
#define MAP_SIZE (100 * 1024 * 1024)
int main(int argc, char *argv[])
{
char *p;
char val;
int i;
puts("before mmap ok, pleace exec 'free -m'!");
sleep(5);
//mmap
p = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(p == NULL) {
perror("fail to malloc");
return -1;
}
puts("after mmap ok, pleace exec 'free -m'!");
sleep(5);
//read
for (i = 0; i < MAP_SIZE; i ) {
val = p[i];
}
puts("read ok, pleace exec 'free -m'!");
sleep(5);
#if 1
//write
memset(p, 0x55, MAP_SIZE);
puts("write ok, pleace exec 'free -m'!");
#endif
//sleep
pause();
return 0;
}
代码非常简单:首先通过mmap分配100M的私有可读可写匿名页面,然后进行读写访问,分别在提示的时候在另外一个窗口执行free -m命令查看输出结果。程序执行结果如下:$ ./anon_rw_demo
before mmap ok, pleace exec 'free -m'!
after mmap ok, pleace exec 'free -m'!
read ok, pleace exec 'free -m'!
write ok, pleace exec 'free -m'!
命令执行结果如下:$ free -m
总计 已用 空闲 共享 缓冲/缓存 可用
内存: 15729 8286 1945 895 5497 6220
交换: 16290 1599 14691
$ free -m
总计 已用 空闲 共享 缓冲/缓存 可用
内存: 15729 8286 1945 895 5497 6220
交换: 16290 1599 14691
$ free -m
总计 已用 空闲 共享 缓冲/缓存 可用
内存: 15729 8286 1945 895 5497 6220
交换: 16290 1599 14691
$ free -m
总计 已用 空闲 共享 缓冲/缓存 可用
内存: 15729 8383 1848 895 5497 6123
交换: 16290 1599 14691
可以看到:第一次提示执行free命令的时候,我们还没有开始通过mmap分配内存,此时free命令输出作为参考。第二次提示执行free命令的时候,我们已经通过mmap分配了100M的内存,此时发现free命令输出内存消耗基本没有变化。第三次提示执行free命令的时候,我们对于分配的匿名页面进行了读操作,此时发现free命令输出内存消耗页基本没有变化, 这基本上会颠覆我们的认知。第四次提示执行free命令的时候,我们对于分配的匿名页面进行了写操作,此时发现free命令输出内存消耗大概为100M。2.内核原理
下面我们从Linux内核的层面来解析发生以上神奇现象的原理。2.1 mmap的内存消耗
mmap申请匿名页的时候,只是申请了虚拟内存(通过vm_area_struct结构来描述,如描述虚拟内存区域的地址范围、访问权限等,以下简称vma),实际的物理内存并没有申请(除了用于管理虚拟内存区域的vma等结构内存的申请),当前虚拟内存和物理内存并没有建立页表映射关系,而真正的申请的匿名页所对应的物理页在实际访问的时候按需分配获得,所以此时我们看不到内存的消耗情况。2.2 第一次读匿名页的内存消耗
通过mmap申请完虚拟内存之后,进程就可以按照之前申请vma的访问权限进行访问,第一发生读访问,这个时候由于虚拟内存和物理内存并没有建立页表映射关系,通过虚拟地址并不能查找到物理内存,所以会发生处理器的异常,最终分析是因为数据访问异常导致,就由处理器架构相关的代码进入了我们通用的缺页异常处理例程中。缺页异常调用链如下:"mm/memory.c"
处理器架构相关异常处理代码
-> handle_mm_fault
-> __handle_mm_fault
-> handle_pte_fault
-> if (!vmf->pte) { ------------------- 1
if (vma_is_anonymous(vmf->vma)) ------------------- 2
return do_anonymous_page(vmf); ------------------- 3
缺页异常进入handle_pte_fault后,在1标签代码处,来判断访问的虚拟内存页的页表项是否为空,为空说明这个这个虚拟页没有和物理页建立映射关系。然后在2标签代码处判断是否为匿名页缺页异常(实际上是判断是否为私有的匿名页,当前当前示例代码场景申请的为私有匿名页面)。在3标签代码处,进行真正的私有匿名页缺页异常处理。下面主要看下第一次读匿名页的处理:do_anonymous_page
->pte_alloc(vma->vm_mm, vmf->pmd) ------------------- 1
->/* Use the zero-page for reads */
if (!(vmf->flags