2005-09-17
C++に標準的に備わっているnew演算子は、メモリの割り付けに失敗するとstd::set_new_handlerで設定したハンドラ関数が呼び出され、それでもまだ失敗する場合にはstd:bad_alloc例外が送出されます。
マルチタスク環境では、あるタスクでメモリの割り付けが失敗したとしても、その後、他のタスクでメモリの解放が行われれば、再びメモリの割り付けができるようになる可能性があります。そこで、今回はnew演算子にタイムアウト機能を付ける方法についてお話します。
new演算子にタイムアウト機能を付ける上で最も手っ取り早いのは、std::set_new_handlerを使う方法です。μITRONの場合、
#include <kernel.h>
#include <new>
void timeout_new_handler()
{
dly_tsk(20);
std::set_new_handler(0);
}
といった、ハンドラ関数をstd::set_new_handlerを使って登録しておけば、割り付けに失敗した場合、とりあえず20msだけ待ってみて、再度割り付けを試みるようになります。そして、そこでも失敗した場合にはstd::bad_alloc例外が送出されます。
もう少しまともなタイムアウトにするには、1msごとに再試行してみて、タイムアウト時間が経過すれば、std::set_new_handler(0);を実行するようにすればよいかと思います。
この方法の難点は、タイムアウト時間をパラメータ化できないことにあります。グローバル変数にタイムアウト時間を入れておくこともできますが、あまり美しいとはいえないでしょう。
タイムアウト時間が定数でよいのであれば、次のようにしてパラメータ化することもできます。
#include <kernel.h>
#include <new>
template <int Timeout>
void timeout_new_handler()
{
dly_tsk(Timeout);
std::set_new_handler(0);
}
これで、std::set_new_handler(&timeout_new_handler<20>);のように、タイムアウト時間をパラメータ化することができるようになります。
さて、ハンドラ関数でタイムアウトを指定するやり方は簡単ですが、決して便利とはいえません。そこで、new演算子の配置構文を使うやり方を考えてみましょう
*1。ここでは、μITRONの可変長メモリプールを使って実装してみることにします。
#include <new>
#include <kernel.h>
#include <kernel_id.h>
void* operator new(std::size_t size, TMO tmout) throw(std::bad_alloc)
{
if (size == 0)
size = 1;
VP blk;
ER ercd = tget_mpl(MPLID, size, &blk, tmout);
if (ercd < 0)
{
std::new_handler nh = std::set_new_handler(0);
if (nh != 0)
{
(*nh)();
ercd = pget_mpl(MPLID, size, &blk);
}
if (ercd < 0)
throw std::bad_alloc();
}
return blk;
}
タイムアウト付きのnewですので、成功するまでnew_handlerを呼ぶことはやめ、1回だけ再試行するようにしています。
上の例では、大域的なnew演算子を多重定義するようにしましたが、クラスのメンバとして同様のnew演算子を定義すれば、可変長メモリプールの代わりに固定長メモリプールを使うこともできます
*2し、もっといろいろなカスタマイズもできるかと思います。
*1 new演算子を多重定義した場合は、対応するdelete演算子、すなわちoperator delete(void*, TMO)も定義しないといけないことに注意してください。
*2 クラスが派生された場合にも対応できるようにするには、可変長メモリプールにするか、固定長と可変長を併用する必要があります。
Comment