Linux 监控之 Memory

2013-04-06 Saturday     linux

在 Linux 的内存分配机制中,优先使用物理内存,当物理内存还有空闲时,不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,这样对于开启过的程序、或是读取刚存取过得数据会比较快,可以提高整体 IO 效率。

free 命令

内存使用情况可以通过 free 命令查看,在 CentOS 中,是 procps-ng 包中的一个程序,该命令实际是读取 /proc/meminfo 文件,对应内核源码中的 fs/proc/meminfo.c。

$ free
         total       used       free     shared     buffers      cached
Mem:    386024     377116       8908          0       21280      155468
-/+ buffers/cache: 200368     185656
Swap:    393552        0      393552

最新版本的 free 会将 buffer/cache 展示在一起,可以通过 free -w 分开查看 buffers 以及 cache;默认的单位是 KB 。

Mem 表示物理内存统计
    total  : 物理内存总大小;
    used   : 已经分配的内存总计(含buffers 与cache ),但其中部分可能尚未使用;
    free   : 未被分配的内存;
    shared : 多个进程共享的内存总额;
    buffers: 系统分配但未被使用的 buffers 数量,磁盘buffers数量;
    cached: 系统分配但未被使用的 cache 数量,磁盘cache数量;

-/+ buffers/cached 表示物理内存的缓存统计
    used: 实际使用的 buffers 与 cache 总量(used-buffers-cached),也是实际使用的内存总量;
    free: 未被使用的 buffers 与 cache 和未被分配的内存之和(buffers+cached+free),这就是系统当前实际可用内存;

Swap 表示硬盘上交换分区的使用情况
    只有 mem 被当前进程实际占用完,即没有了 buffers 和 cache 时,才会使用到 swap 。

其实第一行可以从操作系统的角度去看,即 OS 认为自己总共有 total 的内存,分配了 used 的内存,还有 free 的内存;其中分配的内存中分别有 buffers 和 cached 的内存还没有使用,也就是说 OS 认为还有空闲的内存,又懒得收回之前分配,但已经分配的不能再使用了。

第二行可以认为从进程的角度去看,可用的内存包括了空闲的+buffers+cached,total-空闲的就是时用的内存数。

可以通过如下方式计算:

----- 实际可用内存大小
Free(-/+ buffers/cache) = Free(Mem) + buffers(Mem) + Cached(Mem)
                 185656 =      8908 +        21280 +      155468

----- 物理内存总大小
total(Mem) = used(-/+ buffers/cache) + free(-/+ buffers/cache)
    386024 =                  200368 +                  185656
           = used(Mem) + free(Mem)
           =    377116 +      8908

----- 已经分配的内存大小
Used(Mem) = Used(-/+ buffers/cache) + buffers(Mem) + Cached(Mem)
   377116 =                  200368 +        21280 +     155468

关于内存的其它信息可以参考如下文件。

/proc/meminfo                  // 机器的内存使用信息
/proc/pid/maps                 // 显示进程 PID 所占用的虚拟地址
/proc/pid/statm                // 进程所占用的内存
/proc/self/statm               // 当前进程的信息

新版

如上所述,新版的命令行输入如下。

$ free -w
              total        used        free      shared     buffers       cache   available
Mem:        8070604     2928928      495012     1162676      104156     4542508     3624840
Swap:       8127484      232388     7895096

total     (MemTotal     @ /proc/meminfo)
free      (MemFree      @ /proc/meminfo)
shared    (Shmem        @ /proc/meminfo)  主要是tmpfs使用
buffers   (Buffers      @ /proc/meminfo)
cache     (Cached+Slab  @ /proc/meminfo)
available (MemAvailable @ /proc/meminfo)
used = total - free - buffers - cache     (注意,没有去除shared)

可以通过 free -k -w && cat /proc/meminfo 命令进行测试。

Swap对性能的影响

当内存不足的时候把磁盘的部分空间当作虚拟内存来使用,而实际上并非是在内存不足的时候才使用,有个参数叫做 swappiness,是用来控制 swap 分区使用的,可以直接查看 /proc/sys/vm/swappiness 文件。

默认值是 60,范围是 [0, 100];为 0 表示会最大限度使用物理内存,然后才是 swap;为 100 时,表示尽量使用 swap 分区,把内存上的数据及时的搬运到 swap 空间里面。

分配太多的 Swap 空间会浪费磁盘空间,而 Swap 空间太少,则系统有可能会发生错误。当系统的物理内存用光了,系统就会跑得很慢,但仍能运行;但是如果 Swap 空间也用光了,那么系统就会发生错误。

通常情况下,Swap 空间是物理内存的 2~2.5 倍,最小为 64M;这也根据不同的应用,有不同的配置:如果是桌面系统,则只需要较小的 Swap 空间;而大的服务器则视情况不同需要不同大小的 Swap 空间,一般来说对于 4G 以下的物理内存,配置 2 倍的 swap ,4G 以上配置 1 倍。

另外, Swap 分区的数量对性能也有很大的影响。因为 Swap 交换的操作是磁盘 IO 的操作,如果有多个 Swap 交换区, Swap 空间的分配会以轮流的方式操作于所有的 Swap ,这样会大大均衡 IO 的负载,加快 Swap 交换的速度。

实际上,当空闲物理内存不多时,不一定表示系统运行状态很差,因为内存的 cache 及 buffer 部分可以随时被重用。只有 swap 被频繁调用,才是内存资源是否紧张的依据,可以通过 vmstat 查看

状态查看

可以使用 getswapused.sh 查看 swap 的使用情况,如果不使用 root 用户则只会显示当前用户的 swap,其它用户显示为 0。

参考

关于 Buffer Cache 和 Page Cache 的区别,比较经典的是内核开发者 Robert Love 的相关介绍 What is the major difference between the buffer cache and the page cache? Why were they separate entities in older kernels? Why were they merged later on?

Cache 能否回收

我们分析了cache能被回收的情况,那么有没有不能被回收的cache呢?当然有。我们先来看第一种情况:

tmpfs

Linux 中有个 tmpfs 作为临时文件系统,可以将内存中的一部分空间作为文件系统使用,常见的是 /dev/shm 这个目录,也可以手动创建。

mkdir /tmp/ramdisk

mount -t tmpfs -o size=500M none /tmp/ramdisk

umount /tmp/ramdisk

因为是文件系统,那么正常来说使用的应该是 Page Cache ,可以通过如下命令进行测试。

echo 3 > /proc/sys/vm/drop_caches

free -m

          total        used        free      shared  buff/cache   available Mem:          15359        4946        9437         686         974        9371 Swap:          4095         427        3668 # dd if=/dev/zero of=/tmp/ramdisk/file bs=1M count=400 400+0 records in 400+0 records out 419430400 bytes (419 MB) copied, 0.243054 s, 1.7 GB/s # free -m
          total        used        free      shared  buff/cache   available Mem:          15359        4947        9033        1086        1378        8969 Swap:          4095         427        3668

可以看到 shared buff/cache 有所增加,而 free available 同时减少,如果再次尝试释放内存仍然是无效的,只有当文件被释放掉之后才能被使用。

free -m

         total       used       free     shared    buffers     cached Mem:          3833        750       3083          0          0         49 -/+ buffers/cache:        700       3133 Swap:         4091        410       3681 # dd if=/dev/zero of=/tmp/ramdisk/file bs=1M count=400 400+0 records in 400+0 records out 419430400 bytes (419 MB) copied, 0.208524 s, 2.0 GB/s # free -m
         total       used       free     shared    buffers     cached Mem:          3833       1151       2682          0          0        450 -/+ buffers/cache:        700       3133 Swap:         4091        410       3681

共享内存

这是一种常用的进程间通信 (IPC) 方式,不过不能在 Shell 申请和使用,如下是一个简单的测试用例。

#include #include #include #include #include <sys/ipc.h> #include <sys/shm.h> #include <sys/wait.h>

#define MEMSIZE 100 * 1024 * 1024

int main(void) { int shmid, rc; struct shmid_ds buf; pid_t pid; char *ptr;

    shmid = shmget(IPC_PRIVATE, MEMSIZE, 0600);
    if (shmid < 0) {
            perror("shmget()");
            exit(1);
    }
    rc = shmctl(shmid, IPC_STAT, &buf);
    if (rc < 0) {
            perror("shmctl()");
            exit(1);
    }
    printf("shmid: %d\n", shmid);
    printf("shmsize: %ld\n", buf.shm_segsz);

    pid = fork();
    if (pid < 0) {
            perror("fork()");
            exit(1);
    } else if (pid == 0) {
            ptr = (char *)shmat(shmid, NULL, 0);
            if (ptr == (void *)-1) {
                    perror("shmat()");
                    exit(1);
            }
            bzero(ptr, MEMSIZE);
            strcpy(ptr, "Hello!");
            exit(0);
    } else {
            wait(NULL);
            ptr = (char *)shmat(shmid, NULL, 0);
            if (ptr == (void *)-1) {
                    perror("shmat()");
                    exit(1);
            }
            puts(ptr);
            exit(0);
    }

    return 0; }

程序很简单,就是申请一块约 400M 的共享内存,然后打开一个子进程对这段共享内存做一个初始化操作,父进程等子进程初始化完之后输出一下共享内存的内容,然后退出。

注意,上述的程序在退出之前并没有删除这段共享内存,同样可以通过 free 命令查看其内存的使用情况。

free -m

          total        used        free      shared  buff/cache   available Mem:          15359        5329        9051         686         977        8987 Swap:          4095         427        3668 # ./test shmid: 491522 shmsize: 419430400 Hello! # free -m
          total        used        free      shared  buff/cache   available Mem:          15359        5330        8627        1086        1401        8574 Swap:          4095         427        3668

简单来说,这段共享内存即使没人使用,仍然会长期存放在 cache 中,直到其被删除。删除方法有两种,一种是程序中使用 shmctl(IPC_RMDI) 去删除,或者使用 ipcrm 命令。

—– 查看当前系统的所有共享内存

ipcs -m

—– 删除指定的共享内存

ipcrm -m 491522

实际上,内核底层在实现共享内存 (shm)、消息队列 (msg) 和信号量 (sem) 这些 IPC 机制时,使用的都是 tmpfs,这也是为什么共享内存的操作逻辑与 tmpfs 类似的原因。

只是,一般情况下 shm 占用的内存更多。

mmap

该系统函数将一个文件映射到进程的虚拟内存地址,然后就可以用类似操作内存的方式对文件的内容进行操作。

实际上,其使用范围要更广泛些,例如:A) 通过 malloc() 申请内存时,小段内存内核使用 sbrk() 处理,而大段内存使用 mmap();B) 通过系统调用 exec() 族函数执行时,其本质上是将一个可执行文件加载到内存执行,同样使用 mmap();C) 同时也可以作为共享内存申请使用。

同样,如下是一个简单的测试程序:

#include #include #include #include #include #include <sys/mman.h>

#define MEMSIZE 400 * 1024 * 1024 #define MMAPFILE “/tmp/mmapfile”

int main() { void *ptr; int fd;

    fd = open(MMAPFILE, O_RDWR);
    if (fd < 0) {
            perror("open()");
            exit(1);
    }

    ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, fd, 0);
    if (ptr == NULL) {
            perror("malloc()");
            exit(1);
    }
    printf("%p\n", ptr);
    bzero(ptr, MEMSIZE);
    sleep(100);
    munmap(ptr, MEMSIZE);

    close(fd);
    exit(1); }

上述功能很简单,申请一块 400M 的内存,然后等待 100s 后释放,可以用同样的方式查看。

注意,在使用前要先创建一个相应大小的文件 dd if=/dev/zero of=/tmp/mmapfile bs=1M count=400

同样,在程序退出之后 cached 占用的空间被释放。当使用 mmap() 申请标志状态为 MAP_SHARED 的内存时,内核也是使用的 cache 进行存储的。

实际上,mmap()MAP_SHARED 方式申请的内存,在内核中也是由 tmpfs 实现的。

总结

简单来说,shmget mmap 的共享内存,在内核层都是通过 tmpfs 实现的,而 tmpfs 实现的存储用的都是 cache ,这样只有当使用的空间被删除之后对应的内存才可能会被删除。

常用命令

介绍下与内存相关的命令。

pmap

可以根据进程查看进程相关信息占用的内存情况,可以通过 -x-X-XX 查看详细信息。

$ pmap -x $(pidof mysqld)
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000   37116    9120       0 r-x-- mysqld
0000000002a3e000     872     192      12 r---- mysqld
0000000002b18000     700     300     196 rw--- mysqld
0000000002bc7000     772     628     628 rw---   [ anon ]
00007f0df23ff000       4       0       0 -----   [ anon ]
00007f0df2400000   53248    8472    8472 rw---   [ anon ]
00007f0df5bfc000       4       0       0 -----   [ anon ]
... ...
00007fff995ca000     132      76      76 rw---   [ stack ]
00007fff995f2000       8       4       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
---------------- ------- ------- -------
total kB          705216  141876  129408

输出值: 显示扩展格式
  Address : 虚拟内存开始地址;
  Kbytes  : 占用内存的字节数,单位是KB;
  RSS     : (Resident Set Size)驻留内存的字节数(KB);
  Dirty   : 脏页的字节数(包括共享和私有的)(KB);
  Mode    : 内存页的权限(r=read, w=write, x=execute, s=shared, p=private);
  Mapping : 占用内存的文件、[anon] (分配的内存)、[stack] (堆栈);

$ pmap -d $(pidof mysqld)
Address           Kbytes Mode  Offset           Device    Mapping
0000000000400000   23288 r-x-- 0000000000000000 0ca:00001 mysqld
0000000001cbe000     940 r---- 00000000016be000 0ca:00001 mysqld
0000000001da9000     680 rw--- 00000000017a9000 0ca:00001 mysqld
0000000001e53000     888 rw--- 0000000000000000 000:00000   [ anon ]
0000000001f31000   11680 rw--- 0000000000000000 000:00000   [ anon ]
00007fd784000000    4248 rw--- 0000000000000000 000:00000   [ anon ]
00007fd784426000   61288 ----- 0000000000000000 000:00000   [ anon ]
00007fd78c000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007fd78c021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007fd7d2c67000      12 rw-s- 0000000000000000 000:0000a [aio] (deleted)
00007fd7d2c6a000      12 rw-s- 0000000000000000 000:0000a [aio] (deleted)
00007fd7d2c6d000       4 rw-s- 0000000000000000 000:0000a [aio] (deleted)
00007fd7d2c6e000       4 rw--- 0000000000000000 000:00000   [ anon ]
00007fd7d2c6f000       4 r---- 000000000001f000 0ca:00001 ld-2.17.so
00007fd7d2c70000       4 rw--- 0000000000020000 0ca:00001 ld-2.17.so
00007fd7d2c71000       4 rw--- 0000000000000000 000:00000   [ anon ]
00007ffc8fef3000     132 rw--- 0000000000000000 000:00000   [ stack ]
mapped: 1248492K    writeable/private: 545308K    shared: 128K

输出值: 显示设备格式
  Offset: 文件偏移
  Device: 设备名 (major:minor)
最后一行输出:
  mapped            : 该进程映射的虚拟地址空间大小,也就是该进程预先分配的虚拟内存大小,即ps出的vsz;
  writeable/private : 进程所占用的私有地址空间大小,也就是该进程实际使用的内存大小,不含shared libraries;
  shared            : 进程和其他进程共享的内存大小;
假设上述结构导出到了/tmp/1文件中,可以通过如下命令计算:
  mapped: 所有行Kbytes字段的累加
    awk 'BEGIN{sum=0};{sum+=$2};END{print sum}' /tmp/1
  writeable/private: 模式为w+p,且不是s的内存
    awk 'BEGIN{sum=0};{if($3~/w/ && $3!~/s/) {sum+=$2}};END{print sum}' /tmp/1
  shared: 共享内存数
    awk 'BEGIN{sum=0};{if($3~/s/) {sum+=$2}};END{print sum}' /tmp/1

vmstat

vmstat 不会将自己作为一个有效的进程。

$ vmstat [options] [delay [count]]

常用选项:
  delay: 每多少秒显示一次。
  count: 显示多少次,默认为一次。
  -a: 显示活跃和非活跃内存。
  -f: 显示从系统启动之后 forks 的数量;包括了 fork, vfork, clone 的系统调用,等价于创建的全部任务数。
  -m,--slabs: 显示 slabinfo 。
  -n,--one-header: 只显示一次,而不是周期性的显示。
  -s,--stats: 显示内存统计以及各种活动信息。
  -d,--disk: 显示磁盘的相关统计。
  -D,--disk-sum: 显示硬盘统计信息的摘要。
  -p,--partition device: 显示某个磁盘的统计信息。
  -S,--unit char: 显示输出时的单位,1000(k),1024(K),1000*1000(m),1024*1024(M),默认是 K,不会改变 swap 和 block 。

----- 每秒刷新一次
$ vmstat -S M 1
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
r  b   swpd   free   buff  cache   si   so    bi    bo   in    cs us sy id wa
3  0      0   1963    607   2359    0    0     0     0    0     1 32  0 68  0

各个段的含义为:
  procs 进程
    r: 等待运行的进程数,如果运行队列过大,表示CPU很忙,一般会造成CPU使用率很高。
    b: 不可中断睡眠(uninterruptible sleep)的进程数,也就是被阻塞的进程,通常为IO。
  memory 内存
    swpd: 虚拟内存(Virtual Memory)的使用量,应该是交换区。</li><li>
    free: 空闲内存数,同free命令中的free(Mem)。</li><li>
    buff: 同free命令,已分配未使用的内存大小。</li><li>
    cache: 同free命令,已分配未使用的内存大小。</li><li>
    inact: 非活跃(inactive)内存的大小,使用 -a 选项????。</li><li>
    active: 活跃(active)内存的大小,使用 -a 选项????。
  swap 交换区
    si: 每秒从交换区(磁盘)写到内存的大小。
    so: 每秒写入交换区(磁盘)的大小。
        如果这两个值长期大于0,系统性能会受到影响,即我们平常所说的Thrashing(颠簸)。
  IO
    bi: 每秒读取的块数(blocks/s)。
    bo: 每秒写入的块数(blocks/s)。
  system 系统
    in: 每秒的中断数,包括时钟中断。
    cs: 每秒上线文的交换数。
  CPU
    us: 用户进程执行时间。
    sy: 系统进程执行时间。
    id: 空闲进程执行时间,2.5.41 之后含有 IO-wait 的时间。
    wa: 等待 IO 的时间,2.5.41 之后含有空闲任务。

vmtouch

通过 vmtouch offical site 可以查看文件在内存中的占比,可以下载 本地源码

使用参数可以直接通过 vmtouch -h 查看,如下是简单的示例:

----- 查看/etc目录下有多少在缓存中
$ vmtouch /etc/
           Files: 2844
     Directories: 790
  Resident Pages: 274/9971  1M/38M  2.75%
         Elapsed: 0.15243 seconds

----- 查看一个大文件在缓存中的比例
$ vmtouch -v big-dataset.txt
big-dataset.txt
[                                                            ] 0/169321

           Files: 1
     Directories: 0
  Resident Pages: 0/169321  0/661M  0%
         Elapsed: 0.011822 seconds

----- 缓存部分内容到内存中,分别对应了文本和二进制文件
$ tail -n 10000 big-dataset.txt > /dev/null
$ hexdump -n 102400 big-dataset.txt > /dev/null

----- 再次查看缓存的文件,也就是62页在内存中
$ vmtouch -v big-dataset.txt
big-dataset.txt
[o                                                           ] 62/169321

           Files: 1
     Directories: 0
  Resident Pages: 62/169321  248K/661M  0.0366%
         Elapsed: 0.003463 seconds

----- 将文件加载到内存中
$ vmtouch -vt big-dataset.txt
big-dataset.txt
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 169321/169321
           Files: 1
     Directories: 0
   Touched Pages: 169321 (661M)
         Elapsed: 1.7372 seconds

----- 手动从内存中删除
$ vmtouch -ve big-dataset.txt
Evicting big-dataset.txt
           Files: 1
     Directories: 0
   Evicted Pages: 42116 (164M)
         Elapsed: 0.076824 seconds

----- 将目录下的所有文件锁定到内存中
$ vmtouch -dl /var/www/htdocs/critical/

在 Linux 中,主要是通过 posix_fadvise() 来清除缓存;通过 mincore() 判断页是否存在于缓存中。

smem

Linux 使用的是虚拟内存 (virtual memory),因此要准确的计算一个进程实际使用的物理内存就比较麻烦,如下是在计算内存使用率时比较重要的指标:

  • RSS (Resident Set Size) 表示进程占用的物理内存大小,可以使用 top 命令查询;不过将各进程的 RSS 值相加,通常会超出整个系统的内存消耗,这是因为 RSS 中包含了各进程间共享的内存。
  • PSS (Proportional Set Size) 它将共享内存的大小进行平均后,再分摊到各进程上去,相比来说更准确。
  • USS (Unique Set Size) 也就是 PSS 中只属于本进程的部分,只计算了进程独自占用的内存大小,不包含任何共享的部分。

可以通过 smem 查看,这是一个 Python 写的程序,该程序也可以通过 matplotlib 输出图片;另外,同时提供了一个 smemcap.c 程序,可以打包嵌入式的相关数据,然后通过 -S/--source 指定打包文件。

在 CentOS 中,可以通过如下命令安装。

# yum --enablerepo=epel install smem python-matplotlib

进程内存使用量

简单介绍下如何查看各个进程的内存使用量。

top

可以直接使用 top 命令后,查看 %MEM 的内容,表示该进程内容使用比例,可以通过 -u 指定用户,也就是只监控特定的用户。

常用的命令有。

P: 按%CPU使用率排行
T: 按TIME+排行
M: 按%MEM排行

ps

如下例所示:

$ ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid' | sort -nrk5

其中 rsz 为实际内存,上例实现按内存排序,由大到小。

小结

可以通过 free 查看整个系统内存的使用情况,free(Mem) 过小不会对系统的性能产生影响,实际剩余的内存数为 free(-/+ buffers/cache)。

通过 vmstat 1 3 输出的 swap(si/so) 可以查看交换区的切入/出情况,如果长期不为 0,则可能是因为内存不足,此时应该查看那个进程占用内存较多。

参考

关于内存的介绍可以参考 What every programmer should know about memory,包括了从硬件到软件的详细介绍,内容非常详细。

关于 Linux 内存的介绍可以参考 Linux ate my ram!



如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^


About This Blog

Recent Posts

Categories

Related Links

  • RTEMS
    RTEMS
  • GNU
  • Linux Kernel
  • Arduino

Search


This Site was built by Jin Yang, generated with Jekyll, and hosted on GitHub Pages
©2013-2019 – Jin Yang