Linux kernelでpercpuなデータの入れ替えに使用されるthis_cpu_cmpxchg_double()のメモ。見ているカーネルは3.18。
これはマクロでlinux/include/percpu-defs.hにて下記のように定義されています。
507 #define this_cpu_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 508 __pcpu_double_call_return_bool(this_cpu_cmpxchg_double_, pcp1, pcp2, oval1, oval2, nval1, nval2)
__pcpu_double_call_return_boolマクロも同じファイルにて定義されていて、このような形。
343 #define __pcpu_double_call_return_bool(stem, pcp1, pcp2, ...) \ 344 ({ \ 345 bool pdcrb_ret__; \ 346 __verify_pcpu_ptr(&(pcp1)); \ 347 BUILD_BUG_ON(sizeof(pcp1) != sizeof(pcp2)); \ 348 VM_BUG_ON((unsigned long)(&(pcp1)) % (2 * sizeof(pcp1))); \ 349 VM_BUG_ON((unsigned long)(&(pcp2)) != \ 350 (unsigned long)(&(pcp1)) + sizeof(pcp1)); \ 351 switch(sizeof(pcp1)) { \ 352 case 1: pdcrb_ret__ = stem##1(pcp1, pcp2, __VA_ARGS__); break; \ 353 case 2: pdcrb_ret__ = stem##2(pcp1, pcp2, __VA_ARGS__); break; \ 354 case 4: pdcrb_ret__ = stem##4(pcp1, pcp2, __VA_ARGS__); break; \ 355 case 8: pdcrb_ret__ = stem##8(pcp1, pcp2, __VA_ARGS__); break; \ 356 default: \ 357 __bad_size_call_parameter(); break; \ 358 } \ 359 pdcrb_ret__; \ 360 }) 361
stem##1
というような形でサイズごとに呼び出し先が変わる。this_cpu_cmpxchg_doubleマクロの場合はstemにthis_cpu_cmpxchg_double_
がつくのでthis_cpu_cmpxchg_double_1とかになります。
これらがどこで定義されているかというとinclude/asm-generic/percpu.hのこの部分ですね。
403 #ifndef this_cpu_cmpxchg_double_1 404 #define this_cpu_cmpxchg_double_1(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 405 this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) 406 #endif 407 #ifndef this_cpu_cmpxchg_double_2 408 #define this_cpu_cmpxchg_double_2(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 409 this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) 410 #endif 411 #ifndef this_cpu_cmpxchg_double_4 412 #define this_cpu_cmpxchg_double_4(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 413 this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) 414 #endif 415 #ifndef this_cpu_cmpxchg_double_8 416 #define this_cpu_cmpxchg_double_8(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 417 this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) 418 #endif 419
相変わらずマクロを辿っていく必要がありますね。this_cpu_generic_cmpxchg_doubleマクロがどこにあるかというと、これも先と同じファイルのinclude/asm-generic/percpu.hでした。コードを見るとraw_cpu_generic_cmpxchg_double()の呼び出し前後で割り込みの禁止・許可が行われているのでそろそろ本題に近づいて来ましたかね。
159 #define this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 160 ({ \ 161 int __ret; \ 162 unsigned long __flags; \ 163 raw_local_irq_save(__flags); \ 164 __ret = raw_cpu_generic_cmpxchg_double(pcp1, pcp2, \ 165 oval1, oval2, nval1, nval2); \ 166 raw_local_irq_restore(__flags); \ 167 __ret; \ 168 }) 169
raw_cpu_generic_cmpxchg_doubleもマクロで処理はこうなっています。
96 #define raw_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \ 97 ({ \ 98 int __ret = 0; \ 99 if (raw_cpu_read(pcp1) == (oval1) && \ 100 raw_cpu_read(pcp2) == (oval2)) { \ 101 raw_cpu_write(pcp1, nval1); \ 102 raw_cpu_write(pcp2, nval2); \ 103 __ret = 1; \ 104 } \ 105 (__ret); \ 106 }) 107
最初にpcp1(percpu areaにあるデータ)とovalを比較して同じだった場合に、nvalがpcpのほうに代入されます。この代入はraw_cpu_write()がやっていて、相変わらずマクロを追いかけていく必要があるのですが、最終的には単純にこのような感じのコードになります。
pcp1 = nval1;
これをmm/slub.cのslab_alloc_node()で見てみます。ここでcはs->cpu_slabです。
2408 tid = c->tid; 2409 preempt_enable(); 2410 2411 object = c->freelist; 〜略〜 2433 if (unlikely(!this_cpu_cmpxchg_double( 2434 s->cpu_slab->freelist, s->cpu_slab->tid, 2435 object, tid, 2436 next_object, next_tid(tid)))) {
最初にtid、objectが取得されていて、それをしばらくしてからthis_cpu_cmpxchg_double()を使って現在のデータと比較して、他所から変更されていないようなら下記のような処理をするということですね。
s->cpu_slab->freelist = next_object; s->cpu_slab->tid = next_tid(tid);
マクロを見ていくのは面倒ですねぇ(´・ω・`)
Webエンジニアが知っておきたいインフラの基本 ~インフラの設計から構成、監視、チューニングまで~
- 作者: 馬場俊彰
- 出版社/メーカー: マイナビ
- 発売日: 2014/12/27
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る