操作系统物理内存的组织方式

由于物理地址是延续的,页也是延续的,每个页大小也是一样的。因此关于任何一个地址,只需间接除一下每页的大小,很容易间接算出在哪一页。每个页有一个结构 struct page 示意,这个结构也是放在一个数组外面,这样依据页号,很容易经过下标找到相应的 struct page 结构。

假设是这样,整个物理内存的规划就十分繁难、易治理,这就是最经典的平整内存模型(Flat Memory Model)。

在这种形式下,CPU 也会有多个,在总线的一侧。一切的内存条组成一大片内存,在总线的另一侧,一切的 CPU 访问内存都要过总线,而且距离都是一样的,这种形式称为 SMP(Symmetric multiprocessing),即对称多解决器。当然,它也有一个清楚的缺陷,就是总线会成为瓶颈,由于数据都要走它。

为了提高性能和可裁减性,起初有了一种更初级的形式,NUMA(Non-uniform memory access),非分歧内存访问。在这种形式下,内存不是一整块。每个 CPU 都有自己的本地内存,CPU 访问本地内存不用过总线,因此速度要快很多,每个 CPU 和内存在一同,称为一个 NUMA 节点。然而,在本地内存无余的状况下,每个 CPU 都可以去另外的 NUMA 节点放开内存,这个时刻访问延时就会比拟长。

这样,内存被分红了多个节点,每个节点再被分红一个一个的页面。由于页须要全局惟必定位,页还是须要有全局惟一的页号的。然而由于物理内存不是连起来的了,页号也就不再延续了。于是内存模型就变成了非延续内存模型,治理起来就复杂一些。

这里须要指出的是,NUMA 往往是非延续内存模型。而非延续内存模型不必定就是 NUMA,有时刻一大片内存的状况下,也会有物理内存地址不延续的状况。

的干流场景,NUMA 方式。咱们首先要能够示意 NUMA 节点的概念,于是有了上方这个结构 typedef struct pglist_data pg_data_t,它外面有以下的成员变量:

每一个节点都有自己的 ID:node_id;

node_mem_map 就是这个节点的 struct page 数组,用于形容这个节点外面的一切的页;

node_start_pfn 是这个节点的起始页号;

node_spanned_pages 是这个节点中蕴含不延续的物理内存地址的页面数;

node_present_pages 是真正可用的物理页面的数目。

ZONE_DMA 是指可用于作 DMA(Direct Memory Access,间接内存存取)的内存。DMA 是这样一种机制:要把外设的数据读入内存或把内存的数据传送到外设,原来都要经过 CPU 控制成功,然而这会占用 CPU,影响 CPU 解决其余事件,所以有了 DMA 形式。CPU 只需向 DMA 控制器下达指令,让 DMA 控制器来解决数据的传送,数据传送终了再把消息反应给 CPU,这样就可以束缚 CPU。

关于 64 位系统,有两个 DMA 区域。除了上方说的 ZONE_DMA,还有 ZONE_DMA32。在这里你大略了解 DMA 的原理就可以,不用纠结,咱们前面会讲 DMA 的机制。

ZONE_NORMAL 是间接映射区,就是上一节讲的,从物理内存到虚构内存的内核区域,经过加上一个常量间接映射。

ZONE_HIGHMEM 是上流内存区,就是上一节讲的,关于 32 位系统来说超越 896M 的中央,关于 64 位没必要有的一段区域。

ZONE_MOVABLE 是可移动区域,经过将物理内存划分为可移动调配区域和无法移动调配区域来防止内存碎片。

为了让 CPU 极速访问段形容符,在 CPU 外面有段形容符缓存。CPU 访问这个缓存的速度比内存快得多。雷同关于页面来讲,也是这样的。假设一个页被加载到 CPU 高速缓存外面,这就是一个热页(Hot Page),CPU 读起来速度会快很多,假设没有就是冷页(Cold Page)。由于每个 CPU 都有自己的高速缓存,因此 per_cpu_pageset 也是每个 CPU 一个。

物理内存的基本单位,页的数据结构 struct page。这是一个特意复杂的结构,外面有很多的 union,union 结构是在 C 言语中被用于同一块内存依据状况保留不同类型数据的一种方式。这里之所以用了 union,是由于一个物理页面经常使用形式有多种。

第一种形式,要用就用一整页。这一整页的内存,或许间接和虚构地址空间建设映射相关,咱们把这种称为匿名页(Anonymous Page)。或许用于关联一个文件,而后再和虚构地址空间建设映射相关,这样的文件,咱们称为内存映射文件(Memory-mapped File)。

假设某一页是这种经常使用形式,则会经常使用 union 中的以下变量:

struct address_space *mapping 就是用于内存映射,假设是匿名页,最低位为 1;假设是映射文件,最低位为 0;

pgoff_t index 是在映射区的偏移量;

atomic_t _mapcount,每个进程都有自己的页表,这里指有多少个页表项指向了这个页;

struct list_head lru 示意这一页应该在一个链表上,例如这个页面被换出,就在换出页的链表中;

compound 相关的变量用于复合页(Compound Page),就是将物理上延续的两个或多个页看成一个独立的大页。

第二种形式,仅需调配小块内存。有时刻,咱们不须要一下子调配这么多的内存,例如调配一个 task_struct 结构,只须要调配小块的内存,去存储这个进程形容结构的对象。为了满足对这种小内存块的须要,Linux 系统驳回了一种被称为 slab allocator 的技术,用于调配称为 slab 的一小块内存。它的基本原理是从内存治理模块放开一整块页,而后划分红多个小块的存储池,用复杂的队列来保养这些小块的形态(形态包括:被调配了 / 被放回池子 / 应该被回收)。

关于要调配比拟大的内存,例如到调配页级别的,可以经常使用同伴系统(Buddy System)。

Linux 中的内存治理的“页”大小为 4KB。把一切的闲暇页分组为 11 个页块链表,每个块链表区分蕴含很多个大小的页块,有 1、2、4、8、16、32、64、128、256、512 和 1024 个延续页的页块。最大可以放开 1024 个延续页,对应 4MB 大小的延续内存。每个页块的第一个页的物理地址是该页块大小的整数倍。

假设有多个 CPU,那就有多个节点。每个节点用 struct pglist_data 示意,放在一个数组外面。

每个节点分为多个区域,每个区域用 struct zone 示意,也放在一个数组外面。每个区域分为多个页。

为了繁难调配,闲暇页放在 struct free_area 外面,经常使用同伴系统启动治理和调配,每一页用 struct page 示意。

您可能还会对下面的文章感兴趣: