LRUリストの回収
メモリが少なくなるとページ回収処理が動作します。この処理はPFRA(Page Frame Reclaiming Algorithm)と称されているようで、メモリ管理の中枢をなす処理だと言えます。システムパフォーマンスに大きな影響を及ぼすコンポーネントということは間違いないことで、それだけに難しいです。処理はアバウトでしか分かりません。ここではキャッシュ回収にかかる処理をみてみました。(処理の中にスラブの回収もあるようです。)
キャッシュ回収はzone->active_list/inactive_listの処理に他なりません。active_listの一部のページをからinactive_listへ、inactive_listの一部のページをバディーシステムに返すことでメモリを回収するわけです。
alloc_pageからtry_to_free_pages関数へ、そしてdo_try_to_free_pages関数へとコールされます。try_to_free_pages関数では回収処理のパラメータとなるscan_control scを設定することにあります。.gfp_mask = gfp_maskは呼び出し元のgfp_t gfp_maskです。これでどのzoneから回収すべきか判断されます。.swap_cluster_max = SWAP_CLUSTER_MAXは回収する単位です。一回の回収処理でSWAP_CLUSTER_MAX=32ページ回収します。.may_swap = 1は無名ページの回収もする事を意味します。無名ページの回収はスワップが発生するため、できるものなら避けたいものです。このフラグで無名ページの回収の有無を設定しています。.order = orderは呼び出し元の要求ページ数です。これはisolate_pages_globalコールバック関数で、回収対象を選別する時、できるものなら連続するフレームを要求数だけは回収したいものです。その処理を実現するための情報です。isolate_pages = isolate_pages_globalは回収候補を取り出すためのコールバック関数です。あとのメンバは未調査です。
shrink_zones関数でzone->active_list/inactive_listにかかる処理です。nr_reclaimedには回収したページ数が加算されていきます。alloc_pageから呼ばれるケースではscan_global_lru(sc)は真です。でスラブの回収を行っています。(今回無視)
total_scanned += sc->nr_scannedでスキャンした全ページ数です。その数がsc->swap_cluster_max + sc->swap_cluster_max / 2を超えたら、なかなか回収できないとみなし、wakeup_pdflusでdirtyなページを書き出します。(たぶん)
if (nr_reclaimed >= sc->swap_cluster_max)で回収ページがSWAP_CLUSTER_MAX超えたら、回収処理は終了です。
if (sc->nr_scanned && priority < DEF_PRIORITY - 2)では1個でもスキャンして、priority<10なら、これもなかな回収処理が進まない。とみなし、呼び出しもとのタスクをウエイトさせるようです。(たぶん)
ページ回収ゾーンとして処理します。ZONE_NORMALでのページ要求でZONE_HIGHを回収しても意味ありません。
for_each_zone_zonelistマクロで各ゾーンでループします。まずpopulated_zone関数でそのゾーンが実際に存在するかチェックします。if (scan_global_lru(sc))内の処理は、そのゾーンそのものが回収対象となっているかのチェックだと思います。で回収対象ならsc->all_unreclaimable = 0としてshrink_zone関数をコールします。この返り値は実際回収したページ数です。
zone_page_state(zone, NR_ACTIVE)はactive_listのページ数で、priority分シフトします。priority値が大きいほどスキャンする数が少なくなります。+1は少なくとも1つはスキャンページ数を保障するためです。それをzone->nr_scan_activeに足しこんでいます。これは前回の処理でスキャン数がsc->swap_cluster_max大きくなかった時の数を考慮するためです。またスキャン数をnr_activeにも設定しています。同様の事をinactive_listについても行います。
if (nr_inactive >= sc->swap_cluster_max)ならactive_list、if (nr_active >= sc->swap_cluster_max)ならinactive_listについて処理可です。そうでないならnr_inactive = 0/nr_active = 0とします。そうすることで以降のwhile処理をスキップすることになります。
while (nr_active || nr_inactive)で、nr_active -= nr_to_scanで処理する毎にスキャンページ数を引きながらshrink_active_list関数でactive_listにかかる処理を、同じようにnr_inactive -= nr_to_scanとしながら、shrink_inactive_list関数でinactive_listにかかる処理を行います。そしてこの返り値が実際に解放したページ数になります。
キャッシュ回収はzone->active_list/inactive_listの処理に他なりません。active_listの一部のページをからinactive_listへ、inactive_listの一部のページをバディーシステムに返すことでメモリを回収するわけです。
alloc_pageからtry_to_free_pages関数へ、そしてdo_try_to_free_pages関数へとコールされます。try_to_free_pages関数では回収処理のパラメータとなるscan_control scを設定することにあります。.gfp_mask = gfp_maskは呼び出し元のgfp_t gfp_maskです。これでどのzoneから回収すべきか判断されます。.swap_cluster_max = SWAP_CLUSTER_MAXは回収する単位です。一回の回収処理でSWAP_CLUSTER_MAX=32ページ回収します。.may_swap = 1は無名ページの回収もする事を意味します。無名ページの回収はスワップが発生するため、できるものなら避けたいものです。このフラグで無名ページの回収の有無を設定しています。.order = orderは呼び出し元の要求ページ数です。これはisolate_pages_globalコールバック関数で、回収対象を選別する時、できるものなら連続するフレームを要求数だけは回収したいものです。その処理を実現するための情報です。isolate_pages = isolate_pages_globalは回収候補を取り出すためのコールバック関数です。あとのメンバは未調査です。
unsigned long try_to_free_pages(struct zonelist *zonelist, int order, gfp_t gfp_mask) { struct scan_control sc = { .gfp_mask = gfp_mask, .may_writepage = !laptop_mode, .swap_cluster_max = SWAP_CLUSTER_MAX, .may_swap = 1, .swappiness = vm_swappiness, .order = order, .mem_cgroup = NULL, .isolate_pages = isolate_pages_global, }; return do_try_to_free_pages(zonelist, &sc); }do_try_to_free_pages関数でDEF_PRIORITY=12から0の13回ループ処理を行います。sc->nr_scannedは回収するためにスキャンしたページ数になります。if (!priority)の条件がマッチするというのは、回収処理がなかなか進まない事を意味します。その1つの可能性として、呼び出しもとのプロセスの無名ページが回収され、また読み込まれ(スラアシング)が発生していることが考えられる(漠然として推測です。)ため、そのプロセスにスワップ対象から外していると解釈することにしました。
shrink_zones関数でzone->active_list/inactive_listにかかる処理です。nr_reclaimedには回収したページ数が加算されていきます。alloc_pageから呼ばれるケースではscan_global_lru(sc)は真です。でスラブの回収を行っています。(今回無視)
total_scanned += sc->nr_scannedでスキャンした全ページ数です。その数がsc->swap_cluster_max + sc->swap_cluster_max / 2を超えたら、なかなか回収できないとみなし、wakeup_pdflusでdirtyなページを書き出します。(たぶん)
if (nr_reclaimed >= sc->swap_cluster_max)で回収ページがSWAP_CLUSTER_MAX超えたら、回収処理は終了です。
if (sc->nr_scanned && priority < DEF_PRIORITY - 2)では1個でもスキャンして、priority<10なら、これもなかな回収処理が進まない。とみなし、呼び出しもとのタスクをウエイトさせるようです。(たぶん)
static unsigned long do_try_to_free_pages(struct zonelist *zonelist, struct scan_control *sc) { : : for (priority = DEF_PRIORITY; priority >= 0; priority--) { sc->nr_scanned = 0; if (!priority) disable_swap_token(); nr_reclaimed += shrink_zones(priority, zonelist, sc); if (scan_global_lru(sc)) { shrink_slab(sc->nr_scanned, sc->gfp_mask, lru_pages); if (reclaim_state) { nr_reclaimed += reclaim_state->reclaimed_slab; reclaim_state->reclaimed_slab = 0; } } total_scanned += sc->nr_scanned; if (nr_reclaimed >= sc->swap_cluster_max) { ret = nr_reclaimed; goto out; } if (total_scanned > sc->swap_cluster_max + sc->swap_cluster_max / 2) { wakeup_pdflush(laptop_mode ? 0 : total_scanned); sc->may_writepage = 1; } if (sc->nr_scanned && priority < DEF_PRIORITY - 2) congestion_wait(WRITE, HZ/10); } if (!sc->all_unreclaimable && scan_global_lru(sc)) ret = nr_reclaimed; out: : : return ret; }shrink_zones関数では全てのゾーンについてshrink_zone関数を呼び出します。まずhigh_zoneidx = gfp_zone(sc->gfp_mask)で、呼び出し元が要求するゾーンIDを取得します。enum zone_typeで通常ZONE_DMA/ZONE_NORMAL/ZONE_HIGHMEMと0/1/2となっています。従って呼び出し元の要求するゾーンまでのゾーンを
ページ回収ゾーンとして処理します。ZONE_NORMALでのページ要求でZONE_HIGHを回収しても意味ありません。
for_each_zone_zonelistマクロで各ゾーンでループします。まずpopulated_zone関数でそのゾーンが実際に存在するかチェックします。if (scan_global_lru(sc))内の処理は、そのゾーンそのものが回収対象となっているかのチェックだと思います。で回収対象ならsc->all_unreclaimable = 0としてshrink_zone関数をコールします。この返り値は実際回収したページ数です。
static unsigned long shrink_zones(int priority, struct zonelist *zonelist, struct scan_control *sc) { enum zone_type high_zoneidx = gfp_zone(sc->gfp_mask); unsigned long nr_reclaimed = 0; struct zoneref *z; struct zone *zone; sc->all_unreclaimable = 1; for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) { if (!populated_zone(zone)) continue; if (scan_global_lru(sc)) { if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL)) continue; note_zone_scanning_priority(zone, priority); if (zone_is_all_unreclaimable(zone) && priority != DEF_PRIORITY) continue; /* Let kswapd poll it */ sc->all_unreclaimable = 0; } else { sc->all_unreclaimable = 0; mem_cgroup_note_reclaim_priority(sc->mem_cgroup, priority); } nr_reclaimed += shrink_zone(priority, zone, sc); } return nr_reclaimed; }shrink_zone関数では上から呼ばれた引数から、スキャンする数を決定し、shrink_active_list/shrink_inactive_list関数を呼ぶことでページを回収することになります。返り値回収したページ数で、これはshrink_inactive_list関数でバヂィシステムに返したページ数です。
zone_page_state(zone, NR_ACTIVE)はactive_listのページ数で、priority分シフトします。priority値が大きいほどスキャンする数が少なくなります。+1は少なくとも1つはスキャンページ数を保障するためです。それをzone->nr_scan_activeに足しこんでいます。これは前回の処理でスキャン数がsc->swap_cluster_max大きくなかった時の数を考慮するためです。またスキャン数をnr_activeにも設定しています。同様の事をinactive_listについても行います。
if (nr_inactive >= sc->swap_cluster_max)ならactive_list、if (nr_active >= sc->swap_cluster_max)ならinactive_listについて処理可です。そうでないならnr_inactive = 0/nr_active = 0とします。そうすることで以降のwhile処理をスキップすることになります。
while (nr_active || nr_inactive)で、nr_active -= nr_to_scanで処理する毎にスキャンページ数を引きながらshrink_active_list関数でactive_listにかかる処理を、同じようにnr_inactive -= nr_to_scanとしながら、shrink_inactive_list関数でinactive_listにかかる処理を行います。そしてこの返り値が実際に解放したページ数になります。
static unsigned long shrink_zone(int priority, struct zone *zone, struct scan_control *sc) { : if (scan_global_lru(sc)) { zone->nr_scan_active += (zone_page_state(zone, NR_ACTIVE) >> priority) + 1; nr_active = zone->nr_scan_active; zone->nr_scan_inactive += (zone_page_state(zone, NR_INACTIVE) >> priority) + 1; nr_inactive = zone->nr_scan_inactive; if (nr_inactive >= sc->swap_cluster_max) zone->nr_scan_inactive = 0; else nr_inactive = 0; if (nr_active >= sc->swap_cluster_max) zone->nr_scan_active = 0; else nr_active = 0; } else { nr_active = mem_cgroup_calc_reclaim_active(sc->mem_cgroup, zone, priority); nr_inactive = mem_cgroup_calc_reclaim_inactive(sc->mem_cgroup, zone, priority); } while (nr_active || nr_inactive) { if (nr_active) { nr_to_scan = min(nr_active, (unsigned long)sc->swap_cluster_max); nr_active -= nr_to_scan; shrink_active_list(nr_to_scan, zone, sc, priority); } if (nr_inactive) { nr_to_scan = min(nr_inactive, (unsigned long)sc->swap_cluster_max); nr_inactive -= nr_to_scan; nr_reclaimed += shrink_inactive_list(nr_to_scan, zone, sc); } } throttle_vm_writeout(sc->gfp_mask); return nr_reclaimed; }