リアルタイムOSのセマフォ(★)

[組み込み製品、リアルタイムOS、組み込みドライバーの開発依頼は、フィールドデザインまでお気軽にお問い合わせください。]

セマフォ(排他制御)について

セマフォ(排他制御)の必要性
最初になぜ排他制御用のセマフォが必要なのかを説明します。
あるハードウェアに複数のタスクからアクセスする場合に、何も考えないでコードを書くと下記のようになります。


int lock = 1;
taskA() {
    while(1) {

        while (lock == 0);
        lock = 0;
        //ハードウェアの処理
        lock = 1;
    }
}
taskB() {
    while(1) {

        while (lock == 0);
        lock = 0;
        //ハードウェアの処理
        lock = 1;
    }
}

しかし、このコードでは、taskAがwhile (lock == 0)を抜けた直後に、何かの割り込みが発生し、taskBに制御が移ってしまうと、taskBでもwhile (lock == 0)を抜けてしまい、両方のタスクで同じハードウェアを操作してしまうことになります。
また、while()でlockを待っている間は、自分より優先順位の低いタスクは動作できないという問題もあります。

一番簡単な解決策としては、lockの変数を操作する部分を割り込み禁止にしておけば、taskAからtaskBに移る要因となる割り込みが発生しないので、問題は解決します。しかし、あらゆるところに割り込み禁止のコードがあって、しかも割り込み禁止解除を忘れたりする危険性もあるので、コードが読みにくくなったり、バグが発生しやすくなったりもします。


セマフォ(排他制御)の例
セマフォを使うと下記のように簡単に書くことができます。複数のタスクで安全にハードウェアのアクセスができ、かつ、CPUも有効に利用できます。


SemaphoreHandle_t xSemaphore; //セマフォは生成後、xSemaphoreGive()を呼んで初期値を1にしておく
taskA() {
    while(1) {
        xSemaphoreTake( xSemaphore, portMAX_DELAY );
             //ハードウェアの処理
        xSemaphoreGive( xSemaphore );
    }
}
taskB() {
    while(1) {
        xSemaphoreTake( xSemaphore, portMAX_DELAY );
             //ハードウェアの処理
        xSemaphoreGive( xSemaphore );
    }
}

セマフォ(同期)について

セマフォ(同期)の必要性
最初になぜ同期用のセマフォが必要なのかを説明します。
あるハードウェアに紐づいた割り込みルーチンからデータを受け取るコードを、何も考えないで書くと下記のようになります。


int recv = 0;
int value = 0;
taskA() {
    while(1) {
        while (recv == 0);
        recv = 0;
        //valueの値を利用する。
    }
}
interrupt() {
    recv = 1;
    value = read(); //ハードウェアのデータを読み込む
}

しかし、このコードでは、while()で待っている時間は、自分より優先順位の低い他のタスクが動作できません。


セマフォ(同期)の例
セマフォを使うと下記のように簡単に書くことができます。CPUも有効に利用できます。


SemaphoreHandle_t xSemaphore;
taskA() {
    while(1) {
        xSemaphoreTake( xSemaphore, portMAX_DELAY );
        //valueの値を利用する。
    }
}
interrupt() {
    xSemaphoreGiveFromISR( xSemaphore );
    value = read(); //ハードウェアのデータを読み込む
}