コグノスケ


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

link もっと前
2020年12月13日 >>> 2020年12月13日
link もっと後

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 この記事にコメントする



link もっと前
2020年12月13日 >>> 2020年12月13日
link もっと後

管理用メニュー

link 記事を新規作成

<2020>
<<<12>>>
--12345
6789101112
13141516171819
20212223242526
2728293031--

最近のコメント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年2月7日
    すずき (04/24 02:52)
    「[複数の音声ファイルのラウドネスを統一したい] PCやデジタル音楽プレーヤーで音楽を聞いていると、曲によって音量の大小が激しく...」
  • link 24年4月22日
    すずき (04/23 20:13)
    「[仕事部屋の照明が壊れた] いきなり仕事部屋のシーリングライトが消えました。蛍光管の寿命にしては去年(2022年10月19日の...」
  • link 24年4月17日
    すずき (04/18 22:44)
    「[VSCodeとMarkdownとPlantUMLのローカルサーバー] 目次: LinuxVSCodeのPlantUML Ex...」
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/24 08:36