深掘りandroid low memory killer

15028 ワード

PCにとってメモリは重要です.プログラムにメモリ漏れが発生した場合、通常、システムはプロセスKillを削除します.Linuxでは、システムメモリが不足している場合にプロセスを選択してKillを削除するOOM(Out Of Memory、メモリ不足)という名前のメカニズムを使用してこのタスクを完了します.Androidは組み込み機器のオペレーティングシステムであるため、同じタスクを完了するために新しいメカニズムLow Memory Killerを使用しています.まず、Low Memory Killerメカニズムの原理と、Killされるプロセスをどのように選択するかを見てみましょう.
 
  1.Low Memory Killerの原理とメカニズム
 
Low Memory Killerは、プロセス記述のoom_adj値が同じ範囲にある場合、プロセスはKillによって削除されます.通常、「/sys/module/lowmemorykiller/parameters/adj」でoom_を指定します.adjの最小値は、「/sys/module/lowmemorykiller/parameters/minfree」に空きページの数を格納し、すべての値をカンマで区切って昇順に並べます.例えば、「0,8」を/sys/module/lowmemorykiller/parameters/adjに書き込み、「10244096」を/sys/module/lowmemory-killer/parameters/minfreeに書き込み、プロセスの空きストレージスペースが4096ページに低下した場合、oom_adj値が8以上のプロセスはKillによって削除されます.同様に、プロセスの空きストレージ領域が1024ページに低下するとoom_adj値が0以上のプロセスはKillによって削除されます.lowmemorykiller.でcには、次のような値が指定されています.
static int lowmem_adj[6] = {
    0,
    1,
    6,
    12,
};
static int lowmem_adj_size = 4;
static size_t lowmem_minfree[6] = {
    3*512, // 6MB
    2*1024, // 8MB
    4*1024, // 16MB
    16*1024, // 64MB
};
static int lowmem_minfree_size = 4;

つまり、プロセスの空き領域が3´512ページに下がるとoom_adj値が0以上のプロセスはKillによって削除されます.プロセスの空き領域が2´1024ページに下がるとoom_adj値が10以上のプロセスはKillによって削除されます.より簡明な理解は、以下の条件を満たすプロセスが優先的にKillされることです.
  task_struct->signal_struct->oom_adjが大きいほど優先的にKillされます.
物理メモリを最も多く消費するプロセスはKillが優先されます.
プロセス記述子のsignal_struct->oom_adjはメモリ不足時にプロセスが選択されKillの優先度を表し、値範囲は-17~15である.-17の場合は選択されず、値が大きいほど選択される可能性があります.プロセスが選択されると、カーネルはSIGKILL信号を送信してKillを削除します.
実際、Low Memory Killerドライバは、キャッシュに使用されるストレージ領域の大部分が解放されると考えられますが、キャッシュストレージ領域の大部分がロックされた状態であれば、これは非常に深刻なエラーであり、通常のoom killerがタッチされるまでプロセスはKillによって落とされません.
2.Low Memory Killerの具体的な実現
Low Memory Killerの原理を理解した後,この駆動をどのように実現するかを見てみよう.Low Memory Killer駆動の実現はdrivers/misc/lowmemorykillerにある.c.
このドライバの実装は非常に簡単で、その初期化と終了操作も私たちが今まで見た中で最も簡単で、コードは以下の通りです.
static int __init lowmem_init(void)
{
    register_shrinker(&lowmem_shrinker);
    return 0;
}
static void __exit lowmem_exit(void)
{
    unregister_shrinker(&lowmem_shrinker);
}
module_init(lowmem_init);
module_exit(lowmem_exit);

初期化関数lowmem_initでregister_を通過shrinkerはshrinkerをlowmemとして登録しました.shrinker;終了時に関数lowmem_が呼び出されましたexit、unregister_を介してshrinkerは登録されているlowmemをアンインストールします.shrinker.そのうちlowmem_shrinkerの定義は次のとおりです.
static struct shrinker lowmem_shrinker = {
    .shrink = lowmem_shrink,
    .seeks = DEFAULT_SEEKS * 16
};

 lowmem_shrinkはこのドライバのコア実装であり、メモリが不足するとlowmem_が呼び出されます.shrinkメソッドはKillにいくつかのプロセスを削除します.次に、その具体的な実装を分析し、実装コードは以下の通りである.
static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    struct task_struct *p;
    struct task_struct *selected = NULL;
    int rem = 0;
    int tasksize;
    int i;
    int min_adj = OOM_ADJUST_MAX + 1;
    int selected_tasksize = 0;
    int array_size = ARRAY_SIZE(lowmem_adj);
    int other_free = global_page_state(NR_FREE_PAGES);
    int other_file = global_page_state(NR_FILE_PAGES);
    if(lowmem_adj_size < array_size)
        array_size = lowmem_adj_size;
    if(lowmem_minfree_size < array_size)
        array_size = lowmem_minfree_size;
    for(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
    if(nr_to_scan > 0)
        lowmem_print(3, "lowmem_shrink %d, %x, ofree %d %d, ma %d
", nr_to_scan, gfp_mask, other_free, other_file, min_adj); rem = global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) { lowmem_print(5, "lowmem_shrink %d, %x, return %d
", nr_to_scan, gfp_mask, rem); return rem; } read_lock(&tasklist_lock); for_each_process(p) { if (p->oomkilladj < min_adj || !p->mm) continue; tasksize = get_mm_rss(p->mm); if (tasksize <= 0) continue; if (selected) { if (p->oomkilladj < selected->oomkilladj) continue; if (p->oomkilladj == selected->oomkilladj && tasksize <= selected_tasksize) continue; } selected = p; selected_tasksize = tasksize; lowmem_print(2, "select %d (%s), adj %d, size %d, to kill
", p->pid, p->comm, p->oomkilladj, tasksize); } if(selected != NULL) { lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d
", selected->pid, selected->comm, selected->oomkilladj, selected_tasksize); force_sig(SIGKILL, selected); rem -= selected_tasksize; } lowmem_print(4, "lowmem_shrink %d, %x, return %d
", nr_to_scan, gfp_mask, rem); read_unlock(&tasklist_lock); return rem; }

global_が多く使われていることがわかりますpage_state関数.多くの人がこの関数を見つけられず、linux/vmstat.に定義されています.hでは、そのパラメータはzone_を使用するstat_item列挙はlinux/mmzoneに定義される.hでは、具体的なコードは以下の通りである.
enum zone_stat_item {
    NR_FREE_PAGES,
    NR_LRU_BASE,
    NR_INACTIVE_ANON = NR_LRU_BASE,
    NR_ACTIVE_ANON,
    NR_INACTIVE_FILE,
    NR_ACTIVE_FILE,
#ifdef CONFIG_UNEVICTABLE_LRU
    NR_UNEVICTABLE,
    NR_MLOCK,
#else
    NR_UNEVICTABLE = NR_ACTIVE_FILE, /*       */
    NR_MLOCK = NR_ACTIVE_FILE,
#endif
    NR_ANON_PAGES,        /*       */
    NR_FILE_MAPPED,        /*    */
    NR_FILE_PAGES,
    NR_FILE_DIRTY,
    NR_WRITEBACK,
    NR_SLAB_RECLAIMABLE,
    NR_SLAB_UNRECLAIMABLE,
    NR_PAGETABLE,
    NR_UNSTABLE_NFS,
    NR_BOUNCE,
    NR_VMSCAN_WRITE,
    NR_WRITEBACK_TEMP,    /*        */
#ifdef CONFIG_NUMA
    NUMA_HIT,            /*         */
    NUMA_MISS,            /*          */
    NUMA_FOREIGN,
    NUMA_INTERLEAVE_HIT,
    NUMA_LOCAL,            /*        */
    NUMA_OTHER,            /*         */
#endif
    NR_VM_ZONE_STAT_ITEMS };

振り返るとowmem_shrink関数は、まず定義したlowmem_を決定します.adjとlowmem_minfree配列のサイズ(要素個数)が一致するかどうかは、一致しない場合は最小を基準とします.なぜならlowmemを比較する必要があるからですminfreeの空きストレージ領域の値を指定して、最小min_を決定します.adj値(その条件が満たされると、その配列インデックスによってlowmem_adj内の対応する要素の値を探す).後でmin_を検出adjの値が初期値「OOM_ADJUST_MAX+1」であるかどうかは、もしそうであれば、条件を満たすmin_がないことを示すadj値、そうでなければ次のステップに進みます.次にループを用いて各プロセスブロックを判断しmin_adjは、条件を満たす具体的なプロセスを探す(主にoomkilladjとtask_structを判断することを含む).最後に、見つかったプロセスについてNULL判定を行い、「force_sig(SIGKILL,selected)」を介してSIGKILL信号をカーネルに送信し、選択した「selected」プロセスをKillが削除する.
ここまでLow Memory Killerの解析について,そのメカニズムと原理を理解した後,その実現は非常に簡単で,標準的なLinux OOMメカニズムと類似しているが,実現方式はやや異なることが分かった.標準LinuxのOOM Killerメカニズムはmm/oom_kill.cで実装され、_alloc_pages_may_oom呼び出し(メモリ割り当て時、すなわちmm/page_alloc.c).oom_kill.c最も主要な関数はout_ですof_Memoryは、badプロセスKillを選択し、Killの方法はSIGKILL信号を送信することによっても同じです.out_of_memoryでselect_を呼び出すbad_プロセスはプロセスKillを選択し、選択の根拠はbadness関数で実現され、複数の基準に基づいて各プロセスに採点され、採点が最も高いものが選択され、Killされる.一般的に、メモリの消費量が多いほどoom_adjが大きいほど、選択される可能性があります.