Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RCU資料

 RCU資料

Yasuhiro Ohara

December 28, 2024
Tweet

More Decks by Yasuhiro Ohara

Other Decks in Programming

Transcript

  1. RCU: Read-Copy-Update • マルチスレッドにおけるリーダーライターロックの代わり。 ロックレス • 参照するデータを変更の世代分⽤意して、すげ替える • 参照スレッド側で(スレッドローカル︖)コピーを持ってそれを読む から、Read-Copy

    という名か︖すげ替える部分が Update︖ • QSBR がすごい • RCUには他にも memory barrier とか種類がある • QSBR: Quiescent State Based Reclamation • 多数の参照スレッド、少数(たぶん単⼀を想定)の更新スレッド • 参照スレッド側のオーバヘッドがほぼ皆無︕ • 本資料は QSBR に特化
  2. RCU利⽤例︓⾼速DPDKルータ • RCU︓ロックを回避し、⾼速な同期処理する重要な技術 • DPDK pthreadがコアごとに独⽴して⾼速パケット処理、その間を RCU で調停 • RCUで共有するデータ︓経路表、ポート設定、ACLフィルター設定

    DPDK P1 P4 P2 P3 CPU core1 CPU core2 CPU core3 CPU core4 OS l2fwd Forwarder P1/P2 l3fwd Forwarder P3/P4 lthread_main RCU • lthread_main: • startup_config • console_shell • tap_handler • stat_collector • l2fwd • (l3fwd-lpm)
  3. 操作関数 • rcu_assign_pointer()︓ • ポインタ登録。更新スレッドが使⽤。 • rcu_dereference()︓ • ポインタ取得。参照スレッドが毎回の read

    critical section の先頭で使⽤。 • urcu_qsbr_quiescent_state()︓ • read critical section 外で、read critical section 外だということを、報告 する関数。参照スレッドが使⽤。 • urcu_qsbr_synchronize_rcu()︓ • 前スレッドが read critical section を⼀回以上抜けるまで待つ。更新スレッ ドが使⽤。この関数を呼び出し、帰って来れば、古い共有データのポインタ を保持している参照スレッドは居ないことが保証される。 • urcu_qsbr_register_thread()︓参照スレッド登録。 • urcu_qsbr_unregister_thread()︓参照スレッド登録の解除。
  4. rcu-qsbr 更新スレッド 時間 データ 参照スレッド 参照スレッドのループの ⼀繰り返し期間は、 スレッド間でも、 スレッド内でも、 ⼀定ではない

    参照スレッド 参照スレッド ループ quiescent_state() rcu_assign_pointer() read critical section synchronize_rcu() reclamation: 回収、再利⽤(削除) ⾚か⻘か不明な read critical section ⾚を保持する参照スレッドは居ない (だから消せる) read critical sectionは 毎回 rcu_dereference() し直して最新のポインタ取得 データ更新
  5. Userscace RCU 実装内部 • カウンタを使ってスレッド間の緩い同期を図る • カウンタなのでほぼオーバヘッド無し • rcu_gp_ctr 変数(unsigned

    long)。rcu_global_pointer_counter の略か。 • TLS(Thread-Local Storage)を使⽤。 • futex というカーネルサービスを使ってスレッドの WAKE/WAIT を実現 している • つまり、完全にユーザスペースで動くわけではなく、カーネル futex サービスを利 ⽤してユーザスペースでも機能する RCU ライブラリ、という位置付け。 • quiescent_state()︓ • rcu_gp_ctr を⾃スレッドローカルで更新し、そのカウンタ(カウント数)で待って るスレッドを futex で wakeup。 • rcu_synchronize()︓ • futex で全スレッドを待つ。
  6. urcu-qsbr 使い⽅(更新スレッド) void *rcu_global_ptr_rib; void rib_replace () { #if HAVE_LIBURCU_QSBR

    struct rib *new, *old; /* allocate new */ new = malloc (sizeof (struct rib)); /* retrieve old */ old = rcu_dereference (rcu_global_ptr_rib); if (old) memcpy (new, old, sizeof (struct rib)); /* update new */ rib_edit (new) ... /* assign new */ rcu_assign_pointer (rcu_global_ptr_rib, new); /* wait all readers to update. */ urcu_qsbr_synchronize_rcu (); /* reclaim old */ free (old); #endif /*HAVE_LIBURCU_QSBR*/ }
  7. urcu-qsbr 使い⽅(参照スレッド)(1) #if HAVE_LIBURCU_QSBR urcu_qsbr_register_thread (); #endif /*HAVE_LIBURCU_QSBR*/ /* main

    loop */ while (! force_quit && ! force_stop[lcore_id]) { #if HAVE_LIBURCU_QSBR urcu_qsbr_read_lock (); rib = (struct rib *) rcu_dereference (rcu_global_ptr_rib); #endif /*HAVE_LIBURCU_QSBR*/ パケット受信(); パケット処理(); #if HAVE_LIBURCU_QSBR urcu_qsbr_read_unlock (); urcu_qsbr_quiescent_state (); #endif /*HAVE_LIBURCU_QSBR*/ loop_counter++; } #if HAVE_LIBURCU_QSBR urcu_qsbr_unregister_thread (); #endif /*HAVE_LIBURCU_QSBR*/ } read critical section
  8. urcu-qsbr 使い⽅(参照スレッド)(2) #if HAVE_LIBURCU_QSBR urcu_qsbr_register_thread (); #endif /*HAVE_LIBURCU_QSBR*/ /* main

    loop */ while (! force_quit && ! force_stop[lcore_id]) { #if HAVE_LIBURCU_QSBR urcu_qsbr_read_lock (); rib = (struct rib *) rcu_dereference (rcu_global_ptr_rib); #endif /*HAVE_LIBURCU_QSBR*/ パケット受信(); パケット処理(); #if HAVE_LIBURCU_QSBR urcu_qsbr_read_unlock (); urcu_qsbr_quiescent_state (); #endif /*HAVE_LIBURCU_QSBR*/ loop_counter++; } #if HAVE_LIBURCU_QSBR urcu_qsbr_unregister_thread (); #endif /*HAVE_LIBURCU_QSBR*/ } read critical section read_lock(), read_unlock()は qsbr の場合は本当に何もしない、 呼ばなくても良い
  9. urcu-qsbr 使い⽅(参照スレッド)(3) #if HAVE_LIBURCU_QSBR urcu_qsbr_register_thread (); #endif /*HAVE_LIBURCU_QSBR*/ /* main

    loop */ while (! force_quit && ! force_stop[lcore_id]) { #if HAVE_LIBURCU_QSBR rib = (struct rib *) rcu_dereference (rcu_global_ptr_rib); #endif /*HAVE_LIBURCU_QSBR*/ パケット受信(); パケット処理(); #if HAVE_LIBURCU_QSBR urcu_qsbr_quiescent_state (); #endif /*HAVE_LIBURCU_QSBR*/ loop_counter++; } #if HAVE_LIBURCU_QSBR urcu_qsbr_unregister_thread (); #endif /*HAVE_LIBURCU_QSBR*/ } read critical section read_lock(), read_unlock()は qsbr の場合は本当に何もしない、 呼ばなくても良い
  10. urcu-qsbr 使い⽅(まとめ) 更新スレッド︓ void *var; writer(){ void *old, *new; new

    = create_new (); rcu_assign_pointer (var, new); synchronize_rcu (); free (old); } 参照スレッド︓ reader() { urcu_qsbr_register_thread (); while(1) { rcu_quiescent_state (); //rcu_read_lock(); p = rcu_dereference (var); ... //critical section using p. //rcu_read_unlock(); } urcu_qsbr_unregister_thread (); }
  11. 注意点︓ • 利⽤ユーザ側が⾃分で守らなければいけないルール︓ • read critical section の外から、中に⼊った時には、 rcu_dereference() し

    て最新のポインタを取得することがルール。 • quiescent_state() の前後で、同じ古いポインタを維持・参照しないこと。 rcu_dereference() し直すこと。 • 参照スレッドは全て登録(register_thread())すること。 • ⾃由度︓ • ⼀ループ繰り返しの中では、read critical section の外であれば、 quiescent_state() は前でも後ろでも良い。呼び出し(報告)場所はどこで も⼤丈夫。(毎回rcu_dereference()していれば、)read critical section が細切れにたくさんあって、その途中のどこかでも⼤丈夫。複数回呼んでも ⼤丈夫。 • 更新スレッド⾃⾝を参照スレッド登録するのはちょっと複雑で危険。 デッドロックっぽいスレッド停⽌が起きた。