前回の記事では、EventQueueについてThe EventQueue API Tutorialで割り込みcontextでprintf
を使った場合の問題まで読んでみました。この問題はイベントループで解決できそうですが、プログラムには他にも問題がありそうです。
題材について
この記事で題材として取り上げたのは、The EventQueue APIというTutorialです。
処理遅延が気になる場合
原文には、この見出しはありません。私が付けたものです。
前回の記事で安全に改造したプログラムを示します。
#include "mbed.h"
DigitalOut led1(LED1);
InterruptIn sw(USER_BUTTON, PullUp);
EventQueue queue(32 * EVENTS_EVENT_SIZE);
Thread thread;
void rise_handler(void) {
printf("rise_handler in context %p\r\n", ThisThread::get_id());
// Toggle LED
led1 = !led1;
}
void fall_handler(void) {
printf("fall_handler in context %p\r\n", ThisThread::get_id());
// Toggle LED
led1 = !led1;
}
int main() {
// Start the event queue
thread.start(callback(&queue, &EventQueue::dispatch_forever));
printf("Starting in context %p\r\n", ThisThread::get_id());
// The 'rise' handler will execute in the context of thread 't'
sw.rise(queue.event(rise_handler));
// The 'fall' handler will execute in the context of thread 't'
sw.fall(queue.event(fall_handler));
}
The code is safe now, but we may have introduced another problem: latency.
これでコードは安全になりましたが、ここでもう一つの問題を提起します。遅延です。
After the change above, the call to
rise_handler
will be queued, which means that it no longer runs immediately after the interrupt is raised.
上記のようにコードを変更すると、
rise_handler
の呼び出しはキューに入ります。これは、割り込みが発生した直後には絶対にrise_handler
が実行されないことを意味します。
For this example code, this isn't a problem, but some applications might need the code to respond as fast as possible to an interrupt.
このプログラム例では、遅延は問題となりませんが、割り込みの発生から可能な限り早く応答するコードが、ある種のアプリケーションでは必要とされます。
イベントキューに入ったイベントは、キューに入った時点では実行されません。いつ実行されるかは、オペレーティングシステムによります。割り込み発生後、即座に反応しなくてはならない場合には、キューに入ってから実際に実行されるまでの遅延が問題になってくるというわけです。
Let's assume that
rise_handler
must toggle the LED as quickly as possible in response to the user's action on SW2.
SW2へのユーザの操作に応答して
rise_handler
が可能な限り早くLEDをトグルする場合を考えてみます。
To do that, it must run in interrupt context.
このような場合、
rise_handler
は割り込みcontextで実行しなくてはなりません。
However,
rise_handler
still needs to print a message indicating that the handler was called; that's problematic because it's not safe to callprintf
from an interrupt context.
しかしながら、ハンドラが呼び出されたことを示すために
rise_handler
がメッセージを表示するのも、いまだ必要です。割り込みcontextからprintf
を呼び出すのは安全ではないので問題になります。
メッセージの表示は、人間向けの処理だからキューに入れてしまって構わないのだけれど、LEDの応答は、割り込みのあと速やかに実行したいという状況です。
The solution is to split
rise_handler
into two parts: the time critical part will run in interrupt context, while the non-critical part (displaying the message) will run in user context.
解決法は、
rise_handler
を二つの部分に分けることです。時間に厳しい処理は割り込みcontextで実行し、時間に厳しくないメッセージ表示のような処理はユーザcontextで実行します。
This is easily doable using
queue.call
:
この記述は、
queue.call
を使って簡単に実現できます。
void rise_handler_user_context(void) {
printf("rise_handler_user_context in context %p\r\n", ThisThread::get_id());
}
void rise_handler(void) {
// Execute the time critical part first
led1 = !led1;
// The rest can execute later in user context (and can contain code that's not interrupt safe).
// We use the 'queue.call' function to add an event (the call to 'rise_handler_user_context') to the queue.
queue.call(rise_handler_user_context);
}
急ぎの仕事と急ぎでない仕事を分離してしまえばよいという解決法です。LEDをトグルするという急ぎの仕事は、割り込みcontextでそのまま処理しますが、メッセージを表示するという急ぎでない仕事rise_handler_user_context()
は、イベントキューに入れてあとから処理してしまいます。
この時、main()
の処理を元に戻すのをお忘れなく。
// The 'rise' handler will execute in IRQ context
sw.rise(rise_handler);
// The 'fall' handler will execute in the context of thread 't'
sw.fall(queue.event(fall_handler));
After replacing the code for
rise_handler
as above, the output of our example becomes:
rise_handler
のコードを上のように書き換えると、プログラム例の出力は以下のようになります。
Starting in context 0x20002c50
fall_handler in context 0x20002c90
rise_handler_user_context in context 0x20002c90
fall_handler in context 0x20002c90
rise_handler_user_context in context 0x20002c90
The scenario above (splitting an interrupt handler's code into time critical code and non-time critical code) is another common pattern that you can easily implement with event queues; queuing code that's not interrupt safe is not the only thing you can use event queues for.
割り込みハンドラのコードを時間に厳しいコードと時間に厳しくないコードに分離するという上記の考え方は、イベントキューで簡単に実装することができるもう一つの一般的な手順です。割り込みに対して安全でないコードをキューに入れることは、イベントキューを使用する唯一の動機というわけではありません。
Any kind of code can be queued and deferred for later execution.
あらゆる種類のコードをキューに入れることができ、先送りされて後で実行されます。
We used
InterruptIn
for the example above, but the same kind of code can be used with anyattach()
-like functions in the SDK.
このプログラム例では、
InterruptIn
を使ってきましたが、SDKにあるattach()
系の関数で同じ種類のコードが使用できます。
Examples include
Serial::attach()
,Ticker::attach()
,Ticker::attach_us()
,Timeout::attach()
.
たとえば、
Serial::attach()
、Ticker::attach()
、Ticker::attach_us()
、Timeout::attach()
が含まれます。
ここでattach()
系(attach()
-like)と呼ばれているのは、割り込みが発生したときに呼び出されるcallback関数を登録する関数の事です。InterruptIn
オブジェクトではrise()
とfall()
で割り込み発生時に実行するcallbackを登録していましたが、他のオブジェクトではattach()
メソッドでcallbackを登録しています。
次回は、優先順位について考えます。
関連サイト
Mbed OSのページ
EventQueueのTutorial
Mbed対応Cypress製品のページ
関連記事
EventQueueを使ってみたよ (1)
EventQueueを使ってみたよ (2)
EventQueueを使ってみたよ (3)
EventQueueを使ってみたよ (4)
EventQueueを使ってみたよ (5)
EventQueueを使ってみたよ (6)
EventQueueを使ってみたよ (7)
EventQueueを使ってみたよ (8)
EventQueueを使ってみたよ (9)