cat /proc/meminfo 能看到系统内存的统计信息,在分析内存问题时,灵活掌握这些内存参数的实现原理还是很有必要的。https://www.jianshu.com/p/391f42f8fb0d 这片文章对各个内存统计做了详细的分析,讲的很好。本文主要基于内核源码,讲解如下常见的内存统计参数的内核统计过程。
[root@localhost ~]# cat /proc/meminfo
MemTotal: 3871288 kB
MemFree: 1371388 kB
MemAvailable: 3043204 kB
Buffers: 389692 kB
Cached: 1441792 kB
AnonPages: 204584 kB
Mapped: 86724 kB
Shmem: 26612 kB
Slab: 234280 kB
SReclaimable: 143240 kB
SUnreclaim: 91040 kB
VmallocUsed: 182960 kB
HugePages_Total: 0
HugePages_Free: 0
首先,执行cat /proc/meminfo 后,执行的内核函数是meminfo_proc_show,大体源码是
下文一一介绍free内存、AnonPages匿名页映射内存、Mapped文件映射内存、tmpfs文件系统占用内存和进程间通信shmget所属的共享内存、Slab内存、vmalloc内存的分配和释放过程。介绍之前先介绍一个重要的数组。非SMP系统,vm_stat[NR_VM_ZONE_STAT_ITEMS],这个数组保存了free内存、AnonPages匿名页映射内存等等内存page使用计数。meminfo_proc_show函数主要也是读取该数组,获取各个内存的使用计数。在SMP系统用的是zone->pageset->vm_stat_diff[NR_VM_ZONE_STAT_ITEMS]数组。
分配内存时free内存减少使用计数:__alloc_pages_nodemask->get_page_from_freelist->buffered_rmqueue->__mod_zone_freepage_state
static inline void __mod_zone_freepage_state(struct zone *zone, int nr_pages,
int migratetype)
{
__mod_zone_page_state(zone, NR_FREE_PAGES, nr_pages);
}
static inline void __mod_zone_page_state(struct zone *zone,
enum zone_stat_item item, int delta)
{
zone_page_state_add(delta, zone, item);
}
static inline void zone_page_state_add(long x, struct zone *zone,
enum zone_stat_item item)
{
atomic_long_add(x, &zone->vm_stat[item]);
atomic_long_add(x, &vm_stat[item]);
}
释放内存free内存增加使用计数:__free_pages->__free_pages_ok->free_one_page->__mod_zone_freepage_state
读文件时NR_FILE_PAGES内存增加:generic_file_aio_read->do_generic_file_read->add_to_page_cache_lru->add_to_page_cache->add_to_page_cache_locked->add_to_page_cache_locked
int add_to_page_cache_locked(struct page *page, struct address_space *mapping,
pgoff_t offset, gfp_t gfp_mask)
{
page->mapping = mapping;
page->index = offset;
error = radix_tree_insert(&mapping->page_tree, offset, page);
mapping->nrpages++;
__inc_zone_page_state(page, NR_FILE_PAGES);
}
tmpfs文件系统写文件数据时NR_FILE_PAGES和NR_SHMEM内存增加:sys_write->vfs_write->do_sync_write->generic_file_aio_write->__generic_file_aio_write->generic_file_buffered_write->shmem_write_begin->shmem_getpage_gfp->shmem_add_to_page_cache
shmget 进程间通信共享内存分配时NR_FILE_PAGES和NR_SHMEM内存增加 :do_page_fault->__do_page_fault->handle_mm_fault->handle_pte_fault->do_linear_fault->__do_fault->shm_fault->shmem_fault->shmem_getpage->shmem_getpage_gfp->shmem_add_to_page_cache
高版本内核流程是: do_page_fault->__do_page_fault->handle_mm_fault->handle_pte_fault->do_shared_fault->__do_fault->shm_fault->shmem_fault->shmem_getpage_gfp->shmem_add_to_page_cache
static int shmem_add_to_page_cache(struct page *page,
struct address_space *mapping,
pgoff_t index, gfp_t gfp, void *expected)
{
page->mapping = mapping;
page->index = index;
error = radix_tree_insert(&mapping->page_tree, index, page);
__inc_zone_page_state(page, NR_FILE_PAGES);
__inc_zone_page_state(page, NR_SHMEM);
}
可以发现,tmpfs文件系统和shmget 进程间通信共享分配的内存既属于文件映射内存,也属于共享内存。
文件close时NR_FILE_PAGES内存减少:iput->evict->truncate_inode_pages_final->truncate_inode_pages_range->truncate_inode_page->delete_from_page_cache->__delete_from_page_cache
tmpfs文件系统文件close和进程间通信共享内存释放时NR_SHMEM和NR_FILE_PAGES内存减少:iput->evict->shmem_evict_inode->shmem_truncate_range->shmem_undo_range->truncate_inode_page->delete_from_page_cache->__delete_from_page_cache
void __delete_from_page_cache(struct page *page)
{
struct address_space *mapping = page->mapping;
mapping->nrpages--;
__dec_zone_page_state(page, NR_FILE_PAGES);
if (PageSwapBacked(page))
__dec_zone_page_state(page, NR_SHMEM);
}
cached = global_page_state(NR_FILE_PAGES) -total_swapcache_pages() - i.bufferram;
cache大部分还是来自文件缓存。
available 内存由si_mem_available函数计算返回。
粗略计算一下,available内存=free内存-系统预留内存+pagecache+可回收slab内存。当free内存很少,系统预留内存很大,可能会出现availabel内存比free内存还小。
进程缺页异常匿名页内存NR_ANON_PAGES增加:do_page_fault->__do_page_fault->handle_mm_fault->handle_pte_fault->do_anonymous_page->page_add_new_anon_rmap
void page_add_new_anon_rmap(struct page *page,
struct vm_area_struct *vma, unsigned long address)
{
if (!PageTransHuge(page))
__inc_zone_page_state(page, NR_ANON_PAGES);
else
__inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
__page_set_anon_rmap(page, vma, address, 1);
if (!mlocked_vma_newpage(vma, page))
lru_cache_add_lru(page, LRU_ACTIVE_ANON);//刚分配的匿名页page加入active链表
else
add_page_to_unevictable_list(page);
}
进程缺页异常文件映射情况NR_FILE_MAPPED内存增加:do_page_fault->__do_page_fault->handle_mm_fault->handle_pte_fault->do_linear_fault->__do_fault->page_add_file_rmap
高版本内核流程是:do_page_fault->__do_page_fault->handle_mm_fault->handle_pte_fault->do_linear_fault->do_read_fault->do_set_pte->page_add_file_rmap
void page_add_file_rmap(struct page *page)
{
bool locked;
unsigned long flags;
mem_cgroup_begin_update_page_stat(page, &locked, &flags);
if (atomic_inc_and_test(&page->_mapcount)) {
__inc_zone_page_state(page, NR_FILE_MAPPED);
mem_cgroup_inc_page_stat(page, MEMCG_NR_FILE_MAPPED);
}
mem_cgroup_end_update_page_stat(page, &locked, &flags);
}
NR_ANON_PAGES 和 NR_FILE_MAPPED 进程退出时减少:do_group_exit->do_exit->mmput->exit_mmap->unmap_vmas->unmap_single_vma->unmap_page_range->page_remove_rmap
void page_remove_rmap(struct page *page)
{
if (anon) {
mem_cgroup_uncharge_page(page);
__dec_zone_page_state(page, NR_ANON_PAGES);
}else{
__dec_zone_page_state(page, NR_FILE_MAPPED);
mem_cgroup_dec_page_stat(page, MEMCG_NR_FILE_MAPPED);
mem_cgroup_end_update_page_stat(page, &locked, &flags);
}
}
NR_FILE_MAPPED 和 NR_FILE_PAGES 应该都表示文件映射内存,但是进程退出时NR_FILE_MAPPED内存就会释放掉,NR_FILE_PAGES 表示pagecache,在进程退出时不会释放掉这部分内存。
vmaloc内存分配时增加计数,实际是增加vmalloc虚拟空间:vmalloc->__vmalloc_node_flags->__vmalloc_node->__vmalloc_node_range->__get_vm_area_node->alloc_vmap_area->__insert_vmap_area
vmaloc内存分配时增加计数,实际是减少vmalloc虚拟空间:vfree->__vunmap->remove_vm_area->free_unmap_vmap_area->free_unmap_vmap_area_noflush->free_vmap_area_noflush->try_purge_vmap_area_lazy->__purge_vmap_area_lazy->__free_vmap_area
最后再总结一下meminfo看到的内存计数零散的知识点,有些讲的好的关键点直接从https://www.jianshu.com/p/391f42f8fb0d复制过来。
1 slab和vmaloc分配的内存会被精确统计,meminfo本身就有这个字段
2 get_free_page 分配的内存不会统计到 meminfo里,这里的内存泄漏的隐患
3 系统有一部分内存是可回收的,常见的是pagecahe。meminfo看到的buffer/cache、slab有一部分是回收的,这些可回收的内存加上free内存,再减去内存预留内存,才是真正可分配的内存。
4 struct page、内核虚拟地址、内核物理地址、页帧pfn的转换关系
5 进程内核栈不会被统计在lru链表中
6 buffer 直接读写块设备产生缓存,或者文件系统元数据metadata如superblock占得内存
7 swapcache 表示匿名页被swap out、又被swap in 并且swap in后page的内容没发生变化的情况。swapcache内存存在于lru中,还统计在匿名页anonpage或者shmem??????
8 共享内存 share memoy不属于anonpage,而属于cache。共享内存属于tmpfs文件系统,但是又是内存型文件系统。mmap share anonyous也是基于tmpfs文件系统
9 匿名页anonpage用户空间malloc分配内存、栈内存、mmap指定ANON_PAGE等等,这些内存在进程退出时就会free。进程读写文件产生的pagecache,不属于进程,进程退出这些page cache也不会释放,cache一直在lru中
10 cache 和 swapcache 是两回事。share memory、tmpfs在没有被swap out时属于cache,但是swap out和swap in时算入swap cache。
11 active(file)和inactive(file)除了包含cache和一部分buffer的内存,active(file)和inactive(file)不会包含shmem
12 “Mlocked”并不是独立的内存空间,它与以下统计项重叠:LRU Unevictable,AnonPages,Shmem,Mapped
13 page cache 和所有用户态内存page都在lru list上,kernel stack和huge page除外
14 Mlocked”统计的是被mlock()系统调用锁定的内存大小。被锁定的内存因为不能pageout/swapout,会从Active/Inactive LRU list移到Unevictable LRU list上
15 huge page 在meminfo时独立存在,与其他统计项没有重叠
16 Active(anon)+Inactive(anon)不等于AnonPages,因为Shmem(即Shared memory & tmpfs) 被计入LRU Active/Inactive(anon),但未计入 AnonPages。
17 为什么[Active(file)+Inactive(file)]不等于Mapped?因为LRU Active(file)和Inactive(file)中不仅包含mapped页面,还包含unmapped页面
18 为什么[Active(file)+Inactive(file)]不等于 Cached?因为”Shmem”(即shared memory & tmpfs)包含在Cached中,而不在Active(file)和Inactive(file)中;
19 Page cache用于缓存文件里的数据,不仅包括普通的磁盘文件,还包括了tmpfs文件,tmpfs文件系统是将一部分内存空间模拟成文件系统,由于背后并没有对应着磁盘,无法进行paging(换页),只能进行swapping(交换),在执行drop_cache操作的时候tmpfs对应的page cache并不会回收。
20 meminfo中的DirectMap所统计的不是关于内存的使用,而是一个反映TLB效率的指标。新的CPU硬件支持比4k更大的页面从而达到减少地址数量的目的, 比如2MB,4MB,甚至1GB的内存页,视不同的硬件而定。”DirectMap4k”表示映射为4kB的内存数量, “DirectMap2M”表示映射为2MB的内存数量,以此类推。所以DirectMap其实是一个反映TLB效率的指标
21 不要把 Transparent HugePages (THP)跟 Hugepages 搞混了,THP的统计值是/proc/meminfo中的”AnonHugePages”,在/proc/