目次: Windows
Windowsで「コミット済み」(=仮想メモリの合計)の値を求める方法がさっぱりわかりません。タスクマネージャーの「パフォーマンス」タブには下記のように値が表示されています。
しかしタスクマネージャーの「詳細」タブに表示される、各プロセスのコミットサイズ(=仮想メモリサイズのことらしい)を足しても全く足りません。どういうこと??
コミットサイズが全然信用できない例を挙げれば、AMDのRadeonドライバ関連でAMDRSServ.exeというプロセスがいます。このプロセスをタスクマネージャーで見ると「5MBしか使ってないよ」とおっしゃっています。
ところがプロセスを強制終了させると、突然700MBほど(6.2GB → 5.5GB)仮想メモリが解放されます。700MBも使っているプロセスはありませんでしたが、700MBどこから来た?意味不明ですね。
たぶんカーネル側というかドライバ内で仮想メモリをでかく取ると「コミット済み」と「各プロセスのコミットサイズの合計」の乖離が激しくなるんじゃないか?と予想していますが、調べ方がわかりません。
Windowsを使っていて仮想メモリが枯渇するような事態に陥り、調べる必要が出てきたとしても、タスクマネージャーの表示してる「コミットサイズ」は全然信用できないってことです。ひどい作りだなあ、もう。
新年早々、WindowsとLinuxのメモリ割り当て戦略の基本的な違いをすっかり忘れていて、ひどい目に合いました。
症状としてはSteamでゲームをしてると頻繁にゲームが落ちたり、ブラウザがクラッシュします。
疑った順に、
仮想メモリの枯渇でした。Windowsは仮想メモリを物理メモリ+ページングファイルの合計量までしか割り当てません。私の環境は物理メモリ16GB+ページングファイル1GBに切り詰めていたため、仮想メモリは17GBまでしか確保できません。
ゲーム+ブラウザを起動すると仮想メモリの消費量が17GBを超えるときがあります。仮想メモリの割り当て量が上限ギリギリに達して、ゲームもしくはブラウザの運が悪い方が、仮想メモリを要求すると、割り当てに失敗します。
するとNULLポインタが返り、NULLポインタにアクセスしてゲームorブラウザがクラッシュしてしまうようです。誰一人として、仮想メモリの割り当て失敗を想定せんの?誰か1人くらいVirtualAlloc() が失敗したって教えてくれても良いのに……。
ページングファイルを適当に増やせば(とりあえず16GBくらいにした)安定しました。
気づいたきっかけはゲーム(Cities: Skylines)のクラッシュダンプです。
エラーログを見るとpaging fileの空きが1MBしかありません。Windowsではこれは仮想メモリの空きを表すそうです。これを知らなかったがために、全然関係ないドライバとか熱暴走を疑い、遠回りしてしまいました。
タスクマネージャーで「コミット済み」の値をチェックすると、仮想メモリの使用量がわかります。これがゲーム+ブラウザで17GBを超えていました。
ダメ押しで、下記のようなVirtualAlloc() APIを呼んで仮想アドレスを大量にガメる(物理メモリはほぼ消費しない)プログラムを書いて、わざと仮想メモリだけを枯渇させました。
#include <cstdio>
#include <cstdlib>
#include <windows.h>
#define CNT 16
int main()
{
const size_t s = 1024 * 1024 * 1024;
char buf[1024], *pb;
void *p[CNT];
for (int c = 0; c < CNT; c++) {
p[c] = VirtualAlloc(NULL, s, MEM_COMMIT, PAGE_READWRITE);
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, buf, sizeof(buf) - 1, NULL);
printf("%s\n", buf);
pb = (char*)p[c];
for (size_t i = 0; i < s / 8192; i++)
pb[i] = (char)i;
}
for (int c = 0; c < CNT; c++)
VirtualFree(p[c], s, MEM_DECOMMIT);
return 0;
}
この状態でゲームを動かすと容易に同じクラッシュが起こせます。というわけで仮想メモリの枯渇で確定と判断しました。
WindowsとLinuxの仮想メモリ割り当て戦略は全く違うのに、同じノリでWindowsのページングファイルを削ってしまったことですね……。一応、違いは知っていたんですが、行動に活かせず思い切りハマりました。
Windowsは仮想メモリ割当てが保守的です。仮想メモリの割り当て上限=物理メモリ+ページファイルの合計となります。
Linuxは仮想メモリの割り当て上限>物理メモリ+スワップファイルの合計となります(over commitment)。
WindowsとLinuxのメモリ割り当て戦略は、利点と欠点が逆になるだけで、どっちもどっちです。
今回の教訓をおさらいすると、Windowsを使っているのに、Linuxと同じノリでページファイルを1GBとか小さいサイズに削ると、速攻で仮想メモリが枯渇してひどい目に合うんでやめようね!ってことです。
記事以外の表示(リンクとか編集ボタンとか)が縦に並んでいて、横長のディスプレイで見たときに邪魔なので、ちょっとだけデザインを変えて縦方向の長さを詰めました。
デザインはあまり詳しくないですが、もっと文字が読みやすくなるようにするにはどうしたら良いんでしょうね……?
年末の帰省チケットを解約したとき(2020年12月17日の日記参照)に価格が激変していたことが気になったので、年末年始の羽田→千歳便の各チケットの価格をプロットしてみました。
どうやらチケットの種類に関係なく、最後に付いているアルファベットで価格帯が決まるようです。Aが一番高くて、B, C, D, ... と安くなっていくようです。10月に予約したSUPER VALUE 75 Hが、今日予約できるVALUE 3 Jより高くなるのはこれが理由でした。
ANAの予約システムは、遠い予約日(1/Mくらい)だと、空席予測を強気に出すのかやや割高の運賃設定をしています。
くらいかな?早朝便、深夜便などは1ランク安くなりがちです。しかし、搭乗日が近づいて(増発を決めてしまった12/30など)、誰も乗らないことに気づき始めると、未だかつて見たことない安さのチケットが出現します。
こんな感じですね。安いなあ。
遠い予約日が強気の価格になるのは、SUPER VALUEでも傾向が一緒のようです。SUPER VALUEの予約日は最短でも21日後と、必然的に遠くなるため、COVID-19の状況下ですと、予約日が遠いSUPER VALUEの方がかえって割高で買ってしまう可能性が高いです。
例えば、一番近いSUPER VALUEの予約日は21日後の1/10です。ラインナップはVALUE 3 H, VALUE 1 G, SUPER VALUE 21 J辺りで、今この瞬間はSUPER VALUE 21 Jが一番安く見えますが、1/10はおそらく誰も乗りません(帰省ラッシュがない以上、Uターンラッシュも起きない)から、1/3くらいまで待てば、VALUE 3 Kとかが登場して、SUPER VALUE 21 Jの価格を下回ると思います。
しかも今は「あんしん変更キャンペーン」があるので、SUPER VALUE 21 Jで予約して払い込んでしまい、年始に安い便があれば切り替え、払い戻しを受ければノーリスクで安く乗れるはずです。
未だかつて年末の羽田→千歳便がこんな低価格で投げ売りされたことはありません。どれだけぼったくっても、皆が渋々乗るので「ドル箱路線」と称されたほどです。
10年来、散々、羽田→千歳便でボラれてきた身としては、今年は流石のANAもぼったくりはできなかったか……と思いました。けどまあCOVID-19に関しては同情しかなくて、乗れる機会があったら飛行機乗って応援したいですね。九州辺りに旅行に行きたいな〜。
「あんしん変更キャンペーン」はただでさえ客足が遠のく中、安く乗られ、割安で解約され、ANAとしては散々でしょう。
逆にこっちはあまり同情してません。飛行機の割引制度は縛りが多くて理不尽です。今の方が素直だし普通です。是非、このまま続けて欲しいですね。
メモ: 技術系?の話はFacebookから転記しておくことにした。いろいろ修正。
目次: 車
車に乗ってるとたまに疑問に思うんですが、燃費ってアクセル開度で決まるんですかね?
同じ勾配、同じアクセル開度なら、毎速2500rpmで変速しても、2速5000rpmまで引っ張って、すぐ3, 4, 5速と変速しても、燃料消費量は同じ??なんてことをFacebookに書いてみたところ、会社の同僚のみなさんから色々教えていただけました。
同じアクセル開度なら、エンジンの燃焼効率の最適点が一番燃費が良いので、1速でぶん回すより、4速くらいの方が一般的には燃費が良いはず、とのことです。レスが超早かったです。さすが車の専門家達の会社だなと痛感しますね。
通常、燃焼効率の最適点は公開されていません。代わりとしてエンジン出力の最大点を使おうと思います。燃焼効率最適点=燃費の最大点、エンジン出力の最大点=加速の最大点であり、この2つは一致しません。ただし、効率が悪いにも関わらず最大出力に達するのは、ちょっと考えにくいですし、一致はしなくてもさほど遠くないだろう、という目論見です。
スバルのEJ20エンジンはいろんな車種に乗っていて、自分のレガシィB4のエンジン性能曲線が探せませんでした……。インプレッサWRX STIのEJ20エンジン性能曲線はすぐに出てきた(パワーユニット : ドライビング - WRX STI - SUBARU)ので、これを参考にしようと思います。
もちろん
辺りはわかってます。
が、まあ、ざっくり言って、トルクカーブは2500〜4500rpmくらいまでピークかつ真っ平らです。ほぼどこで変速しても大丈夫!素晴らしいエンジンですね〜。
2000rpm前後の加速が眠くて、突然速くなる感じがするのも、レガシィが重たいからだと思いこんでいたんですけど、2000〜2500rpmでトルクが急激に立ち上がるエンジン特性からくるものなんですね。見覚えあるなと思って調べたら、昔も同じことを言っていました(2010年9月5日の日記、2010年9月7日の日記参照)。完全に忘れてました。
何年乗ってるんだ、今更かよって感想ですけど……。
Facebookで教えていただいたところによると、EJ20はOBD2端子(On-board diagnosticsという故障診断の信号を送受信できる端子)から燃料消費量の情報が取れるはずなので、それを見たらどうか?ELM327互換のアダプタでBluetoothでスマホにデータを送る手もある、という素晴らしいアイデアをいただきました。
昔に付けたメーター(2011年5月4日の日記参照)で、瞬間燃費が表示できないか試してみようかな。
メモ: 技術系?の話はFacebookから転記しておくことにした。大幅に追記。
実家と話し合って、今年末の北海道への帰省を取りやめました。年末がクッソ暇になりました。
帰省用の飛行機チケットを解約したんですが、ANAが最近やってる「あんしん変更キャンペーン」(ANA公式サイト)により、運賃や解約ルールが意味不明になってます。
買ったチケットは、
計95,040円です。
解約の仕方をいくつか試したところ、払戻金額の見積もりがべらぼうに変わり驚きました。
各パターンで何が起きているのでしょう?個人的な予想に過ぎませんが、
バリュー系チケットの値段は需要連動(ANAの解説)らしく、11月のコロナ再流行で需要が急激に落ち込み、スーパーバリュー75Hとバリュー3Jの逆転現象が発生したのでしょう。この需要連動という想定そのものが「直前に需要が急減する」という事態を想定していない節がありますね。
もし10月くらいにスーパーバリュー系を予約した人は、1万円単位で大損してるはずです。年末に飛行機に乗る人は要確認です。
今のANAの一番良くないところは、普通に解約すると5万円近く大損するところですが、ANAのQAを見る限り「コロナ関連で解約の特別対応はしない、所定のキャンセル料を払え」とあるのでANAとしてはパターン1の解約でANAに5万円寄付せよ、という意思なんでしょうか?
私は、パターン3を試してみたので、手数料含めて2,500円くらいで解約できちゃったように見えます。
予約システムのバグだったんですかね?ちゃんと払い戻しされるかなあ??不安だわあ……。
メモ: 技術系?の話はFacebookから転記しておくことにした。
目次: Zephyr
前半ではztest_test_suite() の実装を見ました。戻って見るのは面倒だと思うのでztestの使い方の例を再掲しておきます。
// zephyr/tests/lib/sprint/src/main.c
void test_main(void)
{
ztest_test_suite(test_sprintf, //★変数名★
ztest_unit_test(test_sprintf_double), //★テスト1つ目★
ztest_unit_test(test_sprintf_integer), //★テスト2つ目★
ztest_unit_test(test_vsprintf),
ztest_unit_test(test_vsnprintf),
ztest_unit_test(test_sprintf_string),
ztest_unit_test(test_sprintf_misc));
ztest_run_test_suite(test_sprintf);
}
後半のztest_run_test_suite() マクロの実装を見ます。
// zephyr/subsys/testsuite/ztest/include/ztest_test.h
/**
* @brief Run the specified test suite.
*
* @param suite Test suite to run.
*/
#define ztest_run_test_suite(suite) \
z_ztest_run_test_suite(#suite, _##suite)
// zephyr/subsys/testsuite/ztest/src/ztest.c
void z_ztest_run_test_suite(const char *name, struct unit_test *suite)
{
int fail = 0;
if (test_status < 0) {
return;
}
init_testing(); //★何もしない★
PRINT("Running test suite %s\n", name);
PRINT_LINE;
while (suite->test) {
fail += run_test(suite); //★テストを実行★
suite++;
if (fail && FAIL_FAST) {
break;
}
}
if (fail) {
TC_PRINT("Test suite %s failed.\n", name);
} else {
TC_PRINT("Test suite %s succeeded\n", name);
}
test_status = (test_status || fail) ? 1 : 0;
}
#ifndef KERNEL
...
#else
...
static int run_test(struct unit_test *test)
{
int ret = TC_PASS;
TC_START(test->name);
//★テスト関数1つに対し、1つスレッドを作る★
k_thread_create(&ztest_thread, ztest_thread_stack,
K_THREAD_STACK_SIZEOF(ztest_thread_stack),
(k_thread_entry_t) test_cb, (struct unit_test *)test,
NULL, NULL, CONFIG_ZTEST_THREAD_PRIORITY,
test->thread_options | K_INHERIT_PERMS,
K_NO_WAIT);
k_thread_name_set(&ztest_thread, "ztest_thread");
k_thread_join(&ztest_thread, K_FOREVER);
phase = TEST_PHASE_TEARDOWN;
test->teardown();
phase = TEST_PHASE_FRAMEWORK;
...
#endif /* !KERNEL */
関数run_test() は非常に特徴的で、ztestでは各ユニットテストを実行する際に、専用のスレッドを生成する仕組みになっています。浮動小数点数命令がIllegal Instruction例外になってしまうのは、この仕組みが原因です。
勘の良い人はZephyrのドキュメント(k_thread_create() へのリンク)を見ただけで、何が悪いかわかるかも。
手掛かりはAPIのoptions引数がtest->thread_options | K_INHERIT_PERMSになっていることです。前半で説明したとおりtest->thread_options = 0であり、K_INHERIT_PERMS以外のオプションは指定されません。スレッド内で浮動小数点数命令を使いたいならK_FP_REGSの指定が要るのでは?と思った方、その通りです。大正解。
K_FP_REGSが答えですよと言われても、何だそれ?と思うほうが普通です(私もそうでした)。Zephyrのスレッド生成関数をざっと追いかけましょう。
// (build_dir)/zephyr/include/generated/syscalls/kernel.h
static inline k_tid_t k_thread_create(struct k_thread * new_thread, k_thread_stack_t * stack, size_t stack_size, k_thread_entry_t entry, void * p1, void * p2, void * p3, int prio, uint32_t options, k_timeout_t delay)
{
#ifdef CONFIG_USERSPACE
if (z_syscall_trap()) {
uintptr_t more[] = {
*(uintptr_t *)&p2,
*(uintptr_t *)&p3,
*(uintptr_t *)&prio,
*(uintptr_t *)&options,
*(uintptr_t *)&delay
};
return (k_tid_t) arch_syscall_invoke6(*(uintptr_t *)&new_thread, *(uintptr_t *)&stack, *(uintptr_t *)&stack_size, *(uintptr_t *)&entry, *(uintptr_t *)&p1, (uintptr_t) &more, K_SYSCALL_K_THREAD_CREATE);
}
#endif
compiler_barrier();
//★今回、ユーザー空間は未使用なので、引数を同じ順で渡すだけ★
return z_impl_k_thread_create(new_thread, stack, stack_size, entry, p1, p2, p3, prio, options, delay);
}
// zephyr/kernel/thread.c
#ifdef CONFIG_MULTITHREADING
k_tid_t z_impl_k_thread_create(struct k_thread *new_thread,
k_thread_stack_t *stack,
size_t stack_size, k_thread_entry_t entry,
void *p1, void *p2, void *p3,
int prio, uint32_t options, k_timeout_t delay)
{
__ASSERT(!arch_is_in_isr(), "Threads may not be created in ISRs");
/* Special case, only for unit tests */
#if defined(CONFIG_TEST) && defined(CONFIG_ARCH_HAS_USERSPACE) && !defined(CONFIG_USERSPACE)
__ASSERT((options & K_USER) == 0,
"Platform is capable of user mode, and test thread created with K_USER option,"
" but neither CONFIG_TEST_USERSPACE nor CONFIG_USERSPACE is set\n");
#endif
z_setup_new_thread(new_thread, stack, stack_size, entry, p1, p2, p3,
prio, options, NULL); //★スレッドの情報初期化★
if (!K_TIMEOUT_EQ(delay, K_FOREVER)) {
schedule_new_thread(new_thread, delay);
}
return new_thread;
}
ここまでは入り口です。ユーザー空間を使わない限り、多少チェックが入っているくらいで、ほぼ素通りします。
/*
* The provided stack_size value is presumed to be either the result of
* K_THREAD_STACK_SIZEOF(stack), or the size value passed to the instance
* of K_THREAD_STACK_DEFINE() which defined 'stack'.
*/
char *z_setup_new_thread(struct k_thread *new_thread,
k_thread_stack_t *stack, size_t stack_size,
k_thread_entry_t entry,
void *p1, void *p2, void *p3,
int prio, uint32_t options, const char *name)
{
char *stack_ptr;
...
z_waitq_init(&new_thread->base.join_waiters);
/* Initialize various struct k_thread members */
z_init_thread_base(&new_thread->base, prio, _THREAD_PRESTART, options); //★スレッド生成(共通部分)★
stack_ptr = setup_thread_stack(new_thread, stack, stack_size);
#ifdef KERNEL_COHERENCE
/* Check that the thread object is safe, but that the stack is
* still cached!
*/
__ASSERT_NO_MSG(arch_mem_coherent(new_thread));
__ASSERT_NO_MSG(!arch_mem_coherent(stack));
#endif
arch_new_thread(new_thread, stack, stack_ptr, entry, p1, p2, p3); //★スレッドの生成(アーキテクチャ依存の処理)★
/* static threads overwrite it afterwards with real value */
new_thread->init_data = NULL;
new_thread->fn_abort = NULL;
#ifdef CONFIG_USE_SWITCH
/* switch_handle must be non-null except when inside z_swap()
* for synchronization reasons. Historically some notional
* USE_SWITCH architectures have actually ignored the field
*/
__ASSERT(new_thread->switch_handle != NULL,
"arch layer failed to initialize switch_handle");
#endif
...
void z_init_thread_base(struct _thread_base *thread_base, int priority,
uint32_t initial_state, unsigned int options)
{
/* k_q_node is initialized upon first insertion in a list */
thread_base->user_options = (uint8_t)options; //★オプションはtest->thread_options (= 0) | K_INHERIT_PERMS (= 8) ★
thread_base->thread_state = (uint8_t)initial_state;
thread_base->prio = priority;
thread_base->sched_locked = 0U;
#ifdef CONFIG_SMP
thread_base->is_idle = 0;
#endif
/* swap_data does not need to be initialized */
z_init_thread_timeout(thread_base);
}
// zephyr/arch/riscv/core/thread.c
void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack,
char *stack_ptr, k_thread_entry_t entry,
void *p1, void *p2, void *p3)
{
struct __esf *stack_init;
...
#if defined(CONFIG_FPU) && defined(CONFIG_FPU_SHARING)
if ((thread->base.user_options & K_FP_REGS) != 0) { //★この条件に引っかからず、浮動小数点数演算機能が有効にならない★
stack_init->mstatus |= MSTATUS_FS_INIT;
}
stack_init->fp_state = 0;
#endif
stack_init->mepc = (ulong_t)z_thread_entry_wrapper;
...
アーキテクチャ依存処理arch_new_thread() にやっとK_FP_REGSが出現します。user_optionsはz_init_thread_base() で設定しているとおり、APIの引数optionsの値そのものです。
引数optionsにK_FP_REGSを指定しない場合、mstatus CSRのFSフィールドが設定されず0のままになります。RISC-Vの仕様では、ハードウェアが浮動小数点数命令をサポートしていても、mstatusのFSフィールドが0だとIllegal Instruction例外を発生させます。QEMUも当然この仕様に習った実装になっています(2020年12月10日の日記参照)。
以上がztestで浮動小数点数命令を使うと例外が発生する原因です。原因はわかりましたが、スマートな直し方がわからないので、修正に関しては保留中です。
最初の方でinit_testing(); //★何もしない★ とだけ書いてスルーしてしまった部分がありましたので、実装を載せておきます。
// zephyr/subsys/testsuite/ztest/src/ztest.c
#ifndef KERNEL
...
#else
...
static void init_testing(void)
{
k_object_access_all_grant(&ztest_thread);
}
#endif /* !KERNEL */
// zephyr/(build_dir)/zephyr/misc/generated/syscalls_links/include/sys/kobject.h
static inline void k_object_access_all_grant(const void *object)
{
ARG_UNUSED(object);
}
この通り、何もしていません。
目次: Zephyr
Zephyrのテストに用いられるztestというフレームワークがあります。使い方は、下記の通りで、テスト用の関数を1つずつztest_unit_test() というマクロに渡します。
最終的にtest_sprintfはstruct unit_testの配列になり、配列をztest_run_test_suite() に渡すと最初のテストから順番に実行してくれるという仕組みです。ztestの使い方は主題ではないのでこのくらいにしておきます。
// zephyr/tests/lib/sprint/src/main.c
void test_main(void)
{
ztest_test_suite(test_sprintf, //★変数名★
ztest_unit_test(test_sprintf_double), //★テスト1つ目★
ztest_unit_test(test_sprintf_integer), //★テスト2つ目★
ztest_unit_test(test_vsprintf),
ztest_unit_test(test_vsnprintf),
ztest_unit_test(test_sprintf_string),
ztest_unit_test(test_sprintf_misc));
ztest_run_test_suite(test_sprintf);
}
今回はztestがテスト実行時に何を行うのかを調べ、浮動小数点数命令で例外が発生する原因を探します。まずは前半のztest_test_suite() マクロから。
// zephyr/subsys/testsuite/ztest/include/ztest_test.h
/**
* @brief Define a test suite
*
* This function should be called in the following fashion:.c
* ztest_test_suite(test_suite_name,
* ztest_unit_test(test_function),
* ztest_unit_test(test_other_function)
* );
*
* ztest_run_test_suite(test_suite_name);
* ```
*
* @param suite Name of the testing suite
*/
#define ztest_test_suite(suite, ...) \
static ZTEST_DMEM struct unit_test _##suite[] = { \
__VA_ARGS__, { 0 } \
}
struct unit_test {
const char *name;
void (*test)(void);
void (*setup)(void);
void (*teardown)(void);
uint32_t thread_options;
};
/**
* @brief Define a test function
*
* This should be called as an argument to ztest_test_suite.
*
* @param fn Test function
*/
#define ztest_unit_test(fn) \
ztest_unit_test_setup_teardown(fn, unit_test_noop, unit_test_noop)
/**
* @brief Define a test with setup and teardown functions
*
* This should be called as an argument to ztest_test_suite. The test will
* be run in the following order: @a setup, @a fn, @a teardown.
*
* @param fn Main test function
* @param setup Setup function
* @param teardown Teardown function
*/
#define ztest_unit_test_setup_teardown(fn, setup, teardown) { \
STRINGIFY(fn), fn, setup, teardown, 0 \
}
//// 展開例: ztest_unit_test(test_sprintf_double)
// fn = test_sprintf_double
ztest_unit_test_setup_teardown(test_sprintf_double, unit_test_noop, unit_test_noop)
// fn = test_sprintf_double, setup = unit_test_noop, teardown = unit_test_noop
{
name : STRINGIFY(test_sprintf_double),
test : test_sprintf_double,
setup : unit_test_noop,
teardown : unit_test_noop,
thread_options: 0
}
最初にちょこっと書いたとおり、ztest_test_suite() はstruct unit_testの配列宣言に展開され、ztest_unit_test() は配列の要素の初期化値に展開されます。
構造体unit_testにthread_optionsというメンバーがいます。このメンバーが浮動小数点数命令のサポートに重要な役割を果たします。ztest_unit_test() だと、常にthread_optionsは0になる、という点だけ覚えておいてくれればOKです。
目次: Zephyr
GitHubを見ていたらZephyrのMemberに招待されていました。メーリングリストに来ていたメール(メールのアーカイブへのリンク)によると、現状MAINTAINERS.ymlに記載されているCollaboratorを全員招待したらしいです。
MemberといってもIssueのタグや担当者を付け外しできるようになる程度で、これといった権限はありません。
目次: RISC-V
RISC-VではCPUが単精度浮動小数点F拡張や、倍精度浮動小数点D拡張の命令(flw, fldなど)に対応していても、mstatusのFSビットでFPUを有効にしていないと、命令実行時にIllegal Instruction例外が発生します。
QEMUはこのチェックをどこで行っているのかメモしておきます。
// qemu/target/riscv/translate.c
static void decode_opc(CPURISCVState *env, DisasContext *ctx, uint16_t opcode)
{
/* check for compressed insn */
if (extract16(opcode, 0, 2) != 3) {
if (!has_ext(ctx, RVC)) {
gen_exception_illegal(ctx);
} else {
ctx->pc_succ_insn = ctx->base.pc_next + 2;
if (!decode_insn16(ctx, opcode)) {
/* fall back to old decoder */
decode_RV32_64C(ctx, opcode);
}
}
} else {
uint32_t opcode32 = opcode;
opcode32 = deposit32(opcode32, 16, 16,
translator_lduw(env, ctx->base.pc_next + 2));
ctx->pc_succ_insn = ctx->base.pc_next + 4;
if (!decode_insn32(ctx, opcode32)) { //★この関数がfalseを返す★
gen_exception_illegal(ctx);
}
}
}
static void gen_exception_illegal(DisasContext *ctx)
{
generate_exception(ctx, RISCV_EXCP_ILLEGAL_INST); //★Illegal Instruction例外★
}
関数decode_insn32() で命令をデコードしfalseが返ってきたら、例外を発生させます。このdecode_insn32() という関数は自動生成されており、ビルドディレクトリの下にあります。RV64向けなら下記のようなパスです。
// (build_dir)/libqemu-riscv64-softmmu.fa.p/decode-insn32.c.inc
static bool decode_insn32(DisasContext *ctx, uint32_t insn)
{
switch (insn & 0x0000007f) {
...
case 0x00000007:
/* ........ ........ ........ .0000111 */
switch ((insn >> 12) & 0x7) {
...
case 0x3:
/* ........ ........ .011.... .0000111 */
/* ../target/riscv/insn32.decode:199 */
decode_insn32_extract_i(ctx, &u.f_i, insn);
if (trans_fld(ctx, &u.f_i)) return true; //★このifが成立「しない」★
break;
...
static void decode_insn32_extract_i(DisasContext *ctx, arg_i *a, uint32_t insn)
{
a->imm = sextract32(insn, 20, 12);
a->rs1 = extract32(insn, 15, 5);
a->rd = extract32(insn, 7, 5);
}
// qemu/target/riscv/insn_trans/trans_rvd.c.inc
static bool trans_fld(DisasContext *ctx, arg_fld *a)
{
REQUIRE_FPU; //★このマクロ内でreturn false★
REQUIRE_EXT(ctx, RVD);
TCGv t0 = tcg_temp_new();
gen_get_gpr(t0, a->rs1);
tcg_gen_addi_tl(t0, t0, a->imm);
tcg_gen_qemu_ld_i64(cpu_fpr[a->rd], t0, ctx->mem_idx, MO_TEQ);
mark_fs_dirty(ctx);
tcg_temp_free(t0);
return true;
}
// qemu/target/riscv/insn_trans/trans_rvf.c.inc
#define REQUIRE_FPU do {\
if (ctx->mstatus_fs == 0) \
return false; \
} while (0)
大体の仕組みは把握できましたので、実際にこの部分を通るかどうか見ましょう。
Illegal Instruction例外は実行中に何度も発生する例外ではありません。カーネル内で1度でも発生するとZephyrはログを出して止まります。すなわちQEMUも例外を発生させるのは1度だけです。
何度も通る処理だとブレーク条件が必要でややこしいですが、1度しか通らない処理なら単純にREQUIRE_FPUの行でブレークすれば良いです。条件ctx->mstatus_fs == 0が成立するかどうか簡単に確認できます。
$ gdb qemu/build/qemu-system-riscv64 (gdb) b trans_rvd.c.inc:23 Breakpoint 1 at 0x555555a858b8: file ../target/riscv/insn_trans/trans_rvd.c.inc, line 23. (gdb) r [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff440c700 (LWP 1675592)] [New Thread 0x7ffff3a89700 (LWP 1675593)] *** Booting Zephyr OS build zephyr-v2.4.0-2328-g4f33cf942643 *** Running test suite test_sprintf =================================================================== START - test_sprintf_double [Switching to Thread 0x7ffff3a89700 (LWP 1675593)] Thread 3 "qemu-system-ris" hit Breakpoint 1, trans_fld (ctx=0x7ffff3a883d0, a=0x7ffff3a88290) at ../target/riscv/insn_trans/trans_rvd.c.inc:23 23 REQUIRE_FPU; (gdb) l 18 * this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 static bool trans_fld(DisasContext *ctx, arg_fld *a) 22 { 23 REQUIRE_FPU; //★ここで止めた★ 24 REQUIRE_EXT(ctx, RVD); 25 TCGv t0 = tcg_temp_new(); 26 gen_get_gpr(t0, a->rs1); 27 tcg_gen_addi_tl(t0, t0, a->imm); (gdb) p ctx->mstatus_fs $1 = 0
ちょっとしたメモのつもりが長くなってしまいました。まあいいや。
< | 2021 | > | ||||
<< | < | 01 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | 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 |
31 | - | - | - | - | - | - |
合計:
本日: