signed

QiShunwang

“诚信为本、客户至上”

linux top VIRT RES SHR SWAP DATA内存参数详解

2021/6/9 9:02:36   来源:

总结:
VIRT 虚拟内存中含有共享库、共享内存、栈、堆,所有已申请的总内存空间。
RES  是进程正在使用的内存空间(栈、堆),申请内存后该内存段已被重新赋值。
SHR  是共享内存正在使用的空间。
SWAP 交换的是已经申请,但没有使用的空间,包括(栈、堆、共享内存)。
DATA 是进程栈、堆申请的总空间。

  其实很早之前就想开博客,写一写码农几年自己积攒下来的知识与见解。看过很多文章有过很多感触,有些收获很值得梳理一下认真思考反思的。今天就先从基本的top开始吧,在网上看了很多关于top讲解内存参数的文章,千篇一律,大部分都是不经深思的转载,自我验证的却只有少数,也许大家忙于工作,急于找到问题并快速解决,而忽略了事后思考总结给自己带来成长的乐趣了吧。不能否认生活节奏的加快让我们失去了很多自我反省与总结的时间与机会。好了,闲话到此,让我们一起看下top关于内存参数的真面目吧。
  对于使用Linux/Unix系统的人来说,top已经是在常用不过的工具了,很多人都知道里面的参数的意思,却很少有人知道这些参数值在程序中是怎么反射出来的。例如:VIRT空间为什么比物理内存大?RES的空间在程序中都包含哪几部分?SWAP交换的都是那些空间?SHR是真正开辟的共享内存空间的大小么?DATA的空间大小指的又是什么,它和RES有那些区别?通过这些内存参数怎么看出我的程序有内存泄露等等问题,待我一一给你解答。
  为了搞清楚以上的问题,我们用以下代码进行测试对其一一验证,并得出我们想要的结果。
  科普篇:先说下VIRT、RES、SHR、SWAP、DATA这几个参数的意思吧(其他参数不是本篇的重点在此略过,关于top的博客都能找到)。咱们先看下Linux下的那个无所不知的男人怎么说的:
  VIRT  -- Virtual Image (KB)  VIRT = SWAP +RES  (公式1)
  Thetotal amount of virtual memory used by the task. It includes all code, data andshared libraries plus pages that have been swapped out. (一个任务所使用的虚拟内存的总数。它包括所有的代码,数据和共享库,加上已换出的页面)说的还是比较清楚的,仔细的你或许已经从这段介绍中发现公式1是存在一些问题的(下面会做解释)
  RES  -- Resident size (KB)  RES = CODE + DATA (公式2)
  The non-swappedphysical memory a task has used. (一个任务正在使用的没有交换的物理内存)我们一般称为驻留内存空间。先给大家声明,man给出的这个公式2是有问题的,这个错误万恶的公式2估计误导了很多人。
  SHR  -- Shared Memory size (KB)
  The amount ofshared memory used by a task. It simply reflects memory that could bepotentially shared with other processes. (一个任务使用共享内存的总数。它只是反映可能与其它进程共享的内存)也就是这个进程使用共享内存的大小。
  SWAP  -- Swapped size (KB)
  Theswapped out portion of a task’s total virtual memory image. (换出一个任务的总虚拟镜像的一部分)只是说明了交换的内存来自虚拟内存,但没说明把什么样的内存交换出去。
  DATA  -- Data+Stack size (KB)
  Theamount of physical memory devoted to other than executable code, also known asthe ’data resident set’ size or DRS. (除可执行代码以外的物理内存总量,也被称为数据驻留集或DRS,谷娘了把DRS,没找到符合上下文的意思,知道的兄弟贴上来啊。)
  哦了,以上是man告诉我们的信息,至于对不对需要我们进一步确认。好了写段测试代码来验证吧。测试系统:Linux内核及系统版本2.6.23.1-42.fc8,gcc 版本 4.1.2,top: procps version 3.2.7,测试环境不同测试结果会有所不同,但得出结论的步骤都是相同的。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
 
const  int BUFF_SIZE = 1 << 23;
const  int SHARED_SIZE = 1 << 24;
const  int SHM_TEST_ID = 888;
 
//4 static char GTestMem[1<<20];
//3 char GTestMem1[1<<20];
 
void TestMalloc()
{
    char   szTemp[BUFF_SIZE];
    char*  pszNew = (char*)malloc(BUFF_SIZE * sizeof(char));
    if (pszNew == NULL)
    {
        printf("Malloc memory %d failed.\n", BUFF_SIZE);
        exit(-1);
    }
 
//1    memset(szTemp, 'q', BUFF_SIZE);
//1    memset(pszNew, 'w', BUFF_SIZE);
//2    memset(szTemp, 'q', BUFF_SIZE / 4);
//2    memset(pszNew, 'w', BUFF_SIZE / 4);
    while (1)
    {
        sleep(10);
    }
}
 
void ShMemory()
{
    char   szTemp[BUFF_SIZE];
    char*  pszNew = (char*)malloc(BUFF_SIZE * sizeof(char));
    if (pszNew == NULL)
    {
        printf("Malloc memory %d failed.\n", BUFF_SIZE);
        exit(-1);
    }
 
    int  fdShMem = shmget(SHM_TEST_ID, SHARED_SIZE, 0666|IPC_CREAT);
    if (fdShMem == -1)
    {
        printf("Create shared memory failed.\n");
        exit(-1);
    }
 
    void*  pSHM = shmat(fdShMem, NULL, 0);
    if ( (int)pSHM == -1)
    {
        printf("Attach shared memory failed.\n");
        exit(-1);
    }
 
    memset(pSHM, 't', SHARED_SIZE / 4);
    while (1)
    {
        sleep(10);
    }
}
 
void MallocLeak()
{
    char   szTemp[BUFF_SIZE];
//    for (int i = 0; i < BUFF_SIZE / 4; i++)
//        szTemp[i] = i % 255;
 
    sleep(30);
    char* pszNew = NULL;
    while (1)
    {
        pszNew = (char*)malloc(BUFF_SIZE * sizeof(char));
        if (pszNew == NULL)
        {
            printf("Malloc memory %d failed.\n", BUFF_SIZE);
            exit(-1);
        }
//        memset(pszNew, 7, BUFF_SIZE / 4);
//        free(pszNew);
        sleep(5);
    }
}
 
int main(int argc, char* argv[])
{
//    TestMalloc();
    ShMemory();
    return 0;
}

测试一:使用函数TestMalloc()进行测试。
  PID     USER      VIRT  RES    SHR  S %MEM    SWAP  CODE  DATA   COMMAND
14469   root      19068    776    672  S      0.1      17m          4     16m     test_malloc     测试一
  我们先看DATA是16M可以从代码中可以看出这些空间来自于栈与堆的总和这点是可以确定的。根据man的说法VIRT = 17M + 776K怎么算也达不到19M啊,看来这个公式是不精准的,还有1M多的空间,man说来自于共享库,让我们带着问题继续寻找。RES是一个进程正在使用的空间,而SWAP的空间为什么是17M呢,代码中的16M数据来自于堆与栈,难道把这些空间全置换出去了,这样的话还多出1M的空间呢?这1M的空间和虚拟内存多出的1M空间是不是一块呢。这一轮我们只能确认DATA的空间是代码中栈与堆申请的总空间之和。
测试二:使用函数TestMalloc()并使用注释1进行测试。
 PID     USER      VIRT  RES    SHR  S %MEM    SWAP  CODE  DATA    COMMAND
15828   root      19064  16m    672   S     1.7     1916          4      16m     test_malloc     测试二
  我们看到在memset之后DATA与SHR没有发生变化,而RES的内存达到了16M,也就是说这里是堆与栈正在使用的内存,只要对该块内存进行修改,该进程正在使用内存的情况都会在这里体验出来。Memset之后SWAP的使用空间下降了,我们也可以得出结论,SWAP交换的是已经申请,但没有使用的空间,包括(栈、堆)。
测试三:验证测试二得出的结论,使用函数TestMalloc()并使用注释2进行测试。
  PID     USER      VIRT  RES   SHR  S %MEM  SWAP CODE    DATA   COMMAND
17264    root     19068  4872   672  S     0.5      13m        4        16m    test_malloc     测试三
  RES是4M,分别是使用栈与堆个2M之和,SWAP是13M是栈与堆各6M加上未知1M之和,DATA是16M。第二步得出的结论是正确的,在此我们推翻了man给出的VIRT、RES的公式结论。通过以上三步得出如下结论:
VIRT 虚拟内存中含有代码段、栈、堆,所有已申请的内存空间。
RES  是进程正在使用的内存空间(栈、堆),申请内存后该内存段已被重新赋值。
SWAP 交换的是已经申请,但没有使用的空间,包括(栈、堆)。
DATA 是进程栈+堆的总空间。
测试四:废话不多说使用ShMemory()来揭开SHR的神秘面纱
  PID     USER      VIRT  RES  SHR  S %MEM   SWAP CODE  DATA  COMMAND
18990    root     35444  4868  4772 S     0.5      29m       4      16m    test_malloc     测试四
  代码中我申请了8M栈空间,8M堆空间,16M共享内存,对共享内存前4M内存进行了赋值。我们看下以上三步时的SHR所占用的空间,672KB应该是内核管理共享内存代码段的空间,现在的SHR的值是4M,也就是正在使用的共享内存的空间,其余的12M空间应该在SWAP空间中。而这时SWAP的空间正是栈没使用的空间+堆没有使用的空间+共享内存没有使用的空间之和。多出1M的空间我确实找不到来源啊。

  Man给出的公式,在我的代码中没有得到证实,代码变化万千写法不同就会照成很多不同的结果,运行效率、占用内存各不相同。本文的目的是给大家带来思考,根据自己的知识与认知去看到表面之后的事情。让大家失望的是,我这里没能得出具体的公式结论。内存泄露大家可以使用函数MallocLeak()进行测试。
  最后遗留一个问题,测试一中SWAP多出的1M空间来源于哪?大家一起发现一起完善吧,欢迎大家的反馈。
————————————————
版权声明:本文为CSDN博主「_Vicent_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011547375/article/details/9851455