百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Linux进程内存用量分析之内存映射篇

nanshan 2024-10-09 13:02 15 浏览 0 评论

一、背景

搜索引擎中,使用内存映射的方式加载庞大的索引数据文件已是常态,58自研的搜索引擎ESearch也是如此。ESearch检索节点中,索引数据是分段存储的,段上正排倒排等数据又分别存储在不同文件上,所有索引数据都使用内存映射的方式读取或更新。这样做有许多好处,比如简化了数据加载逻辑,由操作系统管理磁盘缓存等。但是也存在一些问题,比如映射的内存用量不容易控制等。

?ESearch检索节点索引数据的常驻内存用量占服务总常驻内存的60%左右,所以分析和监控内存映射文件的内存用量很有必要的,一方面有助于了解索引的内存使用情况,辅助进行内存优化,提高资源利用率;另一方面也有助于排查I/O相关问题,比如当发生I/O过高问题时,通过各文件映射的内存变化量来分析服务在读取哪些文件等。

本文将介绍两种与内存映射文件相关的内存用量分析方法:1.分析磁盘文件内容加载到物理内存中的总量,可辅助定位I/O相关问题等。2.分析进程使用到的文件数据的内存用量,可帮助了解进程的内存资源、文件的热数据分布等。下面会结合Linux内存管理机制进行介绍。

二、原理分析

为了更加系统的了解内存统计工具和方法,在这一章节对内存映射、Page Cache、页表(Page Table)等相关原理做简单描述。

首先介绍下Page Cache,它把一部分内存用于缓存磁盘数据,减少访问磁盘时间。它与文件系统和内存管理系统有密切关联。

接下来介绍页表,页表是进程中不可或缺的一部分,用于把虚拟地址空间映射到物理地址空间。页表需要存储在连续的物理内存上,X86机器单层页表的情况下,想映射3G用户虚拟空间,需要连续768个大小为4k的物理页。所以同时运行N个进程时,需要N块大小为768页帧的连续内存,在x64中这种情况变得更加严重。

绝大多数进程只会使用整个虚拟地址空间的一小部分,并且这一小部分还是非常离散地分布在整个地址空间范围内,根据进程运行的这个特点,Linux内核设计了四层页表,分别为Page Global Directory(PGD)、Page UpperDirectory(PUD)、Page MiddleDirector(PMD)、Page Table Entry(PTE)。多层页表对比一层页表的一个优势在于对虚拟地址空间中不必要的区域,在PMD或者PTE层中不必创建没用的PMD节点或者PTE节点,多级页表节省了大量内存。另一个优点在于x64及其中适配四级硬件页表,可以增大寻址范围。但是对于不同架构,页表的层次也不同,有的架构只需要实现二层或三层页表。在这里我们以三层举例,如图:


接下来通过内存映射将页表、PageCache、内存映射相关知识串联起来。

2.1 内存映射

ESearch使用mmap的方式打开磁盘文件,将磁盘文件映射到虚拟内存中,如下图所示:



加载磁盘页主要经过页表和Page Cache两层结构,关系图如下:


根据上图模拟访问内存映射中的一段虚拟内存的过程。

1. 先将虚拟地址0x00445500转换成虚拟页,再根据地址映射原理在页表寻找所对应的页帧中是否存在物理页地址。假设页表中0x0044页帧(page frame)内没有指向物理页的指针(具体表现为对应PTE有效位为0);

2.1. 如果存在有效物理页地址,将物理页对应数据返回给用户空间;

2.2. 如果不存在有效物理页地址,内核将会到Page Cache查找;

3. 进程根据打开的文件描述符找到文件的inode数据结构;

4. inode包含了i_mapping域,i_mapping存放一个指向address_space对象的指针,address_space对象是承接文件到基数树的桥梁,能让进程通过inode快速定位到需要查找的物理内存页;基数树是多叉树的一种,Page Cache使用基数树快速定位物理页的位置以及判断物理页是否被加载到内存;

5. 根据文件偏移量,定位到需要访问的页,通过address_space查找该页是否被缓存;

6. address_space中包含了指向基数树根节点(rnode)的指针page_tree,根据rnode可以找到基数树的最高层节点,然后快速查找需要访问的页是否被缓存在物理内存中。基数树结构如下图所示,快速查询方法类似于页表虚拟地址映射物理页的方法;


7.1. 如果定位到,则说明已经缓存,直接将指向该物理页的指针加载到页表中对应的页帧中即可;

7.2 如果没有找到,则发生缺页中断,创建一个页缓存页(假设为0xff007700),同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重复查找页缓存。

2.2 Page Cache 和页表的不同

内存映射中加载页的统计分为两个层级:

1.内核层级:Page Cache保存了多少该文件的磁盘页在内存;

2.进程层级:进程使用了多少该文件的磁盘页,也就是页表中有多少关于该文件的有效物理页。

对于某个进程使用到的内存映射文件,使用页表统计得到的就是当前进程使用到的页;使用Page Cache统计,还会包括预读带入的页、其他进程缓存的页等。对于同一文件二者的加载量一般不相同,一般情况下进程页表加载页的数量都小于等于PageCache加载页的数量。

接下来介绍用mincore系统调用统计Page Cache物理内存用量和用/proc目录下的文件来统计页表中内存映射区间内物理内存加载情况。

2.3 mincore系统调用

mincore系统调用原型是int mincore(void *addr, size_tlength, unsigned char *vec),第一个参数是mmap返回出来的指针,第二个参数是文件长度,第三个参数是uint8数组,该数组是返回值。mincore会统计从addr开始长度为 length字节的虚拟内存页,通过vec返回这些页所映射的数据是否被加载到物理内存中。该页数据在内存中,vec对应的元素就为1,否则为0。mincore系统调用能够很方便的拿到该页是否在PageCache中。接下来介绍页表用量的统计,就使用了/proc目录下的文件。

2.4 /proc目录下的文件

Linux通过/proc目录,以虚拟文件系统的方式提供了访问内核数据的途经。/proc/<pid>目录下提供了每个进程的各种信息,其中我们用到与内存相关的主要有smaps,maps,pagemap三个文件。以下一一介绍它们的用法和不同之处,以及它们之间的优缺点比较。注意一点:只有在这三个文件被访问的时候,内核才会进行统计并在内存中生成文件内容,因此它们平时不会占用内核的计算资源和存储空间。

2.4.1 smaps

/proc/<pid>/smaps其中pid是运行进程的进程号,smaps里面表示着所有内存映射的信息,比如动态链接库,new/malloc使用mmap打开的内存空间,内存映射的文件和栈空间等。下图为smaps输出的一部分,这部分是ESearch服务正排索引文件加载情况的示意图。



其中Size表示所占虚拟内存大小(总大小),Rss表示加载到物理内存的大小。让进程分析自己进程号的smaps文件,得到对应文件真实加载到页表的Rss。利用这样的方式可以获取文件部分加载到页表中的大小。

但是需要注意的是如果在高频统计内存段和其他线程访问的内存段重叠的情况下,对smaps文件进行读操作甚至外部去cat smaps 都会对其他线程造成较大影响。阅读源码时发现生成smaps有大粒度的内存锁的操作,具体一起看代码,以下源码做了精简,生成smaps文件关键源码如下:


代码大致含义如下:

1. 使用ptr_offset_map_lock加锁addr开始到end结束的内存段,该函数会独占内存区间对应的页表;

2. 返回addr对应的页表偏移量的pte,pte变量中保存的是当前addr对应的那一页;

3. for循环内存地址,每次addr加PageSize大小,pte++表示下一个页,for循环内部是给mss赋值,mss内累加了addr到end范围内的每页的状态;

4. 解除对应区间页表占用。

下图为每个mmap打开地址段的标准化输出函数:



每次show_smap都会调用smaps_walk,smaps_walk将会调用smaps_ptr_range来得到addr到end范围内所有页的状况,再用seq_printf格式化。

循环调用show_smap函数就能生成完整的smaps文件内容。以上就是smaps统计一段用内存映射打开的文件中所有页的方法。

总结:

优势:smaps文件易于观察,统计信息丰富,并且容易获得;

缺点:因为生成smaps过程中会多次调用smaps_ptr_range,该过程中都独占该进程页表的一部分,别的线程不能访问。所以读取smaps文件不适用于频繁的内存读写场景。

2.4.2 pagemap

在/proc/<pid>/pagemap中保存着pid进程对应的页表,可以读取该文件的页表信息来计算进程需要的页有多少。首先需要查看源码来判断pagemap文件是否跟smaps文件一样,在生成过程中都需要加锁。以下为关键源码:



1. 从walk中拿出上层定义的pm对象,该对象为pagemapread型,该数据结构中保存了一个指向u64数组的指针,该数组用来保存最终结果;

2. for循环以PageSize为步长,遍历整个从addr到end的内存区间,for循环中ptr_offset_map和pte_to_pagemap_entry都是在填充pfn这个u64类型的变量;

3. 由add_to_pagemap把pfn对象的内容填充到保存最终结果的pm结构体中。根据源码可以知道生成pagemap过程是不加锁的,所以读取该文件比较适合于统计内存占用量这种对数据一致性要求不高的场景。pagemap中每64位代表着当前pid进程页表中对应页的状态信息,这 64位状态信息如下图所示:



在统计内存用量这个场景下,只用到了第63位,该位表示对应的页是否被该进程使用。因为/proc/<pid>下的文件都是特殊文件,mmap打开pagemap、smaps等文件会失败,并且errno会置ENODEV。所以只可以用read/fread打开,打开后使用lseek改变当前文件偏移量(cfo),然后read读取需要的字节数,再展开分析即可。

对比smaps文件,因为pagemap在生成的过程中不加锁,所以对频繁内存读写没有影响,但是一致性没有smaps强。

因为ESearch服务内部可以拿到磁盘文件内存映射返回的指针和长度,所以很方便能找到页表中对应的页。但如果不能拿到映射的地址范围,可以使用maps得到映射地址范围。

/proc/<pid>/maps文件描述了pid进程所有mmap打开文件或者开辟的内存段所在的地址区域,可以根据maps里的内容拿到感兴趣的地址段再进行操作。

2.5 系统命令

以下介绍的两种系统命令也能在一定程度上反应内核Page Cache或页表使用量的情况。

1. free –m会展示 Page Cache的大小。

2. vmstat也会展示Page Cache的大小,而且会展示PageCache中活跃页(active)和非活跃页(inactive)的量。

3.top会展示进程SHR内存占用,包括栈,大于128kb的内存分配,动态链接库,mmap打开的文件等。

4. sar 展示历史系统情况,-B参数展示页情况,其中pgfree展示系统中的空闲页数量,pgsteal表示从Page Cache中清理页的个数,在ESearch集群中该值和kswapd0进程的活跃度成正比。

三、总结

统计页表的工具有top、smaps、pagemap;统计文件占用PageCache的工具有mincore等;



文件映射下的内存统计主要分为两个方面:

1. 页表:可以使用smaps、top中的SHR、 pagemap来查看进程使用的内存;

2. Page Cache:可以使用free、vmstat、mincore系统调用来查看文件映射到物理内存页面所占用的内存。

进程层面和系统层面间的不同主要在于预读(read-ahead)等操作造成额外内存页会计算在Page Cache中,进程页表是不计算预读等操作所读入的内存页,页表用量只表示该进程需要页的数量。

四、参考

1. 内核版本:2.6;

2. 页表定位方式:参考《深入理解计算机系统》562页;

3. 基数树定位方式:参考https://en.wikipedia.org/wiki/Radix_tree。

相关推荐

Linux下C++访问web—使用libcurl库调用http接口发送解析json数据

一、背景这两天由于一些原因研究了研究如何在客户端C++代码中调用web服务端接口,需要访问url,并传入json数据,拿到返回值,并解析。 现在的情形是远程服务端的接口参数和返回类型都是json的字符...

干货 | 这 3 个超经典的Linux实战项目,让你分分钟入门Linux系统

编译安装nginx搭建小游戏网站编译安装流程下载nginx代码wget-P/server/tools/http:nginx.org/download/nginx1.22.0.tar.gz解压并进...

权限管理-树莓派linux⑦

前言当你在看这篇README,我感到非常荣幸。作为支持开源、分享的理念的我,给大家带来一些学习上的乐趣。由于本人并非专业的教育领域人士,很多时候天马行空,随心所欲的表达方式,可能让部分人感到不适。请根...

每天Linux学习:linux文件属性

ls-lih先通过这个命令来观察(-l列表显示目录内容详细,-i第一列显示inode,-h将文件大小显示为我们常见的kb,mb等单位)从截图中我们能看出文件属性由这9列信息组成:第1列:inod...

Linux ln、unlink命令用法

ln命令可以用来创建软链接或硬链接。1.创建软链接:ln-s源文件目标文件例如:ln-s/usr/lib/libc.so/usr/local/lib/libc.so.6这样就创建了一...

Linux 系统启动完整流程

一、启动系统流程简介如上图,简述系统启动的大概流程:1:硬件引导UEFi或BIOS初始化,运行POST开机自检2:grub2引导阶段系统固件会从MBR中读取启动加载器,然后将控制权交给启动加载器GRU...

最火的 CI/CD 平台 Jenkins 详细搭建教程(for Linux)

在正式学习Jenkins之前我们需要对两个名词有一定了解,其一是DevOps,另外一个就是CI/CD。何为DevOps?来自wiki百科介绍DevOps是一系列软件开发实践,强调开发人员(Dev)和测...

hadoop集群搭建详细方法

第一步:搭建配置新的虚拟机格式化之前先把tmp目录下所有与Hadoop有关的信息全部删除rm-rf/tmp/hadoop-centos*开启之后jps只有Java的进程:sudovi/et...

Linux 常用命令集合

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

inode文件索引,你了解嘛?你的Linux基础真的扎实嘛?

一、inode是什么?深入了解inode,就要从文件存储说起来!文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节。读取硬盘的时候,不会一个个扇区地读取,这样效率...

linux实例之创建service服务

前面我们讲过可以通过service命令来启动,重启,停止指定的服务程序。service服务可以在系统启动时,自动运行该服务,我们可以利用这一特点,创建service文件,并且让系统重启时,自动执行命令...

linux之软连接和硬连接的区别

硬连接硬链接是通过索引节点进行的链接。在Linux中,多个文件指向同一个索引节点是允许的,像这样的链接就是硬链接。硬链接只能在同一文件系统中的文件之间进行链接,不能对目录进行创建。如果删除硬链接对应的...

Linux inode 详解

简介索引节点(IndexNode)是Linux/类unix系统文件系统上的一种数据结构,用于存储有关文件或目录的元数据。它包含文件的所有信息,除了文件名和数据。inode在文件系统如何存储和检...

Bash 脚本实例:获取符号链接的目标位置

我们都熟悉Linux中的符号链接,通常称为符号链接或软链接,符号链接是指向任何文件系统中的另一个文件或目录的特定文件。本文将介绍Linux中符号链接的基础知识,并创建一个简单的bash脚本...

windows快捷方式,符号链接,软链接和硬链接

当一个软件大量的向C盘写入数据,而我们又无法修改软件保存数据的位置时,可以使用windows系统的“符号链接”(SymbolicLink)功能,将保存数据的位置修改到其它分区中。符号链接类似于我们熟...

取消回复欢迎 发表评论: