目录
  1. 1. free -m
  2. 2. Linux Cache
    1. 2.1. page数据产生方式
    2. 2.2. 页面描述符
    3. 2.3. address_space结构体
    4. 2.4. 映射关系
    5. 2.5. cached & buffers
    6. 2.6. 手动清理缓存
    7. 2.7. 调整内核缓存倾向
  3. 3. /proc/meminfo

 最近遇到几个有关Linux内存的问题,于是稍微整理了一下有关Linux内存方面的内容稍作记录。

free -m

 一般看内存最直接就是输入free -m命令查看,显示的结果恐怕也是问得最多的(free命令实际是通过/proc/meminfo获得内存数值的)。

1
2
3
4
5
$> free -m
total used free shared buffers cached
Mem: 3922 2731 1190 341 166 963
-/+ buffers/cache: 1601 2320
Swap: 1952 0 1952

total used free shared buffers cached
Mem: 总内存 已使用内存use+slab+buffers+cached 完全空闲内存 进程间共享内存 buffers(详解见下) cached(详解见下)
-/+ buffers/cache: 实际使用内存use+slab 空闲内存free+buffers+cached
Swap: 总swap 已使用swap 空闲swap
  1. Mem:中的used是包括了各进程使用内存+slab+buffers+cached,即2731 = 1601(use+slab) + 166(buffers) + 963(cached) (由于使用-m以兆单位显示,计算时会有一点点出入。)
  2. Mem:中的free则表示完完全全没有被用于其他用途的内存,换句话说就是”多余”的、”浪费”的内存。
  3. -/+ buffers/cache中的used是系统实际使用的内存(slab的缓存也是包括在此used中的),不包括bufferscached,即1601 = 2731(used) - 166(buffers) - 963(cached)
  4. -/+ buffers/cache中的free表示系统空闲内存,指系统最多还可用的内存量。此处的空闲内存是包含了bufferscached的,即2320 = 1190(free) + 166(buffers) + 963(cached)
    理论上认为bufferscached是可释放回收的内存,因为内存的读取速度比硬盘快Linux充分利用内存资源以加快读取速度。但需要注意一点并非所有的bufferscached都可被释放回收
  5. 关于bufferscached
    • cached:是统计所有文件缓存的page总数,即是VFS的page cache总数。
    • buffers:是统计所有block device(块设备)的bd_inode的address_space的page总数。网上资料说对元数据(metadata)的操作也会缓存到buffers中,源码中没找到,此部分待验证。

Linux Cache

 为进一步说明cachedbuffers,需要先弄清楚Linux的内存Cache机制,为了明白Linux的Cache机制需要去了解源码中有关address_space结构体的具体定义。
 Linux为提高读写数据速度和减少磁盘IO,会最大程度的将用到的数据存储在内存中。Linux内存管理是以页(page)为基本单位,当内核进行读操作时,首先检查数据是否存在于page cache中,存在则直接从内存读取数据,不存在则从磁盘读取并将该数据放入page cache,如果内存足够该数据会在page cache中长时间驻留。当内核进行写操作时,会直接在page cache中进行并将该页(page)标记为dirty,内核会周期性将dirty page写回到磁盘并取消ditry标记。
 大部分的file IO都会使用到page cache。但也可以指定不使用page cache,当进程打开文件时使用O_DIRECT标志,不使用page cache而是使用进程用户态地址空间的缓冲区。

page数据产生方式

 内存中的page数据大致有两种产生方式:

  • 读取文件(file IO):这些page中的数据是通过读取文件产生的,这些page的拥有者是该文件的inode。这种方式是最为常用。
  • 直接读取块设备(block IO):这些page中的数据是通过直接操作块设备(如:/dev/sda1)产生的,这些page的拥有者是块设备的主inode(块设备在bdev文件系统中的inode称主inode,在宿主文件系统[如ext4]中的inode称次inode)

页面描述符

 每个page有一个页面描述符(struct page),页面描述符结构中含有mappingindex变量,用于连接page和page cache。

  • mapping:指向该page的inode的address_space对象。
  • index:该page所有者地址空间中以页(page)为单位的偏移量。

address_space结构体

address_space是内存Cache中核心的数据结构,在include/linux/fs.h中定义。
 本次主要留意的是hostnrpages字段,host指向拥有该address_space对象的inode/block_devicenrpages表示该inode/block_device的页总数(解释buffers时会用到此字段,详见下)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct address_space {
struct inode *host; /* owner: inode, block_device */
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* and lock protecting it */
atomic_t i_mmap_writable;/* count VM_SHARED mappings */
struct rb_root i_mmap; /* tree of private and shared mappings */
struct rw_semaphore i_mmap_rwsem; /* protect tree, count, list */
/* Protected by tree_lock together with the radix tree */
unsigned long nrpages; /* number of total pages */
/* number of shadow or DAX exceptional entries */
unsigned long nrexceptional;
pgoff_t writeback_index;/* writeback starts here */
const struct address_space_operations *a_ops; /* methods */
unsigned long flags; /* error bits */
spinlock_t private_lock; /* for use by the address_space */
gfp_t gfp_mask; /* implicit gfp mask for allocations */
struct list_head private_list; /* ditto */
void *private_data; /* ditto */
} __attribute__((aligned(sizeof(long))));

映射关系

 根据page中数据的产生方式不同所映射的关系也有些许差别:

  1. 若page cache中page的数据来自文件(file IO),那么该page中数据的拥有者为该文件的inode。VFS的inode结构体中有i_data字段而address_space则在该i_data字段中。inode结构体中除了有i_data外还有i_mappingi_mapping指向该inode对应的address_spaceaddress_space中的host字段指向所属的inode。大致关系如下图所示,图片来源
    page-address_space-inode关系图
  2. 若page cache中page的数据来自块设备(block IO),那么该page中的数据(块设备的原始数据)拥有者为该块设备的主inode。address_space则在bdev文件系统的inode(主inode)中,i_mapping字段指向主inode中的address_spaceaddress_space中的host指向该主inode

cached & buffers

 上面说了这么多终于可以来解释free -m中的cachedbuffers

  • cachedcached就是进程在读写操作文件(fiel IO)所产生的驻留内存的数据,就是VFS中的page cache
  • buffers

    1. 为了更详细的解释buffers,我们直接查看相关源码。free命令是统计/proc/meminfo中的数值,而/proc/meminfo的值是调用sysinfo获得的。
    2. linux/fs/proc/meminfo.c中有结构体sysinfo,而sysinfo结构体linux/include/uapi/linux/sysinfo.h中使用了bufferram,而bufferram正是free命令中buffers的数值来源。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      struct sysinfo {
      __kernel_long_t uptime; /* Seconds since boot */
      __kernel_ulong_t loads[3]; /* 1, 5, and 15 minute load averages */
      __kernel_ulong_t totalram; /* Total usable main memory size */
      __kernel_ulong_t freeram; /* Available memory size */
      __kernel_ulong_t sharedram; /* Amount of shared memory */
      __kernel_ulong_t bufferram; /* Memory used by buffers */
      __kernel_ulong_t totalswap; /* Total swap space size */
      __kernel_ulong_t freeswap; /* swap space still available */
      __u16 procs; /* Number of current processes */
      __u16 pad; /* Explicit padding for m68k */
      __kernel_ulong_t totalhigh; /* Total high memory size */
      __kernel_ulong_t freehigh; /* Available high memory size */
      __u32 mem_unit; /* Memory unit size in bytes */
      char _f[20-2*sizeof(__kernel_ulong_t)-sizeof(__u32)]; /* Padding: libc5 uses this.. */
      };
    3. mm/page_alloc.c中可以看出bufferram的值来至于函数nr_blockdev_pages()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      void si_meminfo(struct sysinfo *val)
      {

      val->totalram = totalram_pages;
      val->sharedram = global_node_page_state(NR_SHMEM);
      val->freeram = global_page_state(NR_FREE_PAGES);
      val->bufferram = nr_blockdev_pages();
      val->totalhigh = totalhigh_pages;
      val->freehigh = nr_free_highpages();
      val->mem_unit = PAGE_SIZE;
      }
    4. fs/block_dev.c中函数nr_blockdev_pages()返回ret
       可以看到ret是将所有块设备(block device)对应的bd_inode中的i_mappingnrpages累加。i_mapping是指向address_space的,而nrpagesaddress_space结构体中的定义是所有者的页的总数。换句话说,free中的buffers是统计所有块设备操作(block IO)产生的page总数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      long nr_blockdev_pages(void)
      {

      struct block_device *bdev;
      long ret = 0;
      spin_lock(&bdev_lock);
      list_for_each_entry(bdev, &all_bdevs, bd_list) {
      ret += bdev->bd_inode->i_mapping->nrpages;
      }
      spin_unlock(&bdev_lock);
      return ret;
      }
    5. 若想增加buffers的值,直接对块设备进行操作产生block IO,由此产生的page就会被缓存在buffers中。

      1
      $> cat /dev/sda1 > /dev/null

手动清理缓存

 当缓存占用过多时,可手动对缓存进行清理,主要涉及到/proc/sys/vm/drop_caches值的调整。
 在清理缓存前,最好先同步数据,即将内存中被标记为dirty的数据写入磁盘。

1
$> sync

/proc/sys/vm/drop_caches默认值为0(不清除缓存),可选值有123,不同值所清理的缓存各有不同。

1
2
3
4
5
6
#清理page cache
$> echo 1 > /proc/sys/vm/drop_caches
#清理dentries和inodes缓存
$> echo 2 > /proc/sys/vm/drop_caches
#1&2,清理page cache、dentries和inodes缓存
$> echo 3 > /proc/sys/vm/drop_caches

page cache前面讲了很多就不再细说。简单说说dentriesinodesdentryinode在VFS(Virtual file system)中是比较重要的。

  • inodeinode是文件对象的元数据,inode包含如下信息(inode不包含数据和文件名):
    • 文件类型
    • 权限rwx
    • 属组group id
    • 拥有者user id
    • 文件字节数size
    • 时间戳ctime mtime atime
    • 硬链接数
    • inode号
    • 设备标识符
  • dentrydentry即目录项,dentry主要作用是连接文件名和其inode,由于inode中并没有包含文件名及路径信息,因此需要利用dentry构建并维护文件系统的目录树,每个文件的dentry链接到父目录的dentry从而形成了文件系统的结构树。dentry是一个纯粹的内存结构,由文件系统在提供文件访问的过程中在内存中直接创建。dentry结构体在源码中定义include/linux/dcache.hdentry中包含文件名d_name、inode号d_inode、指向父目录的指针d_parent等等信息。
     当需要读取文件/home/mogl/test.txt时,总是从/目录开始查找,每个文件对象对应唯一一个inode/inode number == 0。读取/过程中在内存中创建/dentry并将其缓存(有了缓存访问文件系统时便会非常快捷)。在Linux中目录也是文件,目录文件内容包括目录下的文件名inode number,根据这些内容找到下级文件和其inode

调整内核缓存倾向

 可通过调整/proc/sys/vm/vfs_cache_pressure的值来调整内核清理inodedentry缓存的倾向。
/proc/sys/vm/vfs_cache_pressure默认值为100,内核根据page cacheswap cacheinodedentry缓存保持一个合理的比例。

  • 降低vfs_cache_pressure(vfs_cache_pressure < 100)会导致内核倾向于保留dentry和inode缓存。
  • 增加vfs_cache_pressure(vfs_cache_pressure > 100),则会导致内核倾向于清除缓存重新加载dentries和inodes。

/proc/meminfo

/proc/meminfo是查看内存使用情况最主要的接口,很多命令诸如freevmstat等都从这里获取数值的。/proc/meminfo信息很多,看懂其中的信息能让我们对系统内存的使用情况有更清晰的了解。先来看看/proc/meminfo的内容,然后逐个分析说明。/proc/meminfo的内容是通过fs/proc/meminfo.c 的 meminfo_proc_show()函数获取的。
 参考文章RedHat Knowledgebase/proc/meminfo之迷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
MemTotal:        4016668 kB
MemFree: 1294904 kB
Buffers: 86000 kB
Cached: 985016 kB
SwapCached: 121488 kB
Active: 1580604 kB
Inactive: 969872 kB
Active(anon): 1234064 kB
Inactive(anon): 657144 kB
Active(file): 346540 kB
Inactive(file): 312728 kB
Unevictable: 116 kB
Mlocked: 116 kB
HighTotal: 3166364 kB
HighFree: 1010456 kB
LowTotal: 850304 kB
LowFree: 284448 kB
SwapTotal: 1999868 kB
SwapFree: 1745832 kB
Dirty: 80 kB
Writeback: 0 kB
AnonPages: 1392656 kB
Mapped: 274004 kB
Shmem: 411748 kB
Slab: 100572 kB
SReclaimable: 74076 kB
SUnreclaim: 26496 kB
KernelStack: 5824 kB
PageTables: 25468 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 4008200 kB
Committed_AS: 11152492 kB
VmallocTotal: 122880 kB
VmallocUsed: 70840 kB
VmallocChunk: 25384 kB
HardwareCorrupted: 0 kB
AnonHugePages: 339968 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 32760 kB
DirectMap2M: 878592 kB

  • MemTotal
     系统内存总量。这里的内存总量并非总的物理内存,而是除去硬件和kernel占用后内核能支配的内存。
  • MemFree
     系统完全没有使用的内存。free命令中Mem:行的free(不包含bufferscached)
  • Buffers
     如前面所说buffers是统计所有block device(块设备)的bd_inode的address_space的page总数。即直接操作块设备的block IO会将缓存放到buffers中。
  • Cached
     如前面所说cachedVFSpage cache,操作文件的file IO缓存会放到cached中。
  • SwapCached
     统计曾经被swap out后现在又被swap in(即同时存在于memoryswapfile中)并且从又被swap in起就一直没有改变(即非dirty)的页(page)。这些页(page)如果需要再次被swap out的话无需进行write操作(回写磁盘),这样能节省I/O和提升性能。
  • Active
     最近经常被使用的内存页,Active的内存页一般不会被swap out或回收。
    Active = Active(anon) + Active(file)
  • Inactive
     最近不经常被使用的内存页,Inactive的内存页很可能会被swap out或回收。
    Inactive = Inactive(anon) + Inactive(file)
  • Active(anon)
     最近经常被使用的匿名页,Active(anon)的匿名页一般不会被swap out或回收。
     Linux的内存页大致可分成两种:
    • 文件页(file pages)page cache等文件缓存对于的内存页
    • 匿名页(anonymous pages),进程用户模式下的堆栈或使用mmap匿名映射的内存区
  • Inactive(anon)
     最近不经常被使用的匿名页。
  • Active(file)
     最近经常被使用的文件页。
  • Inactive(file)
     最近不经常被使用的文件页。
  • Unevictable
     内存中不能被移除的内存页。
  • Mlocked
     内存中被mlock()系统调用锁定的内存。Mlocked也是Unevictable的,当Mlocked增加时,Unevictable跟着增加而Active/Inactive则减小。
  • HighTotal
     所有可用的高位内存[ZONE_HIGHMEM(896MB~结束)]。在x86结构中,物理内存分三种区域类型:ZONE_DMA(内存开始的16MB)、ZONE_NORMAL(16MB~896MB)、ZONE_HIGHMEM(896MB~结束)。该区域主要用于用户空间的程序或缓存页,该区域不能直接映射到内核空间。
    HighTotalHighFreeLowTotalLowFree这几个参数在CentOS6已经被除去。
  • SwapTotal
     总swap空间大小。
  • SwapFree
     可用swap空间大小。
  • Dirty
     内存中被标记为dirty的数据,这些数据需要被写回到磁盘中。
  • Writeback
     正准备回写磁盘的内存缓存页。
  • AnonPages
     统计内存中匿名页(anonymous pages)大小。
    • VFS的所有page cache都属于文件页(file pages),都不是匿名页(anonymous pages)。
    • 匿名页(anonymous pages)是和用户进程共生的。一旦进程退出,则匿名页(anonymous pages)也随之释放,并不会像文件页(file pages)那样还缓存在内存中。
  • Mapped
     统计被mmaped的内存大小。
     在内存的file pagesanonymous pages中,page cache属于file pagespage cache中的缓存页可能已经不被进程使用,但仍以缓存被保留在内存中。而另一些page cache的缓存页则正在被进程使用,如libraries或mmap的文件等,这些内存文件页称之为mmaped
     因为shared memorytmpfs属于page cache,故mmaped中包含:share memory(attached)tmpfs(mapped)。结合下面的Shmem一起看。
  • Shmem
     共享内存(shared memory)的内存大小。Shmem统计的是实际分配使用的内存大小,而非申请的内存大小。
    shared memory的内存页会被统计进Cached(page cache)Mapped(attached)
    shared memory包括:
    • SysV shared memory(shmget)
    • POSIX shared memory(shm_open)
    • shared anonymous mmap(mmap)
  • SlabSReclaimableSUnreclaim
    Slab == SReclaimablea + SUnreclaim
    SReclaimable为在内存有压力时可回收的部分,SUnreclaim为在即使在内存有压力时都不可回收的部分。
    Slab是统计内核数据结构缓存大小(dentry&inode),这个和上面说的/proc/sys/vm/vfs_cache_pressure/proc/sys/vm/drop_caches相关内容联系起来。
     若想查看slab缓存更详细内容,可使用slabtop命令。
  • KernelStack
     KernelStack(内核栈)是进程进入内核态(syscall/trap/exception)后使用的,其与用户栈是分开的,用户态时是无法使用内核栈的。
     KernelStack(内核栈)的大小是固定的,从2.6.32版本后默认是16K,此前一般为4K8K
     KernelStack(内核栈)是常驻内存且不可被回收的。
  • PageTables
    page table用于将内存的虚拟地址映射成物理地址,PageTables统计page table所占内存的大小。
     内存地址分配越多page table也会随之增大;若多个进程都命中(attached)相同的共享内存段,PageTables的值会变得比较大。
  • NFS_Unstable
    NFS_Unstable统计已发给NFS Server但尚未写入磁盘的缓存页的大小。
  • Bounce
    Bounce是统计用于块设备bounce buffers的内存大小。
     某些设备只能访问低端内存,当I/O请求需要访问高端内存时,为了解决不能访问高端内存的问题,内核会在低端内存中分配一个临时buffer用于将高端内存的数据拷贝到此buffer区域中,此成为bounce buffers
  • WritebackTmp
     统计被FUSE用作临时回写缓存(temporary writeback buffers)的内存大小。
  • CommitLimitCommitted_AS
    • overcommit机制
       要弄清楚CommitLimitCommitted_AS,需要先解释一下Linux的overcommit机制——Linux允许进程申请超过当前实际可用大小的内存空间。但允许申请并不代表就实际分配如此大小的内存给进程,Linux是在进程使用内存时才实际将内存分配给进程。commit对应进程申请内存。对于overcommit2.6内核版本后的Linux系统可用通过修改/proc/sys/vm/overcommit_memory来调整内存overcommit的行为,/proc/sys/vm/overcommit_memory允许使用012三个值。
      • 0Heuristic overcommit handling,默认值。允许overcommit,但内存会根据算法预测申请内存的行为是否合理,拒绝掉不合理的overcommit申请。
      • 1Always overcommit,允许overcommit,只要进程申请内存就通过申请。
      • 2 Don’t overcommit,禁止overcommit。
    • OOM killer机制
       为了防止内存的overcommit机制导致内存不足,Linux设计了OOM killer机制。当Linux系统发现内存不足时,会比较所有进程的oom_score(/proc/<pid>/oom_score),通过杀死oom_score数值大的进程来释放内存。若要手动调整某个进程的oom_score,则需要通过修改oom_score_adj来实现(echo -20 > /proc/<pid>/oom_score_ad)。
    • CommitLimit
      CommitLimit是内存overcommit的判断值,申请的内存总大小超过CommitLimit的值即为overcommit。
      CommitLimit是通过计算得到的,计算公式——CommitLimit = RAM * (overcommit_ratio/100) + swapovercommit_ratio默认值为50,表示物理内存大小的50%,可通过/proc/sys/vm/overcommit_ratio调整。
       若使用了huge pages(见下一个参数)则需要减去huage pages的大小,即计算公式——CommitLimit = (RAM - huge_pages) * (overcommit/100) + swap
    • Committed_AS
       表示所有进程已申请的内存总数。若Committed_AS超过CommitLimit则表示overcommit。
  • VmallocTotal
    vmalloc是以字节为单位分配虚拟地址连续的内存块。
    VmallocTotal是表示可以vmalloc的内存大小。
  • VmallocUsed
     已被使用的vmalloc虚拟内存大小。
  • VmallocChunk
     统计可用的连续虚拟内存大小。
  • HardwareCorrupted
     统计物理故障内存大小。当系统检测到内存的物理页面故障时,会将故障的内存页删除并统计到HardwareCorrupted
  • AnonHugePages
    AnonHugePages统计的是THP(Transparent HugePages)透明大页THP(Transparent HugePages)和接下来的HugePages不太一样,先看看THP(Transparent HugePages)
    THP(Transparent HugePages)是使管理HugePage变得自动化而创造的。系统中存在着khugepaged进程,此进程会一直扫描所有进程使用的内存并视情况将4k page变成huge page
    THP(Transparent HugePages)是在系统运行时动态分配内存的,而HugePage是在系统启动时预先固定分配并在系统运行时不在改变。
     使用THP(Transparent HugePages)可能会到来一些问题,有时候会需要将其关闭(默认开启),关闭THP(Transparent HugePages)
    • echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
    • echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag
  • HugePages_TotalHugePages_FreeHugePages_RsvdHugePages_SurpHugepagesize
    HugePage称之为大页。Linux内存的标准页大小(page size)为4KHugePage常见大小(page size)为2M
     解释HugePage前,需要先了解TLB(Translation Lookaside Buffer)。Linux系统中进程使用内存地址为虚拟地址(Virtual Address),但数据是要通过内存的物理地址(Physical Address)才能访问的。于是这就涉及到虚拟地址(Virtual Address)物理地址(Physical Address),而page table是专门用于虚拟地址转物理地址的(使用HugePage可减小PageTables大小)。对于使用大量内存的进程来说,查找page table太慢,于是设置了TLB(Translation Lookaside Buffer),用于缓存内存地址映射关系以加快映射。增大page size,使得相同大小的TLB(Translation Lookaside Buffer)能覆盖到更多的内存,从而提高TLB(Translation Lookaside Buffer)的命中率,从而提高内存地址转换的速度,这是HugePage的一个主要作用。
     正如上面所说HugePage是在系统启动时就预先固定分配好的,并且HugePage会常驻内存中。也就是说如果4G大小的内存的系统设置了1G大小的HugePage,那么在系统启动后,系统的可用内存则只有3G。系统启动后可以通过调整/proc/sys/vm/nr_hugepages参数值来调整HugePage的大小。
    • HugePages_Total:分配给huge page的内存页面数目,HugePageSize = HugePages_Total * Hugepagesize
    • HugePages_Free:系统中从未被使用的huge page内存页面数目。
    • HugePages_Rsvd:系统中被分配但仍未被使用的huge page内存页面数目。这里需要注意HugePages_FreeHugePages_Rsvd,当进程申请HugePage时,会预订一块大页内存,此时HugePages_Rsvd会增加但HugePages_Free不会减少。只有当进行写入数据到预订的大页内存时,HugePages_Free才会减少,而此时HugePages_Rsvd也会减少。
    • HugePages_Surp:超过系统设定的常驻HugePages内存页数目的内存页面数目。
    • Hugepagesize:单个huge page的内存页面大小,通常为2048Kb
  • DirectMap4kDirectMap2M
    DirectMap4k表示TLB(Translation Lookaside Buffer)映射为4K的内存数目,DirectMap2M表示TLB(Translation Lookaside Buffer)映射为2M的内存数目。

Powered: Hexo, Theme: Nadya remastered from NadyMain