内存管理框架---节点和区(三)

文章目录

  • 一、 内存管理之节点
    • 1.1 背景介绍
    • 1.2 深入分析
      • 1.2.1 内存节点的定义与结构
      • 1.2.2 内存节点的划分与组织
      • 1.2.3 内存节点与处理器关联
      • 1.2.4 内存节点的操作与使用
    • 1.3 案例分析/实用技巧
      • 1.3.1 如何获取当前处理器对应的内存节点?
      • 1.3.2 如何初始化一个新的内存节点?
  • 二、内存管理之区
    • 2.1 背景介绍
      • 知识回顾:linux为什么要分为三个区:ZONE_DMA ZONE_NORMAL ZONE_HIGHMEM?
    • 2.2 深入分析
      • 2.2.1 内存区的划分
      • 2.2.2 struct zone结构
    • 2.3 案例分析/实用技巧
  • 参考链接

一、 内存管理之节点

1.1 背景介绍

在Linux内核中,内存管理是核心系统的重要组成部分。为了实现高效且稳定的内存管理,内核采用了多种技术和数据结构。其中,内存节点(Node)是用于描述系统中内存分布和组织的重要概念。 本文将深入探讨Linux内核中内存节点的相关技术和数据结构。

1.2 深入分析

1.2.1 内存节点的定义与结构

在Linux内核中,pglist_data结构体用于描述内存节点。该结构体在include/linux/mmzone.h文件中定义。

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; // 页描述数组
    #ifdef CONFIG_PAGE_EXTENSION
        struct page_ext *node_page_ext; // 内存页的扩展属性
    #endif
#endif
    unsigned long node_start_pfn; // 该内存节点的起始物理页编号
    unsigned long node_present_pages; // 物理页的总数
    unsigned long node_spanned_pages; // 物理页的区间范围总大小,包括内存空洞大小
    int node_id; // 节点标识符
};

1.2.2 内存节点的划分与组织

Linux内核中,CPU被划分为多个节点(node),而内存则被分簇(bank)。每个CPU对应一个本地物理内存,即一个CPU-node对应一个内存簇bank。每个内存簇被认为是一个节点。

1.2.3 内存节点与处理器关联

每个内存节点都关联到系统中的一个处理器,在内核中表示为pg_data_t的实例。系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中,而其中的每个节点利用pg_data_t的node_next字段链接到下一个节点。
对于PC这种UMA(统一内存访问)结构的机器来说,只使用了一个称为contig_page_data的静态pg_data_t结构。

1.2.4 内存节点的操作与使用

内核提供了一系列函数和数据结构,用于对内存节点进行操作和管理。例如,通过get_node方法可以获取给定处理器对应的内存节点,而mm/page_alloc.c文件中的setup_node函数用于初始化一个新的内存节点。此外,内核还提供了诸如free_area_init、build_zonelists等函数用于构建和管理内存节点的相关数据结构。

1.3 案例分析/实用技巧

1.3.1 如何获取当前处理器对应的内存节点?

在内核代码中,可以通过CPU的id号获取当前处理器对应的内存节点。例如,可以通过下列代码片段获取当前CPU的内存节点:

struct pglist_data *pd = NODE_DATA(raw_smp_processor_id());

1.3.2 如何初始化一个新的内存节点?

要初始化一个新的内存节点,可以使用mm/page_alloc.c文件中的setup_node函数。该函数接受一个pglist_data结构体实例和一个 zonelist 结构体实例作为参数,并初始化该内存节点的相关数据结构。以下是一个示例代码片段:

struct pglist_data pd = { .node_id = 0, .node_start_pfn = 0 };
struct zonelist z = { .full_zones = 0, .zonelist = { } };
setup_node(NODE_DATA(0), &pd, &z);

二、内存管理之区

include\linux\mmzone.h

2.1 背景介绍

由于不同计算机类型的硬件架构不同, 限制了页框的使用。Linux内核必须处理80×86体系结构的以下两种硬件约束:

  • ISA总线的直接内存存取(DMA)处理器有一个严格的限制:只能对RAM的前16MB寻址。
  • 大容量的RAM使得线性地址空间太小,并不是所有的物理空间都能映射到唯一的线性地址空间。

为了解决以上两个限制,Linux将系统的页划分为不同的内存池,形成不同的内存区。这种划分是逻辑上的分组,对于内核来说,没有任何物理意义。下面解释如何使用struct zone结构来管理这些内存区。

知识回顾:linux为什么要分为三个区:ZONE_DMA ZONE_NORMAL ZONE_HIGHMEM?

  • 这些区域的划分主要考虑了不同计算机类型的硬件架构的限制。
  • isa总线的历史遗留问题,只能访问内存的前16M的空间
  • 大容量的RAM使得线性地址空间太小,并不是所有的物理空间都能映射到唯一的线性地址空间

2.2 深入分析

2.2.1 内存区的划分

在Linux内核中,内存被划分为三个不同的区域,分别是ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。

描述 物理内存
ZONE_DMA 这是用于直接内存存取(DMA)的区域,其物理内存位于系统的前16MB。这个区域的页框只能被DMA设备直接访问,内核不能直接访问这些页框。 <16MB
ZONE_NORMAL 这是正常可寻址的区域,其物理内存位于16MB到896MB之间。内核可以直接访问这个区域的页框。 16~896MB
ZONE_HIGHMEM 这是动态映射的区域,其物理内存位于896MB以上。这个区域的页框不能被内核直接访问,但可以通过特定的API进行动态映射和访问。 >896MB

2.2.2 struct zone结构

内核通过struct zone结构来管理每个内存区。这个结构包含了关于该内存区的各种信息,包括该区的名称、自旋锁、水位值等。下面是对struct zone结构的一些重要成员的解释:
name:一个以NULL结束的字符串,表示该内存区的名称。在内核启动期间进行初始化。
lock:一个自旋锁,用于保护该结构免于并发访问。注意,这个锁只保护结构本身,而不保护驻留在这个区中的所有页。
watermark:一个数组,包含该区的最小值、最低和最高水位值。内核使用这些水位值来设置合适的内存消耗基准。这些水位值会根据系统的空闲内存量进行动态调整。

2.3 案例分析/实用技巧

如何确定某个页框属于哪个节点或管理区?
由每个页框描述符中的flag的高位索引的,比如page_zone()函数就是接收页描述符的地址作为参数,返回页描述符中flag的高位,并到zone_table[ ]数组中确定相应的管理区描述符的地址。具体还要看实际的操作系统,下面只是个简化的示例。

#include 
/* 假设页框描述符的地址为pmd */
struct page_md *pmd;
/* 调用page_zone()函数获取flag的高位索引 */
int index = page_zone(pmd);
/* 查询zone_table[]数组获取相应管理区的描述符地址 */
struct zone *zone = &zone_table[index];
/* 输出结果 */
printk("The page frame belongs to zone %s\n", zone->name);

参考链接

物理内存组织结构
深入浅出Linux内核内存管理基础

你可能感兴趣的:(内存管理,内存管理,内存池,内存节点)