今回は前回の予定通り、カーネルキットのセマフォについて説明します。
前回説明したBLockerは、カーネルキットのセマフォによって、実装されているわけですが、
今回はそのセマフォを直にいじろうというものです。
とはいっても、コードが極端に難しくなるわけではない(というか前回とほとんど同じ)ので、安心してください。
さて、今回のサンプルの全体です。
// sample17.cpp
#include <OS.h> // スレッド生成と時間情報のために必要
// セマフォを使うためにも必要
#include <iostream.h> // カウンター表示のため(cout)に必要
// スレッドによるカウンタークラス
class ThreadAutoCounter
{
public:
ThreadAutoCounter(uint64 count);
~ThreadAutoCounter();
status_t StartCounter1(); // カウンター1のスタート
status_t StartCounter2(); // カウンター2のスタート
status_t StopCounter1(); // カウンター1のストップ
status_t StopCounter2(); // カウンター2のストップ
void PrintCount(); // カウンター内容を標準出力に表示
private:
static int32 CounterFunc(void* data); // スレッド用関数
thread_id m_tid1; // カウンター1番のスレッドID
thread_id m_tid2; // カウンター2番のスレッドID
uint64 m_count; // カウンターの値
sem_id m_semaphore; // 使用するセマフォのID
};
// コンストラクタは、カウンターの初期値を与え、セマフォを構築する
ThreadAutoCounter::ThreadAutoCounter(uint64 count)
:m_count(count)
{
// セマフォを獲得出来るスレッドは1つのみ
m_semaphore = create_sem(1,"theSemaphore");
}
// デストラクタでは、セマフォを削除
ThreadAutoCounter::~ThreadAutoCounter()
{
delete_sem(m_semaphore);
}
// カウンター1をスタートさせる関数
status_t ThreadAutoCounter::StartCounter1()
{
m_tid1 = spawn_thread(ThreadAutoCounter::CounterFunc,
"CounterThread",B_NORMAL_PRIORITY,
this);
return resume_thread(m_tid1);
}
// カウンター2をスタートさせる関数
status_t ThreadAutoCounter::StartCounter2()
{
m_tid2 = spawn_thread(ThreadAutoCounter::CounterFunc,
"CounterThread",B_NORMAL_PRIORITY,
this);
return resume_thread(m_tid2);
}
status_t ThreadAutoCounter::StopCounter1()
{
return kill_thread(m_tid1);
}
status_t ThreadAutoCounter::StopCounter2()
{
return kill_thread(m_tid2);
}
// カウンターを表示する関数
void ThreadAutoCounter::PrintCount()
{
cout << "Counter:" << m_count << endl;
}
// カウンターを増やすスレッド用関数
int32 ThreadAutoCounter::CounterFunc(void* data)
{
// 自分自身のクラスへキャスト
ThreadAutoCounter* counter;
counter = reinterpret_cast<ThreadAutoCounter*>(data);
// 100000までカウント
for (int i=0;i<100000;i++)
{
// counter->m_locker->Lock();
// セマフォをきちんと獲得出来たかどうかチェック
if (acquire_sem(counter->m_semaphore) = B_NO_ERROR)
{
// 一回ローカル変数にコピーしてから1を足す
uint64 count = counter->m_count;
count++;
counter->m_count = count;
// counter->m_locker->Unlock();
release_sem(counter->m_semaphore); // セマフォを放出する
}
}
return B_OK; // スレッドの正常終了時にはB_OKを返す
}
int main()
{
ThreadAutoCounter counter(0); // カウンターの初期値は0
counter.StartCounter1(); // カウンター1をスタート
counter.StartCounter2(); // カウンター2をスタート
bigtime_t start_time;
start_time = system_time(); // OSの起動時間をマイクロ秒で取得
// 10秒間(10000000マイクロ秒)カウンターの内容を表示し続ける
while (system_time() - start_time < 10000000)
{
counter.PrintCount(); // カウンターの内容を表示
}
counter.StopCounter1(); // カウンター1スレッドを破棄
counter.StopCounter2(); // カウンター2スレッドを破棄
return 0;
}
今回の結果は次のようになります。
前回と同じです。
まぁ、実装方法が変わっただけで同じ機能を実現しているわけですから、当然といえば当然なのですが。
まずは、セマフォの構築の仕方です。
ThreadAutoCounterクラスのコンストラクタは次のようになっています。
// コンストラクタは、カウンターの初期値を与え、セマフォを構築する
ThreadAutoCounter::ThreadAutoCounter(uint64 count)
:m_count(count)
{
// セマフォを獲得出来るスレッドは1つのみ
m_semaphore = create_sem(1,"theSemaphore");
}
セマフォはスレッドと同様に、ID番号で管理しています。
そのID番号を格納する変数の型がsem_idなわけです。
コンストラクタで使っているcreate_sem関数の宣言は次のようになっています。
sem_id create_sem(int32 count, const char *name);
引数に指定するものは、
戻り値は、作成されたセマフォのID番号です。
BLockerクラスは、同時にクリティカルセクションに入れるスレッドは1つだけでしたが、
生のセマフォは、任意に指定が出来ます。
ここで指定したcountのことをセマフォカウントと呼びます。
エラーコードについては、
| 値 | 説明 |
|---|---|
| B_BAD_VALUE | 第1引数(count)が0より下(0は大丈夫) |
| B_NO_MEMORY | セマフォの名前を確保するメモリが無い |
| B_NO_MORE_SEMS | すべてのsem_id番号は使われている |
となっています。
セマフォの構築は出来たわけですが、それと対になるセマフォの削除について見てみましょう。
次に示すのはThreadAutoCounterクラスのデストラクタです。
// デストラクタでは、セマフォを削除
ThreadAutoCounter::~ThreadAutoCounter()
{
delete_sem(m_semaphore);
}
delete_sem()関数がセマフォを削除する関数です。
引数には削除したいセマフォのIDを渡します。
基本的に、セマフォの削除はセマフォを構築したスレッドと同じスレッドが行う必要があります。
セマフォを構築したスレッドをセマフォのオーナースレッドと呼び、
セマフォの構築から削除まで一貫して責任を負うこととなります。
| 値 | 説明 |
|---|---|
| B_NO_ERROR | セマフォは正常に削除された |
| B_BAD_SEM_ID | 引数に渡されたsem_idがおかしいか、削除するセマフォのオーナースレッドでは無い |
となっています。
さて、実際のセマフォの使い方について見てみましょう。
// 100000までカウント
for (int i=0;i<100000;i++)
{
// counter->m_locker->Lock();
// セマフォをきちんと獲得出来たかどうかチェック
if (acquire_sem(counter->m_semaphore) = B_NO_ERROR)
{
// 一回ローカル変数にコピーしてから1を足す
uint64 count = counter->m_count;
count++;
counter->m_count = count;
// counter->m_locker->Unlock();
release_sem(counter->m_semaphore); // セマフォを放出する
}
}
わかりやすいように、前回のBLockerを使ったときのコードと並べて書きました。
コメントアウトされているのが、BLockerを使った時のコードです。
見ればわかる通り、BLocker::Lock()関数がacquire_sem()関数に、BLocer::Unlock()関数がrelease_sem()関数に相当します。
acquire_sem()関数とrelease_sem()関数は両方とも引数にsem_idを指定します。
acquire_sem()関数は、セマフォカウントを調べ、0より大きかったら、セマフォカウントを1つ下げます。
この場合で言うと、どのスレッドもクリティカルセクション内に入っていなかったら、
セマフォカウントは1ですので、acuire_sem()関数はクリティカルセクション内に入るときに、
セマフォカウントを0にします。
セマフォがきちんと獲得出来たらB_NO_ERRORを返します。
セマフォカウントが0だったら、セマフォをちゃんと獲得出来るまで戻ってきません。
このとき、割り込みなどにより、セマフォがきちんと獲得出来ないでエラーが返ってくる場合もありますので、
きちんとエラーチェックをして、クリティカルセクションの中に入る必要があります。
acquire_sem()関数の戻り値を以下の表に示します。
| 値 | 説明 |
|---|---|
| B_NO_ERROR | セマフォは問題なく獲得出来た |
| B_BAD_SEM_ID | 渡されたsem_idは有効ではない |
| B_INTERRUPTED | セマフォの獲得中にシグナルによる割り込みが起きた。 セマフォはちゃんと獲得出来ていない。 |
release_sem()関数はセマフォカウントを調べることなく、無条件でセマフォカウントを1増やします。
セマフォカウントを1増やすことにより、他のスレッドがacquire_sem()関数の実行に成功し、
クリティカルセクションに入ることが可能になります。
戻り値を以下に示します。
| 値 | 説明 |
|---|---|
| B_NO_ERROR | セマフォの放出に成功 |
| B_BAD_SEM_ID | 渡されたsem_idが無効 |
セマフォカウントを1下げることをセマフォの獲得、逆に1上げることをセマフォの放出などと言ったりします。
セマフォを直接扱う利点というのは少ないですが、オペレーティングシステムの要素としてのセマフォを学ぶには、
絶好の教材だと思います。
これでセマフォについての説明を終えます。
基本的に使用するには第18回に使用したBLockerでいいと思います。
ただ、一つのトピックとして取り上げてみました。
それではまた次回。
|
|||
|
|