最近遇到几个有关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
==SReclaimable
a +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/enabled
echo 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
的内存数目。