前回Zephyr OSのデバイスツリーの高い壁?を何とか乗り越えた?ので、今回はマルチスレッドに進みたいと思います。といっても前回作成のプログラムで2つ光らせていたLEDの片方を新設のスレッドの制御下に移すというだけのもの。なにはともあれマルチスレッド、と。
※「モダンOSのお砂場」投稿順Indexはこちら
スレッドに関するドキュメント
Zephyr OSのスレッド関係のAPIに関する説明ページが以下に。
分量多いからね、とても全部読んでないんでありますが、スレッドを作って走らせるところは他のRTOSとそれほど変わらないように思います(素人の感想です。)
今回実験用に作製したコード
ターゲットボードは、今回も ST Microelectronics社製 NUCLEO F401REボード(STM32F401REマイコン搭載。Arm Cortex-M4Fコア)です。とりあえず前回作成のLチカプログラムに main()と並行動作するスレッド、threadA を定義してみました。
やっぱり、Zephyrは下位の生々しいところをマクロなど使って隠蔽してくれる部分が多いです。お作法がキッチリしている感じ。でもマクロの上にマクロが積み重なっていたりしてホントのところは良く分からない(個人の感想です。)
#include <zephyr.h> #include <device.h> #include <devicetree.h> #include <drivers/gpio.h> #include <sys/printk.h> #include <string.h> #define GPIO_DRV_NAME DT_LABEL(DT_NODELABEL(gpiob)) #define GPIO_PB3_D3 (3) #define FLAGSB DT_GPIO_FLAGS(DT_NODELABEL(gpiob), gpiob) //#define LED0_NODE DT_ALIAS(led0) #define LED0_NODE DT_NODELABEL(green_led_2) #if DT_NODE_HAS_STATUS(LED0_NODE, okay) #define LED0 DT_GPIO_LABEL(LED0_NODE, gpios) #define PIN DT_GPIO_PIN(LED0_NODE, gpios) #if DT_PHA_HAS_CELL(LED0_NODE, gpios, flags) #define FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios) #endif #endif #define SLEEP_TIME_MS (5000) #define THREAD_SLEEP_TIME (500) #define STACKSIZE (1024) #define PRIORITY (7) K_THREAD_STACK_DEFINE(threadA_stack_area, STACKSIZE); static struct k_thread threadA_data; void threadA(void *dummy1, void *dummy2, void *dummy3) { struct device *devB; bool led_is_onB = true; int ret = 0; ARG_UNUSED(dummy1); ARG_UNUSED(dummy2); ARG_UNUSED(dummy3); devB = device_get_binding(GPIO_DRV_NAME); if (devB == NULL) { return; } ret = gpio_pin_configure(devB, GPIO_PB3_D3, GPIO_OUTPUT_ACTIVE | FLAGSB); if (ret < 0) { return; } while (1) { k_msleep(THREAD_SLEEP_TIME); gpio_pin_set(devB, GPIO_PB3_D3, (int)led_is_onB); led_is_onB = !led_is_onB; } } void main(void) { struct device *dev; bool led_is_on = true; int ret = 0; k_thread_create(&threadA_data, threadA_stack_area, K_THREAD_STACK_SIZEOF(threadA_stack_area), threadA, NULL, NULL, NULL, PRIORITY, 0, K_FOREVER); dev = device_get_binding(LED0); if (dev == NULL) { return; } ret = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS); if (ret < 0) { return; } k_thread_start(&threadA_data); while (1) { gpio_pin_set(dev, PIN, (int)led_is_on); led_is_on = !led_is_on; k_msleep(SLEEP_TIME_MS); } }
上記のコードの「キモ」は以下のAPIです。
k_thread_create
実際にはインライン展開される「関数」として定義されているようです。上記のコードをみると引数多数あり、何だか大変そうです。thread生成上、この引数達が何だか知らない、というわけにもいかないので順番に調べてみました。
-
- &threadA_data 未初期化の k_thread型構造体へのポインタです。threadA_data自体は threadA の直上で大域変数として宣言してあります。各スレッドに必須な固有の記憶領域みたいです。
- threadA_stack_area K_THREAD_STACK_DEFINEマクロで確保される k_thread_stack_t型へのポインタです。スタックというからにはスタックなのでしょう。
- K_THREAD_STACK_SIZEOF(threadA_stack_area) 上記のスタック領域のサイズを与えるのだと思いますが、これまたマクロに包まれてます。
- threadA スレッドとして実行開始されるエントリポイントへの関数ポインタです。
- NULL エントリポイント関数への第一引数、今回はヌル
- NULL エントリポイント関数への第二引数、今回はヌル
- NULL エントリポイント関数への第三引数、今回はヌル
- PRIORITY スレッドの優先順位です。今回は7としています。後で述べます。
- 0スレッドのオプションということですが、今回は分けも分からず0です。
- K_FOREVER k_timeout_t型のスレッドの起動に関する遅延みたいです。後で述べます。
そのスレッドの素性により、スレッドの優先順位の数字は2つに分かれるみたいです。
-
- cooperativeなスレッドはマイナス
- preemptiveなスレッドは0またはプラス
数字が小さい方が優先順位が高いです。上限下限はコンフィギュレーションのオプションで指定なので必要に応じた段階をいかようにも使えるようです。
cooperativeというからにはお作法をまもって自ら制御権を手放す必要がある「ノーブル」なスレッドが上位にあり、制御権を強制的に召し上げられる場合があるpreemptiveなスレッドが下位にいると。今回設定は7なので、preemptiveな中でもヨワヨワ設定(でも他にはmainがいるだけだけです。)
遅延値は K_FOREVER としてしまいました。ここにもいろいろ設定があり、K_NO_WAITなどと設定すると、k_thread_create()した直後からスレッドが走るみたいです。K_FOREVER の場合は、createしても以下のAPIを実行しないと走りだしません。
k_thread_start()
今後スレッドを走らせたり、止めたり、合流させたりするような操作をしてみたいと思うのでまたそのうち。
動作実験の結果
写真でみたら前回と同じです。ただスレッド側の点滅周期は前回の10分の1に短縮したので、外付けLEDはチカチカとせわしなく点滅し、オンボードLEDはゆっくりした周期で点滅します。main()とthreadA()、勝手な周期でLチカしておりますな。マルチスレッドでダブルLチカ、というやつ。
次回もスレッドの操作のあれこれを練習?