今回からバイナリセマフォとミューテックスの話題です。
投稿時の開発環境を記しておきます。
PC:Windows10 OS
IDE: STM32CubeIDE Version1.6.0
Configurator: STM32CubeMX Version6.2.1
Board: STM32Nucleo-F401RE
バイナリセマフォとミューテックス
どちらも共有資源を使用する際にスレッド間で排他制御を行うためのものです。
ミューテックスの方が、やや扱いが難しい分、複雑なことができるように思います。
今回は3つのスレッドをつくり、共通資源へのアクセスに対してバイナリセマフォを使った場合の挙動について確認してみます。
プロジェクトを作成する
IDEを起動し、File- New – STM32 Project を選択し、Target Selection ウィンドウが出たら Board Selector タブを選択し Boards List から NUCLEO-F401RE を選択し Next ボタンを押します。
Project 名に F401RtosV2ThreeThreadSemaphore と入力し、Finishボタンを押します。
Initialize all peripherals with their default Mode ? と聞いてくるので Yesを押します。
RTOSを使えるように設定する
Pinout & Configuration – Categories – Middleware – FREERTOS を選択し、
Mode の Interface で CMSIS_V2 を選択します。
その下の Configuration – Tasks and Queues タブを選択します。
Task Name の下の defaultTask をダブルクリックして選択すると Edit Task のウィンドウが出ます。
Task Name を TaskHigh
Entry Function を StartTaskHigh
Priority を osPriorityHigh
に変更して OKボタンを押します。
次に Tasksの下の Addボタンを押します。
New Task のウィンドウが出てくるので、以下のように変更し OK ボタンを押します。
Task Name を TaskMiddle
Priority を osPriorityNormal
Entry Function を StartTaskMiddle
もう一度Tasksの下のAddボタンを押して もうひとつスレッドをつくります。
New Task のウィンドウが出てくるので、以下のように変更し OK ボタンを押します。
Task Name を TaskLow
Priority を osPriorityLow
Entry Function を StartTaskLow
これで優先順位の異なる3つのスレッドが作成されます。
タイムベース ソースを変更する
Categories – System Core – SYS を選択し、SYS Mode and Configuration – Mode で
Timebase Source を Systick から TIM10 に変更しておきます。
FreeRTOSでは Systick 以外を使うことが推奨されているからです。
ビルドしてエラーがないことを確認しておきます。
つくられたコードの確認
main.cのwhile()ループの少し前に、スレッドを生成するコードがあることを確認しておきます。
TaskHighHandle = osThreadNew(StartTaskHigh, NULL, &TaskHigh_attributes);
TaskMiddleHandle = osThreadNew(StartTaskMiddle, NULL, &TaskMiddle_attributes);
TaskLowHandle = osThreadNew(StartTaskLow, NULL, &TaskLow_attributes);
スレッド本体をコーディングする
以下のようにコーディングします。
void StartTaskHigh(void *argument)
{
for(;;)
{
HAL_UART_Transmit(&huart2, "H enters critical section.\r", 27, 50);
osSemaphoreAcquire(myBinarySem01Handle, osWaitForever);
doWorkH();
HAL_UART_Transmit(&huart2, "H leaves critical section.\r", 27, 50);
osSemaphoreRelease(myBinarySem01Handle);
HAL_UART_Transmit(&huart2, "H delay 1000ms.\r", 16, 50);
osDelay(1000);
}
}
void StartTaskMiddle(void *argument)
{
for(;;)
{
doWorkM();
osDelay(500);
}
}
void StartTaskLow(void *argument)
{
for(;;)
{
HAL_UART_Transmit(&huart2, "L enters critical section.\r", 27, 50);
osSemaphoreAcquire(myBinarySem01Handle, osWaitForever);
doWorkL();
HAL_UART_Transmit(&huart2, "L leaves critical section.\r", 27, 50);
osSemaphoreRelease(myBinarySem01Handle);
}
}
doWork()関数のコーディンング
以下のように追加、コーディングします。
/* USER CODE BEGIN 4 */
void doWorkH();
void doWorkM();
void doWorkL();
void doWorkH()
{
HAL_UART_Transmit(&huart2, "H is working.\r", 14, 10);
HAL_Delay(300);
}
void doWorkM()
{
HAL_UART_Transmit(&huart2, "M is working.\r", 14, 10);
HAL_Delay(500);
}
void doWorkL()
{
HAL_UART_Transmit(&huart2, "L is working.\r", 14, 10);
HAL_Delay(400);
}
/* USER CODE END 4 */
コーディング後、ビルドしてエラーがないことを確認しておきます。
通信環境の設定を行う
どのような挙動になるのか確認するためにマイコンからUARTを使ってPCに表示を行うようにしました。
HAL_UART_Transmit()で文字列をPCに送ります。
(注)HAL_UART_Transmit()も共有資源のひとつです。待ち時間の関数が適当に入っているためなのか、競合は見受けられません。
いずれにしても、この関数の前後にセマフォを入れてしまうと話がややこしくなるので、ここでは深く考えないようにしておきます。
挙動がわかるように表示処理が入っている 程度に軽く考えてください。
VCPを使うので、マイコンボードとPCをUSBケーブルでつなぐだけでシリアル通信を行うことができます。
パソコン側もデータを送受信できるアプリを準備しなければなりません。
ここでは Tera Term を使います。
マイコンボードとパソコンをUSBケーブルで接続しておきます。
Tera Termを起動してシリアル(E)を選択し、ポート(R):で STLink Virtual Com Port が含まれているCOMポートを選択しOKボタンを押します。
こちらの環境では COM4 が Virtual Com Port に割り当てられていました。
STのシステムが仮想的にCOMポートの環境をつくってくれているので使わせて頂きます。
次に メニューの 設定 – シリアルポート から通信パラメータを以下のように設定しOKボタンを押します。
これらは MX_USART2_UART_Init() の中で設定された値に合わせてください。
もうひとつ 設定 – 端末 画面でローカルエコーにチェックを入れておきます。
これでキーを押すと、その文字を送り画面に表示します。
それでは Run – Debug から Run – Resume でプログラムを実行して挙動を確認してみましょう。
プログラムの内容は非常に簡単なので省略させていただきます。
コードを見て頂ければ理解できる内容です。
Tera Termで挙動を確認する
以下の Tera Term の通信ログをご覧ください。
H , M , L は優先順位の違いを示しています(H の優先順位が一番高い)
enters critical section : クリティカルセクションの入口です。
ここではセマフォを獲得する部分です。
leaves critical section : クリティカルセクションの出口です。
ここではセマフォを開放する部分です。
クリティカルセクションとは危険領域の意味で、共有資源へアクセスする部分と考えておけば良いと思います。
優先順位の高い H が仕事に入ろうとする部分 ( H enters critial section. の後 ) について見ていきます。
3つのパターンが画面にキャプチャできたので順に説明していきます。
(1)起動時なので、H がセマフォを獲得できました。
従って、待たされることなくHが仕事をすることができました。 ( H is working )
(2)H がセマフォ獲得の処理に入りましたが、Lがセマフォを使っているために待ちが発生しています。
待っている間に M が起床したので( osDelay(500) から抜けたので )、Mが仕事に入りました。
セマフォを使うと、このように優先順位が逆転することがあります。
このような現象を「上限のない優先度逆転」と言います。
(3)H がセマフォ獲得の処理に入った時に、Lはセマフォを使っていましたが、開放されたのでHが仕事に入りました。
私は少し混乱していますが皆さんは理解できましたか?
実際に動かしてみると、より理解が深まりますのでぜひ試してみてください。
次回はミューテックスを使った場合の挙動について確認します。
お疲れさまでした。