メモリープール


メモリを取得する際(kmalloc関数等)、要求したプロセスを待機させることがあります。取得するメモリがない場合、メモリを確保するためスワップ処理が実行されるからです。そのためカーネルは、ウエイトできないパスでのメモリー獲得のため、幾ばくかのメモリを予約しています。これはGFP_ATOMIC属性で、メモリ獲得関数をコールすることで、通常のメモリが取得出来無い場合、予約メモリからメモリを取得しています。

従ってこのメモリーは、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);
}


最終更新 2012/04/18 17:48:16 - north
(2012/04/18 17:45:38 作成)


検索

アクセス数
3698006
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。