NetBSD Advent Calendar 2023 14日目の記事です。今日はcallout(9)のcallout_reset()の実装を調べてみるで調べきれなかった、calloutに登録した関数について、関数呼び出し(関数発火)の条件判定と関数コールを行っている箇所を調べてみようと思います。
calloutに登録した関数のリストを操作している箇所
まずはcalloutに登録した関数のリストを操作している箇所を探してみます。 callout_softclock()
という関数の中で struct callout_cpu->cc_todo
のリストを順に処理するような実装になっています。
687 /*
688 * callout_softclock:
689 *
690 * Soft interrupt handler, scheduled above if there is work to
691 * be done. Callouts are made in soft interrupt context.
692 */
693 static void
694 callout_softclock(void *v)
695 {
696 callout_impl_t *c;
697 struct callout_cpu *cc;
698 void (*func)(void *);
699 void *arg;
700 int mpsafe, count, ticks, delta;
701 lwp_t *l;
702
703 l = curlwp;
704 KASSERT(l->l_cpu == curcpu());
705 cc = l->l_cpu->ci_data.cpu_callout;
706
707 mutex_spin_enter(cc->cc_lock);
708 cc->cc_lwp = l;
709 while (!CIRCQ_EMPTY(&cc->cc_todo)) {
710 c = CIRCQ_FIRST(&cc->cc_todo);
さらに、 callout_impl_t->c_flags
に CALLOUT_FIRED
あるいは CALLOUT_INVOKING
のビットが立っていた場合に callout_impl_t->c_func
に設定された関数を呼び出す、という処理になっています。 callout_softclock()
の関数名(と関数コメント)を見ると、どうやらソフトウェア割り込みでこの関数が呼ばれるようです。
728 c->c_flags = (c->c_flags & ~CALLOUT_PENDING) |
729 (CALLOUT_FIRED | CALLOUT_INVOKING);
730 mpsafe = (c->c_flags & CALLOUT_MPSAFE);
731 func = c->c_func;
732 arg = c->c_arg;
733 cc->cc_active = c;
734
735 mutex_spin_exit(cc->cc_lock);
736 KASSERT(func != NULL);
737 if (__predict_false(!mpsafe)) {
738 KERNEL_LOCK(1, NULL);
739 (*func)(arg);
740 KERNEL_UNLOCK_ONE(NULL);
741 } else
742 (*func)(arg);
743 mutex_spin_enter(cc->cc_lock);
callout_softclock()
が参照されている箇所を見ると以下のようになっています。249行目のif文のelseブロック中で softint_establich()
関数を使用して callout_softclock()
を登録しています。
236 void
237 callout_init_cpu(struct cpu_info *ci)
238 {
...
242 if ((cc = ci->ci_data.cpu_callout) == NULL) {
...
248 } else {
249 /* Boot CPU, one time only. */
250 callout_sih = softint_establish(SOFTINT_CLOCK | SOFTINT_MPSAFE,
251 callout_softclock, NULL);
252 if (callout_sih == NULL)
253 panic("callout_init_cpu (2)");
254 }
softint_establish(9)を見ると以下のように説明されています。 flags
に指定したソフトウェア割り込みが発生すると、 func
に指定した関数が呼び出されるという挙動になっています。 callout_init_cpu()
内の softint_establish()
ではフラグとして SOFTINT_CLOCK
と SOFTINT_MPSAFE
が指定されており、クロックまたはMPSAFE(カーネルのグローバルロック発生時?)の割り込み時に関数が呼び出される指定になっています。
softint_establish(flags, func, arg)
Register a software interrupt. The flags value must contain one
of the following constants, specifying the priority level for
the soft interrupt:
SOFTINT_CLOCK, SOFTINT_BIO, SOFTINT_NET, SOFTINT_SERIAL
...
The constant func specifies the function to call when the soft
interrupt is executed. The argument arg will be passed to this
function.
そして callout_init_cpt()
は /usr/src/sys/kern/kern_cpu.c
で定義されている以下の関数から呼ばれています。 mi_cpu_attach()
はマシン非依存(Machine Independent)な関数となっています。そのため、 callout_init_cpu()
は各CPUアーキテクチャにおいてCPUがされた際に SOFTINT_CLOCK
または SOFTINT_MPSAFE
による割り込み時に struct callout_cpu->cc_todo
のリストが処理されるという挙動になります。
150 int
151 mi_cpu_attach(struct cpu_info *ci)
152 {
...
194 callout_init_cpu(ci);
まとめ
リストとして管理されている、calloutに登録した関数がどのタイミングで処理されるかを調べてみました。てっきりカーネルスレッド等でループしながら処理しているのかなと思っていましたが、実際はソフトウェア割り込みを起因にしてリストを処理している実装となっていました。