聞いた誰と比較しても、メモリ管理。しかし、メモリ管理は正確に何をするのでしょうか?これは、コンピュータが出てきたときに開始する必要があります。コンピュータが最初に出たとき、メモリリソースは非常にタイトで、わずか数十Kでしたが、ゆっくりと数百Kになり、1週間後には512Mになり、現在は数Gになっています。さまざまなメモリ管理方法がコンピュータ全体に導入されているのは、メモリリソースの不足のためです。
メモリ管理の最終的な目標は、物理メモリを無駄なく使用することです。Linuxは、物理メモリを適切に使用するためのさまざまなメモリ管理方法を設計しました。今日は、Linuxがどのように物理メモリを構成するか、つまり、コンピュータのメモリスティックを管理する方法について説明します。
Linuxは、ノード、ゾーン、ページの3レベルの構造を使用して、物理メモリ全体を記述します。
ノード
現在、コンピュータシステムには2つのアーキテクチャがあります。
- 非均一メモリアクセス(NUMA)は、メモリがノードに分割されていることを意味し、ノードへのアクセスにかかる時間は、CPUとノード間の距離に依存します。各CPUにはローカルノードがあり、ローカルノードにアクセスする時間は他のノードよりも高速です。
- Uniform Memory Access(UMA)は、SMP(Symmetric Multi-Process)対称型マルチプロセッサとも呼ばれます。これは、すべてのプロセッサがメモリへのアクセスに同じ時間を費やすことを意味します。また、メモリ全体にノードが1つしかないことも理解できます。
- NUMAは通常サーバーの分野で使用され、CONFIG_NUMAを介して開くかどうかを構成できます。
ゾーン
ZONEは、物理メモリ全体がいくつかの領域に分割されていることを意味し、各領域には特別な意味があります。
まずカーネルのゾーンの定義を見てください
enum zone_type {
#ifdef CONFIG_ZONE_DMA
/*
* ZONE_DMA is used when there are devices that are not able
* to do DMA to all of addressable memory (ZONE_NORMAL). Then we
* carve out the portion of memory that is needed for these devices.
* The range is arch specific.
*
* Some examples
*
* Architecture Limit
* ---------------------------
* parisc, ia64, sparc <4G
* s390 <2G
* arm Various
* alpha Unlimited or 0-16MB.
*
* i386, x86_64 and multiple other arches
* <16M.
*/
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
/*
* x86_64 needs two ZONE_DMAs because it supports devices that are
* only able to do DMA to the lower 16M but also 32 bit devices that
* can only do DMA areas below 4G.
*/
ZONE_DMA32,
#endif
/*
* Normal addressable memory is in ZONE_NORMAL. DMA operations can be
* performed on pages in ZONE_NORMAL if the DMA devices support
* transfers to all addressable memory.
*/
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
/*
* A memory area that is only addressable by the kernel through
* mapping portions into its own address space. This is for example
* used by i386 to allow the kernel to address the memory beyond
* 900MB. The kernel will set up special mappings (page
* table entries on i386) for each page that the kernel needs to
* access.
*/
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE,
#endif
__MAX_NR_ZONES
};
上図のように各ゾーンの意味をよりよく説明するために。
32ビットシステム:
32ビットシステムでは、物理メモリが4Gであると想定します。
- DMA_ZONEは、X86アーキテクチャでは、一部のDMAデバイスは16M未満のアドレスにしかアクセスできないため、DMA_ZONEが設計されているためです。DMAデバイスがメモリにアクセスすると、DMA_ZONEからメモリを取得します
- HIGHMEM_ZONE:HIGHMEM_ZONEは、32ビット時代の製品です。これは、32ビットシステムの4Gの仮想アドレス空間が、ユーザー空間用に0〜3G、カーネル空間用に3〜4Gに分割されているためです。カーネルの操作を容易にするために、カーネルの物理アドレスと仮想空間を線形にマッピングする必要があります。カーネルには1Gの空間しかなく、物理メモリには4Gしかないため、まったく線形にマッピングすることはできません。このとき、カーネル3G-3G + 896Mのアドレスは、物理メモリ0-896Mの領域に直線的にマッピングされます。896-4Gのマップされていない領域は、highmem_zoneと呼ばれます。ここで、896はクラシックx86アーキテクチャの値ですが、armアーキテクチャの値は調査されていません。
- NORAML_ZONE:16M〜896Mの領域はNORAML_ZONEと呼ばれます。
- 一般に、HIGHMEM_ZONEのメモリ領域はハイエンドメモリと呼ばれ、896M未満のメモリはローエンドメモリと呼ばれ、ローエンドメモリは線形にマッピングされます。
私の32ビットUbuntuマシンを見ると、Noramlゾーン、DMAゾーン、HighMemゾーンがあります。
64ビットシステム
- 64ビットシステムでは、仮想アドレス空間は十分に大きいです。たとえば、アドレス幅の桁数が39の場合です。ユーザースペースとカーネルスペースは同じサイズで、サイズは512Gです。
- この時点で物理メモリが4Gであると想定すると、4G全体をカーネル仮想アドレス範囲にマップできます。したがって、HIGHMEM_ZONEは64ビットマシンには存在しません。
- x86 64ビットマシンでは、DMA転送に使用されるDMA、DMA_32領域がある場合があります。
-
たとえば、私のubuntuマシンでは、/ proc / buddinfoを使用して特定のゾーン情報を確認できます
root@root-OptiPlex-7060:~$ cat /proc/buddyinfo
Node 0, zone DMA 3 3 1 1 3 2 0 0 1 1 3
Node 0, zone DMA32 4053 729 155 166 105 43 151 0 0 0 0
Node 0, zone Normal 33893 8921 6356 1472 1221 101 48 10 4 0 0
別の例として、私のARm64携帯電話の1つを見てみましょう。
root:/ # cat /proc/buddyinfo
Node 0, zone Normal 12 7 148 52 114 39 16 8 5 5 117
Node 0, zone Movable 470 1135 880 340 35 8 4 2 3 0 653
HIGHMEM_ZONEが64ビットマシンに存在しないことがわかります。あと1つだけのNORAML_ZONE
ZONE_MOVABLE:メモリの断片化技術に使用されます。つまり、メモリが断片化されている場合、大規模な連続メモリを調整するには、Moveablezoneの内容を交換して、大規模な連続メモリ領域をスワップアウトする必要があります。
ページ
これは物理ページを表し、物理ページはカーネル内で構造ページとともに表されます。
struct page {
{
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
/*
* Five words (20/40 bytes) are available in this union.
* WARNING: bit 0 of the first word is used for PageTail(). That
* means the other users of this union MUST NOT use the bit to
* avoid collision and false-positive PageTail().
*/
union {
struct { /* Page cache and anonymous pages */
/**
* @lru: Pageout list, eg. active_list protected by
* zone_lru_lock. Sometimes used as a generic list
* by the page owner.
*/
struct list_head lru;
/* See page-flags.h for PAGE_MAPPING_FLAGS */
struct address_space *mapping;
pgoff_t index; /* Our offset within mapping. */
/**
* @private: Mapping-private opaque data.
* Usually used for buffer_heads if PagePrivate.
* Used for swp_entry_t if PageSwapCache.
* Indicates order in the buddy system if PageBuddy.
*/
unsigned long private;
};
struct { /* slab, slob and slub */
union {
struct list_head slab_list; /* uses lru */
struct { /* Partial pages */
struct page *next;
#ifdef CONFIG_64BIT
int pages; /* Nr of pages left */
int pobjects; /* Approximate count */
#else
short int pages;
short int pobjects;
#endif
};
};
struct kmem_cache *slab_cache; /* not slob */
/* Double-word boundary */
void *freelist; /* first free object */
union {
void *s_mem; /* slab: first object */
unsigned long counters; /* SLUB */
struct { /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
};
};
struct { /* Tail pages of compound page */
unsigned long compound_head; /* Bit zero is set */
/* First tail page only */
unsigned char compound_dtor;
unsigned char compound_order;
atomic_t compound_mapcount;
};
struct { /* Second tail page of compound page */
unsigned long _compound_pad_1; /* compound_head */
unsigned long _compound_pad_2;
struct list_head deferred_list;
};
struct { /* Page table pages */
unsigned long _pt_pad_1; /* compound_head */
pgtable_t pmd_huge_pte; /* protected by page->ptl */
unsigned long _pt_pad_2; /* mapping */
union {
struct mm_struct *pt_mm; /* x86 pgds only */
atomic_t pt_frag_refcount; /* powerpc */
};
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
};
struct { /* ZONE_DEVICE pages */
/** @pgmap: Points to the hosting device page map. */
struct dev_pagemap *pgmap;
unsigned long hmm_data;
unsigned long _zd_pad_1; /* uses mapping */
};
/** @rcu_head: You can use this to free a page by RCU. */
struct rcu_head rcu_head;
};
union { /* This union is 4 bytes in size. */
/*
* If the page can be mapped to userspace, encodes the number
* of times this page is referenced by a page table.
*/
atomic_t _mapcount;
/*
* If the page is neither PageSlab nor mappable to userspace,
* the value stored here may help determine what this page
* is used for. See page-flags.h for a list of page types
* which are currently stored here.
*/
unsigned int page_type;
unsigned int active; /* SLAB */
int units; /* SLOB */
};
/* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
atomic_t _refcount;
#ifdef CONFIG_MEMCG
struct mem_cgroup *mem_cgroup;
#endif
/*
* On machines where all RAM is mapped into kernel address space,
* we can simply calculate the virtual address. On machines with
* highmem some memory is mapped into kernel virtual memory
* dynamically, so we need a place to store that address.
* Note that this field could be 16 bits on x86 ... ;)
*
* Architectures with slow multiplication can define
* WANT_PAGE_VIRTUAL in asm/page.h
*/
#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid;
#endif
}
構造体のページ構造は、基本的にはスペースを節約するための和集合であることがわかります。多くの物理ページがあるため、物理ページを表すには多くのページが必要であり、ページにはメモリが必要です。そのため、ページ構造は、編成するコンソーシアムの構造を採用しています。しかし、読みやすさは劣ります。
ページフレーム
物理ページを記述するために、カーネルは構造ページ構造を使用して物理ページを表します。ページのサイズが4Kであると仮定すると、カーネルは物理メモリ全体を4Kの物理ページに分割し、4Kの物理ページの領域はページフレームと呼ばれます。
ページフレーム数(PFN)
物理アドレスは、サイズのブロックに分割されます。たとえば、サイズが4Kの場合、物理ページの領域はページフレームと呼ばれ、各ページフレームの数はPFNと呼ばれます。
物理アドレスとpfnの関係は、物理アドレス>> PAGE_SHIFT = pfnです。
pfnとページの関係:
カーネルでは、いくつかのメモリモデルがサポートされています。CONFIG_FLATMEM(フラットメモリモデル)CONFIG_DISCONTIGMEM(不連続メモリモデル)CONFIG_SPARSEMEM_VMEMMAP(スパースメモリモデル)現在ARM64で使用されているスパースタイプモード
/* memmap is virtually contiguous. */
#define __pfn_to_page(pfn) (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
システムが起動すると、カーネルは構造体ページ全体をカーネル仮想アドレス空間のvmemmap領域にマップするため、構造体ページのベースアドレスはvmemmapであると簡単に考えることができます。
vmemmap + pfnのアドレスは、この構造体ページに対応するアドレスです。
要約:
物理メモリはいくつかのノードに分割され、各ノードにはいくつかのゾーンがあり、各ゾーンはページサイズに分割されています。