コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2021年9月28日 >>> 2021年9月28日
link もっと後

2021年9月28日

マルチコアのブート処理

目次: RISC-V

メインCPUからサブCPUを起こすとき基本的には、

  • CPUのID(RISC-VであればCSRのmhartid)を見て、自身がメインかサブかを知る
  • サブ側は共有RAMをポーリングなどで見張り、メインCPUからの起動司令を受け取るまで待つ
  • メイン側は共有RAMに値を書いてサブ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からの起動司令boot_wait = 1
  • サブCPUで変数を初期化boot_wait = 0

以上の順で実行されるとメインCPU側の起動司令が消されてしまい、ハングアップする可能性があります。この問題の回避のため、変数を1つ追加し、サブCPUのブートが終わるまで、メインCPUは繰り返し起動司令を送るように変更します。

  • 変数を2つ用意する(boot_wait, boot_done)
  • メイン: boot_done = 0
  • メイン: サブから応答(boot_done != 0)があるまで、boot_wait = 1を書き続ける
  • サブ: boot_wait = 0
  • サブ: boot_done = 0
  • サブ: boot_wait == 0なら待つ
  • サブ: boot_done = 1

先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。

不定値への対処を入れたコード

/* メイン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 0が起動したboot_done = 1
  • メインCPUにboot_done = 1が伝わらず、サブCPU 1の起動指令がいつまでも送られない

以上の順で実行されるとメイン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アクセスになります。マルチコアのブートを行うにあたって、常に制限が強いアクセスは必要ありませんが、とりあえずこれで動くはず。

編集者:すずき(2022/04/04 06:05)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2021年9月28日 >>> 2021年9月28日
link もっと後

管理用メニュー

link 記事を新規作成

<2021>
<<<09>>>
---1234
567891011
12131415161718
19202122232425
2627282930--

最近のコメント5件

  • link 24年4月22日
    hdkさん (04/24 08:36)
    「うちのHHFZ4310は15年突破しまし...」
  • link 24年4月22日
    すずきさん (04/24 00:37)
    「ちゃんと数えてないですけど蛍光管が10年...」
  • link 24年4月22日
    hdkさん (04/23 20:52)
    「おお... うちのHHFZ4310より後...」
  • link 20年6月19日
    すずきさん (04/06 22:54)
    「ディレクトリを予め作成しておけば良いです...」
  • link 20年6月19日
    斎藤さん (04/06 16:25)
    「「Preferencesというメニューか...」

最近の記事3件

  • link 24年4月25日
    すずき (04/29 10:08)
    「[AVIFの変換] AVIFが読めないアプリケーションがたまにあるので、AVIF(AV1 Image File Format)...」
  • link 24年2月7日
    すずき (04/24 02:52)
    「[複数の音声ファイルのラウドネスを統一したい] PCやデジタル音楽プレーヤーで音楽を聞いていると、曲によって音量の大小が激しく...」
  • link 24年4月22日
    すずき (04/23 20:13)
    「[仕事部屋の照明が壊れた] いきなり仕事部屋のシーリングライトが消えました。蛍光管の寿命にしては去年(2022年10月19日の...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 04/29 10:08