Linuxのメモリ管理におけるメモリリカバリは非常に重要な位置を占め、システムのメモリは、すべてのプロセスの数百を実行して、限られている後、システムメモリが小さくなってきて、他のタスクのニーズを満たすためにいくつかのメモリリカバリを選択する必要があります。メモリリサイクルプロセスでは、どのようなメモリをリサイクルすることができます、いつリサイクルする、どのようにメモリのリサイクル時にシステムのパフォーマンスへの影響を最小限に抑えるには、メモリのリサイクル戦略、これらは重要な関心事であるだけでなく、本稿の主な焦点です。
1.1 メモリ再生の目的
すべての物理メモリは、たとえば、メモリの回復のカーネルコードセグメントは、システムが正常に実行できない場合は、一般的なカーネルコードセグメント、データセグメント、カーネルkmalloc()メモリのうち、メモリによって占有カーネルスレッドなど、メモリに加えて、リサイクルされていない回復に関与することができますリサイクルの対象です。
リサイクルされるメモリは、主にユーザーランドプロセスが占有するメモリと、カーネル自身が動作中に使用する一部のメモリで構成されます。ユーザーランドプロセスが占有するメモリは、主に共通プロセスコード、データ、スタックなどです。カーネルが使用するメモリは、主にディスクキャッシュが使用する物理メモリとmmap()ファイルで使用される有名なマッピングです。後者のメモリもカーネルが管理用に使用していますが、これを回収してもカーネルの性能に影響が出る程度で、システムが動かなくなることはありません。
1.2 メモリリサイクルのタイミング
1、メモリ不足の回復:grow_buffers()バッファページを取得することはできません、alloc_page_buffers()ページ一時バッファの先頭を取得することはできません、__alloc_pages()その後、与えられたメモリ領域のための連続したページフレームのセットを割り当てることはできません。
2、サイクルの回復:必要に応じて、メモリの回復アルゴリズムを実行するために対応するカーネルスレッドをアクティブにする:kswapd()カーネルスレッドは、メモリ管理領域内の空きページフレームの数がpages_high値の標高を下回ったかどうかを確認します。イベントカーネルスレッドは、ワーカースレッドは、高速メモリキャッシュに位置するすべての空きスラブを取り戻すために。
1.3 メモリリサイクルの戦略
1.3.1 メモリリサイクルの分類
メモリ再生成は主に2種類のメモリを再生成することです。最も最近使用されたメモリと、キャッシュ内の空きスラブです。前者にはユーザプロセスのコードセグメント、データセグメント、スタック、ファイルマップドメモリ、ページキャッシュが含まれ、後者にはディスクキャッシュとその他の空きメモリキャッシュが含まれます。
各メモリ管理ゾーンには、現在アクティブなメッセージを記録するアクティブと、現在非アクティブなメッセージを記録する非アクティブの 2 つのリンク・テーブル・ヘッダを含む lru 構造があります。通常、lru 上の非アクティブ・チェーン・テーブル上のメモリ・ページが呼び出されます。同時に、メモリの再生成中に、対応する最も最近使用されたメモリ・ページがアクティブ・チェーン・テーブルから非アクティブ・チェーン・テーブルに補充されます。各メモリ・ページにはカーネル・データ構造ページで参照されるフラグ・ビットPG_referencedがあり、ページが「非アクティブ」状態から「アクティブ」状態になるまでの時間を2倍にします。例えば、あるページが1時間無応答であっても、偶然に1回アクセスされただけではアクティブとはみなされず、アクティブなページとみなされるには2回アクセスされなければなりません。以下は、あるページが非アクティブとアクティブの連鎖を移動するときの進化を示すグラフです。
スラブメモリーキャッシュには、完全に空いているスラブがあることが多く、これも再利用の対象です。
1.3.2 逆マッピング
ユーザーランドのリニア・アドレス空間を通じて直接アクセスできる物理ページについては、コード・スニペットやスタックなど、特定のファイルにマップされていない物理ページである匿名ページと、ファイルの特定の部分にマップされている物理ページであるマップ済みページの2つのカテゴリに分けることができ、通常はmmap()を使用してマップされます。
匿名マッピングやファイルマッピングの場合、物理メモリの一部が複数のプロセスのページテーブルで使用されることがあります。例えば、匿名マッピングの場合、プロセスをfork()すると、最初に親プロセスの物理メモリを共有することになり、ファイルマッピングの場合、複数のプロセスが同時にファイルの同じ部分にマッピングすることがあります。そのため、ページの再利用時には、すべてのページテーブル参照からページを削除する必要があります。これはリバースマッピングと呼ばれます。ページ・テーブルのアドレスは各プロセスのメモリ記述子に記録されており、プロセスのユーザーランド・アドレス空間を記述するvm_area_structはそれぞれ、それが属するメモリ記述子へのポインタを記録しています。そのため、物理ページを通してそれらを参照するvm_area_structを見つければ、メモリ記述子、ひいてはページ・テーブル、そして対応するページ・テーブル・エントリを見つけることができます。
匿名ページの逆マッピング:
匿名ページの場合、各ページのマッピング・フィールドはanon_vma記述子を指し、anon_vma記述子には、そのページを参照するすべてのvm_area_structが格納されるチェーン・テーブル・ヘッダがあります。これらのデータ構造、page,anon_vma,vm_area_structの関係を下図に示します:
匿名ページの場合は、基本的に他のアドレス空間から参照されます。これは、子プロセスがプロセスをfork()するときに、親プロセスのアドレス空間をコピーするためで、このように参照されます。各 vm_area_struct を anon_vma のチェーン・テーブルに追加するプロセスは以下のとおりです:
現在あるプロセスpが子プロセスcをフォークしているとします。
1.プロセスPがvm_area_structに***物理ページを追加する際、例えばページ欠落例外が発生すると、動的にanon_vmaデータ構造を確保し、anon_vmaが管理するチェーンテーブルにvm_area_structを追加し、vm_area_structのanon_vmaフィールドをanon_vmaに割り当てると同時に、ページのマッピングフィールドをanon_vmaに割り当てます。vm_area_structに要求された以降の物理ページでは、マッピング・フィールドはanon_vmaに割り当てられます。
2.プロセス p が fork() を実行すると、fork の処理中に dup_mmap() が呼び出され、プロセス p の線形アドレス空間をコピーします。チェーンに追加します。この時点で、プロセスpに要求されたページはプロセスcによって共有されます。anon_vmaはページのマッピング・フィールドを通して見つけることができ、anon_vmaからプロセスp,cをたどることができます。
3は、問題を考慮し、プロセスcでのみ欠落ページ例外が要求されたメモリページをトリガされ、そのmmマッピングは、vma_anonのvm_area_structに属するように割り当てられていますが、プロセスpは、ページを使用しないので、物理ページのマッピングフィールドは、vma_anonにぶら下がっvm_area_structの下を指すことはありません可能性があります。はその物理ページを含んでいません。
ファイルマッピングページの逆マッピング:
各ファイル・マッピング・ページについて、そのページ・マッピング・フィールドは、対応するファイルのaddress_spaceデータ構造体を指し、address_spaceには、ファイルの内容をマッピングするすべてのvm_area_構造体を整理するプライオリティ・ツリーを指すprio_tree_root i_mmap構造体フィールドがあります。ファイルの内容を整理するための構造体です。ツリーでは、ツリーノードのベースアドレスとヒープアドレスが、それぞれマップされたファイル内容の開始アドレスと終了アドレスになります。 同時に複数のプロセスがアドレスセグメントをマップする場合、vm_area_struct はノード上のチェーンテーブルでリンクされます。
1.3.3 メモリ・リサイクル・プロセスの紹介
スリープ・リサイクルには関心がなく、メモリー・タイト・リサイクルとサイクル・リサイクルに主眼を置いています:
1、メモリ不足の回復メイン関数はtry_to_free_pages()、関数は、少なくとも32メモリページの回復まで、ページを回復するためにshrink_caches()、shrink_slab()を呼び出すために、12から0への優先順位に応じて、ループを実行します。
以下のヘルパー関数が順番に呼び出されます:
shrink_caches(): shrink_zone()を呼び出して、受信ゾーンチェインテーブルの各ゾーンに対して lru以上のページリサイクルを実行します。
shrink_slab():ディスクインデックスノードキャッシュとディレクトリアイテムインデックスノードと他のディスクキャッシュを取り戻す、ディスクインデックスノードとディレクトリアイテムインデックスノードは、スラブキャッシュから割り当てられているため、これは空きスラブの生成につながる、空きスラブは、その後リサイクルされるcache_reap作業キューの定期的なリサイクルでリサイクルされます。おそらく、このような関数名になっているのは、最終的に空きスラブをクリアするためでしょう。^_^
shrink_zone():メモリ管理領域上のlruテーブル内の非アクティブページをリサイクルし、非アクティブページが不足している場合、refill_inactive_zone()を呼び出して、lru上の非アクティブテーブル内の非アクティブページを補充し、同時にshrink_zone()は、ページをリサイクルするためにshrink_cache()を呼び出します。ページの回復を実行するには、関数の具体的な分析は、次のソースコードの分析を参照することができます。
refill_inactie_zone():特定のルールに従って補助関数は、アクティブなページのlruアクティブチェーンテーブルになります非アクティブチェーンテーブルに移動するために、ページを補充するためにリサイクルすることができます、lruチェーンテーブルでは、ページの2つのタイプがある、1つは、ユーザースペースページに属し、そのようなユーザ状態のプロセスコードセグメント、データセグメント、ページ内のページキャッシュのクラスとして、システムは、アプリケーションへの影響を減らすために、ページキャッシュページをリサイクルすることを優先し、システムの全体的なパフォーマンスのためにも、ユーザ状態のプロセスページを回復するために適切になります。アプリケーションへの影響を減らすために、システムはページキャッシュページの再生を優先し、同時に、システム全体のパフォーマンスのために、ユーザープロセスページの再生も適切に行います。選択は以下の経験式に従って行われます:
交換性向値=マッピング比率/2+負荷価値+交換価値
2.kswapdプロセスは一般にシステム内でスリープしていますが、__alloc_page()が各管理ゾーンの残ページが警告値より低いことを発見すると、kswapdプロセスを起動してページのリサイクルを実行し、リサイクルされたページによって管理ゾーンの残ページがzone->pages_highより高くなるまでリサイクルを停止しません。これは、shrink_zone() と shrink_slab() の呼び出しと本質的に同じです。
3.cache_reap作業キューは、スラブキャッシュ内のアイドルスラブによって占有されているページを取り戻すために定期的に実行されます。
#p#
1.4 関連ソースコードの分析
static void
shrink_zone(struct zone *zone, struct scan_control *sc)
{
unsigned long nr_active;
unsigned long nr_inactive;
//優先度に基づいてスキャン可能なページ数を取得する。,
//は、緊急度が低く、スキャン可能なページ数が最も少ないことを表す。
zone->nr_scan_active += (zone->nr_active >> sc->priority) + 1;
nr_active = zone->nr_scan_active;
if (nr_active >= SWAP_CLUSTER_MAX)
zone->nr_scan_active = 0;
else
nr_active = 0;
zone->nr_scan_inactive += (zone->nr_inactive >> sc->priority) + 1;
nr_inactive = zone->nr_scan_inactive;
//非アクティブなページが少ない場合、それらはとりあえず無視することができ、スキップされたページはnrに記録される。_scan_inactivein
//別の機会に譲る
if (nr_inactive >= SWAP_CLUSTER_MAX)
zone->nr_scan_inactive = 0;
else
nr_inactive = 0;
//再生するページ数を32に設定する
sc->nr_to_reclaim = SWAP_CLUSTER_MAX;
//一度に32ページをスキャンして、ページの再生を開始する。!!!
while (nr_active || nr_inactive) {
if (nr_active) {
//一度にスキャンする非アクティブページの数を設定するには
//入到inactive list里面
sc->nr_to_scan = min(nr_active,
(unsigned long)SWAP_CLUSTER_MAX);
nr_active -= sc->nr_to_scan;
//补充inactive list中的页面
refill_inactive_zone(zone, sc);
}
if (nr_inactive) {
//一度にスキャンするページ数を最大32ページまで設定する
sc->nr_to_scan = min(nr_inactive,
(unsigned long)SWAP_CLUSTER_MAX);
nr_inactive -= sc->nr_to_scan;
//开始正式回收inactive list中的页面
shrink_cache(zone, sc);
//32ページが取り戻され、作業が終了する!!!!
if (sc->nr_to_reclaim <= 0)
break;
}
}
}
static int shrink_list(struct list_head *page_list, struct scan_control *sc)
{
LIST_HEAD(ret_pages);
struct pagevec freed_pvec;
int pgactivate = 0;
int reclaimed = 0;
//最初にスケジューリングする必要があるプロセスがある
cond_resched();
pagevec_init(&freed_pvec, 1);
//ページ_list 連鎖したテーブルの各ページマップが再生成される
while (!list_empty(page_list)) {
struct address_space *mapping;
struct page *page;
int may_enter_fs;
int referenced;
//ページの取得
page = lru_to_page(page_list);
//lruから抜粋
list_del(&page->lru);
//pageロックされ、取り戻せない
if (TestSetPageLocked(page))//page is locked?
goto keep;
BUG_ON(PageActive(page));
//pageライトバックであるため、再要求できない
if (PageWriteback(page))//page is writeback?
goto keep_locked;
sc->nr_scanned++;
/* Double the slab pressure for mapped and swapcache pages */
if (page_mapped(page) || PageSwapCache(page))
sc->nr_scanned++;
//ページが最近訪問されたかどうかを確認する
referenced = page_referenced(page, 1, sc->priority <= 0);
/* In active use or really unfreeable? Activate it. */
//1ページがアクセスされた、2 ページがユーザーランド空間にある、ページがファイルマップページである,
//ページがスワップ・キャッシュにあり、以下の両方の条件が満たされる場合、そのページは再要求されない。
if (referenced && page_mapping_inuse(page))
goto activate_locked;
#ifdef CONFIG_SWAP
//page is anon and page has not been add to swapcache
//ページは匿名マップされたページであり、ページはスワップキャッシュにない。
if (PageAnon(page) && !PageSwapCache(page)) {
//将页面加入到swap cache中
if (!add_to_swap(page))
goto activate_locked;
}
#endif /* CONFIG_SWAP */
//対応するアドレスを取得する_space,対応するファイル_space,または
//swap cacheのアドレスは_space
mapping = page_mapping(page);
may_enter_fs = (sc->gfp_mask & __GFP_FS) ||
(PageSwapCache(page) && (sc->gfp_mask & __GFP_IO));
//ページはあるユーザー・ページ・テーブルにマップされる。
if (page_mapped(page) && mapping) {
//そのページのユーザ・ページ・テーブルのすべてのページ・テーブル・エントリを削除する。
switch (try_to_unmap(page)) {
case SWAP_FAIL:
goto activate_locked;
case SWAP_AGAIN:
goto keep_locked;
case SWAP_SUCCESS:
; /* try to free the page below */
}
}
//ページが汚れている、ははは、ハードディスク上のファイルかスワップキャッシュに書き込む準備をしろ!
if (PageDirty(page)) {
if (referenced)
goto keep_locked;
if (!may_enter_fs)
goto keep_locked;
if (laptop_mode && !sc->may_writepage)
goto keep_locked;
/* Page is dirty, try to write it out here */
//ディスクへのページの書き込み
switch(pageout(page, mapping)) {
case PAGE_KEEP:
goto keep_locked;
case PAGE_ACTIVATE:
goto activate_locked;
case PAGE_SUCCESS:
if (PageWriteback(page) || PageDirty(page))
goto keep;
if (TestSetPageLocked(page))
goto keep;
if (PageDirty(page) || PageWriteback(page))
goto keep_locked;
mapping = page_mapping(page);
case PAGE_CLEAN:
; /* try to free the page below */
}
}
//ページがバッファページの場合、対応するバッファを設定する。_headを解放するために
if (PagePrivate(page)) {
if (!try_to_release_page(page, sc->gfp_mask))
goto activate_locked;
if (!mapping && page_count(page) == 1)
goto free_it;
}
if (!mapping)
goto keep_locked;
/* truncate got there first */
spin_lock_irq(&mapping->tree_lock);
//ページが汚れている場合、またはページの参照カウントが2の場合、ページは再要求されない。
if (page_count(page) != 2 || PageDirty(page)) {
spin_unlock_irq(&mapping->tree_lock);
goto keep_locked;
}
#ifdef CONFIG_SWAP
//到达这里,说明该page只被swap cache或者页高速缓存及
//fpra所共有,需要将其从swap cache上或者页高速缓存上删除。
if (PageSwapCache(page)) {
swp_entry_t swap = { .val = page->private };
//从swap cache上进行删除
__delete_from_swap_cache(page);
spin_unlock_irq(&mapping->tree_lock);
swap_free(swap);
__put_page(page);
/* The pagecache ref */
goto free_it;
}
#endif /* CONFIG_SWAP */
//ページキャッシュからページを削除する。
__remove_from_page_cache(page);
spin_unlock_irq(&mapping->tree_lock);
__put_page(page);
free_it:
unlock_page(page);
reclaimed++;
if (!pagevec_add(&freed_pvec, page))
__pagevec_release_nonlru(&freed_pvec);
continue;
activate_locked:
//ページをアクティブとして設定し、戻ってlruのアクティブチェーンテーブルに入れるのを待つ
SetPageActive(page);
pgactivate++;
keep_locked:
//保持页面的状态不变,放入对应的lru active或inactive連鎖テーブルの
unlock_page(page);
keep:
//その取り戻せないページをret_pages链表中
list_add(&page->lru, &ret_pages);
BUG_ON(PageLRU(page));
}
//これは回復不可能なページをページ_listを返した後、その処理に向かう関数の場合、その関数は
list_splice(&ret_pages, page_list);
//ここでは、解放可能なページは徹底的に解放される。^_^
if (pagevec_count(&freed_pvec))
__pagevec_release_nonlru(&freed_pvec);
mod_page_state(pgactivate, pgactivate);
sc->nr_reclaimed += reclaimed;
return reclaimed;
}