目次: RISC-V
メインCPUからサブCPUを起こすとき基本的には、
RAMの初期値が不定であると仮定すると、サブCPUが下手にポーリングすると、不定値によって条件が成立してしまい、メインCPUからの起動司令がないのに勝手に起動してしまう事態に陥ります。
先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。
/* メインCPUはHARTID=8, サブCPUはHARTID=0...3とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
int boot_wait;
int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メインCPUが実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
init_core[i].boot_wait = 1;
}
}
/* サブCPUが実行する */
void boot_sub(void)
{
int hartid = get_hartid();
while (!init_core[hartid].boot_wait) {
/* busy loop */
}
}
残念ながらこのコードは正常に動作しません。共有RAMつまりinit_core[hartid].boot_waitの値が起動直後から != 0だったとき、boot_sub() はboot_main() からの起動司令を待つことなく起動してしまうからです。
共有RAMの不定値に対処する方法を考えます。基本的にはサブCPUが変数を初期化(boot_wait = 0)してから待ちに入れば良いのですが、新たな問題が生じます。メインCPUとサブCPUの実行順序はどちらが先という保証はないため、
以上の順で実行されるとメインCPU側の起動司令が消されてしまい、ハングアップする可能性があります。この問題の回避のため、変数を1つ追加し、サブCPUのブートが終わるまで、メインCPUは繰り返し起動司令を送るように変更します。
先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。
/* メインCPUはHARTID=8, サブCPUはHARTID=0...3とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
int boot_wait;
int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メインCPUが実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
init_core[i].boot_done = 0;
while (!init_core[i].boot_done) {
init_core[i].boot_wait = 1;
}
}
}
/* サブCPUが実行する */
void boot_sub(void)
{
int hartid = get_hartid();
init_core[hartid].boot_wait = 0;
init_core[hartid].boot_done = 0;
while (!init_core[hartid].boot_wait) {
/* busy loop */
}
init_core[hartid].boot_done = 1;
}
残念ながらこのコードも正常に動作しません。共有RAMへの値の反映が他のCPUに即座に見えること(アトミック性)を暗に期待しているからです。
今日のマルチコアシステムでは、boot_wait = 0としたときに、他のCPUにも即座に同じ値が見えているとは限りません。主な要因としては、
などがあります。通常の変数への代入、参照が他のCPUに即座に値が見えないことにより、おかしくなるパターンはいくつか考えられそうですが、ありがちなパターンとして、
以上の順で実行されるとメインCPU側が起動司令を送らないまま、サブCPU側も何もできずハングアップする可能性があります。この問題の回避のため、通常の変数への代入、参照ではなく他のCPUにも値が見えるように初期化、代入(アトミックアクセスする)必要があります。
従来C言語でアトミックアクセスを行うためには、実装対象アーキテクチャの知識やアセンブラの記述を必要とするなど、やや困難が伴いました。ですがC11でアトミックアクセス用の定義stdatomic.hが追加されたことで、アトミックアクセスはかなり楽になりました。素敵ですね。
ひとまず速度を全く気にせず、全てのアクセスをアトミックアクセスに入れ替えると、こんなコードになるでしょう。
/* メインCPUはHARTID=8, サブCPUはHARTID=0...3とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
atomic_int boot_wait;
atomic_int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メインCPUが実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
atomic_store(&init_core[i].boot_done, 0);
while (!atomic_load(&init_core[i].boot_done)) {
atomic_store(&init_core[i].boot_wait, 1);
}
}
}
/* サブCPUが実行する */
void boot_sub(void)
{
int hartid = get_hartid();
atomic_store(&init_core[hartid].boot_wait, 0);
atomic_store(&init_core[hartid].boot_done, 0);
while (!atomic_load(&init_core[hartid].boot_wait)) {
/* busy loop */
}
atomic_store(&init_core[hartid].boot_done, 1);
}
C11のアトミックアクセスは何も指定しない場合、一番制限の強い(= 確実に他のCPUに見えるものの、アクセス速度は遅い)memory_order_seq_cstアクセスになります。マルチコアのブートを行うにあたって、常に制限が強いアクセスは必要ありませんが、とりあえずこれで動くはず。
< | 2021 | > | ||||
<< | < | 09 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)