スラブの回収
Rev.1を表示中。最新版はこちら。
メモリ回収においてスラブもその対象となります。スラブ用キャッシュ作成時、start_cpu_timer関数がコールされcache_reapをコールバック関数とするワーキュキューが登録されるようになっています。メモリ回収の処理はkfreeでスラブを解放する処理と大筋で同じです。オブジェクトの解放はfree_blockと同じ関数をコールしています。
cache_reap関数のlist_for_each_entryマクロで、cache_chainで繋がれた全てのstruct kmem_cacheのメモリ解放を試みます。drain_array関数でその処理を行います。まずstruct kmem_cache->array[]のプールしている配列を、次にstruct kmem_cache->shared[]を、最後にスラブフリーリストをチェックしています。ます。なおその条件として、if (time_after(l3->next_reap, jiffies))としています。もしstruct kmem_cache->array[]の処理が多くて、l3->next_reap<jiffiesになったとすると、以降のチェックをスキップします。
この時間はl3->next_reap = jiffies + REAPTIMEOUT_LIST3で設定されていて4秒と言うことです。(HZ1秒間発生する割り込みですなわちjiffiesです。)
if (l3->free_touched)はslab_freeからオブジェクトが追加された場合に1に設定されています。直近でここのスラブが使われたなら、今回を解放の対象としないで、次回の対象とすべくl3->free_touched = 0としています。
なお、1キャッシュチェック毎にcond_resched関数でタスク切り替えが必要がチェックし、必要ならスケージューリングするようになっています。
#define REAPTIMEOUT_LIST3 (4*HZ) static void cache_reap(struct work_struct *w) { struct kmem_cache *searchp; struct kmem_list3 *l3; int node = numa_node_id(); struct delayed_work *work = container_of(w, struct delayed_work, work); if (!mutex_trylock(&cache_chain_mutex)) goto out; list_for_each_entry(searchp, &cache_chain, next) { check_irq_on(); l3 = searchp->nodelists[node]; drain_array(searchp, l3, cpu_cache_get(searchp), 0, node); if (time_after(l3->next_reap, jiffies)) goto next; l3->next_reap = jiffies + REAPTIMEOUT_LIST3; drain_array(searchp, l3, l3->shared, 0, node); if (l3->free_touched) l3->free_touched = 0; else { int freed;
freed = drain_freelist(searchp, l3, (l3->free_limit + 5 * searchp->num - 1) / (5 * searchp->num)); STATS_ADD_REAPED(searchp, freed); } next: cond_resched(); } check_irq_on(); mutex_unlock(&cache_chain_mutex); next_reap_node(); out: schedule_delayed_work(work, round_jiffies_relative(REAPTIMEOUT_CPUC)); }drain_array関数で解放の処理を行います。引数のforceは無条件に解放するかどうかの引数で、0ならstruct array_cache->touchedを考慮することです。この変数はkallocでこの配列からオブジェクトが割り当てられる毎に1に設定されています。すなわち直近にここからオブジェクトが割り当てられたと言う事を示す変数です。
cache_reap関数から呼ばれる時、int force=0です。ac->touched!=0なら今回は処理しません。ただし次回処理対象とするためac->touched = 0としています。
forceが0なら、空き要素数を、そうでないなら、最大数/5(ただし空き要素数より大きいと空き要素/2)を、プール配列からの削除といたします。それをfree_block関数kfree(スラブの解放)で処理します。
そして、ac->avail -= tofreeで削除した分を減算し、memmoveで配列を先頭を調整します。
void drain_array(struct kmem_cache *cachep, struct kmem_list3 *l3, struct array_cache *ac, int force, int node) { int tofree; if (!ac || !ac->avail) return; if (ac->touched && !force) { ac->touched = 0; } else { spin_lock_irq(&l3->list_lock); if (ac->avail) { tofree = force ? ac->avail : (ac->limit + 4) / 5; if (tofree > ac->avail) tofree = (ac->avail + 1) / 2; free_block(cachep, ac->entry, tofree, node); ac->avail -= tofree; memmove(ac->entry, &(ac->entry[tofree]), sizeof(void *) * ac->avail); } spin_unlock_irq(&l3->list_lock); } }drain_freelist関数は、int tofree(l3->free_limit + 5 * searchp->num - 1) / (5 * searchp->num))で指定する数だけ、slabs_freeからスラブをリストからはずし、スラブそのものを削除します。
static int drain_freelist(struct kmem_cache *cache, struct kmem_list3 *l3, int tofree) { struct list_head *p; int nr_freed; struct slab *slabp; nr_freed = 0; while (nr_freed < tofree && !list_empty(&l3->slabs_free)) { spin_lock_irq(&l3->list_lock); p = l3->slabs_free.prev; if (p == &l3->slabs_free) { spin_unlock_irq(&l3->list_lock); goto out; } slabp = list_entry(p, struct slab, list); list_del(&slabp->list); l3->free_objects -= cache->num; spin_unlock_irq(&l3->list_lock); slab_destroy(cache, slabp); nr_freed++; } out: return nr_freed; }