目次: 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アクセスになります。マルチコアのブートを行うにあたって、常に制限が強いアクセスは必要ありませんが、とりあえずこれで動くはず。
まれにxtermの256色指定エスケープシーケンスに対応していない端末があってvimの表示が変な色になってしまいます。チェック用のスクリプトを作っておきました。単純に背景色を変更するエスケープシーケンスと、空白文字、色を元に戻すエスケープシーケンスを連打するだけです。
#!/bin/sh
ESC_ORG="\e[0m"
print_colors()
{
for i in ${*};
do
printf " %3d\e[%dm " ${i} ${i};
echo -n ${ESC_ORG}
done
echo
}
print_xterm_colors()
{
for i in ${*};
do
printf " %3d\e[48;5;%dm " ${i} ${i};
echo -n ${ESC_ORG}
done
echo
}
echo "System colors (ESC[Nm):"
print_colors `seq 40 47`
echo
echo "xterm 256 colors (ESC[48;5;Nm):"
for i in `seq 0 8 248`;
do
j=`expr ${i} + 7`
print_xterm_colors `seq ${i} ${j}`
done
実行するとこんな感じになります。
対応していない端末だとこうなりますと言いたいところでしたが、対応していない端末が見当たりませんでした。前はあった気がするんだけどなあ……?
何kHzの音まで聞こえるかテストするサイト、聞こえチェック | Panasonic が、以前Twitterでちょっと話題になりました。
私の場合15kHzまでは聞こえますが、それ以上(17kHz, 19kHz)は全く聞こえません。鳴ってんのか?これ??
まずブラウザの影響を排除するため、上記のサイトから音源をダウンロードします。WavではなくMP3ファイルでした。
直接オーディオプレイヤーで聞いても15kHz以外は聞こえません。ブラウザのせいじゃなかった。私の耳は全くあてにならないので、オシロスコープにご登場願います。
19kHz再生時の波形(グラフはキャプチャし忘れて17kHzのまま。右下の周波数表示が19kHzを示している)
いやあ、バッチリ綺麗にSin波が鳴ってます。私は全く聞こえませんね、これが老いかぁ……。
目次: RISC-V
RISC-Vにはcode modelという概念があり、ざっくり言うとメモリアクセスやジャンプの際に参照するアドレスの作り方を指定します。medlowとmedanyの2つがありmedlowがデフォルトです。
詳しくはGCCのマニュアル RISC-V Options (Using the GNU Compiler Collection (GCC)) やSiFiveのエンジニアによる解説 All Aboard, Part 4: The RISC-V Code Models - SiFive を読んでいただくのが良いかと思いますが、ここではどんなときにエラーになるかに重点を置いて、いくつか例を挙げたいと思います。
モデルmedlowは32bit絶対値でアドレスを指定します。具体的にはlui命令とロード命令などの12ビットオフセットを使います。lui命令とは20ビットのimmediateを12ビット左シフトして、符号拡張する命令のことです。
lui a1, 0x12345 # 12ビットシフトされた値0x12345000がa1に格納される ld a0, 0x678(a1) # アドレスa1 + 0x678 = 0x12345000 + 0x678からa0に値をロードする
このようなコードが生成されます。絶対値は -2GB〜 +2GBまでしか生成できませんので、全てのシンボルが範囲に収まっている必要があります。32bitアドレスを使っている場合は全てのアドレス範囲をカバーできますが、64bitアドレスを使っている場合は0x00000000_00000000〜0x00000000_7fffffffまたは0xffffffff_80000000〜0xffffffff_ffffffffのアドレス範囲にシンボルを配置しなければなりません。範囲外にシンボルを配置しようとすると、
// a.c
extern volatile int *hoge;
void _start(void)
{
*hoge = 1;
}
/* a.ld */
OUTPUT_ARCH("riscv")
ENTRY(_start)
SECTIONS
{
PROVIDE(hoge = 0x100000000); /* 0x1_00000000はmedlowの範囲外 */
}
$ riscv64-zephyr-elf-gcc -march=rv64gc -Wall -g -mabi=lp64d -mcmodel=medlow -nostdlib -T a.ld a.c --save-temp a.o: in function `_start': test-medany/a.c:5:(.text+0x6): relocation truncated to fit: R_RISCV_HI20 against symbol `hoge' defined in *ABS* section in a.out collect2: error: ld returned 1 exit status
リンカーがエラーを出します。hogeのアドレスを変更し -2GB〜 +2GBの範囲(0xffffffff_80000000や0x00000000_70000000など)にするとリンクが通ります。
もう1つのmedanyは、PC相対でアドレス指定します。具体的にはアドレスの場合はauipc命令とaddi命令、ジャンプの場合はauipc命令とjalr命令の12ビットオフセットを使います。auipc命令とは、20ビットのimmediateを12ビット左シフトして、符号拡張したあとPCに加算する命令のことです。
PCが0x1_40000000付近でhogeが0x1_a89abcd0だとすると、
アドレスの場合 140000006: auipc a5,0x689ac # 12ビットシフトされた値0x689ac000 + PC 0x1_40000006 = 0x1_a89ac006がa5に格納される 14000000a: addi a5,a5,-822 # 0x1_a89ac006 - 822 = 0x1_a89abcd0 = hogeのアドレスがa5に格納される ジャンプの場合 140000008: auipc ra,0x689ac # 12ビットシフトされた値0x689ac000 + PC 0x1_40000008 = 0x1_a89ac008がraに格納される 14000000c: jalr -824(ra) # ra 0x1_a89ac006 - 824 = 0x1_a89abcd0 = hogeのアドレスにジャンプする
このようなコードが生成されます。相対アドレスはPCの現在地 -2GB〜 +2GBまでしか生成できません。medlowモデルより対応できる範囲は広がったものの、いかなるアドレスでも対応できるわけではないです。例えばコード領域とデータ領域をあまりにも遠くすると、
// a.c
extern volatile int *hoge;
void _start(void)
{
*hoge = 1;
}
/* a.ld */
OUTPUT_ARCH("riscv")
ENTRY(_start)
MEMORY
{
TEXT(rx) : ORIGIN = 0x0000000140000000, LENGTH = 0x10000
}
SECTIONS
{
/* hogeをコード領域から2GB以上離して配置する */
PROVIDE(hoge = 0x1c89abcd0);
/* コード領域を0x1_40000000にする */
.text : {
*(.text*);
} > TEXT
}
$ riscv64-zephyr-elf-gcc -march=rv64gc -Wall -g -mabi=lp64d -mcmodel=medany -nostdlib -T a.ld a.c --save-temp a.o: in function `_start': test-medany/a.c:5:(.text+0x6): relocation truncated to fit: R_RISCV_PCREL_HI20 against symbol `hoge' defined in *ABS* section in a.out collect2: error: ld returned 1 exit status
シンボルhogeが位置するアドレスは絶対値 -2GB〜 +2GBの範囲外であり、コード領域からも離れているためPC相対 -2GB〜 +2GBの範囲外でもあります。よってmedlowモデルでもmedanyモデルでもリンクエラーとなります。
前回(2021年8月26日の日記参照)同様に自治体の接種会場に行きました。ワクチンは当然同じでファイザー製です。
前回同様に看護師を始めとした医療従事者の皆様は非常に親切かつ効率的に働いていました。ありがてぇ。
問診の先生はやっぱりお疲れモードな雰囲気でした。無理はしないでください……。
前回はワクチンを打ってしばらく経ってから肩が痛くなりましたが、今回は打った直後から肩が痛いです。明日はどうなるんだろうか、これ……。
< | 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 | - | - |
合計:
本日: