精华内容
下载资源
问答
  • Linux内核内存压缩技术

    千次阅读 2017-12-02 22:59:01
    内存无论是多大,总是不够用的(至少目前如此),而当系统内存紧张的时候,系统会将匿名页swap出去disk(flash).而这涉及到IO操作,这RAM操作速度不是一个数量级的。因此,如果频繁地做IO操作,不仅影响flash使用...

    为什么需要内存压缩

    说到压缩这个词,我们都可以想到是降低占用空间,使同样的空间可以存放更多的东西,如我们平时常用的压缩文件等。内存压缩同样也是为了节省内存。
    内存无论是多大,总是不够用的(至少目前如此),而当系统内存紧张的时候,系统会将匿名页swap出去disk(flash).而这涉及到IO操作,这RAM操作速度不是一个数量级的。因此,如果频繁地做IO操作,不仅影响flash使用寿命,还严重影响系统性能。
    内存压缩是一种让IO过程平滑过渡的做法。

    目前主流的内核压缩技术

    ZSWAP

    ZSWAP是在memory与disk(flash)之间的一层“cache”,当内存需要swap出去disk的时候,先通过压缩放到zswap中去,swap的空间按需增长。达到一定程度zswap写到flash中去。

    这里写图片描述

    ZRAM

    使用内存模拟block dev的做法。实际不会写到块设备中去,只会压缩后写到模拟的块设备中,其实也就是还是在内存中,只是通过压缩了,但压缩和解压缩的速度远远比读写IO好。因此在嵌入式设备广泛使用。压缩算法常用LZ4(压缩率不高,但压缩和解压缩速度非常快,对性能影响小)。

    这里写图片描述

    ZCACHE

    oracle提出的一种实现,也是memory与blk dev之间的一层“cache”,与zswap比较接近,但使用的内存分配方法不同。zswap使用zsmalloc,zcache使用zbud或z3fold。

    内存压缩内存分配器

    zbud

    zbud是一个专门为存储压缩page而设计的内存分配器.用于将两个压缩的page存到一个单独的page中.zbud page 被分成chunks,每一个chunk的size是64byte
    这里写图片描述

    zsmalloc

    在内核中,slab分配器用于分配较小块内存,它将多个相同大小的对象放在同一个内存页中,但这些对象可能不能正好占满整个内存页,从而产生碎片造成浪费,于是内核尝试为对象分配多个物理连续内存页从而使碎片尽可能小,但在低内存设备上要分配多个连续内存页可能十分困难,特别在系统运行较长时间后,这变得几乎不可能。最坏情况下,当对象大小略大于PAGE_SIZE/2时,每个内存页接近一般的内存将被浪费。
    所以,我们需要一种新的分配器,用于像vmalloc那样分配只要求虚拟连续的内存块。
    Zsmalloc分配器尝试将多个相同大小的对象存放在组合页(称为zspage)中,这个组合页不要求物理连续,从而提高内存的使用率。但是,使用zsmalloc分配器有以下条件:

    • 不要求物理内存连续
    • 在使用对象前必须显示映射
    • 对象必须在原子上下文中访问

    • 这里写图片描述

    z3fold

    新的内存分配器。

    • 压缩率更高(3x)

    这里写图片描述

    为什么zram不能用zbud?

    这里写图片描述

    这里写图片描述

    这里写图片描述

    统一内存分配接口zpool

    zpool是一个统一的内存分配接口,使用者为zbud,zsmalloc,z3fold.

    const char *zpool_get_type(struct zpool *zpool)
    {
        return zpool->driver->type;
    }
    
    int zpool_malloc(struct zpool *zpool, size_t size, gfp_t gfp,
                unsigned long *handle)
    {
        return zpool->driver->malloc(zpool->pool, size, gfp, handle);
    }
    void zpool_free(struct zpool *zpool, unsigned long handle)
    {
        zpool->driver->free(zpool->pool, handle);
    }

    参考资料

    [1].刘勃:Linux内核中的内存压缩技术.pdf
    [2].https://www.elinux.org/File:Z3fold.pdf
    [3].LinuxConJapan2013-Kamezawa.pdf

    展开全文
  • 管理物理内存的数据结构本身占用内存较,不使用于较大内存情况 无法解决空洞问题,不管是FLAT还是SPARCE模型都无法解决一个节点内的内存空洞问题,必须是一段连续空间比较浪费空间 基于上述两个主要问题linux ...

    在内核FLAT和DISCONTIGMEM管理模型中,其实一直都存在两个问题

    • 管理物理内存的数据结构本身占用内存较多,不使用于较大内存情况
    • 无法解决空洞问题,不管是FLAT还是SPARCE模型都无法解决一个节点内的内存空洞问题,必须是一段连续空间即mem_map数组就很大,即使中间存在黑洞,mem_map数组必须也得申请,比较浪费空间

    基于上述两个主要问题linux 物理内存模型技术演进到SPARCE模型 本意是稀疏即不再是连续的,space模式是从https://lwn.net/Articles/134804/开引入到内核中,下面说明了该模型的几个优势:

    Sparsemem abstracts the use of discontiguous mem_maps[].This kind of mem_map[] is needed by discontiguous memory machines (like in the old CONFIG_DISCONTIGMEM case) as well as memory hotplug systems.Sparsemem replaces DISCONTIGMEM when enabled, and it is hoped that it can eventually become a complete replacement. A significant advantage over DISCONTIGMEM is that it's completely separated from CONFIG_NUMA.  When producing this patch, it became apparent in that NUMA and DISCONTIG are often confused.

    Another advantage is that sparse doesn't require each NUMA node's ranges to be contiguous.  It can handle overlapping ranges between nodes with no problems, where DISCONTIGMEM currently throws away that memory.

    如今大部分系统都是采用SPARCE模型,但是如果不了解前两种模型,那么对理解第三种模型意义就显得没有那么深入。

    SPARCE模型

    针对DISCONTIGMEM模型中一个节点内的mem_map数组必须连续的这一问题,SPARCE模型对技术提出了演进方案,将ZONE_MEM_MAP这一个数组进一步划分颗粒度,将内存划分为一段段SECTION以减少因为空洞带来的mem_map空间浪费,在每个section内划分section mem map,其管理框架大概如下:

     物理内存按照实际情况被划分成一段段section进行管理,每个ZONE下面有若干个section组成,一个节点内的mem_map即不在是连续的,只有存在实际物理内存时才会有相应的section以及section mem map,同时继承了DISCONTIGMEM模型中全局mem_map为一个虚拟的数组一样,SPARCE模型也有一个虚拟的全局数组为vmem_map,整个全局section分布如下图(该图来源于https://blog.csdn.net/bin_linux96/article/details/83343420)所示:

    mem_section管理

     整个mem_section由一个全局mem_section进行管理所有的内存,以方便后续进行转换:

    #ifdef CONFIG_SPARSEMEM_EXTREME
    extern struct mem_section **mem_section;
    #else
    extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
    #endif

     section为管理内存的一个基本单位,所有section都归属于mem_section,mem_section 为一个二维数组,第一维index为 NR_SECTION_ROOTS,二维index为SECTIONS_PER_ROOT,以便减少空间浪费。一个64位系统中的一个虚拟地址被划分为SECTION_ROOTS、SECTION_PER_ROOT、PFN以及页偏移等,以下为arm 64位的地址划分方法(来自于知乎大神Yannhttps://zhuanlan.zhihu.com/p/220068494)具体划分如下:preview

    整个PFNP被划分位ROOT NUMBER、SECTIONS_PER_ROOT以及PFN_SECTION_SHIT(即对应的mem_section内的PFN)。每位架构不相同,其划分地址范围可能不同。

     PAGE与PFN转换

    SPARCE同样定义了PAGE与PFN宏如下:

    #define page_to_pfn __page_to_pfn
    #define pfn_to_page __pfn_to_page

    page_to_pfn(classic sparse方法)

    将相应物理页转换成pfn,调用的时__page_to_pfn:

    #define __page_to_pfn(pg)					\
    ({	const struct page *__pg = (pg);				\
    	int __sec = page_to_section(__pg);			\
    	(unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec)));	\
    })

    pg为指向section数组中的page 结构地址,具体思路

    • 首先调用page_to_section将物理页转换成section 查找要相应的section。
    • 然后根据section获取到相应的section的第一个页的地址即对应的相应的mem_section的mem_map的首地址,
    • 最后根据page的地址减去section mem_map的地址求出pfn

    page_to_section(classic sparse方法)

    page_to_section函数定如下:

    static inline unsigned long page_to_section(const struct page *page)
    {
    	return (page->flags >> SECTIONS_PGSHIFT) & SECTIONS_MASK;
    }

    可以看到section 可以同时存储到page flag中,其page flags可以划分以下几个部分:

    将SECTION、NODE等信息存储到flags中主要是为了节省struct page内存空间,各个段 的大小以及位置有相应宏定义:

    #define SECTIONS_PGOFF		((sizeof(unsigned long)*8) - SECTIONS_WIDTH)
    #define NODES_PGOFF		(SECTIONS_PGOFF - NODES_WIDTH)
    #define ZONES_PGOFF		(NODES_PGOFF - ZONES_WIDTH)
    #define LAST_CPUPID_PGOFF	(ZONES_PGOFF - LAST_CPUPID_WIDTH)
    #define KASAN_TAG_PGOFF		(LAST_CPUPID_PGOFF - KASAN_TAG_WIDTH)
    
    /*
     * Define the bit shifts to access each section.  For non-existent
     * sections we define the shift as 0; that plus a 0 mask ensures
     * the compiler will optimise away reference to them.
     */
    #define SECTIONS_PGSHIFT	(SECTIONS_PGOFF * (SECTIONS_WIDTH != 0))
    #define NODES_PGSHIFT		(NODES_PGOFF * (NODES_WIDTH != 0))
    #define ZONES_PGSHIFT		(ZONES_PGOFF * (ZONES_WIDTH != 0))
    #define LAST_CPUPID_PGSHIFT	(LAST_CPUPID_PGOFF * (LAST_CPUPID_WIDTH != 0))
    #define KASAN_TAG_PGSHIFT	(KASAN_TAG_PGOFF * (KASAN_TAG_WIDTH != 0))
    
    /* NODE:ZONE or SECTION:ZONE is used to ID a zone for the buddy allocator */
    #ifdef NODE_NOT_IN_PAGE_FLAGS
    #define ZONEID_SHIFT		(SECTIONS_SHIFT + ZONES_SHIFT)
    #define ZONEID_PGOFF		((SECTIONS_PGOFF < ZONES_PGOFF)? \
    						SECTIONS_PGOFF : ZONES_PGOFF)
    #else
    #define ZONEID_SHIFT		(NODES_SHIFT + ZONES_SHIFT)
    #define ZONEID_PGOFF		((NODES_PGOFF < ZONES_PGOFF)? \
    						NODES_PGOFF : ZONES_PGOFF)
    #endif
    
    #define ZONEID_PGSHIFT		(ZONEID_PGOFF * (ZONEID_SHIFT != 0))
    
    #define ZONES_MASK		((1UL << ZONES_WIDTH) - 1)
    #define NODES_MASK		((1UL << NODES_WIDTH) - 1)
    #define SECTIONS_MASK		((1UL << SECTIONS_WIDTH) - 1)
    #define LAST_CPUPID_MASK	((1UL << LAST_CPUPID_SHIFT) - 1)
    #define KASAN_TAG_MASK		((1UL << KASAN_TAG_WIDTH) - 1)
    #define ZONEID_MASK		((1UL << ZONEID_SHIFT) - 1)

    __nr_to_section

    根据相应的nr从mem_section数组中查找到相应的struct mem_section结构:

    static inline struct mem_section *__nr_to_section(unsigned long nr)
    {
    #ifdef CONFIG_SPARSEMEM_EXTREME
    	if (!mem_section)
    		return NULL;
    #endif
    	if (!mem_section[SECTION_NR_TO_ROOT(nr)])
    		return NULL;
    	return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
    }

    此时从page中查到section nr是基于mem_section的一个全局索引,需要将其转换成二维索引,其nr划分如下:

    __section_mem_map_addr

    获取到 mem_section中的section mem map:

    static inline struct page *__section_mem_map_addr(struct mem_section *section)
    {
    	unsigned long map = section->section_mem_map;
    	map &= SECTION_MAP_MASK;
    	return (struct page *)map;
    }

    其中section mem_map的基地为map,将其基地返回

    计算出pfn

    最后根据根据page 地址计算出在mem map中pfn:

    (__pg - __section_mem_map_addr(__nr_to_section(__sec)))

    核心思路

     根据page 计算出pfn核心思路就是根据page中的flag获取到相对应的section,然后根据section中的mem_map计算出该page的pfn.理解page中的flag划分 以及nr的划分非常重要。

    注意section_mem_map里面包含一层转换,使转换出的pfn为全局pfn,而不是section mem_map内的pfn。

    pfn_to_page

    根据pfn查找到相应page,实现思路和page_to_pfn相反,根据pfn查找到相对应的mem section然后根据pfn获取到mem secton中的物理页

    #define __pfn_to_page(pfn)				\
    ({	unsigned long __pfn = (pfn);			\
    	struct mem_section *__sec = __pfn_to_section(__pfn);	\
    	__section_mem_map_addr(__sec) + __pfn;		\
    })

    __pfn_to_section

    __pfn_to_section根据pfn查找到相对应的section:

    static inline struct mem_section *__pfn_to_section(unsigned long pfn)
    {
    	return __nr_to_section(pfn_to_section_nr(pfn));
    }

    pfn_to_setion_nr从pfn中获取到,pfn整个划分如下:

    获取到section_mem_map

    __section_mem_map_addr(__sec)

    根据获取到的mem_section调用__section_mem_map_addr,

    计算page

    __section_mem_map_addr(__sec) + __pfn

    获取到section_mem_map然后根据pfn获取到物理页。

    核心思路

    需要了解pfn的划分以及 section_mem_map的基地址在初始化已经转换为全局,不需要先转成section_mem内的index 再转成全局pfn,只需要转成一次就可以

    vmemmap(sparse vmemmap方法)

    述转换过程可以看出每次转换都需要先找到对应的mem_section 然后找到对应的section_mem_map转换效率过低,为了提高转换效率,内核中使用vmemmap虚拟mem map数组来提高效率,相当于DISCONTIGMEM模型中的全局mem_map数组,引入vmemap数组之后转换得到了很大提高,之间查找数组偏移即可,代码如下:

    #define __pfn_to_page(pfn)	(vmemmap + (pfn))
    #define __page_to_pfn(page)	(unsigned long)((page) - vmemmap)

    vmemmap数组功能需要使用CONFIG_SPARSEMEM_VMEMMAP宏开启。

    展开全文
  • 内核需要保持该结构尽可能小,因为对于现代计算机,物理内存包含大量的页帧,即便是增加page一点点空间,都会导致保存所有页帧page结构多占用大量物理内存。例如当页长度为4KB,主内存384MB时大约需要100000页。  ...

    1.页面

        系统的内存划分成大小确定的许多块,这些块也称为页面帧,页帧是系统内存的最小单位。内核需要保持该结构尽可能小,因为对于现代计算机,物理内存包含大量的页帧,即便是增加page一点点空间,都会导致保存所有页帧page结构多占用大量物理内存。例如当页长度为4KB,主内存384MB时大约需要100000页。

         每个物理页面帧由一个 struct page 描述。它在<kernel/include/linux/mm_types.h>中定义如下:

    /*
     * Each physical page in the system has a struct page associated with
     * it to keep track of whatever it is we are using the page for at the
     * moment. Note that we have no way to track which tasks are using
     * a page, though if it is a pagecache page, rmap structures can tell us
     * who is mapping it.
     *
     * The objects in struct page are organized in double word blocks in
     * order to allows us to use atomic double word operations on portions
     * of struct page. That is currently only used by slub but the arrangement
     * allows the use of atomic double word operations on the flags/mapping
     * and lru list pointers also.
     */
    struct page {
    	/* First double word block */
    	unsigned long flags;		/* Atomic flags, some possibly
    					 * updated asynchronously */
    	union {
    		struct address_space *mapping;	/* If low bit clear, points to
    						 * inode address_space, or NULL.
    						 * If page mapped as anonymous
    						 * memory, low bit is set, and
    						 * it points to anon_vma object:
    						 * see PAGE_MAPPING_ANON below.
    						 */
    		void *s_mem;			/* slab first object */
    	};
    
    	/* Second double word */
    	struct {
    		union {
    			pgoff_t index;		/* Our offset within mapping. */
    			void *freelist;		/* sl[aou]b first free object */
    		};
    
    		union {
    #if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
    	defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
    			/* Used for cmpxchg_double in slub */
    			unsigned long counters;
    #else
    			/*
    			 * Keep _count separate from slub cmpxchg_double data.
    			 * As the rest of the double word is protected by
    			 * slab_lock but _count is not.
    			 */
    			unsigned counters;
    #endif
    
    			struct {
    
    				union {
    					/*
    					 * Count of ptes mapped in
    					 * mms, to show when page is
    					 * mapped & limit reverse map
    					 * searches.
    					 *
    					 * Used also for tail pages
    					 * refcounting instead of
    					 * _count. Tail pages cannot
    					 * be mapped and keeping the
    					 * tail page _count zero at
    					 * all times guarantees
    					 * get_page_unless_zero() will
    					 * never succeed on tail
    					 * pages.
    					 */
    					atomic_t _mapcount;
    
    					struct { /* SLUB */
    						unsigned inuse:16;
    						unsigned objects:15;
    						unsigned frozen:1;
    					};
    					int units;	/* SLOB */
    				};
    				atomic_t _count;		/* Usage count, see below. */
    			};
    
    			unsigned int active;	/* SLAB */
    		};
    	};
    
    	/* Third double word block */
    	union {
    		struct list_head lru;	/* Pageout list, eg. active_list
    					 * protected by zone->lru_lock !
    					 * Can be used as a generic list
    					 * by the page owner.
    					 */
    		struct {		/* slub per cpu partial pages */
    			struct page *next;	/* Next partial slab */
    #ifdef CONFIG_64BIT
    			int pages;	/* Nr of partial slabs left */
    			int pobjects;	/* Approximate # of objects */
    #else
    			short int pages;
    			short int pobjects;
    #endif
    		};
    
    		struct slab *slab_page; /* slab fields */
    		struct rcu_head rcu_head;	/* Used by SLAB
    						 * when destroying via RCU
    						 */
    		/* First tail page of compound page */
    		struct {
    			compound_page_dtor *compound_dtor;
    			unsigned long compound_order;
    		};
    
    #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS
    		pgtable_t pmd_huge_pte; /* protected by page->ptl */
    #endif
    	};
    
    	/* Remainder is not double word aligned */
    	union {
    		unsigned long private;		/* Mapping-private opaque data:
    					 	 * usually used for buffer_heads
    						 * if PagePrivate set; used for
    						 * swp_entry_t if PageSwapCache;
    						 * indicates order in the buddy
    						 * system if PG_buddy is set.
    						 */
    #if USE_SPLIT_PTE_PTLOCKS
    #if ALLOC_SPLIT_PTLOCKS
    		spinlock_t *ptl;
    #else
    		spinlock_t ptl;
    #endif
    #endif
    		struct kmem_cache *slab_cache;	/* SL[AU]B: Pointer to slab */
    		struct page *first_page;	/* Compound tail pages */
    	};
    
    #ifdef CONFIG_MEMCG
    	struct mem_cgroup *mem_cgroup;
    #endif
    
    	/*
    	 * On machines where all RAM is mapped into kernel address space,
    	 * we can simply calculate the virtual address. On machines with
    	 * highmem some memory is mapped into kernel virtual memory
    	 * dynamically, so we need a place to store that address.
    	 * Note that this field could be 16 bits on x86 ... ;)
    	 *
    	 * Architectures with slow multiplication can define
    	 * WANT_PAGE_VIRTUAL in asm/page.h
    	 */
    #if defined(WANT_PAGE_VIRTUAL)
    	void *virtual;			/* Kernel virtual address (NULL if
    					   not kmapped, ie. highmem) */
    #endif /* WANT_PAGE_VIRTUAL */
    
    #ifdef CONFIG_KMEMCHECK
    	/*
    	 * kmemcheck wants to track the status of each byte in a page; this
    	 * is a pointer to such a status block. NULL if not tracked.
    	 */
    	void *shadow;
    #endif
    
    #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
    	int _last_cpupid;
    #endif
    }

    下面对page的主要字段进行描述:

    • flags:用来描述页面状态的标志,这些标志在<kernel/include/linux/page-flags.h>中定义如下:
    enum pageflags {
    	PG_locked,		/* Page is locked. Don't touch. */
    	PG_error,
    	PG_referenced,
    	PG_uptodate,
    	PG_dirty,
    	PG_lru,
    	PG_active,
    	PG_slab,
    	PG_owner_priv_1,	/* Owner use. If pagecache, fs may use*/
    	PG_arch_1,
    	PG_reserved,
    	PG_private,		/* If pagecache, has fs-private data */
    	PG_private_2,		/* If pagecache, has fs aux data */
    	PG_writeback,		/* Page is under writeback */
    #ifdef CONFIG_PAGEFLAGS_EXTENDED
    	PG_head,		/* A head page */
    	PG_tail,		/* A tail page */
    #else
    	PG_compound,		/* A compound page */
    #endif
    	PG_swapcache,		/* Swap page: swp_entry_t in private */
    	PG_mappedtodisk,	/* Has blocks allocated on-disk */
    	PG_reclaim,		/* To be reclaimed asap */
    	PG_swapbacked,		/* Page is backed by RAM/swap */
    	PG_unevictable,		/* Page is "unevictable"  */
    #ifdef CONFIG_MMU
    	PG_mlocked,		/* Page is vma mlocked */
    #endif
    #ifdef CONFIG_ARCH_USES_PG_UNCACHED
    	PG_uncached,		/* Page has been mapped as uncached */
    #endif
    #ifdef CONFIG_MEMORY_FAILURE
    	PG_hwpoison,		/* hardware poisoned page. Don't touch */
    #endif
    #ifdef CONFIG_TRANSPARENT_HUGEPAGE
    	PG_compound_lock,
    #endif
    #if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT)
    	PG_young,
    	PG_idle,
    #endif
    	__NR_PAGEFLAGS,
    
    	/* Filesystems */
    	PG_checked = PG_owner_priv_1,
    
    	/* Two page bits are conscripted by FS-Cache to maintain local caching
    	 * state.  These bits are set on pages belonging to the netfs's inodes
    	 * when those inodes are being locally cached.
    	 */
    	PG_fscache = PG_private_2,	/* page backed by cache */
    
    	/* XEN */
    	/* Pinned in Xen as a read-only pagetable page. */
    	PG_pinned = PG_owner_priv_1,
    	/* Pinned as part of domain save (see xen_mm_pin_all()). */
    	PG_savepinned = PG_dirty,
    	/* Has a grant mapping of another (foreign) domain's page. */
    	PG_foreign = PG_owner_priv_1,
    
    	/* SLOB */
    	PG_slob_free = PG_private,
    };

    下表列出了一些常用的标志及其含义所示:

    标志名 含义
    PG_locked 页被锁定,例如,在磁盘I/O操作中涉及的页
    PG_error 在传输页时发生I/O错误
    PG_referenced 刚刚访问过的页
    PG_uptodate 在完成读操作后置位,除非发生磁盘I/O错误
    PG_dirty 页已经被修改
    PG_lru 页在活动或者非活动链表中
    PG_active 页在活动页链表中
    PG_slab 包含在slab中的页框
    PG_arch_1 特定于体系结构的页面状态位。一般的代码保证当页面首次进入页面缓存时,该位将被清除。
    PG_reserved 保留给内核或没有使用的页
    PG_private 如果page中的private成员非空,则需要设置该标志。参考下面对private的解释。
    PG_writeback 正在将页写到磁盘上
    PG_compound 通过扩展分页机制处理页框
    PG_swapcache 页属于对换高速缓存
    PG_mappedtodisk 页框中的所有数据对应于磁盘上分配的块
    PG_reclaim 为回收内存对页已经做了写入磁盘的标记
    PG_swapbacked 该page的后备存储器是swap
    PG_unevictable 该page被锁住,不能交换,并会出现在LRU_UNEVICTABLE链表中,它包括的几种page:ramdisk或ramfs使用的页、shm_locked、mlock锁定的页。
    PG_mlocked 该page在vma中被锁定,一般是通过系统调用mlock()锁定了一段内存

    下面列出了测试,设置和清除这些标志的宏,他们都定义在<kernel/include/linux/page-flags.h>中:

    /*
     * Macros to create function definitions for page flags
     */
    #define TESTPAGEFLAG(uname, lname)					\
    static inline int Page##uname(const struct page *page)			\
    			{ return test_bit(PG_##lname, &page->flags); }
    
    #define SETPAGEFLAG(uname, lname)					\
    static inline void SetPage##uname(struct page *page)			\
    			{ set_bit(PG_##lname, &page->flags); }
    
    #define CLEARPAGEFLAG(uname, lname)					\
    static inline void ClearPage##uname(struct page *page)			\
    			{ clear_bit(PG_##lname, &page->flags); }
    
    #define __SETPAGEFLAG(uname, lname)					\
    static inline void __SetPage##uname(struct page *page)			\
    			{ __set_bit(PG_##lname, &page->flags); }
    
    #define __CLEARPAGEFLAG(uname, lname)					\
    static inline void __ClearPage##uname(struct page *page)		\
    			{ __clear_bit(PG_##lname, &page->flags); }
    
    #define TESTSETFLAG(uname, lname)					\
    static inline int TestSetPage##uname(struct page *page)			\
    		{ return test_and_set_bit(PG_##lname, &page->flags); }
    
    #define TESTCLEARFLAG(uname, lname)					\
    static inline int TestClearPage##uname(struct page *page)		\
    		{ return test_and_clear_bit(PG_##lname, &page->flags); }
    
    #define __TESTCLEARFLAG(uname, lname)					\
    static inline int __TestClearPage##uname(struct page *page)		\
    		{ return __test_and_clear_bit(PG_##lname, &page->flags); }
    
    #define PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)		\
    	SETPAGEFLAG(uname, lname) CLEARPAGEFLAG(uname, lname)
    
    #define __PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)		\
    	__SETPAGEFLAG(uname, lname)  __CLEARPAGEFLAG(uname, lname)
    
    #define TESTSCFLAG(uname, lname)					\
    	TESTSETFLAG(uname, lname) TESTCLEARFLAG(uname, lname)
    
    #define TESTPAGEFLAG_FALSE(uname)					\
    static inline int Page##uname(const struct page *page) { return 0; }
    
    #define SETPAGEFLAG_NOOP(uname)						\
    static inline void SetPage##uname(struct page *page) {  }
    
    #define CLEARPAGEFLAG_NOOP(uname)					\
    static inline void ClearPage##uname(struct page *page) {  }
    
    #define __CLEARPAGEFLAG_NOOP(uname)					\
    static inline void __ClearPage##uname(struct page *page) {  }
    
    #define TESTSETFLAG_FALSE(uname)					\
    static inline int TestSetPage##uname(struct page *page) { return 0; }
    
    #define TESTCLEARFLAG_FALSE(uname)					\
    static inline int TestClearPage##uname(struct page *page) { return 0; }
    
    #define __TESTCLEARFLAG_FALSE(uname)					\
    static inline int __TestClearPage##uname(struct page *page) { return 0; }
    
    #define PAGEFLAG_FALSE(uname) TESTPAGEFLAG_FALSE(uname)			\
    	SETPAGEFLAG_NOOP(uname) CLEARPAGEFLAG_NOOP(uname)
    
    #define TESTSCFLAG_FALSE(uname)						\
    	TESTSETFLAG_FALSE(uname) TESTCLEARFLAG_FALSE(uname)
    • mapping:
    1. 当page->mapping == NULL,该page属于交换高速缓存页(swap cache),当需要使用地址空间时会指定交换分区的地址空间swapper_space。
    2. 当page->mapping != NULL,并且bit[0] == 0,该page属于页缓存或文件映射,mapping指向inode address_space。
    3. 当page->mapping != NULL,并且bit[0] != 0,该page属于匿名映射,且设置了PAGE_MAPPING_ANON位(bit[0] )被,page->mapping指向struct anon_vma对象。
    4. 当page->mapping != NULL,并且bit[1] != 0,bit[0] != 0,该page映射在VM_MERGEABLE区域的匿名页面上,如果启用了CONFIG_KSM,那么PAGE_MAPPING_KSM(bit[1]) 位与PAGE_MAPPING_ANON位一起被设置;然后page->mapping,不是指向anon_vma, 而是指向KSM与合并页相关联的private数据结构。

         内核定义了一些API来取得mapping指针的状态,如下所示:

    /*
     * On an anonymous page mapped into a user virtual memory area,
     * page->mapping points to its anon_vma, not to a struct address_space;
     * with the PAGE_MAPPING_ANON bit set to distinguish it.  See rmap.h.
     *
     * On an anonymous page in a VM_MERGEABLE area, if CONFIG_KSM is enabled,
     * the PAGE_MAPPING_KSM bit may be set along with the PAGE_MAPPING_ANON bit;
     * and then page->mapping points, not to an anon_vma, but to a private
     * structure which KSM associates with that merged page.  See ksm.h.
     *
     * PAGE_MAPPING_KSM without PAGE_MAPPING_ANON is currently never used.
     *
     * Please note that, confusingly, "page_mapping" refers to the inode
     * address_space which maps the page from disk; whereas "page_mapped"
     * refers to user virtual address space into which the page is mapped.
     */
    #define PAGE_MAPPING_ANON	1
    #define PAGE_MAPPING_KSM	2
    #define PAGE_MAPPING_FLAGS	(PAGE_MAPPING_ANON | PAGE_MAPPING_KSM)
    
    /* 获取page->mapping anon_vma位状态,如果该位被设置,则page->mapping指向该page
     * 的anon_vma私有数据
     */
    static inline int PageAnon(struct page *page)
    {
    	return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
    }
    
    #ifdef CONFIG_KSM
    /*
     * A KSM page is one of those write-protected "shared pages" or "merged pages"
     * which KSM maps into multiple mms, wherever identical anonymous page content
     * is found in VM_MERGEABLE vmas.  It's a PageAnon page, pointing not to any
     * anon_vma, but to that page's node of the stable tree.
     */
    /* 获取page->mapping anon_vma位和ksm位状态,如果都被设置,则page->mapping指向该page
     * 的KSM私有数据
     */
    static inline int PageKsm(struct page *page)
    {
    	return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) ==
    				(PAGE_MAPPING_ANON | PAGE_MAPPING_KSM);
    }
    #else
    TESTPAGEFLAG_FALSE(Ksm)
    #endif
    • s_mem:第一个slab对象。
    • index:这个字段有两个用途,它的意义与该页面的状态有关。如果页面是文件映射的一部分,它就是页面在文件中的偏移。如果页面是交换高速缓存的一部分,它就是在交换地址空间中(swapper_space)address_space的偏移量。此外,如果包含页面的块被释放以提供给一个特殊的进程,那么被释放的块的顺序存放在index中。这在函数__free_pages_ok()中设置。
    • freelist:所在slab的第一个free对象。
    • _mapcount:页映射计数器, 被页表映射的次数,也就是说该page同时被多少个进程共享。 初始值为-1,如果只被一个进程的页表映射了,该值为0. 如果该page处于伙伴系统中,该值为PAGE_BUDDY_MAPCOUNT_VALUE(-128),内核通过判断该值是否为PAGE_BUDDY_MAPCOUNT_VALUE来确定该page是否属于伙伴系统。
    • inuse:16:在当前slab中,已经分配的slab数量。
    • objects:15:在当前slab中,总slab数量。
    • frozen:1:如果为1,表示只能由活动CPU分配SLAB其他CPU只能向其释放SLAB。
    • _count:引用计数,表示内核中引用该page的次数, 如果要操作该page, 引用计数会+1, 操作完成-1. 当该值为0时, 表示没有引用该page的位置,所以该page可以被解除映射,这往往在内存回收时是有用的
    • active:SLAB用于管理页面中可用的SLAB对象。
    • lru:对SLAB来说,通过lru将其链接到SLAB的partial、free等链表中;在伙伴系统中,即当页空闲时,lru指向伙伴系统链表中相邻元素。
    • private:是一个指向"私有"数据的指针,虚拟内存管理会忽略该数据。根据页的用途,可以用不同的方式指向该指针。大多数情况下它用于将页与数据缓冲区关联起来。private私有数据指针, 由应用场景确定其具体的含义:
      a.如果设置了PG_private标志,则private字段指向struct buffer_head
      b.如果设置了PG_compound,则指向struct page
      c.如果设置了PG_swapcache标志,private存储了该page在交换分区中对应的位置信息swp_entry_t。
      d.如果_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE,说明该page位于伙伴系统,private存储该伙伴的阶
    • slab_cache:指向本页关联的SLAB描述符。

    2.页面初始化

        系统在初始化管理区(zone)后,紧接着就会建立和初始化zone相关的page。在【Linux内核学习笔记二】内存管理-管理区(zone)中已经讲述了free_area_init_core()用于向每个zone填充相关信息 。 free_area_init_core()向每个zone填充相关信息的过程中调用memmap_init()建立和初始化page,并设置所有page的PG_reserved位。

            memmap_init()通过memmap_init_zone()建立和初始化某个zone中的page,代码如下:

    /*
     * Initially all pages are reserved - free ones are freed
     * up by free_all_bootmem() once the early boot process is
     * done. Non-atomic initialization, single-pass.
     */
    /**
     * 将zone中的页框PG_reserved置位。表示该页不可用。
     * 同时初始化页框中其他值。
     */
    void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
    		unsigned long start_pfn, enum memmap_context context)
    {
    	pg_data_t *pgdat = NODE_DATA(nid);
    	unsigned long end_pfn = start_pfn + size;
    	unsigned long pfn;
    	struct zone *z;
    	unsigned long nr_initialised = 0;
    
    	if (highest_memmap_pfn < end_pfn - 1)
    		highest_memmap_pfn = end_pfn - 1;
    
    	z = &pgdat->node_zones[zone];
    	for (pfn = start_pfn; pfn < end_pfn; pfn++) {
    		/*
    		 * There can be holes in boot-time mem_map[]s
    		 * handed to this function.  They do not
    		 * exist on hotplugged memory.
    		 */
    		if (context == MEMMAP_EARLY) {
    			if (!early_pfn_valid(pfn))
    				continue;
    			if (!early_pfn_in_nid(pfn, nid))
    				continue;
    			if (!update_defer_init(pgdat, pfn, end_pfn,
    						&nr_initialised))
    				break;
    		}
    
    		/*
    		 * Mark the block movable so that blocks are reserved for
    		 * movable at startup. This will force kernel allocations
    		 * to reserve their blocks rather than leaking throughout
    		 * the address space during boot when many long-lived
    		 * kernel allocations are made. Later some blocks near
    		 * the start are marked MIGRATE_RESERVE by
    		 * setup_zone_migrate_reserve()
    		 *
    		 * bitmap is created for zone's valid pfn range. but memmap
    		 * can be created for invalid pages (for alignment)
    		 * check here not to call set_pageblock_migratetype() against
    		 * pfn out of zone.
    		 */
    		if (!(pfn & (pageblock_nr_pages - 1))) {
                            //创建page
    			struct page *page = pfn_to_page(pfn);
                            //初始化page
    			__init_single_page(page, pfn, zone, nid);
    			set_pageblock_migratetype(page, MIGRATE_MOVABLE);
    		} else {
    			__init_single_pfn(pfn, zone, nid);
    		}
    	}
    }
    

             memmap_init_zone()中,在一个循环中建立和初始化了该zone地址范围内的所有page,函数pfn_to_page()用于创建page,

    __init_single_page()用于初始化page,__init_single_page()代码如下所示:

    static void __meminit __init_single_page(struct page *page, unsigned long pfn,
    				unsigned long zone, int nid)
    {
    	//建立page到zone和node的映射
            set_page_links(page, zone, nid, pfn);
            init_page_count(page);
    	page_mapcount_reset(page);
    	page_cpupid_reset_last(page);
    
    	INIT_LIST_HEAD(&page->lru);
    #ifdef WANT_PAGE_VIRTUAL
    	/* The shift won't overflow because ZONE_NORMAL is below 4G. */
    	if (!is_highmem_idx(zone))
    		set_page_address(page, __va(pfn << PAGE_SHIFT));
    #endif
    }

    3.页面和zone,node之间的映射

           在上一节中内核已经通过set_page_links()将页面映射到了zone和node。映射过程中,内核使用struct page的flags中的字段来保存页所属的zone以及node。通过page_zone(),可以获取某个page所在的zone。

           以下是页面和zone,node之间的映射的一些api:

    static inline struct zone *page_zone(const struct page *page)
    {
    	return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
    }
    
    static inline void set_page_zone(struct page *page, enum zone_type zone)
    {
    	page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
    	page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
    }
    
    static inline void set_page_node(struct page *page, unsigned long node)
    {
    	page->flags &= ~(NODES_MASK << NODES_PGSHIFT);
    	page->flags |= (node & NODES_MASK) << NODES_PGSHIFT;
    }

           其中NODE_DATA为全局的node表。在UMA结构中, 只有一个node结点即contig_page_data, 此时NODE_DATA直接指向了全局的contig_page_data, 而与node的编号nid无关,NODE_DATA在<kernel/include/linux/mmzone.h>中定义如下:

    #ifndef CONFIG_NEED_MULTIPLE_NODES
    
    extern struct pglist_data contig_page_data;
    #define NODE_DATA(nid)		(&contig_page_data)

            在NUMA结构的系统中, 所有的node都存储在node_data数组中,NODE_DATA直接通过node编号索引即可,以x86-

    32系统为例,NODE_DATA的在<kernel/arch/x86/include/asm/mmzone_32.h>中定义如下:

    #ifdef CONFIG_NUMA
    extern struct pglist_data *node_data[];
    #define NODE_DATA(nid)	(node_data[nid])
    #endif /* CONFIG_NUMA */

     

    展开全文
  • Linux内核如何区分匿名页映射?

    千次阅读 2019-07-29 11:44:30
    物理内存,用于描述物理页的page结构体就越占用内存也就越,因此为了减少内存占用,page结构体大量使用了union联合体结构。如下我们只列举了本文下面要讲述的一些page成员。 include/linux/mm_ty...

    page结构体关键成员

    内核中使用page结构体来描述物理内存,每个物理页就对应一个page结构体来描述,所以page结构体占用的内存大小是与系统物理内存大小成正比的。物理内存越大,用于描述物理页的page结构体就越多,占用的内存也就越大,因此为了减少内存的占用,page结构体大量使用了union联合体结构。如下我们只列举了本文下面要讲述的一些page成员。

    include/linux/mm_types.h:
    
    struct page {
    	unsigned long flags;		/* Atomic flags, some possibly
    					 * updated asynchronously */
    	union {
    		struct address_space *mapping;	/* If low bit clear, points to
    						 * inode address_space, or NULL.
    						 * If page mapped as anonymous
    						 * memory, low bit is set, and
    						 * it points to anon_vma object:
    						 * see PAGE_MAPPING_ANON below.
    						 */
    		void *s_mem;			/* slab first object */
    		atomic_t compound_mapcount;	/* first tail page */
    		/* page_deferred_list().next	 -- second tail page */
    	};
    
    ...
          atomic_t _refcount;
    
    ...
    
    	/*
    	 * On machines where all RAM is mapped into kernel address space,
    	 * we can simply calculate the virtual address. On machines with
    	 * highmem some memory is mapped into kernel virtual memory
    	 * dynamically, so we need a place to store that address.
    	 * Note that this field could be 16 bits on x86 ... ;)
    	 *
    	 * Architectures with slow multiplication can define
    	 * WANT_PAGE_VIRTUAL in asm/page.h
    	 */
    	void *virtual;			/* Kernel virtual address (NULL if
    					   not kmapped, ie. highmem) */
    ...
    }
    /*
     * The struct page can be forced to be double word aligned so that atomic ops
     * on double words work. The SLUB allocator can make use of such a feature.
     */
    #ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE
    	__aligned(2 * sizeof(unsigned long))
    #endif
    ;
    
    • flags
      描述页的状态
    • _refcount
      描述页被引用计数
    • mapping
      这个结构体很关键,如果低位为0,它用于描述该页被映射的地址空间结构体,指向一个文件系统页缓存结构体struct address_space;如果低位为1,那么它指向一个匿名映射结构体struct anon_vma。
      因此这个mapping可能是一个(struct address_space *)类型的指针,也可能是一个(struct anon_vma *)类型的指针。
    • virtual
      注意这个virtual表示的是Kernel virtual address,内核虚拟地址而不是用户空间虚拟地址,表示物理页对应的内核虚拟地址,如果是高端内存,可能并没有kmap到内核虚拟地址,此时为NULL。
      一个物理页可能同时被映射到内核虚拟地址空间和用户虚拟地址空间,并不冲突,因为页表是通过虚拟地址转换为物理地址的,所以可能存在多对一的关系。

    mapping怎么区分匿名页映射的

    内核中的映射分为两种,一种为匿名映射,一种为文件映射,匿名映射对应的结构体为struct anon_vma ;文件映射会对应页缓存,结构体为struct address_space。由于地址对齐的关系,每个 struct address_spacestruct anon_vma 结构体都不会存放在奇数地址上,所以假如不做任何处理,mapping正常情况下的最低位肯定是0,那么由于page结构体在内存中会大量存在,为了充分利用每个bit的空间,这里使用了如下操作:

    • 保存struct anon_vma地址到mapping
    page->mapping = (void *)&anon_vma + 1;
    
    • 从mapping获取struct anon_vma地址
    struct anon_vma * anon_vma = (struct anon_vma *)(page->mapping - 1);
    

    通过保存struct anon_vma结构体地址时加1操作,提取struct anon_vma结构体地址时减1操作,从而可以利用在mapping中的最低位来区分当前page是否为匿名映射:

    #define PAGE_MAPPING_ANON   0x1
    
    static __always_inline int PageMappingFlags(struct page *page)
    {   
        return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) != 0;
    }
        
    static __always_inline int PageAnon(struct page *page)
    {
        page = compound_head(page);
        return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
    }
    
    

    这里可以看到通过判断mapping成员的最低位来区分是否为匿名映射。


    Linux 4.14 source code

    展开全文
  • free: free显示系统中可用和可用的...used:已使用多大。 free:未使用内存。 Shared:多个进程共享的内存总额。 Buff/cached:缓冲区和缓存的总和。 available:估计有多少内存可用于启动新应用程序而无需交换。 ...
  • 和其他很大型项目一样,Linux内核实现了很通用的数据结构,而且提倡大家在开发时重用。 一、链表结构 链表是一种存放和操作可变数量元素(常称为节点)的数据结构。链表和静态数组的不同之处在于,链表所包含的...
  • Linux内核OOM机制的详细分析

    千次阅读 2014-06-18 11:25:15
    Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。典型的情况是:某天一台机器突然ssh远程...
  • Linux 内核(kernel)

    2018-04-23 01:45:48
    内核文件就是在/boot 目录下以vmlinuz开头的文件,有时候会发现boot下有好几个以vmlinuz开头的文件,说明可以有内核,不过启动的时候只能选择一个来加载,Linux是单内核、模块化体系的。输出的三列信息分别为...
  • Linux内核裁减

    2010-11-21 22:56:00
    内核配置  内核配置的方法很,make config、make ... <br /> 需要牢记:不必要的驱动越内核就越,不仅运行速度慢、占用内存多,在少数情况下、还会引发其他问题。具体步骤如下: <br
  • linux内核裁减

    千次阅读 2007-11-27 08:38:00
    内核配置 内核配置的方法很,make config、make xconfig、make menuconfig、make oldconfig等等,它们的功能都是一样的,区别应该从... 需要牢记:不必要的驱动越内核就越,不仅运行速度慢、占用内存多,在
  • Linux 内核模块开发基本知识

    千次阅读 2014-05-30 23:28:49
    避免内核镜像太占用内存资源,所以可以编译进内核模块中去。 特点:动态的加载与卸载;不会被编译进内核镜像文件。 2、 学习方法。 范例程序->思维导图(指导性,纲领性,很重要)->自己动手编写程序。...
  • Linux内核 有个机制叫OOMkiller(Out-Of-Memorykiller),该机制会监控那些占用内存,尤其是瞬间很快消耗大量内存的进程,为了 防止内存耗尽而内核会把该进程杀掉。典型的情况是:某天一台机器突然ssh远程登录不...
  • miss,而CPU访问cache速度远大于内存访问,这样综合看来上下文切换花销还是很的。无用功占用CPU; 所以追求吞吐量和低延迟,这两个目标是矛盾的 编译内核选项有如下 服务器版追求吞吐量,配置为不抢占
  • Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。典型的情况是:某天一台机器突然ssh远程登录...
  • 需要在linux网卡驱动中加入一个自己的驱动,实现在内核态完成一些报文处理(这个过程可以实现一种零COPY的网络报文截获),对于复杂报文COPY下必要的数据交给用户态来完成(因为过于复杂的报文消耗CPU太,会导致...
  • stack size修改 hxl2009关注2人评论14575...处理方法是,首先通过优化连接池,将连接数控制在了800个左右,然后通过修改内核的stack size值,从默认的10M修改到1M,使连接占用内存大大减少。 ulimit -a 默认的st.
  • linux内存映射(一)

    千次阅读 2019-06-07 11:30:21
    由于所有用户进程总的虚拟地址空间比可用的物理内存大,因此只有最常用的部分才与物理页帧关联。这不是问题,因为大多数程序只占用实际可用内存的一小部分。在将磁盘上的数据映射到进程的虚拟地址空间的时,内核...
  • linux虚拟内存机制

    2013-06-19 13:20:09
    内核在CPU的4G线性地址空间上为进程分配连续空间后,会专门有一个机制(数据结构)M用来保存管理哪些进程占用了哪些线性... 但若进程占用资源,而内存较小的情况下,在进程切换时,上面的那个机制就会把活动少的进程
  • linux内存理解和整理

    2016-08-15 23:43:43
    用户进程内存空间,也是系统内核分配给该进程的VM(虚拟内存),但并不表示这个进程占用了这么多的RAM(物理内存)。这个空间有多大?命令top输出的VIRT值告诉了我们各个进程内存空间的大小(进程内存
  • 这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把次小的写合并成单次的写等等。 cache:是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以...

空空如也

空空如也

1 2 3 4 5 6
收藏数 118
精华内容 47
关键字:

linux内核占用多大内存

linux 订阅