C プログラミングにおいて、ユーザ空間で作業用メモリが必要な場合は、(1) 自動変数として宣言してユーザスタック領域に割り当ててもらうか、(2) malloc() を呼び出して割り当ててもらう、のどちらかが一般的だと思います。
一方、カーネル空間で作業用メモリが必要な場合は、カーネルスタック領域はあまり大きくないことから自動変数は控えめにして、カーネル空間の関数 uma_zalloc() もしくは malloc() を呼び出して割り当ててもらうのが一般的です。
uma_zalloc() は、socket 構造体のように同一構造体が多数必要となる場合に、効率よくメモリ割り当てを行う関数です。malloc() はそれ以外の場合に用いられる一般的なメモリ割り当て関数で、ユーザ空間の malloc() と使い方が似ています(引数は若干異なります)。
下記は、それぞれの関数の呼び出し例です。この例では、uma_zalloc() は socket 構造体用の作業用メモリを、malloc() は sockaddr 構造体用の作業用メモリを、それぞれ割り当てています。
so = uma_zalloc(socket_zone, M_NOWAIT | M_ZERO);
sa = malloc(sizeof(struct sockaddr), M_SONAME, M_WAITOK);
引数の M_WAITOK および M_NOWAIT はフラグで、メモリがすぐに確保できない場合に、その thread が sleep しても良いかどうかを示しています。詳しい利用方法は「man 9 uma」と「man 9 malloc」で見ることができます。
なお、UMA は「Universal Memory Allocator」の略です。uma_zalloc() は sys/vm/uma.h で定義された inline 関数で、実際に呼び出される関数 uma_zalloc_arg() は sys/vm/uma_core.c で定義されています。また、malloc() は sys/sys/malloc.h および sys/kern/kern_malloc.c で定義されています。
カーネルのメモリ管理については「The Design and Implementation of the FreeBSD Operating System, Second Edition」の第6章第3節「Kernel Memory Management」(p.230〜p.244)で解説されています。
それによると、カーネル内のメモリは、細かく見ると下記の階層構造で管理されているそうです。
名前 | 構造体名 | 定義ファイル |
---|---|---|
bucket | struct uma_bucket | sys/vm/uma_int.h |
zone | struct uma_zone | sys/vm/uma_int.h |
keg | struct uma_keg | sys/vm/uma_int.h |
slab | struct uma_slab | sys/vm/uma_int.h |
vmem | struct vmem | sys/kern/subr_vmem.c |
vm_map | struct vm_map | sys/vm/vm_map.h |
1番下の層は vm_map で、アドレス空間を管理しています。2番目の層は vmem で、実メモリをページ単位で管理しています。
下から3番目の層は slab で、固定サイズのオブジェクト用の作業用メモリをページ単位にまとめて効率よく管理しています。実メモリの確保には vmem を利用しています。4番目の層は keg で、slab のチェーンを管理しています。(slab は板状のものを、keg は小樽を意味する言葉だそうです)
下から5番目の層は zone で、型を持つオブジェクト(例:構造体)を管理しています。実際の割り当て処理に加えて、各オブジェクトの初期化処理や終了処理も行います。メモリ領域管理には keg を利用しています。6番目の層は bucket で、割り当て速度向上のため zone を CPU ごとに管理しています。
uma_zalloc() は bucket から割り当てるようです。
malloc() は、ページサイズ以下の割り当てでは内部的には uma_zalloc() を利用していて、2の冪乗ごとに zone が用意されているようです。ページサイズを超える場合は、vmem に割り当ててもらうようです。
なお、uma_zcreate() の引数で初期化/終了処理関数を2組指定できます。1組目の ctor/dtor は個々の zone の alloc/free のたびに毎回呼び出されます。2組目の uminit/fini は slab から bucket にまとめて取得/返却するときに1回づつ呼び出されます。
解説を読んで構造体を眺めただけでは理解があまりに浅いため、このような曖昧な文章しか書くことができません。きちんと理解するためには、やはりソースコードを読み込まないといけませんね…
以下、Universal Memory Allocator (UMA) の参考文献です。
Network Buffer Allocation in the FreeBSD Operating System (Bosko Milekic, 2004-05)
New UMA features for more efficient memory layout (Jeffrey Roberson, 2009-01-31)
UMA は Jeffrey Roberson さんが2002年頃から開発を始めて、2004年頃から Bosko Milekic さんが開発に参加したようです。sys/vm/uma.h をはじめ UMA のソースコードの先頭付近には著作権者としてお二人の名前が書かれています。
上記の BSDCan 2004 の Bosko Milekic さんの論文は、主に通信用の mbuf と cluster のメモリ管理についてです。