Linux 3.3 の新機能 Per-cgroup TCP buffer limits (2)
(2014-03-06 追記) この部分のコード,sysctl パラメータ経由で TCP buffer に対して制限をかけるというコードはうまく動かないようで Linux 3.13 で削除されています.これがなんか値が合わないなあ,ってなってた原因ですかね? (新しいコード見てないのでわかりません :p) > tcp_memcontrol: Remove setting cgroup settings via sysctl
Linux 3.3 の新機能 Per-cgroup TCP buffer limits - TenForwardの日記 の続編です.カーネルのコードを読んだりしていますが,私はその辺りの知識は殆どありませんので,間違いがある場合は指摘していただけるとありがたいです.
Per-cgroup TCP buffer limits に関するドキュメント
この制限を設定するには cgroup filesystem の memory.kmem.tcp.limit_in_bytes というファイルに値をセットするわけですが,この機能に関するドキュメントはカーネルソースの Documentation/cgroup/memory.txt 内の
: 75 memory.kmem.tcp.limit_in_bytes # set/show hard limit for tcp buf memory 76 memory.kmem.tcp.usage_in_bytes # show current tcp buf memory allocation : 261 2.7 Kernel Memory Extension (CONFIG_CGROUP_MEM_RES_CTLR_KMEM) 262 263 With the Kernel memory extension, the Memory Controller is able to limit 264 the amount of kernel memory used by the system. Kernel memory is fundamentally 265 different than user memory, since it can't be swapped out, which makes it 266 possible to DoS the system by consuming too much of this precious resource. 267 268 Kernel memory limits are not imposed for the root cgroup. Usage for the root 269 cgroup may or may not be accounted. 270 271 Currently no soft limit is implemented for kernel memory. It is future work 272 to trigger slab reclaim when those limits are reached. 273 274 2.7.1 Current Kernel Memory resources accounted 275 276 * sockets memory pressure: some sockets protocols have memory pressure 277 thresholds. The Memory Controller allows them to be controlled individually 278 per cgroup, instead of globally. 279 280 * tcp memory pressure: sockets memory pressure for the tcp protocol.
これだけですね.
TCP が使うメモリ量のシステム全体の制御
ところでこれ以前にシステム全体の TCP のメモリを制御する sysctl パラメータに net.ipv4.tcp_mem というものがあるのをご存知の方も多いかと思います.
$ sudo sysctl -a | grep net.ipv4.tcp_mem net.ipv4.tcp_mem = 191406 255208 382812
この三つの値は最初から最小値,pressure値,最大値です.Pressure というのは,この値を超えた所でカーネルが TCP が使うメモリ量の圧縮を始めます.そしてこの圧縮モードは消費量が min の値以下になるまで続きます.詳しくは Documentation/networking/ip-sysctl.txt をどうぞ.
312 tcp_mem - vector of 3 INTEGERs: min, pressure, max 313 min: below this number of pages TCP is not bothered about its 314 memory appetite. 315 316 pressure: when amount of memory allocated by TCP exceeds this number 317 of pages, TCP moderates its memory consumption and enters memory 318 pressure mode, which is exited when memory consumption falls 319 under "min". 320 321 max: number of pages allowed for queueing by all TCP sockets. 322 323 Defaults are calculated at boot time from amount of available 324 memory.
では,これと Per-cgroup TCP buffer limits の関係は? (私は最初は全く関係ないと思ってました)
コードを読む
カーネルのコードで memory.kmem.tcp.limit_in_bytes を設定している部分を調べてみました.このあたりのコードを理解するにはまず,Documentation/cgroups/resource_counter.txtを見ておくと cgroup のリソースコントロールの仕組みが分かって良いかもしれません.
この機能の cgroup を通じての値の入出力は net/ipv4/tcp_memcontrol.c で記述されています.ファイルの冒頭の方で
static struct cftype tcp_files[] = { { .name = "kmem.tcp.limit_in_bytes", .write_string = tcp_cgroup_write, .read_u64 = tcp_cgroup_read, .private = RES_LIMIT, }, : (snip) };
という風に定義されており,memory.kmem.tcp.limit_in_bytes の設定は tcp_cgroup_write 関数,読み取りは tcp_cgroup_read 関数で行うことになっています.tcp_cgroup_write は短い関数で
static int tcp_cgroup_write(struct cgroup *cont, struct cftype *cft, const char *buffer) { struct mem_cgroup *memcg = mem_cgroup_from_cont(cont); unsigned long long val; int ret = 0; switch (cft->private) { case RES_LIMIT: /* see memcontrol.c */ ret = res_counter_memparse_write_strategy(buffer, &val); if (ret) break; ret = tcp_update_limit(memcg, val); break; default: ret = -EINVAL; break; } return ret; }
となっています.res_counter_memparse_write_strategy はファイルに書かれた内容の読み取りを行っており,kernel/res_counter.c で定義されています.実質はこんな感じ.
int res_counter_memparse_write_strategy(const char *buf, unsigned long long *res) { : (snip) *res = memparse(buf, &end); : (snip) *res = PAGE_ALIGN(*res); return 0; }
memparse は,例えば cgroup ファイルシステムのファイルに echo "10M" > memory.kmem.tcp.limit_in_bytes みたいに "K" とか "M" とか付けた場合にバイトへの変換を行っているだけの関数で lib/cmdline.c にあります.
PAGE_ALIGN は,設定値の次のページ境界を取得するマクロで (include/linux/mm.h)
/* to align the pointer to the (next) page boundary */ #define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
これで値がページ単位 (x86 の場合 4096) 毎になるんだと思います.
で,この後実際に tcp_update_limit で設定されます.
static int tcp_update_limit(struct mem_cgroup *memcg, u64 val) { : (snip) for (i = 0; i < 3; i++) tcp->tcp_prot_mem[i] = min_t(long, val >> PAGE_SHIFT, net->ipv4.sysctl_tcp_mem[i]); : (snip) return 0; }
色々処理はありますが,実質は上記がコアの部分の処理で,
- min_t はマクロで,上記だと long 型で比較を行い,val >> PAGE_SHIFT の値と net->ipv4.sysctl_tcp_mem[i] の値を比較して小さい方を取得する
- PAGE_SHIFT は x86 の場合 12 (つまり 1<<12 = 4096)
- tcp→tcp_prot_mem[3] は,初期化関数の tcp_init_cgroup 内で sysctl の tcp_mem の値を入れているので,sysctl の tcp_mem の 3 つのパラメータと同じ意味と思われる.こんな感じ
tcp->tcp_prot_mem[0] = net->ipv4.sysctl_tcp_mem[0];
tcp->tcp_prot_mem[1] = net->ipv4.sysctl_tcp_mem[1];
tcp->tcp_prot_mem[2] = net->ipv4.sysctl_tcp_mem[2];
つまり tcp_mem の最小値,pressure値,最大値のそれぞれの値を memory.kmem.tcp.limit_in_bytes の値を比較し,小さい方を新しい設定値として設定していることになります.結構乱暴ですが,制限を行うのに新たな仕組みを作ることなく,従来からある仕組みをうまく利用しているコードということになりますね.
結局どういう制限がかかるのか?
Per-cgroup TCP buffer limits の機能を有効にすると,
そしてその制限は,memory.kmem.tcp.limit_in_bytes の値が
- 4096 (x86の場合) 以下の場合:"4096 4096 4096" という変な(?)設定になる
- tcp_mem の最小値より大きく pressure より小さな値の場合:"最小値 memory.kmem.tcp.limit_in_bytesの値 memory.kmem.tcp.limit_in_bytesの値" というこれも若干変な(?)設定になる
- pressure より大きく最大値より小さい場合:"最小値 pressure値 memory.kmem.tcp.limit_in_bytesの値" という設定になる.これは自然ですかね.
- 最大値より大きい場合:net.ipv4.tcp_mem の設定がそのまま使われる
ということになります.
先に示した文書などでは sysctl との関係は全く触れられておらず,この機能を有効にすると tcp_mem がグループ毎に効くようになる,というのは全くわかりませんね.もう少し機能がちゃんと実装されたら (soft_limit とか),情報も充実するのでしょうか? (^^;)
(ちなみに sysctl で設定した値が cgroup 毎にかかる,というのはこの機能関連のコミットの際のログを見ればそれっぽい事は書いてあります.でもまあ普通はコミットログなんて見ないわなー ^^;)
(Linux 3.3 の新機能 Per-cgroup TCP buffer limits (3) - TenForwardの日記へ続く)