2020年 12月 3日

Zephyr の collaborator その 2

目次: Zephyr を調べる - まとめリンク

Zephyr の Watchdog の Collaborator になりました。先日(2020年 11月 17日の日記参照)Collaborator になった RISC-V も Watchdog も、元々メンテナーが不在の領域です。

発端は SiFive HiFive1 を買った記念に Watchdog ドライバを書いたら、割とあっさり動いたので、PR を送ったことです。送ったまでは良かったのですが、メンテナーが不在でした。またこのパターン……。

Zephyr は周辺ドライバが充実しているのが売りですが、メンテナーは不足気味です。今ならサブシステムの Collaborator くらいなら、何の実績もなくても気前良く追加してくれます。

RTOS 興味ある人はいかがですか?

編集者: すずき(更新: 2020年 12月 4日 21:43)

コメント一覧

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



2020年 12月 5日

東京オリンピックと祝日とカレンダー

日本政府は未だに東京オリンピックやる気満々らしく、内閣府の発表(国民の祝日について - 内閣府)によると、2020年に引き続いて来年 2021年も、海の日、スポーツの日、山の日が本来と異なる日付に移動されるようです。

  • 海の日: 7月 第3月曜 7/19 → 7/22
  • スポーツの日: 10月 第2月曜 10/11 → 7/23
  • 山の日: 8/11 → 8/8(日曜日なので 8/9 が振替休日になる)

混乱しそうなのでこのサイトのカレンダー機能にも反映しておきました。カレンダー機能は便利で実装して良かったと思っている機能の一つですが、まさかこんなに東京オリンピック関係で祝日が蹂躙されるとは思っていませんでしたね。

編集者: すずき(更新: 2020年 12月 5日 22:51)

コメント一覧

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



2020年 12月 8日

Zephyr と浮動小数点数命令のサポート その 1 - ボードの設定とビルド

目次: Zephyr を調べる - まとめリンク

RISC-V 向け Zephyr には、既存のボード設定がいくつか含まれていますが、私が持っているボード(SiFive HiFive1 Rev.B)や QEMU 用の設定では、浮動小数点数命令(単精度浮動小数点数命令 = F 拡張、倍精度浮動小数点数命令 = D 拡張)は未サポートです。

実は QEMU は浮動小数点数命令をサポートしている CPU タイプもあります(-cpu rv32 もしくは -cpu rv64)。せっかくサポートしているのに使わないのは勿体無いですよね?

浮動小数点数命令のサポートを追加、ビルド

新たに qemu_rv64_virt ボードを追加し、ハードウェア浮動小数点数命令をサポートします。Zephyr には既に仕組みがあるので、特に難しくはありません。CPU_HAS_FPU と CPU_HAS_FPU_DOUBLE_PRECISION を select するだけです。

D 拡張を有効にして sprintf のテストを実行するとクラッシュする

config BOARD_QEMU_RV64_VIRT
	bool "QEMU RV64 virt target"
	depends on SOC_QEMU_RV64_VIRT
	select QEMU_TARGET
	select 64BIT
	select CPU_HAS_FPU                     #★F 拡張に対応★
	select CPU_HAS_FPU_DOUBLE_PRECISION    #★D 拡張に対応★

この場合 RV64IMAFDC の意味になりますが、select CPU_HAS_FPU だけ指定して、D 拡張だけ外した RV64IMAFC にすることもできます。しかし Zephyr SDK のツールチェーンが対応していないため、下記のようにリンク時に猛烈にエラーが出て怒られます。

F 拡張だけ有効にしてビルドすると大量のエラー
[105/110] Linking C executable zephyr/zephyr_prebuilt.elf
FAILED: zephyr/zephyr_prebuilt.elf
: && ccache zephyr-sdk/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc   zephyr/CMakeFiles/zephyr_prebuilt.dir/misc/empty_file.c.obj -o zephyr/zephyr_prebuilt.elf  -Wl,-T  zephyr/linker.cmd  -Wl,-Map=zephyr/build32/zephyr/zephyr_prebuilt.map  -Wl,--whole-archive  app/libapp.a  zephyr/libzephyr.a  zephyr/arch/common/libarch__common.a  zephyr/arch/arch/riscv/core/libarch__riscv__core.a  zephyr/lib/libc/minimal/liblib__libc__minimal.a  zephyr/lib/posix/liblib__posix.a  zephyr/subsys/testsuite/ztest/libsubsys__testsuite__ztest.a  zephyr/drivers/serial/libdrivers__serial.a  -Wl,--no-whole-archive  zephyr/kernel/libkernel.a  zephyr/CMakeFiles/offsets.dir/./arch/riscv/core/offsets/offsets.c.obj  -L"zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0"  -Lzephyr/build32/zephyr  -lgcc  -Wl,--print-memory-usage  zephyr/arch/common/libisr_tables.a  -mabi=lp64f  -march=rv64imafc  -Wl,--gc-sections  -Wl,--build-id=none  -Wl,--sort-common=descending  -Wl,--sort-section=alignment  -Wl,-u,_OffsetAbsSyms  -Wl,-u,_ConfigAbsSyms  -nostdlib  -static  -no-pie  -Wl,-X  -Wl,-N  -Wl,--orphan-handling=warn && :
zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clzdi2.o): ABI is incompatible with that of the selected emulation:
  target emulation `elf32-littleriscv' does not match `elf64-littleriscv'
zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: failed to merge target specific data of file zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clzdi2.o)
zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_ctzdi2.o): ABI is incompatible with that of the selected emulation:
  target emulation `elf32-littleriscv' does not match `elf64-littleriscv'
zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: failed to merge target specific data of file zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_ctzdi2.o)
zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clz.o): ABI is incompatible with that of the selected emulation:
  target emulation `elf32-littleriscv' does not match `elf64-littleriscv'
zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: failed to merge target specific data of file zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clz.o)
Memory region         Used Size  Region Size  %age Used
             RAM:       49220 B       256 KB     18.78%
        IDT_LIST:          57 B         2 KB      2.78%
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

上記のエラーについて Zephyr SDK を作っている人に聞いてみたところ「RV64IMAFC は本当に必要?」と逆に聞かれてしまいました。うーん、現状そんな変な CPU はこの世にないし、対応する理由も思いつきません。要らないですね。

編集者: すずき(更新: 2020年 12月 14日 01:50)

コメント一覧

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



2020年 12月 9日

Zephyr と浮動小数点数命令のサポート その 2 - 実行

目次: Zephyr を調べる - まとめリンク

ひとまず F 拡張と D 拡張の双方を有効にし RV64IMAFDC(省略形だと RV64GC ともいう)でビルドして、実行します。使用するのは tests/lib/sprintf で、その名の通り sprintf() のテストです。しかし実行するやいなや、エラーで吹き飛んでしまいます。

D 拡張を有効にして sprintf のテストを実行するとクラッシュする
$ cmake -G Ninja -DBOARD=qemu_rv64_virt ../tests/lib/sprintf/
$ ninja
$ ninja run

[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: riscv64
*** Booting Zephyr OS build zephyr-v2.4.0-2328-g4f33cf942643  ***
Running test suite test_sprintf
===================================================================
START - test_sprintf_double
E: Exception cause Illegal instruction (2)
E: Faulting instruction address = 0x80000cba
E:   ra: 0x80003836  gp: 0x00000000  tp: 0x00000000  t0: 0x00000000
E:   t1: 0x00000000  t2: 0x00000000  t3: 0x00000000  t4: 0x00000000
E:   t5: 0x00000000  t6: 0x00000000  a0: 0x8000bdb0  a1: 0x00000000
E:   a2: 0x00000000  a3: 0x00000000  a4: 0x00000000  a5: 0x80006cb6
E:   a6: 0x00000000  a7: 0x00000000

E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0
E: Current thread: 0x80009820 (unknown)
E: Halting system

ログには非常に役立つ情報が出ています。エラーの理由は Illegal Instruction 例外、アドレスは 0x80000cba だと言っています。なるほど。この付近を逆アセンブルします。

例外が発生した命令を特定する
$ riscv64-zephyr-elf-objdump -drS build32/zephyr/zephyr.elf

0000000080000cb2 <test_sprintf_double>:
{
    80000cb2:   7121                    addi    sp,sp,-448
    80000cb4:   af22                    fsd     fs0,408(sp)
        sprintf(buffer, "%e", var.d);
    80000cb6:   00006797                auipc   a5,0x6
    ★fld 命令で Illegal Instruction 例外★
    80000cba:   ad27b407                fld     fs0,-1326(a5) # 80006788 <__font_entry_end>
    80000cbe:   e2040653                fmv.x.d a2,fs0
    80000cc2:   00007597                auipc   a1,0x7
    80000cc6:   ad658593                addi    a1,a1,-1322 # 80007798 <__clz_tab+0xf78>

...

QEMU の rv64 CPU 指定は倍精度浮動小数 D 拡張命令に対応しているのに、なぜエラーになるのか?原因は ztest という Zephyr のテストフレームワークの仕組みによるものです。次回以降、エラーの原因を追います。

編集者: すずき(更新: 2020年 12月 14日 01:53)

コメント一覧

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



2020年 12月 10日

QEMU RISC-V エミュレーションで例外が起きるときの仕組み

目次: RISC-V - まとめリンク

RISC-V では CPU が単精度浮動小数点 F 拡張や、倍精度浮動小数点 D 拡張の命令(flw, fld など)に対応していても、mstatus の FS ビットで FPU を有効にしていないと、命令実行時に Illegal Instruction 例外が発生します。

QEMU はこのチェックをどこで行っているのかメモしておきます。

QEMU RISC-V 例外発生部分

// 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 向けなら下記のようなパスです。

QEMU RISC-V 命令デコード部分

// (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 で mstatus_fs の値を確認する
$ 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年 9月 21日 22:59)

コメント一覧

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



2020年 12月 11日

Zephyr の Member になった

目次: Zephyr を調べる - まとめリンク

GitHub を見ていたら Zephyr の Member に招待されていました。メーリングリストに来ていたメール(メールのアーカイブへのリンク)によると、現状 MAINTAINERS.yml に記載されている Collaborator を全員招待したらしいです。

Member といっても Issue のタグや担当者を付け外しできるようになる程度で、これといった権限はありません。

編集者: すずき(更新: 2020年 12月 13日 19:07)

コメント一覧

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



2020年 12月 12日

Zephyr と浮動小数点数命令のサポート その 3 - ztest の仕組み、前編

目次: Zephyr を調べる - まとめリンク

Zephyr のテストに用いられる ztest というフレームワークがあります。使い方は、下記の通りで、テスト用の関数を 1つずつ ztest_unit_test() というマクロに渡します。

最終的に test_sprintf は struct unit_test の配列になり、配列を ztest_run_test_suite() に渡すと最初のテストから順番に実行してくれるという仕組みです。ztest の使い方は主題ではないのでこのくらいにしておきます。

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() マクロから。

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 です。

編集者: すずき(更新: 2020年 12月 14日 01:58)

コメント一覧

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



2020年 12月 13日

Zephyr と浮動小数点数命令のサポート その 4 - ztest の仕組み、後編

目次: Zephyr を調べる - まとめリンク

前半では ztest_test_suite() の実装を見ました。戻って見るのは面倒だと思うので ztest の使い方の例を再掲しておきます。

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() マクロの実装を見ます。

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日の日記参照)。


mstatus FS フィールドの意味

以上が ztest で浮動小数点数命令を使うと例外が発生する原因です。原因はわかりましたが、スマートな直し方がわからないので、修正に関しては保留中です。

おまけ

最初の方で init_testing(); //★何もしない★ とだけ書いてスルーしてしまった部分がありましたので、実装を載せておきます。

補足: 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);
}

この通り、何もしていません。

編集者: すずき(更新: 2020年 12月 14日 02:12)

コメント一覧

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



2020年 12月 17日

年末の帰省

実家と話し合って、今年末の北海道への帰省を取りやめました。年末がクッソ暇になりました。

帰省用の飛行機チケットを解約したんですが、ANA が最近やってる「あんしん変更キャンペーン」(ANA 公式サイト)により、運賃や解約ルールが意味不明になってます。

買ったチケットは、

  • 行き: 羽田→千歳のスーパーバリュー75H
  • 帰り: 千歳→羽田のスーパーバリュー75H

計 95,040円です。

解約の仕方で払戻金が全然違う

解約の仕方をいくつか試したところ、払戻金額の見積もりがべらぼうに変わり驚きました。

  • パターン1 行き帰り同時解約: 払戻金額 46,020円
  • パターン2 帰り解約→行き解約: 払戻金額 51,120円+16,000円くらい
  • パターン3 帰り解約→行きを安い便(バリュー3J とか)に変更→行き解約: 払戻金額 51,120円+10,800円+30,640円

各パターンで何が起きているのでしょう?個人的な予想に過ぎませんが、

パターン 1
スーパーバリュー75 は搭乗 2週間前の場合、キャンセル料 50%なので、「あんしん変更キャンペーン」がなければこんなもんです。
パターン 2
解約処理がイカレたのか、「あんしん変更キャンペーン」により、帰りだけ解約=予約変更と見なすのか、何だか知りませんが、キャンセル料なしで帰りのスーパーバリュー75 の全額がしれっと返ってきます。意味がわからん。
パターン 3
まず、現在、運賃の逆転現象が発生しています。通常は、
スーパーバリュー75 < バリュー3
ですが、最近のコロナによる需要減で、
スーパーバリュー75 > バリュー3
になっているので、本来高いはずのチケットに変えると 1万円返ってくる謎現象が発生しています。この時点で意味不明ですが、バリュー3 のキャンセル料は 5%ですから、手数料も含めて 2000円くらいで解約できます。キャンセル料 50%はどこ行った?

バリュー系チケットの値段は需要連動(ANA の解説)らしく、11月のコロナ再流行で需要が急激に落ち込み、スーパーバリュー75H とバリュー 3J の逆転現象が発生したのでしょう。この需要連動という想定そのものが「直前に需要が急減する」という事態を想定していない節がありますね。

もし 10月くらいにスーパーバリュー系を予約した人は、1万円単位で大損してるはずです。年末に飛行機に乗る人は要確認です。

ANA の想定はパターン 1 なのか?

今の ANA の一番良くないところは、普通に解約すると 5万円近く大損するところですが、ANA の QA を見る限り「コロナ関連で解約の特別対応はしない、所定のキャンセル料を払え」とあるのでANA としてはパターン 1 の解約で ANA に 5万円寄付せよ、という意思なんでしょうか?

私は、パターン 3 を試してみたので、手数料含めて 2,500円くらいで解約できちゃったように見えます。

予約システムのバグだったんですかね?ちゃんと払い戻しされるかなあ??不安だわあ……。

メモ: 技術系?の話は Facebook から転記しておくことにした。

編集者: すずき(更新: 2021年 8月 2日 11:11)

コメント一覧

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



2020年 12月 19日

車の燃費

車に乗ってるとたまに疑問に思うんですが、燃費ってアクセル開度で決まるんですかね?

同じ勾配、同じアクセル開度なら、毎速 2500rpm で変速しても、2速 5000rpm まで引っ張って、すぐ 3, 4, 5 速と変速しても、燃料消費量は同じ??なんてことを Facebook に書いてみたところ、会社の同僚のみなさんから色々教えていただけました。

同じアクセル開度なら、エンジンの燃焼効率の最適点が一番燃費が良いので、1速でぶん回すより、4速くらいの方が一般的には燃費が良いはず、とのことです。レスが超早かったです。さすが車の専門家達の会社だなと痛感しますね。

マイカーだとどの辺りか?

通常、燃焼効率の最適点は公開されていません。代わりとしてエンジン出力の最大点を使おうと思います。燃焼効率最適点=燃費の最大点、エンジン出力の最大点=加速の最大点であり、この 2つは一致しません。ただし、効率が悪いにも関わらず最大出力に達するのは、ちょっと考えにくいですし、一致はしなくてもさほど遠くないだろう、という目論見です。

スバルの EJ20 エンジンはいろんな車種に乗っていて、自分のレガシィ B4 のエンジン性能曲線が探せませんでした……。インプレッサ WRX STI の EJ20 エンジン性能曲線はすぐに出てきた(パワーユニット : ドライビング - WRX STI - SUBARU)ので、これを参考にしようと思います。


EJ20 エンジン性能曲線(スバルのサイトから引用)

もちろん

  • 町中でアクセル全開しないから、このカーブにはならない
  • 同じ EJ20 でもレガシィと WRX STI は別物レベルで性能が違う

辺りはわかってます。

が、まあ、ざっくり言って、トルクカーブは 2500〜4500rpm くらいまでピークかつ真っ平らです。ほぼどこで変速しても大丈夫!素晴らしいエンジンですね〜。

2000rpm 前後の加速が眠くて、突然速くなる感じがするのも、レガシィが重たいからだと思いこんでいたんですけど、2000〜2500rpm でトルクが急激に立ち上がるエンジン特性からくるものなんですね。見覚えあるなと思って調べたら、昔も同じことを言っていました(2010年 9月 5日の日記2010年 9月 7日の日記参照)。完全に忘れてました。

何年乗ってるんだ、今更かよって感想ですけど……。

Facebook で教えていただいたところによると、EJ20 は OBD2 端子(On-board diagnostics という故障診断の信号を送受信できる端子)から燃料消費量の情報が取れるはずなので、それを見たらどうか?ELM327 互換のアダプタで Bluetooth でスマホにデータを送る手もある、という素晴らしいアイデアをいただきました。

昔に付けたメーター(2011年 5月 4日の日記参照)で、瞬間燃費が表示できないか試してみようかな。

メモ: 技術系?の話は Facebook から転記しておくことにした。大幅に追記。

編集者: すずき(更新: 2020年 12月 27日 02:58)

コメント一覧

  • Twitterから 
    > 大学時代に知り合った人たちを思い出すと、大阪、京都の人は地元好きだったなー、と思った。

    とありますが、関西と北部九州は愛郷心強い人が多い印象受けますねー
    日本一愛郷心が高いのは福岡県とのことです 
    (2021年04月28日 13:38:08)
  • すずき 
    なるほど、地元愛 No.1 は福岡なんですね。言われてみると福岡の方々も地元好きだった気がします。
    地元の方言を隠さない、というのも共通しているなあと思います。 
    (2021年05月02日 00:08:36)
open/close この記事にコメントする



2020年 12月 20日

ANA のチケット代の仕組み

年末の帰省チケットを解約したとき(2020年 12月 17日の日記参照)に価格が激変していたことが気になったので、年末年始の羽田→千歳便の各チケットの価格をプロットしてみました。


年末年始の羽田→千歳便チケット価格

どうやらチケットの種類に関係なく、最後に付いているアルファベットで価格帯が決まるようです。A が一番高くて、B, C, D, ... と安くなっていくようです。10月に予約した SUPER VALUE 75 H が、今日予約できる VALUE 3 J より高くなるのはこれが理由でした。

ANA の予約システムは、遠い予約日(1/M くらい)だと、空席予測を強気に出すのかやや割高の運賃設定をしています。

  • VALUE 3 H(21360円〜)
  • VALUE 1 F(27560円〜)

くらいかな?早朝便、深夜便などは 1ランク安くなりがちです。しかし、搭乗日が近づいて(増発を決めてしまった 12/30 など)、誰も乗らないことに気づき始めると、未だかつて見たことない安さのチケットが出現します。

  • VALUE 3 K(13560円〜)
  • VALUE 1 G(26260円〜)

こんな感じですね。安いなあ。

現在は特殊な状況に置かれている

遠い予約日が強気の価格になるのは、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 で予約して払い込んでしまい、年始に安い便があれば切り替え、払い戻しを受ければノーリスクで安く乗れるはずです。

天下の ANA がこんな苦境に陥るとは誰が予想しただろうか

未だかつて年末の羽田→千歳便がこんな低価格で投げ売りされたことはありません。どれだけぼったくっても、皆が渋々乗るので「ドル箱路線」と称されたほどです。

10年来、散々、羽田→千歳便でボラれてきた身としては、今年は流石の ANA もぼったくりはできなかったか……と思いました。けどまあ COVID-19 に関しては同情しかなくて、乗れる機会があったら飛行機乗って応援したいですね。九州辺りに旅行に行きたいな〜。

「あんしん変更キャンペーン」はただでさえ客足が遠のく中、安く乗られ、割安で解約され、ANA としては散々でしょう。

逆にこっちはあまり同情してません。飛行機の割引制度は縛りが多くて理不尽です。今の方が素直だし普通です。是非、このまま続けて欲しいですね。

メモ: 技術系?の話は Facebook から転記しておくことにした。いろいろ修正。

編集者: すずき(更新: 2020年 12月 21日 02:02)

コメント一覧

  • hdk 
    羽田鹿児島便、ANA直接予約はほとんどしたことがないんですが、楽天などのパック旅行でも1, 2週間前でも案外安いのが残っていることがよくありました。パックは変更がきかないものの、そもそも繁忙期を避ければどこに1泊分含まれているのかわからないくらい安いんですが、年末などはほとんど満席なのになぜか共同運航便だけ空席あり(しかもANA側で見ると満席)みたいなこともあった気がします。お得に行こうとするといろいろ調べてみないとわからないですね。 
    (2020年12月21日 08:17:55)
  • すずき 
    パック旅行や旅行代理店に割り当てられた専用枠って、もはや法則性のある料金体系ではない(会社間の政治力で価格が変わる)ですし、一般消費者に全く理解不能な価格になるのは、どうしようもないですねえ……。逐一探すしかなさそう。 
    (2020年12月21日 19:00:13)
open/close この記事にコメントする



2020年 12月 31日

デザイン変更

記事以外の表示(リンクとか編集ボタンとか)が縦に並んでいて、横長のディスプレイで見たときに邪魔なので、ちょっとだけデザインを変えて縦方向の長さを詰めました。

デザインはあまり詳しくないですが、もっと文字が読みやすくなるようにするにはどうしたら良いんでしょうね……?

編集者: すずき(更新: 2020年 12月 31日 18:17)

コメント一覧

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



こんてんつ

open/close wiki
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 過去日記について

その他の情報

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