コグノスケ


2020年12月3日

Zephyrのcollaboratorその2

目次: Zephyr

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

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

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

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

編集者:すずき(2023/09/24 12:00)

コメント一覧

  • コメントはありません。
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/05 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はこの世にないし、対応する理由も思いつきません。要らないですね。

編集者:すずき(2023/09/24 11:59)

コメント一覧

  • コメントはありません。
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のテストフレームワークの仕組みによるものです。次回以降、エラーの原因を追います。

編集者:すずき(2023/09/24 11:59)

コメント一覧

  • コメントはありません。
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/09/21 22:59)

コメント一覧

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



2020年12月11日

ZephyrのMemberになった

目次: Zephyr

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

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

編集者:すずき(2023/09/24 12:00)

コメント一覧

  • コメントはありません。
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です。

編集者:すずき(2023/09/24 12:00)

コメント一覧

  • コメントはありません。
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);
}

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

編集者:すずき(2023/09/24 12:00)

コメント一覧

  • コメントはありません。
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/08/02 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から転記しておくことにした。大幅に追記。

編集者:すずき(2023/09/30 14:52)

コメント一覧

  • Twitterからさん(2021/04/28 13:38)
    > 大学時代に知り合った人たちを思い出すと、大阪、京都の人は地元好きだったなー、と思った。

    とありますが、関西と北部九州は愛郷心強い人が多い印象受けますねー
    日本一愛郷心が高いのは福岡県とのことです
  • すずきさん(2021/05/02 00:08)
    なるほど、地元愛 No.1 は福岡なんですね。言われてみると福岡の方々も地元好きだった気がします。
    地元の方言を隠さない、というのも共通しているなあと思います。
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さん(2020/12/21 08:17)
    羽田鹿児島便、ANA直接予約はほとんどしたことがないんですが、楽天などのパック旅行でも1, 2週間前でも案外安いのが残っていることがよくありました。パックは変更がきかないものの、そもそも繁忙期を避ければどこに1泊分含まれているのかわからないくらい安いんですが、年末などはほとんど満席なのになぜか共同運航便だけ空席あり(しかもANA側で見ると満席)みたいなこともあった気がします。お得に行こうとするといろいろ調べてみないとわからないですね。
  • すずきさん(2020/12/21 19:00)
    パック旅行や旅行代理店に割り当てられた専用枠って、もはや法則性のある料金体系ではない(会社間の政治力で価格が変わる)ですし、一般消費者に全く理解不能な価格になるのは、どうしようもないですねえ……。逐一探すしかなさそう。
open/close この記事にコメントする



2020年12月31日

デザイン変更

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

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

編集者:すずき(2020/12/31 18:17)

コメント一覧

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



こんてんつ

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 サイトの情報