0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zephyr(その3:タスク→スレッド)

0
Posted at

Zephyrでタスク(スレッド)

RTOSとしてのZephyrのタスクを扱う。Linux系ということもあり、実質はスレッドである。また、優先度も扱ってみる。その2同様、Wio Terminal利用。

題材

2つのスレッドAおよびBを動かし、それぞれで、メッセージを表示。その順番により、タスク(スレッド)スイッチを把握する。スイッチ動作を確認するため、スレッド内にあえてsleepなどを用いていない部分もある。ソースコードを見た方が早いだろう。なお、今回の構成は下記。

% tree 
.
├── CMakeLists.txt
├── prj.conf
└── src
    └── main.c

Buildや書き込みについては、その2を参照。

ソースコード+検証

CMakeLists.txt

CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(1stThread)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

ここはノーコメント。

prj.conf

2つのケース実施。

Case 1:prj.confそのものを使用しない

Case 2:下記を使用

prj.conf
CONFIG_TIMESLICING=n

これは同一優先度をもつスレッドで、タイムスライスを無効化するもの。デフォルトでは有効化されている。以降、いくつかサンプルを紹介するが、Case 1または2のいずれかも明記。

main.c

スレッドAとBが同一優先度+prj.conf未使用(上述Case 1)

main.c
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#define STACK_SIZE 1024
#define PRIORITY 7
#define BASE 1000000

// スレッドA
void thread_a_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (10*BASE) == 0) {
	       printk("Thread A: %d\n", loop_count/(10*BASE));
	    }
        if (loop_count >= 50*BASE) {
            loop_count = 0;
        }
    }    
}

// スレッドB
void thread_b_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (10*BASE) == 0) {
	        printk("\tThread B: %d\n", loop_count/(10*BASE));
	    }
        if (loop_count >= 50*BASE) {
            loop_count = 0;
        }
    }    
    
}

// スレッドを静的に定義および起動
K_THREAD_DEFINE(thread_a_id, STACK_SIZE, thread_a_entry, NULL, NULL, NULL, PRIORITY, 0, 0);
K_THREAD_DEFINE(thread_b_id, STACK_SIZE, thread_b_entry, NULL, NULL, NULL, PRIORITY, 0, 0);

まず、Zephyrでは、マクロ「K_THREAD_DEFINE()」によりスレッド(タスク)を作成するのが簡単なようだ。この場合、main()でのスレッド作成は不要。

本コードでは、定期的に、Thread AおよびBのメッセージ+カウンタを表示するだけ。後述する関数などにより、スレッド(タスク)スイッチを明示的に実施させないため、相当数ループさせている。ループ数については、トライ&エラーで選んだもの。ボードにより異なるであろう。実行結果は下記となる。

*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
Thread A: 1
        Thread B: 1
Thread A: 2
        Thread B: 2
Thread A: 3
        Thread B: 3
Thread A: 4
        Thread B: 4
Thread A: 5
        Thread B: 5
Thread A: 1
        Thread B: 1
Thread A: 2
        Thread B: 2
...

スレッドAとB同一優先度のため、それぞれのメッセージが順番に表示されており、スレッドが交互に実施されていることが想定される。かなりの回数(1千万回(=10*BASE))ごとの表示ではなく、かなり少ない数ごとの表示であれば、結果が多少異なる可能性はある。

なお、当然であるが、prj.confに「CONFIG_TIMESLICING=y」と記載しても同様の結果となる。

スレッドAとBが同一優先度+prj.conf使用(上述Case 2)

コードは先ほどのものと同様。結果のみ下記。

*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
Thread A: 1
Thread A: 2
Thread A: 3
Thread A: 4
Thread A: 5
Thread A: 1
Thread A: 2
Thread A: 3
Thread A: 4
Thread A: 5
Thread A: 1
...

タイムスライスが無効化されているため、スレッドAの結果しか見えず、スレッドスイッチが行われないように見える。こちらも、かなりの回数(1千万回(=10*BASE))ごとの表示ではなく、かなり少ない数ごとの表示であれば、結果が多少異なる可能性はある。

スレッドAとBが同一優先度+k_yield()使用+prj.conf未使用(上述Case 1)

同一または高い優先度へのスイッチを可能とするk_yield()を利用。スレッドのみコード記載。

// スレッドA
void thread_a_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (10*BASE) == 0) {
	        printk("Thread A: %d\n", loop_count/(10*BASE));
	    }
        if (loop_count >= 50*BASE) {
            loop_count = 0;
	        k_yield(); // 同一または高優先度タスク(スレッド)へスイッチ
        }
    }    
}

// スレッドB
void thread_b_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (10*BASE) == 0) {
	        printk("\tThread B: %d\n", loop_count/(10*BASE));
	    }
        if (loop_count >= 50*BASE) {
            loop_count = 0;
	        k_yield(); // 同一または高優先度タスク(スレッド)へスイッチ
        }
    }    
}

カウンタが最大値(50*BASE)になった時、k_yield()を実行する。結果を記載。

*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
        Thread B: 1
Thread A: 1
        Thread B: 2
Thread A: 2
        Thread B: 3
Thread A: 3
        Thread B: 4
Thread A: 4
        Thread B: 5
Thread A: 5
        Thread B: 1
Thread A: 1
        Thread B: 2
Thread A: 2
...

タイムスライスが有効のため、k_yield()の効果が見られず、最初の結果と同じである。

スレッドAとBが同一優先度+k_yield()使用+prj.conf使用(上述Case 2)

コードは一つ前(k_yield()含む)と同じ。結果を記載。

*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
Thread A: 1
Thread A: 2
Thread A: 3
Thread A: 4
Thread A: 5
        Thread B: 1
        Thread B: 2
        Thread B: 3
        Thread B: 4
        Thread B: 5
Thread A: 1
Thread A: 2
Thread A: 3
Thread A: 4
Thread A: 5
        Thread B: 1
        Thread B: 2
...

カウンタが最大値(50*BASE)になった時、k_yield()実行により、スレッドスイッチ(AからBへ or BからAへ)が行われていることがわかる。

スレッドAの優先度がスレッドBよりも高い+prj.conf未使用(上述Case 1)

スレッドAの優先度がBよりも高いが、工夫をすれば、Bも実行されるコードである。

main.c
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#define STACK_SIZE 1024
#define PRIORITY 7
#define BASE 100000

// スレッドA
void thread_a_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (100*BASE) == 0) {
	        printk("Thread A: %d\n", loop_count/(100*BASE));
	        k_msleep(10); // 低優先度タスクも含めて全てのタスクへスイッチ
	    }
        if (loop_count >= 500*BASE) {
            loop_count = 0;
        }
    }    
}

// スレッドB
void thread_b_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % BASE == 0) {
	        printk("\tThread B: %d\n", loop_count/BASE);
	    }
        if (loop_count >= 5*BASE) {
            loop_count = 0;
        }
    }    
    
}

// 優先度 A:PRIORITY-1  B:PRIORITY
K_THREAD_DEFINE(thread_a_id, STACK_SIZE, thread_a_entry, NULL, NULL, NULL, PRIORITY-1, 0, 0);
K_THREAD_DEFINE(thread_b_id, STACK_SIZE, thread_b_entry, NULL, NULL, NULL, PRIORITY, 0, 0);

スレッドAにはk_msleep()が使われている。これにより、低優先度含めて全てのスレッドにスイッチされる。ただし、スレッドBにはk_msleep()は使われていない。スレッドAとBの繰り返し回数を工夫した。結果を下記する。

Thread A: 1
Thread A: 2
Thread A: 3
        Thread B: 1
Thread A: 4
Thread A: 5
        Thread B: 2
Thread A: 1
Thread A: 2
Thread A: 3
        Thread B: 3
Thread A: 4
Thread A: 5
        Thread B: 4
Thread A: 1
...

優先度が低いにも関わらず、スレッドAのk_msleep()のタイミングで、スレッドBが実行されていることがわかる。

動的スレッド作成のコード

マクロ「K_THREAD_DEFINE()」ではなく、同じ内容で、「k_thread_create()」を用いた動的スレッド作成を用いるコードを下記する。

main.c
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#define STACK_SIZE 1024
#define PRIORITY 7
#define BASE 1000000

// スレッド管理用構造体
struct k_thread thread_a_data;
struct k_thread thread_b_data;

// スタック領域
K_THREAD_STACK_DEFINE(thread_a_stack, STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_b_stack, STACK_SIZE);

// スレッドA
void thread_a_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (10*BASE) == 0) {
	        printk("Thread A: %d\n", loop_count/(10*BASE));
	    }
        if (loop_count >= 50*BASE) {
            loop_count = 0;
	        k_yield(); // 同一または高優先度タスクへスイッチ
        }
    }    
}

// スレッドB
void thread_b_entry(void *p1, void *p2, void *p3)
{
    volatile uint32_t loop_count = 0;

    ARG_UNUSED(p1);
    ARG_UNUSED(p2);
    ARG_UNUSED(p3);

    while (1) {
        loop_count++;
	    if (loop_count % (10*BASE) == 0) {
	        printk("\tThread B: %d\n", loop_count/(10*BASE));
	    }
        if (loop_count >= 50*BASE) {
            loop_count = 0;
	        k_yield(); // 同一または高優先度タスクへスイッチ
        }
    }    
}

int main(void)
{
    printk("Starting threads dynamically...\n");

    // スレッドAを動的に生成・起動
    k_thread_create(&thread_a_data,                 
                    thread_a_stack,
                    K_THREAD_STACK_SIZEOF(thread_a_stack),
                    thread_a_entry,                 
                    NULL, NULL, NULL,              
                    PRIORITY,                       
                    0,                              
                    K_NO_WAIT);                     

    // スレッドBを動的に生成・起動
    k_thread_create(&thread_b_data,
                    thread_b_stack,
                    K_THREAD_STACK_SIZEOF(thread_b_stack),
                    thread_b_entry,
                    NULL, NULL, NULL,
                    PRIORITY,
                    0,
                    K_NO_WAIT);

    return 0;
}

終わりに

タスクではなくスレッドであることがLinuxを感じる。いずれにせよ、まずはRTOSとしての優先度付きマルチスレッドの基本を理解。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?