内存管理

源辰布道师【刘经欢】 源辰布道师【刘经欢】     2022-08-03     501

关键词:

13. 内存管理

13.1. 引言

Linux对物理内存的描述机制有两种:UMANUMALinux把物理内存划分为三个层次来管理:存储节点(Node)、管理区(Zone)和页面 Page)。UMA对应一致存储结构,它只需要一个Node就可以描述当前系统中的物理内存,但是NUMA的出现打破了这种平静,此时需要多个 Node,它们被统一定义为一个名为discontig_node_data的数组。为了和UMA兼容,就将描述UMA存储结构的描述符 contig_page_data放到该数组的第一个元素中。内核配置选项CONFIG_NUMA决定了当前系统是否支持NUMA机制。此时无论UMA NUMA,它们都是对应到一个类型为pg_data_t的数组中,便于统一管理。

 71. Node ZonePage的关系

 

上图描述Linux管理物理内存的三个层次之间的拓扑关系。从图中可以看出一个存储节点由pg_data_t描述,一个UMA系统中只有一个Node,而 NUMA中则可以存在多个Node。它由CONFIG_NODES_SHIFT配置选项决定,它是CONFIG_NUMA的子选项,所以只有配置了 CONFIG_NUMA,该选项才起作用。UMA情况下,NODES_SHIFT被定义为0MAX_NUMNODES也即为1

include/linux/numa.h

 

#ifdef CONFIG_NODES_SHIFT

#define NODES_SHIFT CONFIG_NODES_SHIFT

#else

#define NODES_SHIFT 0

#endif

 

#define MAX_NUMNODES (1 << NODES_SHIFT)

这里主要介绍UMA机制。contig_page_data被定义如下:

mm/page_alloc.c

struct pglist_data __refdata contig_page_data = { .bdata = &bootmem_node_data[0] };

EXPORT_SYMBOL(contig_page_data);

struct pglist_data即是pg_data_t的原型。了解pg_data_t中的结构成员对于了解内存管理是必经之路:

enum zone_type {

ZONE_DMA,

ZONE_NORMAL,

ZONE_MOVABLE,

......

__MAX_NR_ZONES

};

 

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 /* means !SPARSEMEM */

struct page *node_mem_map;

#ifdef CONFIG_CGROUP_MEM_RES_CTLR

struct page_cgroup *node_page_cgroup;

#endif

#endif

struct bootmem_data *bdata;

 

...... /* for CONFIG_MEMORY_HOTPLUG */

 

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;

} pg_data_t;

注意到zonelist中的_zonerefs元素,它用来实现分配器分配内存时候的管理区后援功能。MAX_ZONES_PER_ZONELIST被定 义为所有节点中包含的最多管理区的和并加上1,加1的目的是在后援链表中,可以检测是否遍历到最后一个节点了,如果是说明申请失败。

/* Maximum number of zones on a zonelist */

#define MAX_ZONES_PER_ZONELIST (MAX_NUMNODES * MAX_NR_ZONES)

 

struct zonelist {

struct zonelist_cache *zlcache_ptr; // NULL or &zlcache

struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];

#ifdef CONFIG_NUMA

struct zonelist_cache zlcache; // optional ...

#endif

};

节点中的管理区都在free_area_init_core函数中初始化。调用关系如下所示:

start_kernel->setup_arch->paging_init->bootmem_init->bootmem_free_node->free_area_init_node->free_area_init_core

在理想的计算机体系结构中,一个物理页框就是一个内存存储单元,可用于任何事情:存放内核数据和用户数据,磁盘缓冲数据等。热河中磊的数据页都可以存放在 任何页框中,没有什么限制。但是,实际的计算机体系结构有硬件的制约,这制约页框可以使用的方式。尤其是Linux内核必须处理80x86体系结构的两种 硬件约束:

最后一种限制不仅存在于80x86,而存在于所有的体系结构中。为了应对这两种限制,Linux把每个内存节点的物理内存划分为多个(通常为3个)管理区(zone)。在80x86 UMA体系结构中的管理区为:

对于ARM来说,ZONE_HIGHMEM被名为ZONE_MOVABLE的宏取代,而ZONE_DMA也不会仅限于最低的16MB,而可能对应所有的内存区域,此时只有内存节点ZONE_DMA有效,所以ZONE_DMA并不一定名副其实的用来作为DMA访问之用。

ZONE_DMAZONE_NORMAL区包含内存的"常规"页框,通过把它们线性的映射到线性地址的第4 GB0xc0000000-0xcfffffff),内核就可以直接访问。相反ZONE_HIGHMEM或者ZONE_MOVABLE区包含的内存页不 能由内核直接访问,尽管它们也线性地映射到了线性地址空间的第4GB。每个内存管理区都有自己的描述符struct zone。它用来保存管理区的跟踪信息:内存使用统计,空闲区,锁定区等。

include/linux/mmzone.h

struct zone {

/* Fields commonly accessed by the page allocator */

unsigned long pages_min, pages_low, pages_high;

 

unsigned long lowmem_reserve[MAX_NR_ZONES];

struct per_cpu_pageset pageset[NR_CPUS];

 

struct free_area free_area[MAX_ORDER];

 

ZONE_PADDING(_pad1_)

 

/* Fields commonly accessed by the page reclaim scanner */

spinlock_t lru_lock;

struct {

struct list_head list;

unsigned long nr_scan;

} lru[NR_LRU_LISTS];

 

unsigned long recent_rotated[2];

unsigned long recent_scanned[2];

 

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];

};

在申请内存时,会遇到两种情况:如果有足够的空闲页可用,请求就会被立刻满足;否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被 释放。不过有些内存请求不能被阻塞。这种情况发生在处理中断或在执行临界区内的代码时。在这些情况下,一条内核控制路径应使用原子内存分配请求 (GFP_ATOMIC)。原子请求从不被阻塞;如果没有足够的空闲页,则仅仅是分配失败而已。

内核为了尽可能保证一个原子内存分配请求成功,它为原子内存分配请求保留了一个页框池,只有在内存不足时才使用。保留内存的数量存放在min_free_kbytes变量中,单位为KB

mm/page_alloc.c

 

int min_free_kbytes = 1024;

.....

 

/* min_free_kbytes = sqrt(lowmem_kbytes * 16); */

lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);

min_free_kbytes = int_sqrt(lowmem_kbytes * 16);

min_free_kbytes由当前直接映射区的物理内存数量决定。也即ZONE_DMAZONE_NORMAL内存管理区的可用页框数决定,这可以 通过nr_free_buffer_pages获取。尽管可以通过/proc/sys/vm/min_free_kbytes来修改该它的大小,但是 min_free_kbytes的初始值范围必须是[128K, 64M]。管理区描述符中的pages_min成员存储了管理区内保留页框的数目。这个字段与pages_lowpages_high字段一起被用在内 存分配和回收算法中。pages_low字段总是被设为pages_min的值的5/4,而pages_high则总是被设为pages_min的值的3 /2。这些值在模块快初始化module_init调用的init_per_zone_pages_min中被设置。

 27. 页面分配控制

名称

大小

pages_min

min_free_kbytes >> (PAGE_SHIFT - 10)

pages_low

pages_min * 5 / 4

pages_high

pages_min * 3 / 2

 

free_area_init_core中对管理区初始化的代码部分如下,后续章节将对该函数进一步分析。

        zone->spanned_pages = size;

        zone->present_pages = realsize;

 

        zone->name = zone_names[j];

        spin_lock_init(&zone->lock);

        spin_lock_init(&zone->lru_lock);

        zone_seqlock_init(zone);

        zone->zone_pgdat = pgdat;

 

        zone->prev_priority = DEF_PRIORITY;

 

        zone_pcp_init(zone);

        for_each_lru(l) {

            INIT_LIST_HEAD(&zone->lru[l].list);

            zone->lru[l].nr_scan = 0;

        }

        zone->recent_rotated[0] = 0;

        zone->recent_rotated[1] = 0;

        zone->recent_scanned[0] = 0;

        zone->recent_scanned[1] = 0;

        zap_zone_vm_stats(zone);

        zone->flags = 0;

13.2. page管理项

struct page {

    unsigned long flags;        /* Atomic flags, some possibly

                     * updated asynchronously */

    atomic_t _count;        /* Usage count, see below. */

    union {

        atomic_t _mapcount;    /* Count of ptes mapped in mms,

                     * to show when page is mapped

                     * & limit reverse map searches.

                     */

        struct {        /* SLUB */

            u16 inuse;

            u16 objects;

        };

    };

每一个物理页框都需要一个对应的page结构来进行管理:记录分配状态,分配和回收,互斥以及同步操作。对该结构成员的解释如下:

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,

......

__NR_PAGEFLAGS,

......

}

以上是页标志位的可能取值,通常不应该直接使用这些标志位,而应该内核预定义好的宏,它们在相同的头文件中被定义,但是它们是被间接定义的,也即通过##连字符来统一对它们进行定义。

#define TESTPAGEFLAG(uname, lname)                    \

static inline int Page##uname(struct page *page)             \

            { return test_bit(PG_##lname, &page->flags); }

......

TESTPAGEFLAG(Locked, locked)

PAGEFLAG(Error, error)

PAGEFLAG(Referenced, referenced) TESTCLEARFLAG(Referenced, referenced)

 28. 页标志宏函数

扩展函数/

用途

TESTPAGEFLAG(uname, lname)

Page##uname

测试PG_##lname

SETPAGEFLAG(uname, lname)

SetPage##uname

设置PG_##lname

CLEARPAGEFLAG(uname, lname)[a]

ClearPage##uname

清除PG_##lname

TESTSETFLAG(uname, lname)

TestSetPage##uname

测试并设置PG_##lname

TESTCLEARFLAG(uname, lname)

TestClearPage##uname

测试并清除PG_##lname

PAGEFLAG(uname, lname)[b]

TESTPAGEFLAG
SETPAGEFLAG
CLEARPAGEFLAG

当于同时扩展了三个宏,也即三个函数

PAGEFLAG_FALSE(uname)

Page##uname

永远返回0

TESTSCFLAG(uname, lname)

TESTSETFLAG
TESTCLEARFLAG

当于同时扩展了两个宏,也即两个函数

SETPAGEFLAG_NOOP(uname)

SetPage##uname

空操作

CLEARPAGEFLAG_NOOP(uname)

ClearPage##unam

空操作

__CLEARPAGEFLAG_NOOP(uname)

__ClearPage##uname

空操作

TESTCLEARFLAG_FALSE(uname)

TestClearPage##uname

永远返回0

[a] 以上三个宏分别对应test_bitset_bitclear_bit,是原子操作,与它们对应的是有三个开头
为下划线的同名函数__SETPAGEFLAG等与它们相对应,但不是原子操作,这里不再列出。

[b] 与此对应也有__PAGEFLAG的宏存在。

:内存管理--内存管理的概念

1.内存管理的概念1.1内存空间的分配与回收1.2内存空间的扩充补充:操作系统的四大特性1.3地址转换转换的三种方式:转入内存的三种方式详解1.4存储保护1.5小结 查看详情

关于内存管理

内存管理涉及根据数据库更改的需求为OracleDatabase实例内存结构维护最佳大小。必须管理的内存结构是系统全局区域(SGA)和实例程序全局区域(实例PGA)。Oracle数据库支持各种由初始化参数设置选择的内存管理方法。Oracle建议... 查看详情

rt-thread--内存管理(代码片段)

内存管理的功能特点RT-Thread操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情... 查看详情

内存管理与运行时

所有运行时的内存管理都是针对虚拟内存进行管理的。 内存管理策略:1)手动管理:c、c++;2)引用计数;3)垃圾收集。 运行时管理策略: 手动管理:内存分散管理,随时释放; 引用计数:1)手动管理:计数为... 查看详情

stm32内存管理(代码片段)

动态内存管理根据需要分配內存和回收内存通常在一块较大且连续的内存空间上进行分配和回收动态内存管理解决的问题内存资源稀缺,通过内存复用增加任务的并发性动态内存管理的本质时间换空间,通过动态分配和... 查看详情

内存管理

作为一个JVM进程,EXecutor的内存管理建立在JVM的内存管理之上,Spark对JVM的对内空间进行了更为详细的分配,以充分利用内存。同时,Spark引入了堆外内存,使之可以直接在工作节点的系统内存中开辟空间,进一步优化内存的使用... 查看详情

操作系统-页式内存管理

页式内存管理上A.段式内存管理1.指的是一段连续的内存空间2.段式内存管理-程序的各个部分相对独立(数据段,代码段),早期x86处理器无法通过一个寄存器访问所有内存单元,解决早期程序运行的重定位问题段式内存管理的应用... 查看详情

使用自动内存管理(代码片段)

本节提供有关Oracle数据库的自动内存管理功能的背景信息,并包含有关启用此功能的说明。涵盖以下主题:关于自动内存管理启用自动内存管理监视和调整自动内存管理关于自动内存管理管理实例内存的最简单方法是让Oracle数据... 查看详情

操作系统内存管理

1.内存管理方法     内存管理主要包含虚地址、地址变换、内存分配和回收、内存扩充、内存共享和保护等功能。 2.连续分配存储管理方式     连续分配是指为一个用户程序分配连续的内... 查看详情

内存管理技术

任何语言都会涉及到内存的管理和使用,很多语言要求开发人员自己进行所有内存的管理工作,如c++等。而内存管理要求的技术难度很大,很多开发人员不能很好地完成,同时也成为意向沉重的负担。java则不同,其为内存管理... 查看详情

了解动态内存管理函数melloccallocfreerealloc,实现内存管理自由!(代码片段)

动态内存管理笔记自取链接:动态内存管理笔记文章目录动态内存管理导言一、mallco开辟动态内存空间二、free释放动态内存空间三、calloc初始化+开辟动态内存空间四、realloc调整动态内存空间大小小结导言众所周知~,... 查看详情

linux操作系统原理—内存管理—页式内存管理技术

目录文章目录目录虚拟内存技术页式内存管理技术虚拟内存技术虚拟内存技术是操作系统实现的一种高效的物理内存管理方式,具有以下作用:使得进程间彼此隔离:通过将物理内存和虚拟地址空间联系起来,并将虚拟地址空间... 查看详情

操作系统——内存管理:内存管理策略

1.内存管理策略1.1背景1.1.1基本硬件1.1.2地址绑定1.1.3逻辑地址空间与物理地址空间1.1.4动态加载1.2交换1.3连续内存分配1.3.1内存保护1.3.2内存分配1.3.3内存碎片1.4分段1.5分页1.6页表结构1.6.1分层分页1.6.2哈希页表1.6.3倒置页表  计... 查看详情

spark从入门到精通spark内存管理详解-堆内&堆外内存管理

前言Spark作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解Spark内存管理的基本原理,有助于更好地开发Spark应用程序和进行性能调优。本文将详细介绍两部分内容,第一部分介绍Spa... 查看详情

linux内存管理子系统(概念入门)

...模型Linux虚拟地址空间分布虚拟地址转化为物理地址物理内存分配大纲内存管理子系统内存管理模型地址映射管理物理地址分配管理子系统简介Linux内核系统构成管理模型Linux内存子系统管理模型上面的三个部分主要做物理内存分... 查看详情

linux内存管理子系统(概念入门)

...模型Linux虚拟地址空间分布虚拟地址转化为物理地址物理内存分配大纲内存管理子系统内存管理模型地址映射管理物理地址分配管理子系统简介Linux内核系统构成管理模型Linux内存子系统管理模型上面的三个部分主要做物理内存分... 查看详情

sylixos定长内存管理

1.定长内存管理介绍所谓定长内存,指的是用户每次分配获得的内存大小是相同的,即使用的是有确定长度的内存块。同时,这些内存块总的个数也是确定的,即整个内存总的大小也是确定的。这和通常理解的内存池的概念是一... 查看详情

ios中的内存管理(代码片段)

在讲述iOS中的内存管理之前,先介绍一下其他语言中的内存管理,其实内存管理主要讲的是堆内存的管理,因为其他类型的内存,比如栈内存、全局变量/静态区内存、常量等各种语言的内存管理都差不多。Java中... 查看详情