内存管理

Posted 源辰布道师 【刘经欢】

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存管理相关的知识,希望对你有一定的参考价值。

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的宏存在。

以上是关于内存管理的主要内容,如果未能解决你的问题,请参考以下文章

ION 内存管理

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

Spark内存管理详解(下)——内存管理

RT-Thread--内存管理

内存管理 浅析 内存管理/内存优化技巧

内存管理相关API列表

(c)2006-2024 SYSTEM All Rights Reserved IT常识