最近遇到几个有关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 |
- Mem:中的used是包括了
各进程使用内存+slab+buffers+cached,即2731 = 1601(use+slab) + 166(buffers) + 963(cached)(由于使用-m以兆单位显示,计算时会有一点点出入。) - Mem:中的free则表示完完全全没有被用于其他用途的内存,换句话说就是”多余”的、”浪费”的内存。
- -/+ buffers/cache中的used是系统实际使用的内存(slab的缓存也是包括在此used中的),不包括
buffers和cached,即1601 = 2731(used) - 166(buffers) - 963(cached)。 - -/+ buffers/cache中的free表示系统空闲内存,指系统最多还可用的内存量。此处的空闲内存是包含了
buffers和cached的,即2320 = 1190(free) + 166(buffers) + 963(cached)
理论上认为buffers和cached是可释放回收的内存,因为内存的读取速度比硬盘快Linux充分利用内存资源以加快读取速度。但需要注意一点并非所有的buffers和cached都可被释放回收。 - 关于
buffers和cached:cached:是统计所有文件缓存的page总数,即是VFS的page cache总数。buffers:是统计所有block device(块设备)的bd_inode的address_space的page总数。网上资料说对元数据(metadata)的操作也会缓存到buffers中,源码中没找到,此部分待验证。
Linux Cache
为进一步说明cached和buffers,需要先弄清楚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),页面描述符结构中含有mapping和index变量,用于连接page和page cache。
mapping:指向该page的inode的address_space对象。index:该page所有者地址空间中以页(page)为单位的偏移量。
address_space结构体
address_space是内存Cache中核心的数据结构,在include/linux/fs.h中定义。
本次主要留意的是host和nrpages字段,host指向拥有该address_space对象的inode/block_device、nrpages表示该inode/block_device的页总数(解释buffers时会用到此字段,详见下)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19struct 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中数据的产生方式不同所映射的关系也有些许差别:
- 若page cache中page的数据来自文件(file IO),那么该page中数据的拥有者为该文件的inode。VFS的
inode结构体中有i_data字段而address_space则在该i_data字段中。inode结构体中除了有i_data外还有i_mapping,i_mapping指向该inode对应的address_space。address_space中的host字段指向所属的inode。大致关系如下图所示,图片来源
- 若page cache中page的数据来自块设备(block IO),那么该page中的数据(
块设备的原始数据)拥有者为该块设备的主inode。address_space则在bdev文件系统的inode(主inode)中,i_mapping字段指向主inode中的address_space。address_space中的host指向该主inode。
cached & buffers
上面说了这么多终于可以来解释free -m中的cached和buffers。
cached:cached就是进程在读写操作文件(fiel IO)所产生的驻留内存的数据,就是VFS中的page cache。buffers:- 为了更详细的解释
buffers,我们直接查看相关源码。free命令是统计/proc/meminfo中的数值,而/proc/meminfo的值是调用sysinfo获得的。 在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
16struct 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.. */
};在mm/page_alloc.c中可以看出
bufferram的值来至于函数nr_blockdev_pages()1
2
3
4
5
6
7
8
9
10void 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;
}在fs/block_dev.c中函数
nr_blockdev_pages()返回ret。
可以看到ret是将所有块设备(block device)对应的bd_inode中的i_mapping的nrpages累加。i_mapping是指向address_space的,而nrpages在address_space结构体中的定义是所有者的页的总数。换句话说,free中的buffers是统计所有块设备操作(block IO)产生的page总数。1
2
3
4
5
6
7
8
9
10
11long 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;
}若想增加
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(不清除缓存),可选值有1、2和3,不同值所清理的缓存各有不同。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前面讲了很多就不再细说。简单说说dentries和inodes。dentry和inode在VFS(Virtual file system)中是比较重要的。
- inode:
inode是文件对象的元数据,inode包含如下信息(inode不包含数据和文件名):- 文件类型
- 权限
rwx - 属组
group id - 拥有者
user id - 文件字节数
size - 时间戳
ctime mtime atime - 硬链接数
- inode号
- 设备标识符
- dentry:
dentry即目录项,dentry主要作用是连接文件名和其inode,由于inode中并没有包含文件名及路径信息,因此需要利用dentry构建并维护文件系统的目录树,每个文件的dentry链接到父目录的dentry从而形成了文件系统的结构树。dentry是一个纯粹的内存结构,由文件系统在提供文件访问的过程中在内存中直接创建。dentry结构体在源码中定义include/linux/dcache.h。dentry中包含文件名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的值来调整内核清理inode和dentry缓存的倾向。
/proc/sys/vm/vfs_cache_pressure默认值为100,内核根据page cache和swap cache将inode和dentry缓存保持一个合理的比例。
- 降低
vfs_cache_pressure(vfs_cache_pressure < 100)会导致内核倾向于保留dentry和inode缓存。 - 增加
vfs_cache_pressure(vfs_cache_pressure > 100),则会导致内核倾向于清除缓存重新加载dentries和inodes。
/proc/meminfo
/proc/meminfo是查看内存使用情况最主要的接口,很多命令诸如free、vmstat等都从这里获取数值的。/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
46MemTotal: 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(不包含buffers和cached) - Buffers
如前面所说buffers是统计所有block device(块设备)的bd_inode的address_space的page总数。即直接操作块设备的block IO会将缓存放到buffers中。 - Cached
如前面所说cached是VFS的page cache,操作文件的file IO缓存会放到cached中。 - SwapCached
统计曾经被swap out后现在又被swap in(即同时存在于memory和swapfile中)并且从又被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匿名映射的内存区
- 文件页(file pages),
- 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~结束)。该区域主要用于用户空间的程序或缓存页,该区域不能直接映射到内核空间。
HighTotal、HighFree、LowTotal和LowFree这几个参数在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 pages和anonymous pages中,page cache属于file pages。page cache中的缓存页可能已经不被进程使用,但仍以缓存被保留在内存中。而另一些page cache的缓存页则正在被进程使用,如libraries或mmap的文件等,这些内存文件页称之为mmaped。
因为shared memory和tmpfs属于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)
- SysV shared memory(
- Slab 、SReclaimable 、SUnreclaim
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,此前一般为4K或8K。
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)的内存大小。 - CommitLimit、Committed_AS
- overcommit机制
要弄清楚CommitLimit和Committed_AS,需要先解释一下Linux的overcommit机制——Linux允许进程申请超过当前实际可用大小的内存空间。但允许申请并不代表就实际分配如此大小的内存给进程,Linux是在进程使用内存时才实际将内存分配给进程。commit对应进程申请内存。对于overcommit在2.6内核版本后的Linux系统可用通过修改/proc/sys/vm/overcommit_memory来调整内存overcommit的行为,/proc/sys/vm/overcommit_memory允许使用0、1和2三个值。0:Heuristic overcommit handling,默认值。允许overcommit,但内存会根据算法预测申请内存的行为是否合理,拒绝掉不合理的overcommit申请。1:Always 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) + swap。overcommit_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。
- 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/enabledecho never > /sys/kernel/mm/redhat_transparent_hugepage/defrag
- HugePages_Total、HugePages_Free、HugePages_Rsvd、HugePages_Surp、Hugepagesize
HugePage称之为大页。Linux内存的标准页大小(page size)为4K而HugePage常见大小(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_Free和HugePages_Rsvd,当进程申请HugePage时,会预订一块大页内存,此时HugePages_Rsvd会增加但HugePages_Free不会减少。只有当进行写入数据到预订的大页内存时,HugePages_Free才会减少,而此时HugePages_Rsvd也会减少。
- HugePages_Surp:超过系统设定的常驻HugePages内存页数目的内存页面数目。
- Hugepagesize:单个huge page的内存页面大小,通常为
2048Kb
- HugePages_Total:分配给huge page的内存页面数目,
- DirectMap4k、DirectMap2M
DirectMap4k表示TLB(Translation Lookaside Buffer)映射为4K的内存数目,DirectMap2M表示TLB(Translation Lookaside Buffer)映射为2M的内存数目。