メモリープール
メモリを取得する際(kmalloc関数等)、要求したプロセスを待機させることがあります。取得するメモリがない場合、メモリを確保するためスワップ処理が実行されるからです。そのためカーネルは、ウエイトできないパスでのメモリー獲得のため、幾ばくかのメモリを予約しています。これはGFP_ATOMIC属性で、メモリ獲得関数をコールすることで、通常のメモリが取得出来無い場合、予約メモリからメモリを取得しています。
従ってこのメモリーは、GFP_ATOMIC属性でkmalloc関数等をコールすることで、任意カーネルパスにおいても、この予約メモリの使用ができる事を意味します。
反面メモリープールは、動的にメモリープールを定義することで、その識別子を有しているパス下のみで、使用出きるようにした機能です。
メモリープールは、struct mempool_sで管理されています。
whileループで、pool->min_nrまで、allocコールバック関数でメモリを確保し、そのアドレスをadd_element()で、メモリープールのelements[]に1つづつ設定していくだけです。
remove_element()で--pool->curr_nrのエレメントを返します。pool->curr_nrが0ならメモリープールはすべて使い切っています。
prepare_to_wait()でプロセスを、メモリプールのウエイトリストに繋ぎます。そこから起床したら再度メモリープールの残存数をチェックします。起床処理中に、他のプロセスが、たった今開放されたメモリープールを使ったかもしれないからです。もしそうなら、io_schedule_timeout()でCPUの実行権を譲渡します。
ループを抜けると、メモリプールがとりあえず存在する可能性があるということです。goto repeat_allocで再度メモリーの獲得を行うことになります。
そうでないなら、freeコールバック関数で、実際にメモリを開放することになります。
従ってこのメモリーは、GFP_ATOMIC属性でkmalloc関数等をコールすることで、任意カーネルパスにおいても、この予約メモリの使用ができる事を意味します。
反面メモリープールは、動的にメモリープールを定義することで、その識別子を有しているパス下のみで、使用出きるようにした機能です。
メモリープールは、struct mempool_sで管理されています。
typedef struct mempool_s { spinlock_t lock; int min_nr; メモリープールの数 int curr_nr; 現在使用しているメモリープールのインデックス void **elements; メモリープールの個々のアドレス void *pool_data; alloc関数コール時の引数 mempool_alloc_t *alloc; メモリ割り当て関数 mempool_free_t *free; メモリ開放関数 wait_queue_head_t wait; メモリープールを使い切った時の、メモリ獲得時のウエイトリスト } mempool_t;メモリープール作成は、mempool_create()で作成されます。min_nrはメモリープールの数、alloc_fnはメモリー獲得関数、free_fnはメモリ開放関数、pool_dataはalloc_fnのコール時の引数となり、通常は、pool_dataをスラブキャッシュとして、スラブからの割り当て関数(kmem_cache_alloc/ kmem_cache_free)のヘルパー関数として定義しているようです。
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data) { return mempool_create_node(min_nr,alloc_fn,free_fn, pool_data,-1); }mempool_create_node()で実際のメモリープールが作成されます。kmalloc_node()でmempool_t *poolを割り当て、引数に対応するメンバーで初期化します。
whileループで、pool->min_nrまで、allocコールバック関数でメモリを確保し、そのアドレスをadd_element()で、メモリープールのelements[]に1つづつ設定していくだけです。
mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data, int node_id) { mempool_t *pool; pool = kmalloc_node(sizeof(*pool), GFP_KERNEL | __GFP_ZERO, node_id); if (!pool) return NULL; pool->elements = kmalloc_node(min_nr * sizeof(void *), GFP_KERNEL, node_id); if (!pool->elements) { kfree(pool); return NULL; } spin_lock_init(&pool->lock); pool->min_nr = min_nr; pool->pool_data = pool_data; init_waitqueue_head(&pool->wait); pool->alloc = alloc_fn; pool->free = free_fn; while (pool->curr_nr < pool->min_nr) { void *element; element = pool->alloc(GFP_KERNEL, pool->pool_data); if (unlikely(!element)) { free_pool(pool); return NULL; } add_element(pool, element); } return pool; } static void add_element(mempool_t *pool, void *element) { BUG_ON(pool->curr_nr >= pool->min_nr); pool->elements[pool->curr_nr++] = element; }mempool_alloc()で、メモリープールを考慮したメモリーの獲得を行います。まず、メモリープールのコールバック関数での取得を試みます。ここでは__GFP_WAITでない。ということです。成功すれば復帰です。そうでないなら取得dきませんでした。メモリープールから取得することになります。
remove_element()で--pool->curr_nrのエレメントを返します。pool->curr_nrが0ならメモリープールはすべて使い切っています。
prepare_to_wait()でプロセスを、メモリプールのウエイトリストに繋ぎます。そこから起床したら再度メモリープールの残存数をチェックします。起床処理中に、他のプロセスが、たった今開放されたメモリープールを使ったかもしれないからです。もしそうなら、io_schedule_timeout()でCPUの実行権を譲渡します。
ループを抜けると、メモリプールがとりあえず存在する可能性があるということです。goto repeat_allocで再度メモリーの獲得を行うことになります。
void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask) { void *element; unsigned long flags; wait_queue_t wait; gfp_t gfp_temp; might_sleep_if(gfp_mask & __GFP_WAIT); gfp_mask |= __GFP_NOMEMALLOC; /* don't allocate emergency reserves */ gfp_mask |= __GFP_NORETRY; /* don't loop in __alloc_pages */ gfp_mask |= __GFP_NOWARN; /* failures are OK */ gfp_temp = gfp_mask & ~(__GFP_WAIT|__GFP_IO); repeat_alloc: element = pool->alloc(gfp_temp, pool->pool_data); if (likely(element != NULL)) return element; spin_lock_irqsave(&pool->lock, flags); if (likely(pool->curr_nr)) { element = remove_element(pool); spin_unlock_irqrestore(&pool->lock, flags); return element; } spin_unlock_irqrestore(&pool->lock, flags); if (!(gfp_mask & __GFP_WAIT)) return NULL; gfp_temp = gfp_mask; init_wait(&wait); prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE); smp_mb(); if (!pool->curr_nr) { io_schedule_timeout(5*HZ); } finish_wait(&pool->wait, &wait); goto repeat_alloc; } static void *remove_element(mempool_t *pool) { BUG_ON(pool->curr_nr <= 0); return pool->elements[--pool->curr_nr]; }mempool_free()は、メモリープールのメモリを開放する時にコールされます。現在使用しているエレメント数が、最小エレメント数より小さい場合、そのメモリは開放しません。add_element()でメモリープールのエレメントに戻し、このメモリープールで待っているプロセスを、起床させます。
そうでないなら、freeコールバック関数で、実際にメモリを開放することになります。
void mempool_free(void *element, mempool_t *pool) { unsigned long flags; if (unlikely(element == NULL)) return; smp_mb(); if (pool->curr_nr < pool->min_nr) { spin_lock_irqsave(&pool->lock, flags); if (pool->curr_nr < pool->min_nr) { add_element(pool, element); spin_unlock_irqrestore(&pool->lock, flags); wake_up(&pool->wait); return; } spin_unlock_irqrestore(&pool->lock, flags); } pool->free(element, pool->pool_data); }