4

バックグラウンド

マルチコンポーネントの C++ コードベースがあります。主要な実行可能ファイルを構成する 1 つの中心的なコンポーネントと、動的モジュール (.so ファイル) にコンパイルされる多数のコンポーネントがあります。中央の実行可能ファイルは、実行時にそれらをロードおよびアンロードできます (必要に応じてホットスワップします)。

Scheduler.h と呼ばれるファイルが 1 つあります。このファイルは、Scheduler特定の時間または間隔で同期イベントを提供するクラスと、スケジューラへの要求に使用されるいくつかのヘルパー クラスを宣言します。タイミング データを保持するクラスと、単一の純粋仮想関数を持つEvent抽象クラスがあります。また、Scheduler.h のほとんどの機能の定義を含む Scheduler.cpp もあります (ヘッダー ファイルで宣言および定義されるテンプレート クラスを除く)。actionDoEvent

はのEventサブクラスへのポインタを所有しておりaction、これによってスケジューラの機能が制御されます。Scheduler.h は、こ​​れらのサブクラスのいくつかを提供します。

action次のように宣言されています。

class action
{
    action();
    virtual ~action();
    virtual DoEvent() = 0;
};

FunctionCallActionのサブクラスはaction、次のように宣言および定義されます。

template <class R, class T>
class FunctionCallAction : public action
{
public:
    FunctionCallAction(R (*f)(T), T arg) : argument(arg), callback(f) {}
    ~FunctionCallAction() {}
    void DoEvent() { function(argument); }
private:
    R (*callback)(T);
    T argument;
};

HelloAction、別のサブクラスは、次のように宣言されています。

// In Scheduler.h
class HelloAction : public action
{
    ~HelloAction();
    void DoEvent();
};

// in Scheduler.cpp
HelloAction::~HelloAction() {}
void HelloAction::DoEvent() { cout << "Hello world" << endl; }

私の動的ライブラリの 1 つは、CloneWatchCloneWatch.h で宣言され、CloneWatch.cpp で定義されており、このスケジューラ サービスを使用しています。コンストラクターで、300 秒ごとに実行されるようにスケジュールされた永続的なイベントを作成します。デストラクタで、このイベントを削除します。このモジュールが読み込まれると、既存のスケジューラ オブジェクトへの参照が取得されます。モジュールを「ロード」するプロセスには、 を使用dlopen()してライブラリを開き、dlsym()ファクトリ メソッド (適切な名前の ) を検索し、Factoryこのファクトリ メソッドを使用してオブジェクトのインスタンスを作成する必要があります (セマンティクスは関係ありません)。ライブラリを閉じるには、ファクトリ メソッドによって作成されたオブジェクトが削除されdlclose()、プロセスのアドレス空間からライブラリを削除するために呼び出されます。

実行時のライブラリのロードとアンロードは、コマンドによって制御されます。

// relevant declarations
const float DB_CLEAN_FREQ = 300;
event_t cleanerevent; // event_t is a typedef to an integral type
void * RunDBCleaner(void *); // static function of CloneWatch
Scheduler& scheduler;

// in constructor:
Event e(DB_CLEAN_FREQ, -1, new FunctionCallAction<void *, void *>(CloneWatch::RunDBCleaner, (void *) this));
cleanerevent = scheduler.AddEvent(e);

// in destructor:
scheduler.RemoveEvent(cleanerevent);

Scheduler::RemoveEvent怠惰です。イベントの優先キュー全体をトラバースするのではなく、「キャンセルされたイベント」のセットを維持します。イベント処理中に、キャンセルされたイベントのセット内の ID と一致する ID を持つイベントがキューからポップされた場合、そのイベントは実行または再スケジュールされず、すぐにクリーンアップされます。イベントをクリーンアップするプロセスでは、イベントがaction所有するオブジェクトを削除する必要があります。

問題

私が抱えている問題は、プログラム セグメントに障害があることです。障害は、スケジューラのイベント ループ内で発生します。これは、大まかに次のようになります。

while (!eventqueue.empty() && e.Due())
{
    Event e = eventqueue.top();
    eventqueue.pop();
    if (cancelled.find(e.GetID()) != cancelled.end())
    {
        cancelled.erase(e.GetID());
        e.Cancel();
        continue;
    }

    QueueUnlock();
    e.DoEvent();
    QueueLock();

    e.Next();

    if (e.ShouldReschedule()) eventqueue.push(e);
}

への呼び出しはe.Cancel、イベントのアクションを削除します。を呼び出すと、イベントのアクションe.Next 削除される場合があります (イベントが勝手に期限切れになった場合のみ。この場合、e.ShouldReschedule は false を返し、イベントは破棄されます)。テスト目的で、アクション クラスとサブクラスのデストラクタにいくつかの print ステートメントを追加して、何が起こっているかを確認しました。

キッカー

イベントが から削除された場合、e.Next期限切れになるまで、すべてが通常どおり進行します。ただし、モジュールをアンロードすると、キャンセルされたリストを通じてイベントがリタイアされ、アクションのデストラクタが呼び出されるとすぐに、プログラムでセグメンテーション エラーが発生します。これは、スケジューラがイベントを遅延削除するため、モジュールがアンロードされた後に発生します。

デストラクタには入りませんが、すぐに失敗します。イベントのアクションの管理された削除と管理されていない削除のさまざまな組み合わせを試し、さまざまな場所や方法でそれを実行しました。私はそれを valgrind と gdb で実行しましたが、どちらもセグメンテーション違反が発生したことを丁寧に通知してくれました。私の人生では、理由を特定することはできません (ただし、どちらの使い方もよくわかりません)。 .

ループの本体も呼び出しe.Cancelて削除を強制し、イベントを再スケジュールする行をコメントアウトして、イベントが実行されるとすぐにキャンセルされるようにすると、障害は発生しません。

アクションも に置き換えましたHelloActionが、これは問題ありません。のデストラクタについて非常に具体的なことはFunctionCallAction、明らかに問題がある場所です。セマンティック エラーはほぼ解消されましたが、これはコンパイラまたは動的リンカのあいまいな動作の結果であると思われます。誰かが問題を見ていますか?

4

1 に答える 1

5

これはコンパイラの動作です。

問題はFunctionCallAction、ヘッダー ファイルで定義されている (宣言されているだけではない) ことです。これはテンプレート クラスであることの必然的な副作用ですが、クラスがヘッダー ファイルで定義されている場合、通常のクラスを a の機能で宣言FunctionCallAction<void *, void *>しても同じ結果が得られます。

これは、異常な状況で意図しない副作用を持つテンプレート クラスに対する平凡な制限でした。

その理由は、クラスの定義がヘッダー ファイルにある場合、それを使用する各ファイルにコンパイルされるためです。動的ライブラリのコードから使用しているため、コンパイルされている場所です。したがって、ライブラリがアンロードされると、デストラクタのコードと残りのクラス全体が存在しなくなります。

FunctionCallAction非テンプレート クラスを作成し、その宣言のみを Scheduler.h に残し、その定義を Scheduler.cpp に移動することで、この問題を解決しました。このように、関数は、動的モジュールによって個別に提供されるのではなく、常にロードされるコア実行可能ファイルによって提供されます。

アクションのデストラクタへの呼び出しは、デストラクタ自体がプロセスのアドレス空間の一部ではなくなったため、セグメント違反でした。

于 2012-08-22T15:22:45.203 に答える