/proc/meminfo 内存参数内核分配/释放过程介绍

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,大体源码是

  1. void si_meminfo(struct sysinfo *val)
  2. {
  3.     val->totalram = totalram_pages;//total总内存page
  4.     val->sharedram = 0;
  5.     val->freeram = global_page_state(NR_FREE_PAGES);//free总内存page
  6.     val->bufferram = nr_blockdev_pages();//buffer总内存page数,直接读写块设备分配
  7.     val->totalhigh = totalhigh_pages;
  8.     val->freehigh = nr_free_highpages();
  9.     val->mem_unit = PAGE_SIZE;
  10. }
  11. static int meminfo_proc_show(struct seq_file *m, void *v)
  12. {
  13.     struct sysinfo i;
  14.     long available;
  15.     struct vmalloc_info vmi;
  16.     si_meminfo(&i);//获取totalfreebuffer总内存page数存入i.totalrami.freerami.bufferram
  17.     cached = global_page_state(NR_FILE_PAGES) - total_swapcache_pages() - i.bufferram;
  18.     get_vmalloc_info(&vmi);//基于vmalloc映射的虚拟空间计算出vmalloc消耗的物理内存总数
  19.     //计算 NR_FREE_PAGES,NR_ANON_PAGES,NR_SHMEM,NR_ANON_PAGES,NR_FILE_MAPPED,NR_SLAB_RECLAIMABLE,NR_SLAB_UNRECLAIMABLE 内存page数存入pages[lru]
  20.     for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
  21.         pages[lru] = global_page_state(NR_LRU_BASE + lru);
  22.     //计算available内存
  23.     available = si_mem_available();
  24.     seq_printf(m,
  25.         "MemTotal:       %8lu kB\n"//系统总内存 i.totalram
  26.         "MemFree:        %8lu kB\n"//free内存 i.freeram
  27.         "Buffers:        %8lu kB\n"//buffer内存 i.bufferram
  28.         "Cached:         %8lu kB\n"//cache内存 cached
  29.         ........
  30.         "AnonPages:      %8lu kB\n"//匿名页映射内存 global_page_state(NR_ANON_PAGES)
  31.         "Mapped:         %8lu kB\n"//文件映射内存 global_page_state(NR_FILE_MAPPED)
  32.         //tmpfs文件系统占用内存和进程间通信共享内存 global_page_state(NR_SHMEM)
  33.         "Shmem:          %8lu kB\n"
  34.         "Slab:           %8lu kB\n"//slab内存 global_page_state(NR_SLAB_RECLAIMABLE) +global_page_state(NR_SLAB_UNRECLAIMABLE)
  35.         "SReclaimable:   %8lu kB\n"//可回收slab内存 global_page_state(NR_SLAB_RECLAIMABLE)
  36.         "SUnreclaim:     %8lu kB\n"//不可回收slab内存 global_page_state(NR_SLAB_UNRECLAIMABLE)
  37.         ......
  38.         "VmallocUsed:    %8lu kB\n"//vmalloc分配内存 vmi.used >> 10
  39.         .....
  40.         K(i.totalram),
  41.         K(i.freeram),
  42.         K(i.bufferram),
  43.         K(cached),
  44.         ...
  45.         K(global_page_state(NR_ANON_PAGES)),
  46.         K(global_page_state(NR_FILE_MAPPED)),
  47.         K(global_page_state(NR_SHMEM)),
  48.         K(global_page_state(NR_SLAB_RECLAIMABLE) +global_page_state(NR_SLAB_UNRECLAIMABLE)),
  49.         K(global_page_state(NR_SLAB_RECLAIMABLE)),
  50.         K(global_page_state(NR_SLAB_UNRECLAIMABLE)),
  51.         ...
  52.         vmi.used >> 10,
  53. }

下文一一介绍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]数组。

1:free内存的分配和释放过程

分配内存时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

2 文件缓存内存NR_FILE_PAGES 和共享内存NR_SHMEM

读文件时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);
}

3 cached 内存

cached = global_page_state(NR_FILE_PAGES) -total_swapcache_pages() - i.bufferram;

cache大部分还是来自文件缓存。

4 available 内存

available 内存由si_mem_available函数计算返回。

  1. long si_mem_available(void)
  2. {
  3.         long available;
  4.         unsigned long pagecache;
  5.         unsigned long wmark_low = 0;
  6.         unsigned long pages[NR_LRU_LISTS];
  7.         struct zone *zone;
  8.         int lru;
  9.         for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
  10.                 pages[lru] = global_page_state(NR_LRU_BASE + lru);
  11.         //wmark_low累加各个内存zone low水位page
  12.         for_each_zone(zone)
  13.                 wmark_low += zone->watermark[WMARK_LOW];
  14.         //available内存来源1:free page-系统预留内存
  15.         available = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
  16.         pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE];
  17.         pagecache -= min(pagecache / 2, wmark_low);//正常情况wmark_low更小
  18.         //available内存来源2:pagecache-min(pagecache/2,各内存zone内存low水位值累加)
  19.         available += pagecache;
  20.       //available内存来源3:slab可回收内存-min(slab可回收内存page/2,各内存zone内存low水位值累加)
  21.         available += global_page_state(NR_SLAB_RECLAIMABLE) -
  22.                      min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low);
  23.         if (available < 0)
  24.                 available = 0;
  25.         return available;
  26. }

粗略计算一下,available内存=free内存-系统预留内存+pagecache+可回收slab内存。当free内存很少,系统预留内存很大,可能会出现availabel内存比free内存还小。

5  匿名页映射内存NR_ANON_PAGES和文件映射内存NR_FILE_MAPPED

进程缺页异常匿名页内存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,在进程退出时不会释放掉这部分内存。

6  slab可回收内存NR_SLAB_RECLAIMABLE 和 slab不可回收内存NR_SLAB_UNRECLAIMABLE

  1. //分配 slab  page时根据 kmem_cache 是否有可回收属性把page个数加入NR_SLAB_RECLAIMABLE或者NR_SLAB_UNRECLAIMABLE 内存计数
  2. static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
  3. {
  4.     //实际分配内存page,分配page数由s->oo决定
  5.     page = alloc_slab_page(alloc_gfp, node, oo);
  6.     mod_zone_page_state(page_zone(page),
  7.         (s->flags & SLAB_RECLAIM_ACCOUNT) ?NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
  8.         1 << oo_order(oo));
  9. }
  10. //释放slab  page时根据 kmem_cache是否有可回收属性把page个数从NR_SLAB_RECLAIMABLE或者NR_SLAB_UNRECLAIMABLE 内存计数中减去
  11. static void __free_slab(struct kmem_cache *s, struct page *page)
  12. {
  13.     mod_zone_page_state(page_zone(page),
  14.         (s->flags & SLAB_RECLAIM_ACCOUNT) ?NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
  15.         -pages);
  16.      //这里执行__free_pages()最终释放page
  17.     __free_memcg_kmem_pages(page, order);
  18. }

7  vmalloc 内存

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//smaps中也有单个进程的统计,这个统计值与进程的RSS/PSS是有重叠的,如果用户进程用到了THP,进程的RSS/PSS也会相应增加

你可能感兴趣的:(linux,内核,内存管理)