Linux 内核内存管理一(物理内存管理)

目录

1.内存电路实现

2.内存泄露

2.1物理内存碎片解决机制

2.1.1 page结构 

2.1.2 内存区域

2.1.3 物理内存管理结构

 3. 伙伴算法系统

3.1 基础版本

3.2 伙伴系统的改进和完善

3.2.1 页面迁移分类

3.2.2 per-CPU 页面缓存

 3.3 伙伴分配器接口

3.4 伙伴系统的初始化

3.4.1 memblock管理器

3.4.2 memblock内存释放与buddyinfo系统的初始化

 3.5 CMA 分配器

4. slab slob slub机制--buddyinfo的更精细补充

4.1 slab机制

 4.2 实现

参考​​​​​​​


1.内存电路实现

  · D触发器  
         ... ... ...

2.内存泄露

670e81afebed4b4cb86dea308df9613a.png

2.1物理内存碎片解决机制

 思想:①划分区域权限②划分大小相同的块

9f07cfd3e640443ea0f5718890f14f22.png

 ZONE_DMA  专门为DMA划分的区域(X86使用)  防止内存碎片的影响而找不到连续内存

struct node 针对物理UMA和NUMA 内存架构的概念,多核CPUCPU和内存的架构 UMA和NUMA_张孟浩_jay的博客-CSDN博客_uma和numaCPU和内存之间的架构分为两种:1、UMAUMA全称为 Uniform Memory Access,叫做一致性内存访问多个CPU通过同一根总线来访问内存。无论多个CPU是访问内存的不同内存单元还是相同的内存单元,同一时刻,只有一个CPU能够访问内存。CPU之间通过总线串行的访问内存,所以会出现访问瓶颈!2、NUMANon-Uniform Memory Access ,非一致性内存访问。每个CPU都分配了一块内存,这样的话,多个CPU可以同时并行访问各自的内存,这样的话,读写内存的效率就上来了。https://blog.csdn.net/qq_40276626/article/details/121096365

struct zone  同样一块内存划分权限功能的概念 

struct page 针对Linux系统管理的概念

2.1.1 page结构 

struct page {
	/* First double word block */
	unsigned long flags;	
	struct address_space *mapping;
	struct {
		union {
			pgoff_t index;		/* Our offset within mapping. */
			void *freelist;		/* slub first free object */
		};

		union {
			/* Used for cmpxchg_double in slub */
			unsigned long counters;

			struct {
				union {
					atomic_t _mapcount;
					struct {
						unsigned inuse:16;
						unsigned objects:15;
						unsigned frozen:1;
					};
				};
				atomic_t _count;		/* Usage count, see below. */
			};
		};
	};

	/* Third double word block */
	union {
		struct list_head lru;	/* Pageout list, eg. active_list
					 * protected by zone->lru_lock !
					 */
		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
		};
	};

	/* Remainder is not double word aligned */
	union {
		unsigned long private;	
		//... ...
        struct kmem_cache *slab;	/* SLUB: Pointer to slab */
		struct page *first_page;	/* Compound tail pages */
	};


}
//... ...
};

·分类

page cache. 页缓存

page anon 匿名页

page slab slab页

... ...

·物理页帧pfn(page frame addr)和物理地址paddr

pfn=paddr>>PAGE_SHIFT

b1e090c5981846a88afd29fd4613b587.png

2.1.2 内存区域

·node (服务器才用的到很多node,一般的嵌入式只需要一个node)

typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];
	struct zonelist node_zonelists[MAX_ZONELISTS];
	int nr_zones;

/*平坦内存模型:把全部系统内存表示为连续的地址空间 所有指令数据和堆栈都包含在相同的地址空间*/
#ifdef CONFIG_FLAT_NODE_MEM_MAP	
	struct page *node_mem_map;
#endif

/* 稀疏内存模型 */
#ifdef CONFIG_MEMORY_HOTPLUG   
	spinlock_t node_size_lock;
#endif
	unsigned long node_start_pfn; /* 该节点起始的页帧号 */
	unsigned long node_present_pages; /* total number of physical pages */
	unsigned long node_spanned_pages; /* total size of physical page
					     range, including holes */
	int node_id;
	wait_queue_head_t kswapd_wait;
	struct task_struct *kswapd;
	int kswapd_max_order;
	enum zone_type classzone_idx;
} pg_data_t;

详见 Linux物理内存管理(一)_腾讯新闻

·zone

struct zone {
	unsigned long watermark[NR_WMARK]; /*当前管理分区的水位管理 用于物理内存碎片管理 */
	unsigned long percpu_drift_mark;
	unsigned long		lowmem_reserve[MAX_NR_ZONES]; /* 申请内存时使用 */
	unsigned long		dirty_balance_reserve;

#ifdef CONFIG_NUMA
	int node;
	unsigned long		min_unmapped_pages;
	unsigned long		min_slab_pages;
#endif
	struct per_cpu_pageset __percpu *pageset;
	/* free areas of different sizes */
	spinlock_t		lock;
	int                     all_unreclaimable; /* All pages pinned */
#ifdef CONFIG_MEMORY_HOTPLUG
	/* see spanned/present_pages for more description */
	seqlock_t		span_seqlock;
#endif
	struct free_area	free_area[MAX_ORDER]; /* 剩余内存 */

#ifndef CONFIG_SPARSEMEM
	unsigned long		*pageblock_flags;
#endif /* CONFIG_SPARSEMEM */

#ifdef CONFIG_COMPACTION
	unsigned int		compact_considered;
	unsigned int		compact_defer_shift;
	int			compact_order_failed;
#endif
	ZONE_PADDING(_pad1_)
	/* Fields commonly accessed by the page reclaim scanner */
	spinlock_t		lru_lock;
	struct lruvec		lruvec;

	struct zone_reclaim_stat reclaim_stat;
	unsigned long		pages_scanned;	   /* since last reclaim */
	unsigned long		flags;		   /* zone flags, see below */

	/* Zone statistics */
	atomic_long_t		vm_stat[NR_VM_ZONE_STAT_ITEMS];
	unsigned int inactive_ratio;

	ZONE_PADDING(_pad2_)
	wait_queue_head_t	* wait_table;
	unsigned long		wait_table_hash_nr_entries;
	unsigned long		wait_table_bits;

	/* Discontig memory support fields. */
	struct pglist_data	*zone_pgdat;

	/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
	unsigned long		zone_start_pfn;/* 这块zone管理的起始页帧号*/

	unsigned long		spanned_pages;	/* total size, including holes */
	unsigned long		present_pages;	/* amount of memory (excluding holes) */

	const char		*name;
} ____cacheline_internodealigned_in_smp;

2.1.3 物理内存管理结构

综合上述,我们的物理内存整体管理结构如下:

358c59514802452e8d1a3f58f244d955.png

对应的数据结构关联:

82e0984ff3354820bc32240b17fe0d7d.png

 3. 伙伴算法系统

3.1 基础版本

伙伴算法的接口在proc目录下:

cat /proc/buddyinfo 
Node 0, zone      DMA     23     15      4      5      2      3      3      2      3      1      0 
Node 0, zone   Normal    149    100     52     33     23      5     32      8     12      2     59 
Node 0, zone  HighMem     11     21     23     49     29     15      8     16     12      2    142 

其管理原理是:按照2的阶乘大小,将连续的free page划分成不同的部分(2^0 ,2^1... ...2^10个page),并用链表组织起来。用户正式使用的时候,按照申请的大小到不同的链表中去分配,分配剩余的连续page快降级到下一级链表中去;同时,对于两块相同大小,并且相领的page块(叫做伙伴page),可以合并升级到上一级链表。这样就将不同大小的连续内存page动态的管理起来。( 当高阶链表上的没有元素,全部集中在低阶链表上的时候,表明系统的碎片化比较严重了,此时我们很难分配出大块的内存page了。伙伴系统的算法一定程度上缓解了page碎片化的问题)

b92644d9af244675b1c28427b254f46b.png

 在buddyinfo中free page不同类型做管理:1e74c9aa1a0945e1b809bfeb457029c9.png 

3.2 伙伴系统的改进和完善

3.2.1 页面迁移分类

 从上文描述可知,内核在buddyinfo的基础上,将free页面根据不同的属性进行分类:

·可移动的(可以在需要的时候,迁移页面内容的位置来组合成新的大块内存页,但是会产生重映射的消耗)

·不可移动页面(对于内核这种静态链接地址的程序来说)

·可回收页面(发生内存不足时,可以进行内存回收)

         其存在意义就是,弥补了buddyinfo 算法无法处理进程分配了不释放的缺陷,将伙伴算法的管理细化到了页面类型的层面。

3.2.2 per-CPU 页面缓存

  由于内存页面属于公共资源,系统中频繁分配释放单个页面,会因为获得释放锁,CPU之间的同步操作产生无谓大量消耗。因此对于少量的页面分配,在zone结构中,给每个CPU都分配了_percpu *pageset变量,这样每个CPU都会在本地生成少量页面的副本缓存,分配的时候,先去缓存中去拿;释放也是放到缓存中。这样就提升了多核系统的性能。

 3.3 伙伴分配器接口

示意图

定义: 

/* gfp_mask:伙伴系统管理的分区 GFP_DMA GFP_NORMAL 等
 */
 #define alloc_pages(gfp_mask, order) \
		alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

extern void __free_pages(struct page *page, unsigned int order);
extern void free_pages(unsigned long addr, unsigned int order);
extern void free_hot_cold_page(struct page *page, int cold);
extern void free_hot_cold_page_list(struct list_head *list, int cold);

#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)

使用举例:

#include <linux/mm.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gfp.h>

#define PAGE_ORDER 1
struct page *page;
unsigned long int virt_addr;

static int __init hello_init(void)
{
    page = alloc_pages(GFP_KERNEL, PAGE_ORDER);
    printk("page frame num : %lx\n", page_to_pfn(page));
    printk("physical addr : %x\n", page_to_phys(page));
    printk("virtual addr : %x\n", (unsigned int)page_address(page));

    virt_addr = (unsigned int)page_to_virt(page);
    printk("virtual addr : %lx\n", virt_addr);

    return 0;
}

static void __exit hello_exit(void)
{
    free_pages(virt_addr, PAGE_ORDER);
//    __free_pages(page, PAGE_ORDER);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("wit");
MODULE_LICENSE("GPL");

 结果:

代码实现:

            .........
	/* First allocation attempt */
	page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
			zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
			preferred_zone, migratetype); /* 快速分配,直接从buddy链表分配 */
	if (unlikely(!page))
		page = __alloc_pages_slowpath(gfp_mask, order,
				zonelist, high_zoneidx, nodemask,
				preferred_zone, migratetype);/* 慢速分配,当内存页面不足,则进行memory 压缩 合并 迁移....... */

3.4 伙伴系统的初始化

3.4.1 memblock管理器

       为了让系统能够正常启动,早期的内存是预先被划分成了不同的区块的,比如系统镜像区域,程序运行区域,多媒体编解码区域,空闲区域等。 

        在系统启动之初,我们的内存只是一块芯片,没有人告诉系统内存的状态,大小,分配情况这些信息。内存系统要真正将内存管理起来,需要在启动过程中,获得内存的分区,大小,分配状态等信息。

·系统早期的内存管理(buddyinfo 初始化之前)

        memblock是内核在系统启动早期用于管理物理内存的机制,它从dtb中解析出物理内存信息,并通过特定的数据结构管理这些信息。同时它还在memblock初始化之后,伙伴系统启用之前,承担系统的内存分配任务。

  在程序员眼中,物理内存无非就是一段段的地址空间,这些空间可以通过起始地址和地址长度来表示。Memblock模型正是基于这一思想抽象出来的,它为每段地址分配一个如下面定义的region结构,该结构包含了其起始地址、长度和一些标志信息:

struct memblock_region {
	phys_addr_t base;
	phys_addr_t size;
	enum memblock_flags flags;
#ifdef CONFIG_NUMA
	int nid;
#endif
}

        相关的接口和数据结构

 memblock包含memory类型和reserved类型两种memory region,分别表示扫描后的可用内存块和已经被使用的内存块。 它们的关系如下:
(1) memory类型     
  内核会为所有这种类型的内存建立线性映射,因此所有处于线性映射区且未被标记为nomap的内存都保存在memory类型中
(2) reserved类型   
  (a)设备树中明确标记为需要保留的内存,它们一般会有特殊的用途     
  (b)内核image,initrd以及dtb驻留的内存     
  (c)memblock内存分配器已经分配出去且未释放的内存     
  (d)用于contiguous dma等的cma内存 这些内存由于有特殊用途或正在使用,因此不会进入伙伴系统或被memblock分配器再次分配。

从设备上可以cat出来memory和reserved两块的信息: 

 

 初始化流程:

3.4.2 memblock内存释放与buddyinfo系统的初始化

    主要在mem_init()中通过函数来扫描所有的memblock region来建立page和region的联系:

#define for_each_memblock(memblock_type, region)					\
	for (region = memblock.memblock_type.regions;				\
	     region < (memblock.memblock_type.regions + memblock.memblock_type.cnt);	\
	     region++)


/*
 * mem_init() marks the free areas in the mem_map and tells us how much
 * memory is free.  This is done after various parts of the system have
 * claimed their memory after the kernel image.
 */
void __init mem_init(void)
{
	unsigned long reserved_pages, free_pages;
	struct memblock_region *reg;
	int i;
    /* ... ... */
    
    printk(KERN_INFO "Memory:");
	num_physpages = 0;
	for_each_memblock(memory, reg) {
		unsigned long pages = memblock_region_memory_end_pfn(reg) -
			memblock_region_memory_base_pfn(reg);
		num_physpages += pages;
		printk(" %ldMB", pages >> (20 - PAGE_SHIFT));
	}
/* ... ... */
}

 3.5 CMA 分配器

 伙伴算法管理的最大内存是受到限制的,在这种情况下,大块内存分配可以使用CMA去分配。

其管理使用位图,orderbitmap 的值表示位图每个bit位管理多大的连续page。

4. slab slob slub机制--buddyinfo的更精细补充

4.1 slab机制

          伙伴系统是以page为单位管理的,但是实际上内存中存在大量几字节  几十字节的结构体(eg task_struct)内存分配,这样每存储一个结构体都使用一个page的话是极大地浪费,因此提出了slab机制。slab就是将一个4k的page均匀地分割成32,64,128这样的区域,用于保存匹配的结构体数据,提高内存利用率。

        ​​​​​​​        ​​​​​​​        

 4.2 实现

4.2.1  结构体kmem_cache

具体是使用slab  还是slob slub由不同的系统配置决定,以slub定义为例:

/*
 * Slab cache management.
 */
struct kmem_cache {
	struct kmem_cache_cpu __percpu *cpu_slab; /* 每CPU变量,CPU本地缓存 */
	/*... ... ... ...*/
	struct list_head list;	/* List of slab caches */

	struct kmem_cache_node *node[MAX_NUMNODES]; /* 管理slab链表 */
};

struct kmem_cache_node {
	spinlock_t list_lock;	/* Protect partial list and nr_partial */
	unsigned long nr_partial;
	struct list_head partial; 
#ifdef CONFIG_SLUB_DEBUG
	atomic_long_t nr_slabs;
	atomic_long_t total_objects;
	struct list_head full;
#endif
};

kmem_cache_node结构体管理所有的slab节点组成的链表结构,每次申请先从cpu_slab的本地缓存申请slab,其次从kmem_cache_node申请,都申请不到就调用buddyinfo的接口alloc_pages申请新的page进行分配。

 

 4.2.2 申请接口

kmem_cache_create:

/**
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.名称
 * @size: The size of objects to be created in this cache.使用kmem_cache的结构体大小
 * @align: The required alignment for the objects. 对齐长度
 * @flags: SLAB flags                               申请类型
 * @ctor: A constructor for the objects.    结构体的构造函数,每次申请结构体内存都会调用这个函数对结构体进行初始化
 *
 * Returns a ptr to the cache on success, NULL on failure.
 * Cannot be called within a int, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache.
 *
 * @name must be valid until the cache is destroyed. This implies that
 * the module calling this has to destroy the cache before getting unloaded.
 *
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 */
struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,
	unsigned long flags, void (*ctor)(void *))

函数功能就是为我们要使用的结构体申请一个kmem_cache的节点,申请成功返回kmem_cache的指针cache_ptr,我们再使用kmem_cache_alloc(cache_ptr, GFP_KERNEL)去kmem_cache里申请slab来存放我们的结构体。

4.3 slab管理的封装----kmalloc 

 由于kmem_cache系列接口的易用性不是很好,也不够简洁,日常生活中用的更多的是kmalloc/kfree接口。


#define GFP_NOWAIT	(GFP_ATOMIC & ~__GFP_HIGH) /* 不阻塞 立即返回 */
#define GFP_ATOMIC	(__GFP_HIGH) /* 在终端中申请内存  不可以睡眠*/

#define GFP_KERNEL	(__GFP_WAIT | __GFP_IO | __GFP_FS) /*内核发起的内存申请 可以睡眠*/

#define GFP_USER	(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
void *__kmalloc(size_t size, gfp_t flags)
{
	return __do_kmalloc(size, flags, __builtin_return_address(0));
}
EXPORT_SYMBOL(__kmalloc);

kmalloc实现:

        略

 

参考​​​​​​​

1.linux内存管理(二)memblock - 知乎

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值