非プリエンプティブOSの利点は何ですか?これらの特典の価格は?


14

むき出しの金属MCUの場合、バックグラウンドループとタイマー割り込みアーキテクチャを備えた自家製コードと比較して、非プリエンプティブOSの利点は何ですか?これらの利点の中で、プロジェクトがバックグラウンドループアーキテクチャで自家製コードを使用するのではなく、プリエンプティブOSを採用するのに十分魅力的ですか?

質問の説明:

これらすべてが私の質問に答えてくれたことに本当に感謝しています。答えはもうすぐそこにあると感じています。ここで質問にこの説明を追加します。これは、私自身の考慮事項を示しており、質問を絞り込んだり、より正確にしたりするのに役立ちます。

私がやろうとしているのは、一般的なプロジェクトに最適なRTOSを選択する方法を理解することです。
これを実現するには、すべてのアプリケーションに最適なRTOSが存在しないため、さまざまな種類のRTOSおよび対応する価格からの基本概念と最も魅力的な利点のより良い理解が役立ちます。
数年前にOSに関する本を読みましたが、もう持っていません。ここに質問を投稿する前にインターネットで検索したところ、この情報が最も役立つことがわかりました:http : //www.ustudy.in/node/5456
さまざまなRTOSのWebサイトでの紹介、プリエンプティブスケジューリングと非プリエンプティブスケジューリングを比較する記事など、他にも多くの役立つ情報があります。
しかし、プリエンプティブではないRTOSを選択する場合、およびタイマー割り込みとバックグラウンドループを使用して独自のコードを作成する方がよい場合について言及したトピックは見つかりませんでした。
確かな答えはあるが、満足していない。
特に業界の実践において、より経験のある人々からの答えや見解を知りたいです。

私のこれまでの理解は
、OSの使用の有無にかかわらず、特定の種類のスケジューリングコードは常に必要です。たとえ次のようなコードの形式であってもです。

    in the timer interrupt which occurs every 10ms  
    if(it's 10ms)  
    {  
      call function A / execute task A;  
    }  
    if(it's 50ms)  
    {  
      call function B / execute task B;  
    }  

利点1:
ノンプリエンプティブOSは、スケジューリングコードの方法/プログラミングスタイルを指定するため、エンジニアが以前に同じプロジェクトにいなくても同じビューを共有できます。次に、コンセプトタスクに関する同じビューを使用して、エンジニアはさまざまなタスクに取り組んでテストし、可能な限り独立してプロファイルを作成できます。
しかし、これから実際にどれだけ得ることができますか?エンジニアが同じプロジェクトで作業している場合、プリエンプティブOSを使用せずに同じビューをうまく共有できます。
あるエンジニアが別のプロジェクトまたは会社の出身である場合、以前にOSを知っていれば、その恩恵を受けることができます。しかし、もし彼がそうしなかったとしても、新しいOSや新しいコードを学ぶことは彼にとって大きな違いにはならないようです。

利点2:
OSコードが十分にテストされている場合、デバッグの時間を節約できます。これは本当に良い利点です。
しかし、アプリケーションに5つのタスクしかない場合、タイマー割り込みとバックグラウンドループを使用して独自のコードを記述するのは本当に面倒ではないと思います。

ここでの非プリエンプティブOSは、非プリエンプティブスケジューラを備えた商用/無料/レガシーOSを指します。
この質問を投稿したとき、私は主に次のような特定のOSについて考えています:
(1)KISS Kernel(A Small NonPreemptive RTOS-Claimed by its website)
http://www.frontiernet.net/~rhode/kisskern.html
(2)uSmartX (軽量RTOS-Webサイトが主張)
(3)FreeRTOS(プリエンプティブRTOSですが、理解しているように、非プリエンプティブRTOSとしても構成できます)
(4)uC / OS(FreeRTOSと同様)
(5 )一部の企業のレガシーOS /スケジューラコード(通常、社内で作成および管理されてい
ます)(新しいStackOverflowアカウントからの制限のため、リンクを追加できません)

私が理解しているように、非プリエンプティブOSは次のコードの集まりです。
(1)非プリエンプティブ戦略を使用するスケジューラ。
(2)タスク間通信、相互排他、同期、および時間制御のための機能。
(3)メモリ管理。
(4)その他の有用施設/ファイルシステムのようなライブラリ、ネットワークスタック、GUI等(FreeRTOSとのuC / OSは、これらを提供していますが、彼らはまだ動作している場合、スケジューラは、ノンプリエンプティブとして設定されているとき、私はよく分からない)
の一部それらは常に存在するとは限りません。しかし、スケジューラは必須です。


それは一言で言えばそれです。マルチスレッド化する必要があるワークロードがあり、オーバーヘッドに余裕がある場合は、スレッド化OSを使用します。それ以外の場合、ほとんどの場合、単純な時間またはタスクベースの「スケジューラ」で十分です。そして、プリエンプティブまたは協調マルチタスクが最適かどうかを判断するために...オーバーヘッドと、必要なマルチタスクをどれだけ制御したいかが考えられます。
akohlsmith

回答:


13

これはやや話題の匂いがしますが、軌道に乗って戻そうと思います。

プリエンプティブマルチタスキングとは、オペレーティングシステムまたはカーネルが現在実行中のスレッドを中断し、スケジューリングヒューリスティックに基づいて別のスレッドに切り替えることができることを意味します。ほとんどの場合、実行中のスレッドにはシステム上で他のことが行われているという概念はありません。これがコードにとって意味することは、カーネルがスレッドの途中でスレッドを中断することを決定した場合マルチステップ操作(PWM出力の変更、新しいADCチャネルの選択、I2Cペリフェラルからのステータスの読み取りなど)と、これら2つのスレッドが互いに干渉しないように、しばらく別のスレッドを実行します。

任意の例:マルチスレッドの組み込みシステムを初めて使用し、I2C ADC、SPI LCD、およびI2C EEPROMを備えた小さなシステムがあるとします。ADCから読み取り、EEPROMにサンプルを書き込むスレッドと、最後の10個のサンプルを読み取り、それらを平均してSPI LCDに表示するスレッドの2つを用意することをお勧めします。経験の浅いデザインは、次のようになります(非常に単純化されています)。

char i2c_read(int i2c_address, char databyte)
{
    turn_on_i2c_peripheral();
    wait_for_clock_to_stabilize();

    i2c_generate_start();
    i2c_set_data(i2c_address | I2C_READ);
    i2c_go();
    wait_for_ack();
    i2c_set_data(databyte);
    i2c_go();
    wait_for_ack();
    i2c_generate_start();
    i2c_get_byte();
    i2c_generate_nak();
    i2c_stop();
    turn_off_i2c_peripheral();
}

char i2c_write(int i2c_address, char databyte)
{
    turn_on_i2c_peripheral();
    wait_for_clock_to_stabilize();

    i2c_generate_start();
    i2c_set_data(i2c_address | I2C_WRITE);
    i2c_go();
    wait_for_ack();
    i2c_set_data(databyte);
    i2c_go();
    wait_for_ack();
    i2c_generate_start();
    i2c_get_byte();
    i2c_generate_nak();
    i2c_stop();
    turn_off_i2c_peripheral();
}

adc_thread()
{
    int value, sample_number;

    sample_number = 0;

    while (1) {
        value = i2c_read(ADC_ADDR);
        i2c_write(EE_ADDR, EE_ADDR_REG, sample_number);
        i2c_write(EE_ADDR, EE_DATA_REG, value);

        if (sample_number < 10) {
            ++sample_number;
        } else {
            sample_number = 0;
        }
    };
}

lcd_thread()
{
    int i, avg, sample, hundreds, tens, ones;

    while (1) {
        avg = 0;
        for (i=0; i<10; i++) {
            i2c_write(EE_ADDR, EE_ADDR_REG, i);
            sample = i2c_read(EE_ADDR, EE_DATA_REG);
            avg += sample;
        }

        /* calculate average */
        avg /= 10;

        /* convert to numeric digits for display */
        hundreds = avg / 100;
        tens = (avg % 100) / 10;
        ones = (avg % 10);

        spi_write(CS_LCD, LCD_CLEAR);
        spi_write(CS_LCD, '0' + hundreds);
        spi_write(CS_LCD, '0' + tens);
        spi_write(CS_LCD, '0' + ones);
    }
}

これは非常に粗雑で高速な例です。このようにコーディングしないでください!

ここで、プリエンプティブマルチタスクOSは、これらのスレッドのいずれかをコードの任意の行(実際には任意のアセンブリ命令)で一時停止し、他のスレッドに実行時間を与えることができます。

それについて考えてください。OS adc_thread()がEEアドレスの設定と実際のデータの書き込みの間に中断することを決定した場合に何が起こるか想像してみてください。lcd_thread()I2C周辺機器をいじって実行し、必要なデータを読み取り、adc_thread()再び実行するようになったとき、EEPROMは以前と同じ状態になりません。物事はまったくうまくいきません。さらに悪いことに、それはほとんどの場合でも動作するかもしれませんが、常に動作するわけではありません。

これは最良の例です。OSはのコンテキストを横取りしてi2c_write()からadc_thread()実行を再開することを決定するかもしれませんlcd_thread()!物事は非常に速く非常に乱雑になります。

プリエンプティブなマルチタスク環境で動作するコードを作成するときは、ロックメカニズムを使用する必要がありますで動作するコードを作成する場合、を使用して、コードが不適切なタイミングで中断された場合にすべての地獄が崩れないようにする必要があります。

一方、協調マルチタスクは、各スレッドが実行時間を放棄するタイミングを制御することを意味します。コーディングは簡単ですが、すべてのスレッドが実行に十分な時間を確保できるように、コードを慎重に設計する必要があります。別の不自然な例:

char getch()
{
    while (! (*uart_status & DATA_AVAILABLE)) {
        /* do nothing */
    }

    return *uart_data_reg;
}

void putch(char data)
{
    while (! (*uart_status & SHIFT_REG_EMPTY)) {
        /* do nothing */
    }

    *uart_data_reg = data;
}

void echo_thread()
{
    char data;

    while (1) {
        data = getch();
        putch(data);
        yield_cpu();
    }
}

void seconds_counter()
{
    int count = 0;

    while (1) {
        ++count;
        sleep_ms(1000);
        yield_cpu();
    }
}

そのコードは思ったとおりに機能しません。また、機能しているように見えても、エコースレッドのデータレートが増加すると機能しません。繰り返しますが、少し見てみましょう。

echo_thread()バイトがUARTに現れるのを待ってからそれを取得し、書き込む余地ができるまで待ってから書き込みます。その後、他のスレッドに実行の順番を与えます。seconds_counter()カウントをインクリメントし、1000ms待機してから、他のスレッドに実行の機会を与えます。その間に2バイトがUARTに到着した場合sleep()、CPUが他のことをしている間、仮想のUARTに文字を格納するFIFOがないため、それらを見逃す可能性があります。

この非常に貧弱な例を実装する正しい方法yield_cpu()は、ビジーループがある場所に置くことです。これは物事を進めるのに役立ちますが、他の問題を引き起こす可能性があります。たとえば、タイミングが重要であり、予想よりも時間がかかる別のスレッドにCPUを譲る場合、タイミングをスローすることができます。プリエンプティブマルチタスクOSでは、すべてのスレッドが正しくスケジュールされるようにスレッドを強制的に中断するため、この問題は発生しません。

さて、これはタイマーとバックグラウンドループと何の関係があるのでしょうか?タイマーとバックグラウンドループは、上記の協調マルチタスクの例に非常に似ています。

void timer_isr(void)
{
    ++ticks;
    if ((ticks % 10)) == 0) {
        ten_ms_flag = TRUE;
    }

    if ((ticks % 100) == 0) {
        onehundred_ms_flag = TRUE;
    }

    if ((ticks % 1000) == 0) {
        one_second_flag = TRUE;
    }
}

void main(void)
{
    /* initialization of timer ISR, etc. */

    while (1) {
        if (ten_ms_flag) {
            if (kbhit()) {
                putch(getch());
            }
            ten_ms_flag = FALSE;
        }

        if (onehundred_ms_flag) {
                    get_adc_data();
            onehundred_ms_flag = FALSE;
        }

        if (one_second_flag) {
            ++count;
                    update_lcd();
            one_second_flag = FALSE;
        }
    };
}

これは、協調スレッドの例にかなり近いように見えます。イベントを設定するタイマーと、イベントを検索してアトミックに動作するメインループがあります。ADCとLCDの「スレッド」が互いに干渉することを心配する必要はありません。一方が他方を中断することはないからです。それでも、「スレッド」に時間がかかりすぎることを心配する必要があります。たとえば、get_adc_data()30msかかるとどうなりますか?キャラクターを確認してエコーする3つの機会を逃すことになります。

ループ+タイマーの実装は、手元のタスクに固有のコードを設計できるため、協調マルチタスクマイクロカーネルよりも実装がはるかに簡単な場合がよくあります。各サブシステムに非常に具体的かつ予測可能な方法でタスクを実行する時間を与える固定システムを設計するほど、実際にはマルチタスクではありません。協調的マルチタスクシステムでさえ、各スレッドに汎用タスク構造を持たなければならず、実行する次のスレッドは非常に複雑になる可能性があるスケジューリング機能によって決定されます。

3つのシステムすべてのロックメカニズムは同じですが、それぞれに必要なオーバーヘッドはまったく異なります。

個人的に、私はほとんど常にこの最後の標準であるループ+タイマー実装にコーディングしています。スレッドは非常に控えめに使用する必要があるものだと思います。記述とデバッグがより複雑になるだけでなく、オーバーヘッドも大きくなります(プリエンプティブなマルチタスクマイクロカーネルは、愚かな単純なタイマーとメインループイベントフォロワーよりも常に大きくなります)。

スレッドで作業している人なら誰でも感謝するということわざもあります。

if you have a problem and use threads to solve it, yoeu ndup man with y pemro.bls

:-)


詳細な例であるakohlsmithに返信いただき、ありがとうございます。ただし、協力的なマルチタスクではなく、単純なタイマーとバックグラウンドループアーキテクチャを選択する理由をあなたの回答から結論付けることはできません。誤解しないでください。さまざまなスケジューリングに関する多くの有用な情報を提供してくれたあなたの返信に本当に感謝しています。意味がありません。
ハイラン

これについてもう少し作業してください。
ハイラン

ありがとう、akohlsmith。最後に書いた文章が好きです。それを認識するのにしばらく時間がかかりました:)あなたの答えのポイントに戻って、あなたはほとんど常にループ+タイマーの実装にコーディングします。それでは、この実装をあきらめて、ノンプリエンプティブOSに切り替えた場合、どうしてそうしましたか?
ハイラン

他の誰かのOSを実行していたときに、協調型マルチタスクシステムとプリエンプティブマルチタスクシステムの両方を使用しました。Linux、ThreadX、ucOS-ii、またはQNXのいずれか。これらの状況のいくつかでさえ、私はシンプルで効果的なタイマー+イベントループを使用しました(poll()すぐに思い浮かびます)。
akohlsmith

私は組み込みのスレッド処理やマルチタスク処理のファンではありませんが、複雑なシステムでは唯一の正気なオプションであることを知っています。缶詰のマイクロオペレーティングシステムを使用すると、すぐに起動して実行でき、多くの場合、デバイスドライバーも提供されます。
akohlsmith

6

マルチタスクは、多くのマイクロコントローラプロジェクトで便利な抽象化になりますが、ほとんどの場合、真のプリエンプティブスケジューラは重すぎて不要です。私は100以上のマイクロコントローラープロジェクトを実行しました。私は何度も協調タスクを使用しましたが、これに関連する手荷物での先制タスクの切り替えはこれまで適切ではありませんでした。

協調的タスク処理に優先されるプリエンプティブタスク処理の問題は次のとおりです。

  1. もっとヘビー級。プリエンプティブタスクスケジューラはより複雑で、コードスペースが多く、サイクルが長くなります。また、少なくとも1つの割り込みが必要です。多くの場合、これはアプリケーションの許容できない負担です。

  2. 同時にアクセスされる可能性のある構造の周りにはミューテックスが必要です。協調システムでは、アトミック操作であるべきところの途中でTASK_YIELDを呼び出さないでください。これは、キュー、共有グローバルステート、および多くの場所への展開に影響します。

一般的に、特定のジョブにタスクを割り当てることは、CPUがこれをサポートでき、ジョブがいくつかの個別のイベントに分割するのが面倒な履歴依存操作が十分にあるほど複雑な場合に意味があります。これは通常、通信入力ストリームを処理する場合に当てはまります。そのようなことは、通常、以前の入力に応じて、状態が大きく駆動されます。たとえば、オペコードバイトの後に、各オペコードに固有のデータバイトが続く場合があります。それから、他の何かがそれらを送信したい気がするとき、これらのバイトの問題があります。入力ストリームを処理する別のタスクを使用すると、外に出て次のバイトを取得しているようにタスクコードに表示させることができます。

全体的に、タスクは状態コンテキストが多い場合に役立ちます。タスクは基本的に、PCが状態変数である状態マシンです。

マイクロがしなければならない多くのことは、一連のイベントへの応答として表現できます。その結果、通常、メインイベントループが発生します。これは、考えられる各イベントを順番にチェックし、先頭に戻ってすべてを繰り返します。イベントの処理に数サイクル以上かかる場合、通常、イベントの処理後にイベントループの開始点に戻ります。これは、実際には、リスト内のチェックされた場所に基づいて、イベントに暗黙の優先順位があることを意味します。多くの単純なシステムでは、これで十分です。

時々、もう少し複雑なタスクを取得します。これらは多くの場合、一連の少数の個別の処理に分割できます。これらの場合、内部フラグをイベントとして使用できます。私はこのようなことをローエンドのPICで何度も行ってきました。

上記のような基本的なイベント構造を持っているが、たとえばUARTを介してコマンドストリームに応答する必要がある場合は、受信したUARTストリームを別のタスクで処理すると便利です。一部のマイクロコントローラーは、独自の呼び出しスタックを読み書きできないPIC 16のように、マルチタスク用のハードウェアリソースが限られています。このような場合、UARTコマンドプロセッサの擬似タスクと呼ばれるものを使用します。メインイベントループは他のすべてを処理しますが、処理するイベントの1つは、UARTが新しいバイトを受信したことです。その場合、この疑似タスクを実行するルーチンにジャンプします。UARTコマンドモジュールにはタスクコードが含まれており、タスクの実行アドレスといくつかのレジスタ値はそのモジュールのRAMに保存されます。イベントループによってジャンプされたコードは、現在のレジスタを保存し、保存されたタスクレジスタをロードし、タスクの再開アドレスにジャンプします。タスクコードは、逆を行うYIELDマクロを呼び出し、最終的にメインイベントループの開始に戻ります。場合によっては、メインイベントループがパスごとに1回(通常は優先度の低いイベントにするために下部で)疑似タスクを実行します。

PIC 18以降では、コールスタックがファームウェアで読み書きできるため、真の協調タスクシステムを使用します。これらのシステムでは、再起動アドレス、他のいくつかの状態、およびデータスタックポインターが各タスクのメモリバッファーに保持されます。他のすべてのタスクを1回実行するには、タスクがTASK_YIELDを呼び出します。これにより、現在のタスクの状態が保存され、次の利用可能なタスクのリストを調べて、その状態が読み込まれ、実行されます。

このアーキテクチャでは、メインイベントループは別のタスクであり、ループの先頭でTASK_YIELDを呼び出します。

PIC用のすべてのマルチタスクコードは無料で利用できます。確認するには、http://www.embedinc.com/pic/dload.htmでPIC開発ツールリリースをインストールしてください。8ビットPICの場合はSOURCE> PICディレクトリ、16ビットPICの場合はSOURCE> DSPICディレクトリで、名前に「task」が含まれるファイルを探します。


まれですが、mutexが協調的マルチタスクシステムで必要になる場合があります。典型的な例は、クリティカルセクションへのアクセスを必要とするISRです。これは、設計を改善するか、重要なデータに適切なデータコンテナを選択することで、ほぼ常に回避できます。
akohlsmith

@akoh:はい、SPIバスへのアクセスなど、共有リソースを処理するために数回mutexを使用しました。私の要点は、mutexはプリエンプティブシステムにある限り本質的に必要ではないということでした。私は、それらが決して必要ではない、または協調システムで決して使われないと言うつもりはなかった。また、協調システムのミューテックスは、単一ビットをチェックするTASK_YIELDループでスピンするのと同じくらい簡単です。プリエンプティブシステムでは、通常、カーネルに組み込む必要があります。
オリンラスロップ

@OlinLathrop:ミューテックスに関しては、非プリエンプティブシステムの最も重要な利点は、割り込みと直接やり取りする場合(本来はプリエンプティブ)または保護されたリソースを保持する必要がある場合にのみ必要であると思います「yield」呼び出しの間に費やしたい時間を超えているか、「yight」可能性のある呼び出しの周りに保護されたリソースを保持したい(「データをファイルに書き込む」など)。「データの書き込み」コール内でyieldが発生することが問題になる場合がありましたが、次を含めました。
supercat

...すぐに書き込むことができるデータの量を確認する方法、およびある程度の量が利用できることを確認する方法(おそらく、可能性がある)(ダーティフラッシュブロックのレクラメーションを促進し、適切な数が回収されるまで待機する) 。
supercat

こんにちは、オリン、あなたの返事がとても好きです。その情報は私の質問をはるかに超えています。多くの実際的な経験が含まれています。
ハイラン

1

編集:(以前の投稿を下に残します。おそらく誰かの助けになるでしょう。)

あらゆる種類のマルチタスクOSおよび割り込みサービスルーチンは、競合するシステムアーキテクチャではありません-であるべきではありません。これらは、システムのさまざまなレベルのさまざまなジョブを対象としています。割り込みは、デバイスの再起動、割り込みのないデバイスのポーリング、ソフトウェアの計時など、雑用を処理するための簡単なコードシーケンスを目的としています。通常、バックグラウンドは、差し迫ったニーズが満たされました。タイマーを再起動してLEDを切り替えるか、別のデバイスにパルスを送るだけで済む場合、通常、ISRはすべてを安全にフォアグラウンドで実行できます。それ以外の場合は、バックグラウンドに(フラグを設定するか、メッセージをキューイングすることにより)何かを行う必要があることを通知し、プロセッサを解放する必要があります。

バックグラウンドループが単なるアイドルループである非常に単純なプログラム構造を見ましたfor(;;){ ; }。すべての作業はタイマーISRで行われました。これは、プログラムがタイマー期間未満で終了することが保証されている一定の操作を繰り返す必要があるときに機能します。特定の限られた種類の信号処理が思い浮かびます。

個人的には、取り出しをクリーンアップするISRを作成し、タイマー期間のほんの一部で実行できる乗算と加算のような単純な場合でも、バックグラウンドが実行する必要がある他のすべてを引き継ぐようにします。どうして?いつか、プログラムに別の「単純な」機能を追加するという素晴らしいアイデアが得られ、「ちょっと、それをするのに短いISRが必要です」と突然、以前の単純なアーキテクチャは、私が計画していなかった相互作用を成長させます一貫して発生します。これらはデバッグするのはあまり楽しくありません。


(2種類のマルチタスクの以前に投稿された比較)

タスクの切り替え:プリエンプティブMTは、スレッドがCPU不足にならないようにすること、優先度の高いスレッドが準備が整うとすぐに実行されることなど、タスクの切り替えを処理します。協調MTでは、プログラマーが一度にプロセッサーを長時間保持しないようにする必要があります。また、長すぎる長さも決定する必要があります。つまり、コードを変更するたびに、コードセグメントがその時間量子を超えているかどうかを認識する必要があるということです。

非アトミック操作の保護:PMTでは、分割してはならない操作の途中でスレッドスワップが発生しないようにする必要があります。たとえば、特定の順序で、または最大時間内に処理する必要がある特定のデバイスとレジスタのペアの読み取り/書き込み。CMTを使用すると、非常に簡単です。このような操作の途中でプロセッサを譲らないでください。

デバッグ:スレッドの切り替えがいつ/どこで発生するかを計画するため、一般にCMTの方が簡単です。スレッド間の競合状態と、PMTを使用したスレッドセーフでない操作に関連するバグは、スレッドの変更が確率的であり、再現できないため、デバッグが特に困難です。

コードを理解する:PMT用に記述されたスレッドは、まるで独立しているように書かれています。CMT用に作成されたスレッドはセグメントとして作成され、選択したプログラム構造によっては、読者が追跡するのが難しくなる場合があります。

スレッドセーフではないライブラリコードの使用:PMTスレッドセーフで呼び出す各ライブラリ関数を確認する必要があります。printf()およびscanf()およびそれらのバリアントは、ほとんど常にスレッドセーフではありません。CMTを使用すると、プロセッサを具体的に譲る場合を除き、スレッドの変更は発生しないことがわかります。

機械装置を制御したり、外部イベントを追跡したりするための有限状態機械駆動システムは、多くの場合、実行することはあまりないため、CMTの適切な候補です。モーターの起動または停止、フラグの設定、次の状態の選択など。したがって、状態変更関数は本質的に簡潔です。

ハイブリッドアプローチは、これらの種類のシステムで非常にうまく機能します。CMTは、1つのスレッドとして実行されるステートマシン(したがって、ほとんどのハードウェア)を管理し、1つまたは2つのスレッドは、ステートによって開始されるより長い実行中の計算を実行します変化する。


お返事ありがとう、JRobert。しかし、それは私の質問に合わせたものではありません。プリエンプティブOSと非プリエンプティブOSを比較しますが、非プリエンプティブOSと非OSは比較しません。
ハイラン

そう-ごめん。私の編集はあなたの質問によりよく対処する必要があります。
Jロバート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.