解析进程 /proc/pid/maps 和 /proc/pid/smaps

目录

/proc//maps

背景

具体描述

代码实现

实践

/proc/pid/smaps

smaps各子项详解

代码实现

代码调用的路径如下:

小结


/proc/<pid>/maps

背景

相对于/proc/meminfo和dumpsys meminfo可以看到系统整体的内存信息,我们还需要能够具体到每一个进程内存占用统计的信息。

在分析内存问题的时候,会经常依赖kernel的proc文件系统下各个进程的文件节点,从中获取当前进程的详细内存信息,今天就来简单介绍一下比较常用的一个节点/proc/pid/maps

Proc/pid/maps显示进程映射了的内存区域和访问权限。对应内核中的操作集为proc_pid_maps_op,具体的导出函数为:show_map

内核中进程的一段地址空间用一个vm_area_struct结构体表示,所有地址空间存储在 :struct task_struct -> struct mm_struct -> mmap链表中。

具体描述

如下为一个vm_area_struct结构体的描述,详解描述了一个虚拟内存区域的信息。

vm_area_struct

struct vm_area_struct {
    /* The first cache line has the info for VMA tree walking. */
 
    unsigned long vm_start;     /* Our start address within vm_mm. */               //区域的首地址
    unsigned long vm_end;       /* The first byte after our end address within vm_mm. */    //区域的尾地址
 
    /* linked list of VM areas per task, sorted by address */
    //该vma在一个进程的vma链表中的前驱vma和后驱vma指针,链表中的vma都是按地址来排序的
    struct vm_area_struct *vm_next, *vm_prev;                           //vma表,下一个vma;vma表,上一个vma
 
    struct rb_node vm_rb;                                       //红黑树中对应的节点
 
    /*
     * Largest free memory gap in bytes to the left of this VMA.
     * Either between this VMA and vma->vm_prev, or between one of the
     * VMAs below us in the VMA rbtree and its ->vm_prev. This helps
     * get_unmapped_area find a free area of the right size.
     */
    unsigned long rb_subtree_gap;
 
    /* Second cache line starts here. */
 
    struct mm_struct *vm_mm;    /* The address space we belong to. */               // 所属的内存描述符,vma属于哪个mm_struct
    pgprot_t vm_page_prot;      /* Access permissions of this VMA. */               //vma的访问权限
    unsigned long vm_flags;     /* Flags, see mm.h. */                     
 
    /*
     * For areas with an address space and backing store,
     * linkage into the address_space->i_mmap interval tree.
     *
     * For private anonymous mappings, a pointer to a null terminated string
     * in the user process containing the name given to the vma, or NULL
     * if unnamed.
     */
    //shared联合体用于和address space关联
    union {
        struct {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } shared;
        const char __user *anon_name;
    };
 
    /*
     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
     * list, after a COW of one of the file pages.  A MAP_SHARED vma
     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
     * or brk vma (with NULL file) can only be in an anon_vma list.
     */
    /*anno_vma_chain和annon_vma用于管理源自匿名映射的共享页*/
    struct list_head anon_vma_chain; /* Serialized by mmap_sem &
                      * page_table_lock */ 
    struct anon_vma *anon_vma;  /* Serialized by page_table_lock */             //指向匿名域的指针
 
    /*该vma上的各种标准操作函数指针集*/
    const struct vm_operations_struct *vm_ops;
 
    /* Information about our backing store: */
    unsigned long vm_pgoff;     /* 映射文件的偏移量,以PAGE_SIZE为单位 */
    struct file * vm_file;      /* 映射的文件,没有则为NULL;可以通过这个区分匿名页和文件页*/
    void * vm_private_data;     /* was vm_pte (shared mem) */
 
#ifdef CONFIG_SWAP
    atomic_long_t swap_readahead_info;
#endif
#ifndef CONFIG_MMU
    struct vm_region *vm_region;    /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
#endif
    struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
#ifdef CONFIG_SPECULATIVE_PAGE_FAULT
    seqcount_t vm_sequence;
    atomic_t vm_ref_count;      /* see vma_get(), vma_put() */
#endif
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
    ANDROID_VENDOR_DATA(1);
} __randomize_layout;

下表为每一个用vm_area_struct描述的内存区域的具体信息,表格第一列的字段对应上面vm_area_struct结构体内部的一些成员。

vm_start

“-”前一列,如00377000

此段虚拟地址空间起始地址

vm_pgoff

第四列,如00000000

对有名映射,表示此段虚拟内存起始地址在文件中以页为单位的偏移。对匿名映射,它等于0或者vm_start/PAGE_SIZE

vm_flags

第三列,如r-xp

此段虚拟地址空间的属性。每种属性用一个字段表示,r表示可读,w表示可写,x表示可执行,p和s共用一个字段,互斥关系,p表示私有段,s表示共享段,如果没有相应权限,则用’-’代替

vm_file->f_dentry->d_inode->i_sb->s_dev

第五列,如fd:00

映射文件所属设备号。对匿名映射来说,因为没有文件在磁盘上,所以没有设备号,始终为00:00。对有名映射来说,是映射的文件所在设备的设备号

vm_file->f_dentry->d_inode->i_ino

第六列,如9176473

映射文件所属节点号。对匿名映射来说,因为没有文件在磁盘上,所以没有节点号,始终为00:00。对有名映射来说,是映射的文件的节点号

vm_end

“-”后一列,如00390000

此段虚拟地址空间结束地址

第七列,如/system/bin/init

对有名来说,是映射的文件名。对匿名映射来说,是此段虚拟内存在进程中的角色。[stack]表示在进程中作为栈使用,[heap]表示堆。其余情况则无显示

具体的/proc/pid/maps文件实例:

vm_start  -vm_end vm_flags vm_pgoff f_entry                              映射的文件名/虚拟内存的作用
5567882000-55678b2000 r--p 00000000 fd:06 564                            /system/bin/init
55678b2000-556797b000 r-xp 00030000 fd:06 564                            /system/bin/init
556797b000-556797f000 r--p 000f9000 fd:06 564                            /system/bin/init
556797f000-5567980000 rw-p 000fc000 fd:06 564                            /system/bin/init
5567980000-5567981000 rw-p 00000000 00:00 0                              [heap]                                     //映射到堆
7f14800000-7f14c00000 rw-p 00000000 00:00 0                              [anon:libc_malloc]
7f14d22000-7f14d24000 rw-p 00000000 00:00 0
7f14d24000-7f15d22000 ---p 00000000 00:00 0
7f15d22000-7f15d23000 ---p 00000000 00:00 0
7f15d23000-7f15e1f000 rw-p 00000000 00:00 0                              [anon:stack_and_tls:508]
7f15e1f000-7f15e20000 ---p 00000000 00:00 0
7f15e20000-7f15e40000 rw-s 00000000 00:12 9778                           /dev/__properties__/properties_serial
7f15e40000-7f15e60000 rw-s 00000000 00:12 9777                           /dev/__properties__/u:object_r:wifi_prop:s0
7f15e60000-7f15e80000 rw-s 00000000 00:12 9776                           /dev/__properties__/u:object_r:wifi_log_prop:s0
7f15e80000-7f15ea0000 rw-s 00000000 00:12 9775                           /dev/__properties__/u:object_r:vold_prop:s0
7f15ea0000-7f15ec0000 rw-s 00000000 00:12 9774                           /dev/__properties__/u:object_r:vndk_prop:s0
7f15ec0000-7f15ee0000 rw-s 00000000 00:12 9773                           /dev/__properties__/u:object_r:virtual_ab_prop:s0
7f15ee0000-7f15f00000 rw-s 00000000 00:12 9772                           /dev/__properties__/u:object_r:vendor_zygote_disable_gl_preload:s0
7f15f00000-7f15f20000 rw-s 00000000 00:12 9771                           /dev/__properties__/u:object_r:vendor_wifienhancelog_prop:s0
7f15f20000-7f15f40000 rw-s 00000000 00:12 9770                           /dev/__properties__/u:object_r:vendor_touchfeature_prop:s0
7f15f40000-7f15f60000 rw-s 00000000 00:12 9769                           /dev/__properties__/u:object_r:vendor_thermal_normal_prop:s0
7f15f60000-7f15f80000 rw-s 00000000 00:12 9768                           /dev/__properties__/u:object_r:vendor_socket_hook_prop:s0
7f15f80000-7f15fa0000 rw-s 00000000 00:12 9767                           /dev/__properties__/u:object_r:vendor_slad_prop:s0
7f15fa0000-7f15fc0000 rw-s 00000000 00:12 9766                           /dev/__properties__/u:object_r:vendor_security_patch_level_prop:s0
7f15fc0000-7f15fe0000 rw-s 00000000 00:12 9765                           /dev/__properties__/u:object_r:vendor_ro_audio_prop:s0
7f15fe0000-7f16000000 rw-s 00000000 00:12 9764                           /dev/__properties__/u:object_r:vendor_payment_security_prop:s0
7f16000000-7f16020000 rw-s 00000000 00:12 9763                           /dev/__properties__/u:object_r:vendor_panel_info_prop:s0
7f16020000-7f16040000 rw-s 00000000 00:12 9762                           /dev/__properties__/u:object_r:vendor_mtkcam_prop:s0

代码实现

///proc/pid/maps节点的操作集;seq_operations序列操作
static const struct seq_operations proc_pid_maps_op = {
    .start  = m_start,
    .next   = m_next,
    .stop   = m_stop,
    .show   = show_map
};
show_map()
|   |->show_map_vma()
|   |   |->file_inode()
|   |   |->show_vma_header_prefix()
|   |   |   |->seq_file操作,遍历VMA的信息
|   |   |->arch_vma_name()

实践

编写能够可视化maps文件的工具

https://www.cnblogs.com/arnoldlu/p/10272466.html

/proc/pid/smaps

/proc/PID/smaps 文件是基于 /proc/PID/maps 的扩展,他展示了一个进程的内存消耗,比同一目录下的maps文件更为详细。

smaps文件能够将maps文件中展示的所有vma内存区都进行详细统计,输出更为详细的数据;

在maps文件中,只会输出每一个vma内存区域的起始/结束地址,属性,偏移量,主从设备号以及映射问文件等等,如下所示

5567882000-55678b2000 r--p 00000000 fd:06 564                            /system/bin/init
55678b2000-556797b000 r-xp 00030000 fd:06 564                            /system/bin/init
556797b000-556797f000 r--p 000f9000 fd:06 564                            /system/bin/init
556797f000-5567980000 rw-p 000fc000 fd:06 564                            /system/bin/init
5567980000-5567981000 rw-p 00000000 00:00 0                              [heap]
7f14800000-7f14c00000 rw-p 00000000 00:00 0                              [anon:libc_malloc]
7f14d22000-7f14d24000 rw-p 00000000 00:00 0
7f14d24000-7f15d22000 ---p 00000000 00:00 0
7f15d22000-7f15d23000 ---p 00000000 00:00 0
7f15d23000-7f15e1f000 rw-p 00000000 00:00 0                              [anon:stack_and_tls:508]
7f15e1f000-7f15e20000 ---p 00000000 00:00 0
7f15e20000-7f15e40000 rw-s 00000000 00:12 9778                           /dev/__properties__/properties_serial
7f15e40000-7f15e60000 rw-s 00000000 00:12 9777                           /dev/__properties__/u:object_r:wifi_prop:s0
7f15e60000-7f15e80000 rw-s 00000000 00:12 9776                           /dev/__properties__/u:object_r:wifi_log_prop:s0
7f15e80000-7f15ea0000 rw-s 00000000 00:12 9775                           /dev/__properties__/u:object_r:vold_prop:s0
7f15ea0000-7f15ec0000 rw-s 00000000 00:12 9774                           /dev/__properties__/u:object_r:vndk_prop:s0
7f15ec0000-7f15ee0000 rw-s 00000000 00:12 9773                           /dev/__properties__/u:object_r:virtual_ab_prop:s0
7f15ee0000-7f15f00000 rw-s 00000000 00:12 9772                           /dev/__properties__/u:object_r:vendor_zygote_disable_gl_preload:s0
7f15f00000-7f15f20000 rw-s 00000000 00:12 9771                           /dev/__properties__/u:object_r:vendor_wifienhancelog_prop:s0

但是smaps文件能够给每一个vma内存区域进行统计,在maps的基础上进行集成统计,显示的信息更加详细和直观

5567882000-55678b2000 r--p 00000000 fd:06 564                            /system/bin/init       //对应maps中的第一个vma内存区域
Size:                192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  64 kB
Pss:                  42 kB
Shared_Clean:         44 kB
Shared_Dirty:          0 kB
Private_Clean:        20 kB
Private_Dirty:         0 kB
Referenced:           64 kB
Anonymous:             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
VmFlags: rd mr mw me dw
55678b2000-556797b000 r-xp 00030000 fd:06 564                            /system/bin/init       //对于maps文件中的第二个vma内存区域
Size:                804 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                 356 kB
Pss:                 261 kB
Shared_Clean:        148 kB
Shared_Dirty:          0 kB
Private_Clean:       208 kB
Private_Dirty:         0 kB
Referenced:          356 kB
Anonymous:             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
VmFlags: rd ex mr mw me dw

smaps各子项详解

2.1 Size:虚拟内存空间大小。但是这个内存值不一定是物理内存实际分配的大小,因为在用户态上,虚拟内存总是延迟分配的。这个值计算也非常简单,就是该VMA的开始位置减结束位置。

 延迟分配就是当进程申请内存的时候,Linux会给他先分配页,但是并不会区建立页与页框的映射关系,意思就是说并不会分配物理内存,而当真正使用的时候,就会产生一个缺页异常,硬件跳转page fault处理程序执行,在其中分配物理内存,然后修改页表(创建页表项)。异                      常处理完毕,返回程序用户态,继续执行。

2.2 Rss:是实际分配的内存,这部分物理内存已经分配,不需要缺页中断就可以使用的。
这里有一个公式计算Rss:
Rss=Shared_Clean+Shared_Dirty+Private_Clean+Private_Dirty

share/private:该页面是共享还是私有。
dirty/clean:该页面是否被修改过,如果修改过(dirty),在页面被淘汰的时候,就会把该脏页面回写到交换分区(换出,swap out)。有一个标志位用于表示页面是否dirty。

share/private_dirty/clean 计算逻辑:
查看该page的引用数,如果引用>1,则归为shared,如果是1,则归为private,同时也查看该page的flag,是否标记为_PAGE_DIRTY,如果不是,则认为干净的。

2.3 Pss(proportional set size):是平摊计算后的实际物理使用内存(有些内存会和其他进程共享,例如mmap进来的)。实际上包含下面private_clean+private_dirty,和按比例均分的shared_clean、shared_dirty。

举个计算Pss的例子:
如果进程A有x个private_clean页面,有y个private_dirty页面,有z个shared_clean仅和进程B共享,有h个shared_dirty页面和进程B、C共享。那么进程A的Pss为:
x + y + z/2 + h/3

2.4 Referenced:当前页面被标记为已引用或者包含匿名映射(The amount of memory currently marked as referenced or a mapping associated with a file may contain anonymous pages)。

Linux内存管理的页面替换算法里讲过,当某个页面被访问后,Referenced标志被设置,如果该标志设置了,就 不能将该页移出。

2.5 Anonymous:匿名映射的物理内存,这部分内存不来自于文件的内存大小。

2.6 ShmemPmdMapped:PMD页面已经被映射的共享(shmem / tmpfs)内存量。在官方文档中,这样解释:"ShmemPmdMapped" shows the ammount of shared (shmem/tmpfs) memory backed by huge pages.

2.7 Shared/Private_Hugetlb:由hugetlbfs页面支持的内存使用量,由于历史原因,该页面未计入“ RSS”或“ PSS”字段中。 并且这些没有包含在Shared/Private_Clean/Dirty 字段中。

2.8 Swap:存在于交换分区的数据大小(如果物理内存有限,可能存在一部分在主存一部分在交换分区)

2.9 SwapPss:这个我并没有找到对应解释,但从源码可以得知,计算逻辑就跟pss一样,只不过针对的是交换分区的内存。

2.10 KernelPageSize:内核一页的大小
2.11 MMUPageSize:MMU页大小,大多数情况下,和KernelPageSize大小一样。

2.12 Locked:常驻物理内存的大小,这些页不会被换出。相当于被锁住在物理内存中

2.13 THPeligible:映射是否符合分配THP的条件。如果为true,则为1,否则为0。 它仅显示当前状态。

THP,透明大页(Transparent Huge Pages),RHEL 6 开始引入,目的是使用更大的内存页面(memory page size) 以适应越来越大的系统内存,让操作系统可以支持现代硬件架构的大页面容量功能。与标准大页的区别在于分配机制,标准大页管理是预分配的方式,而透明大页管理则是动态分配的方式。

2.14 VmFlags:表示与特定虚拟内存区域关联的内核标志。

VmFlags


//VmFlags标志详解
rd  - readable
wr  - writeable
ex  - executable
sh  - shared
mr  - may read
mw  - may write
me  - may execute
ms  - may share
gd  - stack segment growns down
pf  - pure PFN range
dw  - disabled write to the mapped file
lo  - pages are locked in memory
io  - memory mapped I/O area
sr  - sequential read advise provided
rr  - random read advise provided
dc  - do not copy area on fork
de  - do not expand area on remapping
ac  - area is accountable
nr  - swap space is not reserved for the area
ht  - area uses huge tlb pages
ar  - architecture specific flag
dd  - do not include area into core dump
sd  - soft-dirty flag
mm  - mixed map area
hg  - huge page advise flag
nh  - no-huge page advise flag
mg  - mergable advise flag

代码实现

5567882000-55678b2000 r--p 00000000 fd:06 564                            /system/bin/init       //----------------------------------------------show_map_vma()
                                                                                                //----------------------------------------------__show_smap()
Size:                192 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  64 kB
Pss:                  42 kB
Shared_Clean:         44 kB
Shared_Dirty:          0 kB
Private_Clean:        20 kB
Private_Dirty:         0 kB
Referenced:           64 kB
Anonymous:             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
VmFlags: rd mr mw me dw                                                                         //---------------------------------------------show_smap_vma_flags()

代码调用的路径如下:

show_smap()
    |->smap_gather_stats                     //最重要的获取信息渠道,遍历vma下的所有page,并且找到他们的物理页,判断属于什么页
    |   |->mss->swap += shmem_swapped;
    |   |->walk_page_vma
    |   |   |->walk_p4d_range ->
    |   |   |   |->walk_pud_range ->
    |   |   |   |   |->walk_pmd_range ->      //因为这里只设置了pmd的回调,所以只会执行到  walk->pmd_entry(pmd, addr, next, walk);不会执行到pte_entry()
    |   |   |   |   |   ->walk_pte_range -> pte_entry()
    |->show_map_vma
    |   |->vma_get_anon_name
    |->__show_smap
    |->show_smap_vma_flags

小结

后续总结更多代码实现细节。

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答: /proc/2042/smaps是一个特殊的文件,它提供了关于进程2042的虚拟内存映射的详细信息。当我们执行cat /proc/2042/smaps时,实际上是在读取该进程对应的虚拟内存区间的信息。这个过程涉及到两个主要步骤:open()和read()。首先,用户态的open("/proc/2042/smaps")函数会陷入内核,通过/proc/2042/smaps文件相关的inode来找到进程2042的相关信息,并将这些信息搜集到proc_maps_private结构中。然后,open()函数将proc_maps_private结构和smaps文件的具体操作函数结构放到file->private_data中,并返回smaps文件对应的文件描述符fd。接下来,用户态的read(fd)函数通过传入的文件描述符fd找到之前open()函数准备好的file结构,并进行读取操作,将文件内容显示给我们。\[1\]\[2\]所以,执行cat /proc/2042/smaps实际上是通过open()和read()函数来读取进程2042的虚拟内存映射信息。\[1\]\[2\] #### 引用[.reference_title] - *1* *2* [cat /proc/$pid/smaps浅析(一)](https://blog.csdn.net/wennuanddianbo/article/details/96473862)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Linux内存管理 -- /proc/{pid}/smaps讲解](https://blog.csdn.net/armlinuxww/article/details/109022698)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

repinkply

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值