linux内核学习(3)-内存管理之物理内存管理

1.物理页面

  • 物理内存管理的最小单位是页
  • 内核中使用struct page数据结构描述一个物理页面,简化后如下
struct page{
	unsigned long 			flags;		//标志位集合
	atomic_t				_count;		//内核中引用该页面的次数
	atomic_t				_mapcount;  //内核中引用该页面的次数
	unsigned long			private;
	struct address_space	*mapping
	pgoff_t					index
	struct list_head		lru;
	void 					*virtual;
}

1.1 标志位

  • flags是页面的标志位集合,具体定义在/include/linux/page-flags.h文件中
  • 除了存放标志位还可以存放SETION编号、NODE编号和ZONE编号等

1.2 _count和_mapcount引用计数

  • _count 表示内核中引用该页面的次数,为0表示该page页面为空闲和即将要被释放的页面
  • _mapcount表示这个页面被进程映射的个数,主要用于RMAP反向映射机制
    1._mapcount等于-1,表示没有pte映射到页面中
    2._mapcount等于0,表示只有父进程映射了页面
  • 内核通过以下两个宏来统计这两个引用计数
static inline int page_mapcount(struct page *page)
static inline int page_count(struct page *page)

1.3 mapping

  • 当被用于文件缓存时(page cache),mapping指向和这个文件缓存相关联的address_space对象
  • 当被用于匿名页面时(Anonymous Page),mapping指向一个anon_vma数据结构,主要用于反向映射

1.4 lru

  • 主要用在页面回收的LRU链表算法中

1.5 virtual

  • 指向页面所对应的虚拟地址,在高端内存情况下,因为高端内存不会线性映射到内核空间,这个值为NULL

1.6物理页面的描述

  • 内核为每个物理页面都分配了一个struct page数据结构
  • 采用mem_map[]数组的形式存放这些struct page数据结构,并且和物理页面一对一映射

2.内存管理区

  1. ZONE_DMA:用于执行DMA操作,只适用于intel x86
  2. ZONE_NORMAL:用于线性映射物理内存
  3. ZONE_HGHMEN:用于管理高端内存,在64位处理器中不需要这个管理区

2.1 内存管理区描述符

  • 内存管理区描述符用数据结构struct zone,位于include/linux/mmzone.h中
struct zone {
        /* Read-mostly fields */
		/*只读域*/
        /* zone watermarks, access with *_wmark_pages(zone) macros */
        unsigned long watermark[NR_WMARK];
        long lowmem_reserve[MAX_NR_ZONES];
        struct pglist_data      *zone_pgdat;
        struct per_cpu_pageset __percpu *pageset;
        /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
        unsigned long           zone_start_pfn;
		unsigned long           managed_pages;
        unsigned long           spanned_pages;
        unsigned long           present_pages;

        const char              *name;


        /* Write-intensive fields used from the page allocator */
		/*写敏感域*/
        ZONE_PADDING(_pad1_)
        /* free areas of different sizes */
        struct free_area        free_area[MAX_ORDER];

        /* zone flags, see below */
        unsigned long           flags;

        /* Primarily protects free_area */
        spinlock_t              lock;

        /* Write-intensive fields used by compaction and vmstats. */
        ZONE_PADDING(_pad2_)
		spinlock_t				lru_lock;
		struct					lruvec;
     

		/*内存管理区的统计信息*/
        ZONE_PADDING(_pad3_)
        /* Zone statistics */
        atomic_long_t           vm_stat[NR_VM_ZONE_STAT_ITEMS];
        
} ____cacheline_internodealigned_in_smp;

  • struct zone可以分为读敏感区写敏感区统计计数
  • ZONE_PADDING()时让zone->lock和zone->lru_lock分布在不同的缓存行,避免发生缓存伪共享
  • watermark:每个内存管理区在系统启动时会计算出三个水位值,WMARK_MIN,WMARK_LOW和WMARK_HIGH
  • lowmem_reserve:内存管理区中预留的内存
  • zone_pgdat:指向内存节点
  • pageset:用于维护Per-CPU上的一系列页面,减少自旋锁的竞争
  • zone_start_pfn:内存管理区中开始页面的页帧号
  • managed_pages:内存管理区中被伙伴系统管理的页面数量
  • spanned_pages:内存管理区包含的页面数量
  • present_pages:内存管理区里实际管理的页面数量
  • free_area:管理空闲区域的数组
  • lock:并行访问时用于对内存管理区保护的自旋锁。保护struct zone本身,而不是内存管理区所描述的内存地址空间
  • lur_lock:用于对内存管理区中LRU并发访问时进行保护的自旋锁
  • lruvec:lru链表集合
  • vm_stat:内存管理计数器

2.2 辅助操作函数

  • 位于include/linux/mmzone.h中
#define for_each_zone(zone)                             \
        for (zone = (first_online_pgdat())->node_zones; \
             zone;                                      \
             zone = next_zone(zone))

static inline int is_highmem(struct zone *zone)#define zone_idx(zone)          ((zone) - (zone)->zone_pgdat->node_zones)

  • for_each_zone()用来遍历所有的内存管理区
  • is_highmem用来检测是否属于ZONE_HGHMEN
  • zone_idx返回当前zone所在的内存节点的编号

3.分配和释放页面

3.1 页面分配函数

static inline struct page *alloc_pages(gfp_t gfp_mask,unsigned int order)
  • 用来分配2的order次幂个连续的物理页面,返回一个物理页面的struct page数据结构
  • gfp_mask表示分配掩码,order不能大于MAX_ORDER
unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order)
  • 返回分配内存的内核空间的虚拟地址
#define alloc_page(gfp_mask) alloc_pages(gfp_mask,0)
#define __get_free_page(gfp_mask) \
		__get_free_pages((gfp_mask),0)
  • 只分配一个物理页面
unsigned long get_zeroed_page(gfp_t gfp_mask)
  • 返回一个全填充为0的页面

3.2 页面释放函数

void __free_pages(struct page *page,unsigned int order);
#define __free_page(page)	__free_pages((page),0)
#define free_page(addr)		free_pages((addr),0)

3.3 分配掩码gfp_mask

  • gfp是get free pages的缩写
  • gfp_mask是一个unsigned类型的变量
typedef unsigned __bitwise__ gfp_t;
  • gfp_mask大致可以分为几类
    1. 内存管理区修饰符(zone modifier)
    2. 移动修饰符(mobility and placement modifier)
    3. 水位修饰符(watermark modifier)
    4. 页面回收修饰符(page reclaim modifier)
    5. 行动修饰符(action modifier)

(1)内存管理区修饰符

用来表示从哪些内存管理区中来分配物理内存

  • __GFP_DMA:从ZONE_DMA中分配内存
  • __GFP_DMA32:从ZONE_DMA32中分配内存
  • __GFP_HIGHMEM:优先从ZONE_HIGHMEM中分配内存

(2)移动修饰符

说明分配出来的页面具有的迁移属性

  • __GFP_MOVABLE:页面可以被迁移或者回收
  • __GFP_RECLAIMABLE:在slab中指定了SLAB_RECLAIM_ACCOUNT标志位,表示slab可以通过shrinkers回收
  • __GFP_HARDWALL:使能cpuset分配策略
  • __GFP_THISNODE:从指定的内存节点中分配内存
  • __GFP_ACCOUNT:分配过程会被kmemcg记录

(3)水位修饰符

控制是否可以访问系统紧急预留的内存

  • __GFP_HIGH:分配内存具有高优先级,并且这个分配很有必要
  • __GFP_ATOMIC:分配内存的过程不能进行回收或睡眠,并具有很高优先级,常见场景是在中断上下文中分配内存
  • __GFP_MEMALLOC:分配过程中允许访问所有的内存,包括系统预留的紧急内存
  • __GFP_NOMEMALLOC:分配过程不允许访问系统预留的紧急内存

(4) 页面回收修饰符
(5)行动修饰符

  • 定义常用的掩码组合,叫做类型标志(Type Flag)
标志描述
GFP_KERNEL最常见的标志位,主要用于分配内核使用的内存,分配过程会引起睡眠
GFP_ATOMIC和GFP_KERNEL相反,使用在不能睡眠的分配路径上
GFP_USER、GFP_HIGHUSER、GFP_HIGHUSER_MOVABLE为用户空间分配内存,GFP_HIGHUSER优先使用高端内存
GFP_NOIO、GFP_NOFS不会启动IO的操作,不会访问文件系统的操作

3.4.内存碎片化

伙伴系统算法的基本条件

  • (1)两个块大小相同
  • (2)两个块地址连续
  • (3)两个块必须是同一个大块中分配出来的

在这里插入图片描述

  • 在上图中P0和P3变成了空洞,产生了外碎片化(External Fragmentation)。
  • 后果是明明系统又足够的内存,但无法输出一大段连续的物理内存供页面分配器使用
  • 解决外碎片化的技术叫做内存规整(Memeory Compaction),即通过移动页面的位置让空闲页面连城一片
  • 但是内核本身使用的内存页面不能随便迁移,
  • 反碎片法(Anti-Fragmentation)是通过迁移类型实现的
  • 迁移类型按页块(Page Block)来划分的,按迁移属性分为
    1.不可移动类型UNMOVABLE:在内存中有固定的位置,使用GFP_KERNEL这个标志分配的内存就是不能迁移的
    2.可移动类型MOVABLE:可移动的页面,通常指通过应用程序分配的页面
    3. 可回收页面:不能直接移动但是可以回收
    在这里插入图片描述

4.分配小块内存

slab来源于内部使用的数据结构,可以理解为一个内存池

4.1 分配接口

#创建slab描述符

struct kmem_cache *
kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags,
	void (*ctor)(void*))
//name:slab描述符的名称
//size:缓存对象的大小
//align:缓存对象需要对齐的字节数
//flags:分配掩码
//ctor:对象的构造函数

#释放slab描述符

void kmem_cache_destroy(struct kmem_cache *s)

#分配缓存对象

void *kmem_cache_alloc(struct kmem_cache *,gfp_t flags)

#释放缓存对象

void kmem_cache_free(struct kmem_cache *,void *)

4.2 slab分配思想

slab机制的核心分配思想实在空闲时建立缓存对象池,包括本地对象缓存池和共享对象缓存池
本地对象缓冲池为每个CPU创建一个本地对象缓冲池,优先从当前CPU的本地对象缓冲池中分配
共享对象缓冲池当本地缓冲池没有空闲对象时,从共享对象缓冲池中取一批空闲对象搬移到本地缓冲池

4.2.1 slab描述符

struct kmem_cache时slab描述符

struct kmem_cache {
        struct array_cache __percpu *cpu_cache;

/* 1) Cache tunables. Protected by slab_mutex */
        unsigned int batchcount;
        unsigned int limit;
        unsigned int shared;

        unsigned int size;
        struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */

        slab_flags_t flags;             /* constant flags */
        unsigned int num;               /* # of objs per slab */

/* 3) cache_grow/shrink */
        /* order of pgs per slab (2^n) */
        unsigned int gfporder;

        /* force GFP flags, e.g. GFP_DMA */
        gfp_t allocflags;

        size_t colour;                  /* cache colouring range */
        unsigned int colour_off;        /* colour offset */
        struct kmem_cache *freelist_cache;
        unsigned int freelist_size;

        /* constructor func */
        void (*ctor)(void *obj);
/* 4) cache creation/removal */
        const char *name;
        struct list_head list;
        int refcount;
        int object_size;
        int align;

struct kmem_cache_node *node[MAX_NUMNODES];

其中,cpu_cache表示本地CPU的对象缓冲池

4.2.2 slab

(1)slab机制原理
在这里插入图片描述
slab机制分两步完成
第一步:

  • 使用kmem_cache_create()函数创建一个slab描述符,并使用struct kmem_cache数据结构来描述.
  • struct kmem_cache数据结构中,struct array_cache指向指向本地缓存池,kmem_list3是指向slab节点的node指针
  • slab节点中有三个链表:slab_partial,slab_full,slab_free

第二步:

  • 从slab描述符中分配空闲对象
  • 一个CPU要从一个slab描述符中分配对象,首先从本地缓冲池获取
  • 如果本地缓冲池中没有对象,则访问共享缓冲池

建立slab所使用的物理页面需要向页面分配器申请,这个过程可能会睡眠。建立好一个slab后会把这个slab添加到slab空闲链表中,通过lru成员挂入链表

4.2.3 slab回收

slab通过两种方式回收内存:

  1. 使用kmem_cache_free释放一个对象。
  2. 通过slab系统注册的定时器扫描slab描述符,回收一部分空闲对象

4.3 kmalloc机制

  • kmalloc()函数的核心是slab机制
  • 分配一个30字节的小内存,可以用kmalloc(30,GFP_KERNEL)
  • 系统会从kmalloc-32的slab描述符中分配一个对象
void *kmalloc(size_t size,gfp_t flags)
void kfree(const void *)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值