<style> u { text-decoration-color: gray; text-decoration-style: wavy; /* 波線 */ } </style> # バリア バリアとは簡単に言うと**agentとカーネルを同期するための値**である。論文では $A_{seq}$ や $T_{seq}$ のように紹介されている。バリアによって、<u>スケジューリングの決定が最新の状態を反映できているのかを見極めることができる</u>。 バリアは、ステータスワードやメッセージを介してカーネルからagentに公開され、agentは最新のバリアをトランザクションに指定して使う。 :::warning ステータスワードのバリアは [ghost_status_word.barrier](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/include/uapi/linux/ghost.h#L192) である。 メッセージのバリアは [ghost_msg.seqnum](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/include/uapi/linux/ghost.h#L255) である。 ::: バリアには、**agentのバリア**と**ghOStスレッドのバリア**で区別される。論文では、それぞれ $A_{seq}$ と $T_{seq}$ のように表記されている。それぞれについて詳しく見ていく。 # ghOSt スレッドのバリア ghOSt スレッドのバリアは、自スレッドに関連するメッセージ(TASK_NEW など)が送信されるたびにインクリメントされ、そのメッセージのヘッダに添えられて agent に渡される。 バリア値はメッセージヘッダの seqnum という名前のフィールドに格納される。barrier という名前で使われたり seqnum という名前で使われたりややこしいので注意。 バリアを介した同期は、以下の手順で使われる。 1. agent はタスクごとにバリアを記憶しておき、メッセージが到着する度に新しい値に更新しておく。 2. トランザクションを行うとき、next タスクのバリアを [ghost_txn.task_barrier](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/include/uapi/linux/ghost.h#L665) に書き込む。 3. カーネルがトランザクションを処理するときは、task_barrier が最新の値かどうかを検証する。最新の値であれば成功、そうでなければ失敗となる。 つまり、agent がメッセージを読み終えてからタスク p をコミットするまでの間に、p に関する新しいメッセージが発行された場合、そのコミットは失敗する。このようにして、カーネルと agent の同期が行われているのだ。 ghOSt スレッドのバリアによってコミットが失敗する例。 ![7EEAAD03-5EF6-4173-96F3-E6CE6EC0151E](https://hackmd.io/_uploads/BJTKrEjE6.jpg) ## カーネル側の処理 TASK_NEW が発行されるときの処理を見ていく。 TASK_NEW を発行するカーネル側の関数は [task_deliver_msg_task_new](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4581-L4610) である。 ```c static void task_deliver_msg_task_new(struct rq *rq, struct task_struct *p, bool runnable) { struct bpf_ghost_msg *msg = this_cpu_ghost_msg(); ... // ① この中でp->ghost.status_word->barrierの値がインクリメントされる if (__task_deliver_common(rq, p)) return; ... // ② msg->seqnumにバリア値を添えてメッセージキューにプッシュする produce_for_task(p, msg); } ``` ① [\_\_task_deliver_common](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4516-L4579) の実装を見ていく。この関数は、タスク関連のメッセージが発行されるときに、必ず呼び出されることになっている。 ```c static inline int __task_deliver_common(struct rq *rq, struct task_struct *p) { ... // p->ghost.status_word->barrierの値をインクリメントする task_barrier_inc(rq, p); ... return 0; } ``` ② [produce_for_task](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4345-L4349) の実装を見ていく。 ```c static inline int produce_for_task(struct task_struct *p, struct bpf_ghost_msg *msg) { // task_barrier_get(p)はp->status_word.barrierの値を読み出す return __produce_for_task(p, msg, task_barrier_get(p)); } ``` メイン処理は [\_\_produce_for_task](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4253-L4343) で行われる。task_barrier_get で読み出されたバリア値は、メッセージの seqnum にセットされたあとメッセージキューにプッシュされる。 ```c static inline int __produce_for_task(struct task_struct *p, struct bpf_ghost_msg *msg, uint32_t barrier) { ... msg->seqnum = barrier; ... // msgをメッセージキューにプッシュする return _produce(p->ghost.dst_q, barrier, msg->type, payload, payload_size); } ``` ## ユーザー側の処理 ユーザー側ではメッセージを1つずつ処理するときに以下の [DispatchMessage](https://github.com/google/ghost-userspace/blob/9ca0a1fb6ed88f0c4b0b40a5a35502938efa567f/lib/scheduler.h#L616) 関数が実行される(※ バリアに関する処理のみが分かるように、ほかの処理を省略している)。メッセージを処理する度に、タスクのバリアをその値で更新していることがわかる。 ```cpp template <typename TaskType> void BasicDispatchScheduler<TaskType>::DispatchMessage(const Message& msg) { // msgはメッセージのデータ構造 // msgに対応するタスクのオブジェクトへのポインタ TaskType* task; ... // taskのseqnumを新しい値に更新する task->Advance(msg.seqnum()); } ``` トランザクションをコミットするときには、その時点でのseqnumの値をトランザクションの target_barrier フィールドにセットする必要がある。以下はFIFOスケジューラの実装を参考にした([ソースコード](https://github.com/google/ghost-userspace/blob/9ca0a1fb6ed88f0c4b0b40a5a35502938efa567f/schedulers/fifo/per_cpu/fifo_scheduler.cc#L249-L310))。 ```cpp void FifoScheduler::FifoSchedule(const Cpu& cpu, BarrierToken agent_barrier, bool prio_boost) { // nextは次実行状態にしたいタスクのオブジェクトへのポインタ FifoTask* next = nullptr; ... RunRequest* req = enclave()->GetRunRequest(cpu); if (next) { ... // トランザクションにnextのseqnumをセットする req->Open({ .target = next->gtid, .target_barrier = next->seqnum, // ここ! .agent_barrier = agent_barrier, .commit_flags = COMMIT_AT_TXN_COMMIT, }); // コミット if (req->Commit()) { // トランザクションに成功! } else { ... } } ... } ``` # agent のバリア agent のバリア値は様々なケースでインクリメントされる。ghOSt スレッドのバリアと同様、メッセージの seqnum に最新のバリア値が格納されて agent に渡されるが、**メッセージを発行しないタイミングでバリア値がインクリメントされるときもある**ため、注意が必要。 agent は以下の順序で処理を行う必要がある。 1. agent のステータスワードからバリア値を読み出す。 2. メッセージを処理していく。 3. 読み出していたバリアを [ghost_txn.agent_barrier](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/include/uapi/linux/ghost.h#L664) にセットしてトランザクションを発行する。 重要なのは、**メッセージの処理を始める前のagentバリアをトランザクションで渡す**、という点である。もし 1.と 2.の順序を入れ替えてしまったら、バリアが機能しなくなるので注意! ## カーネル側の処理 カーネル側での agent のバリア値の管理は少し複雑になっている。 [ghost_rq.agent_barrier](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/sched.h#L109) と [ghost_status_word.barrier](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/include/uapi/linux/ghost.h#L192) の2段階で管理している。 カーネルで使われている関数を以下にまとめる。 ### ★ [agent_barrier_get](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L724-L735) 1. ghost_rq.agent_barrier を読み出す 2. その値を agent の ghost_status_word.barrier に書き込む 3. その値を返す ### ★ [agent_barrier_inc](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L700-L711) 1. ghost_rq.agent_barrier を読み出す 2. その値をインクリメントし、ghost_rq.agent_barrier に書き込む 3. 同じ値を agent の ghost_status_word.barrier にも書き込む ### ★ [produce_for_agent](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4351-L4358) ```c static inline int produce_for_agent(struct rq *rq, struct bpf_ghost_msg *msg) { struct task_struct *agent = rq->ghost.agent; agent_barrier_inc(rq); return __produce_for_task(agent, msg, agent_barrier_get(agent)); } ``` CPU関連メッセージが送信されるときに呼び出される関数。 ここではメッセージを発行する前にagentバリアをインクリメントしている。 つまり、<u>agentが最新のCPUメッセージをすべて受け取っているかどうか</u>、をagentバリアで確認することができる。 :::warning :dart: タスク関連メッセージが送信されるときには [produce_for_task](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4345-L4349) が呼ばれる。 ::: [\_\_produce_for_task](https://github.com/google/ghost-kernel/blob/edd5f9490d82df24c16f90a62f7be05c6c389867/kernel/sched/ghost.c#L4253-L4343) の実装は上述したものと同じで、最新のagentバリアがメッセージのseqnumフィールドにセットされて送信される。 ## ユーザー側の処理 agentのワークフローは上で示したとおり。 FifoSchedulerの実装を見ながら理解を進めていく。 agentスレッドのメイン部分は以下のように FifoScheduler::Schedule の呼び出しをループする、というもの([ソースコード](https://github.com/google/ghost-userspace/blob/9ca0a1fb6ed88f0c4b0b40a5a35502938efa567f/schedulers/fifo/per_cpu/fifo_scheduler.cc#L385-L408))。 ```cpp void FifoAgent::AgentThread() { ... while (!Finished() || !scheduler_->Empty(cpu())) { scheduler_->Schedule(cpu(), status_word()); ... } } ``` [FifoScheduler::Schedule](https://github.com/google/ghost-userspace/blob/9ca0a1fb6ed88f0c4b0b40a5a35502938efa567f/schedulers/fifo/per_cpu/fifo_scheduler.cc#L312-L326) では以下のような処理が行われている。最初に agent バリアを読み出しておき、その値を [FifoScheduler::FifoSchedule]() に渡しているのがポイント。 ```cpp void FifoScheduler::Schedule(const Cpu& cpu, const StatusWord& agent_sw) { // ★ 最初にagentバリアを読み出しておく BarrierToken agent_barrier = agent_sw.barrier(); ... // 溜まっているメッセージの処理を行う Message msg; while (!(msg = Peek(cs->channel.get())).empty()) { DispatchMessage(msg); Consume(cs->channel.get(), msg); } // FifoScheduleの実行(agent_barrierを引数に渡す) FifoSchedule(cpu, agent_barrier, agent_sw.boosted_priority()); } ``` [FifoScheduler::FifoSchedule](https://github.com/google/ghost-userspace/blob/9ca0a1fb6ed88f0c4b0b40a5a35502938efa567f/schedulers/fifo/per_cpu/fifo_scheduler.cc#L249-L310) では以下のように引数で受け取ったagent_barrierをトランザクションのagent_barrierに用いている。 ```cpp void FifoScheduler::FifoSchedule(const Cpu& cpu, BarrierToken agent_barrier, bool prio_boost) { CpuState* cs = cpu_state(cpu); FifoTask* next = nullptr; // nextタスク // nextタスクを選び、nextにセットしておく RunRequest* req = enclave()->GetRunRequest(cpu); if (next) { // トランザクションの発行 req->Open({ .target = next->gtid, .target_barrier = next->seqnum, .agent_barrier = agent_barrier, // agent_barrierをセットしている .commit_flags = COMMIT_AT_TXN_COMMIT, }); ... } } ```