この記事はLinux Advent Calendar 2015の12/13の記事です。
一応、kernel ver4.3のソースはチェックしたつもりです。
が、Kernelのソースを見るのはずいぶん久しぶりなので、最新情報を見落としているかもしれません。
lowmem_reserve_ratioの目的
Linux Kernelには、物理アドレス上、低いアドレス(lowmem)のメモリをなるべく使わずに取っておく(reserve)機能があります。lowmem_reserve_ratioとは、この"取っておく"メモリをどれくらい確保するのかを調整するための値です。
これは、低位アドレスのメモリを使わないようにする理由は、以下の例ようにその領域を必要とするものがあるからです。
- kernel自身
ざっくりした言い方をすると、x86の32bitのシステムでは「kernelが簡単に使うことができる領域」は0~896MBの間です。それより高いアドレスの領域ではSLABまたはSLUBとよばれるkernelが使うメモリ管理機構はこの領域では使えません。また、仮想アドレスがストレートマップされていないので、一時的に仮想アドレスを割り当てるなど、面倒なことをしなければなりません。 - レガシーなデバイス
0~16Mbyte、あるいは64bit環境における0~4Gbyteなど、低位のアドレスのメモリしかアクセスできないデバイスがあります。このようなデバイスのために、低位のメモリ領域を空けておく必要があります。
通常のプロセスがメモリを浪費して上記のようなメモリまで食いつくしてしまうのはよくありません。ですから、このように低位のメモリをあらかじめreserveしておく必要があるわけです。
Zone
lowmem_reserve_ratioについて知るには、前提知識として、ZONEのことを知っておかなければなりません。
低位アドレスを管理するためにLinux Kernelは物理メモリをZONEと呼ばれる領域に分けて管理していて、
Kernel4.3では以下のようなZONEが定義されています。一番上のZONE_DMAが最も低い物理アドレスを管理するためのZONEで、下に行くにつれ高位のアドレスを管理するZONEになります。
- ZONE_DMA
- ZONE_DMA32
- ZONE_NORMAL
- ZONE_HIGHMEM
- ZONE_MOVABLE
- ZONE_DEVICE
なお、これらのZONEのうち、実際にどのZONEにどれくらいのメモリが割り当てられているかは、アーキテクチャ、搭載されているメモリ量、ブートオプションなどによって異なります。例えばZONE_DMAが示す領域は、kernelのソースに書かれているようにそれぞれのアーキテクチャによって異なっています。
#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
ZONEの構成は/proc/zoneinfoを参照することで把握することができます。各ZONEのpresentの数がそのZONEに割り当てられているメモリのpage数です。
(/proc/zoneinfo)
Node 0, zone DMA
pages free 3973
min 21
low 26
high 31
scanned 0
spanned 4095
present 3997
managed 3973
:
:
Node 0, zone DMA32
pages free 788519
min 4811
low 6013
high 7216
scanned 0
spanned 1044480
present 893356
managed 873600
:
:
Node 0, zone Normal
pages free 567443
min 3612
low 4515
high 5418
scanned 0
spanned 655360
present 655360
managed 655360
:
:
Node 0, zone Movable
pages free 2450391
min 14082
low 17602
high 21123
scanned 0
spanned 2620928
present 2620928
managed 2555065
ZONE_MOVABLEとZONE_DEVICEは実は他のZONEとは用途が異なります。ZONE_MOVABLEは当該領域をすべてmemory migrationが可能な領域とするZONEで、メモリホットプラグなどの用途で利用します。通常はこのZONEにメモリが割り当てられてはいませんが、ブートオプションで例えば movablecore=10G と指定すると、10GのMOVABLEなZONEが割り当てられます。
(ZONE_DEVICEは筆者も今回初めて知ったのですが、理解が間違っていなければNon Volatile MemoryをホットプラグするためのZONEのようです。現在のところ、このZONEはkernelのconfigとコンパイルが必要で、通常は有効になっていません。)
メモリの確保とZone
さて、物理メモリの利用者がその確保を要求するときには、要求側でGFP_xxxなどのオプションを指定することで、どのZONEを利用するかを指定します。例えば、以下ではalloc_pages()の__GFP_DMA32によって、4GByte以下の領域であるZONE_DMA32、あるいはそれより低位のZONE_DMAのメモリを確保するように要求しています。
(arch/x86/kvm/mmu.c)
static int alloc_mmu_pages(struct kvm_vcpu *vcpu)
{
struct page *page;
int i;
/*
* When emulating 32-bit mode, cr3 is only 32 bits even on x86_64.
* Therefore we need to allocate shadow page tables in the first
* 4GB of memory, which happens to fit the DMA32 zone.
*/
page = alloc_page(GFP_KERNEL | __GFP_DMA32);
if (!page)
return -ENOMEM;
GFP_HIGHUSERであれば、ZONE_HIGHMEM以下の低位のZONEならなんでもOKという指定、GFP_HIGHUSER_MOVABLEなら、ZONE_REMOVABLE,ZONE_HIGHMEM,ZONE_NORMAL,ZONE_DMA32,ZONE_DMAのいずれのZONEでもOKという指定になります。
ところで、GFP_HIGHUSERが指定されても、ZONE_DMA32やZONE_DMAなどのメモリも使えるようにしているのはなぜでしょうか?ZONE_HIGHMEMのメモリだけを割り当てればよいように思いませんか?
これは、システムのメモリ構成によっては利用できないメモリの量が大きくなってしまうからです。仮に1Gbyteのメモリを搭載しているx86(32bit)のシステムを考えてみましょう。(ハードウェアやファームウェアが利用している領域については考えないことにします)。そうするとこの場合、
- ZONE_DMA: 0から16Mbyteまで,
- ZONE_NORMAL: 16Mbyteから896Mbyteまで,
- ZONE_HIGHMEM: 896Mbyteから1Gまで
となります。つまり、ZONE_HIGHMEMはたったの128Mbyteしかないわけです。ZONE_HIGHUSERを指定すると1Gbyteのうち128Mbyteしか使えないのは少々バランスが悪いですね。なので、kernelは「高位のZONEでもOK」というメモリを要求しているものに対しては、
- 高位のアドレスを管理するZONEを優先的に割り当てる。
- 高位のZONEの空きメモリが無ければ、より低位のZONEを割り当てる。
- ただし、低位のZONEを割り当てる量は、本当に低位のZONEを要求するものよりもハンディキャップをつける。
という戦略をとっています。このハンディキャップの元となるのがlowmem_reserve_ratioです。
ハンディキャップの値の参照
まず、lowmem_reserve_ratioによって、それぞれのzoneが実際にどれくらいのハンディキャップを与えられているかを見てみましょう。これは、/proc/zoneinfoのprotectionを参照することでわかります。
Node 0, zone DMA
:
protection: (0, 3412, 5972, 15953)
:
:
Node 0, zone DMA32
:
protection: (0, 0, 2560, 12540)
:
:
Node 0, zone Normal
:
protection: (0, 0, 0, 79845)
:
:
Node 0, zone Movable
protection: (0, 0, 0, 0)
:
値がそれぞれ4つありますが、これは以下のように読み取ります。
まず、Zone Normalの値に注目しましょう。3つが0で、4つめが79845(page)となっています。
これは4番目のZONEとなっているMovable ZoneでもOKなメモリ要求に対して、3番目のZoneであるNormal Zoneからメモリ確保しようとすると、79845pageのハンディキャップが与えられていることになります。Normal Zoneを直接要求されたときと比べて、79845pageも余分にメモリに空きがないと、このNormal Zoneからメモリを確保できないということなのです。
DME32 zoneが(0, 0, 2560, 12540)なのは、Movable Zoneの要求に対してDMA32のZoneからメモリを確保する場合は、12540 pageの余分な空きメモリが必要で、Normal Zoneの要求に対しては2560 pageの余分な空きメモリが必要ということです。
同じようにDMA Zoneのprotectionが(0, 3412, 5972, 15953)ですから、それぞれDMA32 Zoneのメモリを確保したいときにZone DMAを使うときは3412page(13Mbyte)のハンディキャップが与えられています。Normal ZoneやMovable Zoneを要求した時はprotectionの値だけでDMA Zoneのサイズ(16Mbyte)の値を超えているので、これらの要求をしたときはDMA Zoneからメモリを確保できないことになります。
このハンディキャップはkernelのソースの以下の場所で使われています。
(mm/page_alloc.c)
/*
* Return true if free pages are above 'mark'. This takes into account the order
* of the allocation.
*/
static bool __zone_watermark_ok(struct zone *z, unsigned int order,
unsigned long mark, int classzone_idx, int alloc_flags,
long free_pages)
{
/* free_pages may go negative - that's OK */
long min = mark;
int o;
long free_cma = 0;
free_pages -= (1 << order) - 1;
if (alloc_flags & ALLOC_HIGH)
min -= min / 2;
if (alloc_flags & ALLOC_HARDER)
min -= min / 4;
#ifdef CONFIG_CMA
/* If allocation can't use CMA areas don't use free CMA pages */
if (!(alloc_flags & ALLOC_CMA))
free_cma = zone_page_state(z, NR_FREE_CMA_PAGES);
#endif
if (free_pages - free_cma <= min + z->lowmem_reserve[classzone_idx]) <-- ここでハンディキャップとして計算されています。
return false;
lowmem_reserve_ratioとreserve量の計算
ここまで説明してようやく/proc/sys/vm/lowmem_reserve_ratioについて説明することができます。lowmem_reserve_ratioの実際の値は/proc/sys/vm/lowmem_reserve_ratioを参照することで見ることができます。
$ cat /proc/sys/vm/lowmem_reserve_ratio
256 256 32
値が256とかになっていて比率のようには見えませんが、実際はkernelはこの値の逆数を比率として使用します。
また、値が3つ定義されている理由は、このkernelはZONEが全部で4つ(ZONE_DMA, ZONE_DMA32, ZONE_NORMAL, ZONE_MOVABLE)使えるようにコンパイルされており、その下位3つのZONEをガードするために3つの定義をしています。
kernelはこの値と各ZONEのメモリ量をもとにして、protectionの値を決めています。
static void setup_per_zone_lowmem_reserve(void)
{
struct pglist_data *pgdat;
enum zone_type j, idx;
for_each_online_pgdat(pgdat) {
for (j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = pgdat->node_zones + j;
unsigned long managed_pages = zone->managed_pages;
zone->lowmem_reserve[j] = 0;
idx = j;
while (idx) {
struct zone *lower_zone;
idx--;
if (sysctl_lowmem_reserve_ratio[idx] < 1)
sysctl_lowmem_reserve_ratio[idx] = 1;
lower_zone = pgdat->node_zones + idx;
lower_zone->lowmem_reserve[j] = managed_pages /
sysctl_lowmem_reserve_ratio[idx];<------ ★
managed_pages += lower_zone->managed_pages;<----- ★
}
}
}
この計算は、自分のZoneより上位のZoneのメモリ量がどれくらいあるかの値に対して、lowmem_reserve_ratioの値で割って、ハンディキャップの値として計算しています。
例としてDMA32 Zoneが4Gbyte, Normal Zoneが6Gbyte, Movable Zoneが8Gbyteあるとしましょう。
Movable Zoneのメモリ確保要求に対して、Normal Zone(3つ目のZone)から確保するときは8Gbyte / 32 (lowmem_reserve_ratioの3つめの値) = 256Mbyteのハンディキャップが与えられます。
Movable Zoneのメモリ確保要求に対して、DMA32 Zone(2つめのZone)からメモリを確保するときは (6+8)Gbyte / 256 (lowmem_reserve_ratioの2つめの値) = 56Mbyteのハンディキャップ、それに対してNormal Zoneの要求の時には6Gbyte / 256 = 24Mbyteのハンディキャップとなります。
結構ややこしいですね。
lowmem_reserve_ratioの値の変更
/proc/sys/vm/lowmem_reserve_ratioは値を書き込んで更新することができ、それぞれの値をintの最大値に指定すれば、ハンディキャップがほとんどなくなります。
# echo "2147483647 2147483647 2147483647" > /proc/sys/vm/lowmem_reserve_ratio
Node 0, zone DMA
:
protection: (0, 0, 0, 0)
^^^^^^^^^^^
すべて0になっている
逆にlowmem_reserve_ratioをすべてを1に設定すれば、ハンディキャップが最大になります。
lowmem_reserve_ratioは普通は変更する必要のない値です。が、最近はPCI Expressのように64bitのアクセスが可能なデバイスが主流となり、DMA ZoneやDMA32 Zoneが必要なケースは随分と減ってきています。また、x86を32bitのシステムとして使うことももうほとんどないでしょう。よって、カリカリにチューニングしたい場合は上記のように変更するのもありといえばありかもしれません。
(変更はあくまでも自己責任で実施しましょう! 上記変更がもとでトラブルになっても筆者は保証できませんし、有償サポートをしている会社でもまず保証しないでしょう。特にout of memoryを回避したいからとかいう理由で変更するのはやめましょう。lowmem_reserve_ratioを変更するのではなく、メモリの見積もりを見直したり、メモリリークをなくすようにするべきです。)
参考
kernelのDocumentation/sysctl/vm.txtにはlowmem_reserve_ratioの解説が書かれています。
以上。