GNU/Linux >> Linux の 問題 >  >> Linux

C++/Linux のシステム全体のグローバル変数/セマフォ/ミューテックス?

プロセス間の相互排除には、ファイル ロックを使用できます。 Linux では、コードは flock の呼び出しでクリティカル セクションを保護するのと同じくらい簡単です。 .

int fd_lock = open(LOCK_FILE, O_CREAT);

flock(fd_lock, LOCK_EX);

// do stuff

flock(fd_lock, LOCK_UN);

POSIX 互換性が必要な場合は、fcntl を使用できます .


すべてのプロセスが共通の名前に同意できる場合は、名前付きセマフォを使用できます。

<ブロック引用>

名前付きセマフォは、/somename の形式の名前で識別されます。;つまり、頭文字のスラッシュとそれに続く 1 つまたは複数の文字で構成される最大 NAME_MAX-4 (つまり、251) 文字のヌル終了文字列で、いずれもスラッシュは使用されません。 sem_open(3) に同じ名前を渡すことにより、2 つのプロセスが同じ名前付きセマフォで動作できます。 .


私は shared-pthread-mutex ソリューションの使用を検討しましたが、ロジックの競合が気に入らなかったのです。そこで、アトミック組み込み関数を使用してこれを行うクラスを作成しました

#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

using std::string;

//from the command line - "ls /dev/shm" and "lsof /dev/shm/<name>" to see which process ID has access to it

template<typename PAYLOAD>
class InterprocessSharedVariable
{
protected:
    int mSharedMemHandle;
    string const mSharedMemoryName;
    bool mOpenedMemory;
    bool mHaveLock;
    pid_t mPID;

    // this is the shared memory structure
    typedef struct 
    {
        pid_t mutex;
        PAYLOAD payload;
    }
    tsSharedPayload;


    tsSharedPayload* mSharedData;


    bool openSharedMem()
    {
        mPID = getpid();

        // The following caters for the shared mem being created by root but opened by non-root,
        //  giving the shared-memory 777 permissions.
        int openFlags = O_CREAT | O_RDWR;
        int shareMode = S_IRWXU | S_IRWXG | S_IRWXO;

        // see https://stackoverflow.com/questions/11909505/posix-shared-memory-and-semaphores-permissions-set-incorrectly-by-open-calls
        // store old
        mode_t old_umask = umask(0);

        mSharedMemHandle = shm_open (mSharedMemoryName.c_str(), openFlags, shareMode);

        // restore old
        umask(old_umask);

        if (mSharedMemHandle < 0) 
        {
            std::cerr << "failed to open shared memory"  << std::endl;
            return false;
        }

        if (-1 == ftruncate(mSharedMemHandle, sizeof(tsSharedPayload)))
        {
            std::cerr <<  "failed to resize shared memory" << std::endl;
            return false;
        }

        mSharedData = (tsSharedPayload*) mmap (NULL, 
                                            sizeof(tsSharedPayload),
                                            PROT_READ | PROT_WRITE,
                                            MAP_SHARED,
                                            mSharedMemHandle,
                                            0);

        if (MAP_FAILED == mSharedData)
        {
            std::cerr << "failed to map shared memory" << std::endl;
            return false;
        }

        return true;
    }


    void closeSharedMem()
    {
        if (mSharedMemHandle > 0)
        {
            mSharedMemHandle = 0;
            shm_unlink (mSharedMemoryName.c_str());
        }
    }

public:
    InterprocessSharedVariable () = delete;

    InterprocessSharedVariable (string const&& sharedMemoryName) : mSharedMemoryName(sharedMemoryName)
    {
        mSharedMemHandle = 0;
        mOpenedMemory = false;
        mHaveLock = false;
        mPID = 0;
    }

    virtual ~InterprocessSharedVariable ()
    {
        releaseSharedVariable ();
        closeSharedMem ();
    }

    // no copying
    InterprocessSharedVariable (InterprocessSharedVariable const&) = delete;
    InterprocessSharedVariable& operator= (InterprocessSharedVariable const&) = delete;


    bool tryLockSharedVariable (pid_t& ownerProcessID)
    {
        // Double-checked locking.  See if a process has already grabbed the mutex.  Note the process could be dead
        __atomic_load (&mSharedData->mutex, &ownerProcessID, __ATOMIC_SEQ_CST);

        if (0 != ownerProcessID)
        {
            // It is possible that we have started with the same PID as a previous process that terminated abnormally
            if (ownerProcessID == mPID)
            {
                // ... in which case, we already "have ownership"
                return (true);
            }

            // Another process may have the mutex.  Check whether it is alive.
            // We are specifically looking for an error returned with ESRCH
            // Note that if the other process is owned by root, "kill 0" may return a permissions error (which indicates the process is running!)
            int processCheckResult = kill (ownerProcessID, 0);

            if ((0 == processCheckResult) || (ESRCH != errno))
            {
                // another process owns the shared memory and is running
                return (false);
            }

            // Here: The other process does not exist ((0 != processCheckResult) && (ESRCH == errno))
            // We could assume here that we can now take ownership, but be proper and fall into the compare-exchange
            ownerProcessID = 0;
        }

        // It's possible that another process has snuck in here and taken ownership of the shared memory.
        // If that has happened, the exchange will "fail" (and the existing PID is stored in ownerProcessID)

        // ownerProcessID == 0 -> representing the "expected" value
        mHaveLock = __atomic_compare_exchange_n (&mSharedData->mutex,
                                                &ownerProcessID,      //"expected"
                                                mPID,                 //"desired"
                                                false,                //"weak"
                                                __ATOMIC_SEQ_CST,     //"success-memorder"
                                                __ATOMIC_SEQ_CST);    //"fail-memorder"

        return (mHaveLock);
    }


    bool acquireSharedVariable (bool& failed, pid_t& ownerProcessID)
    {
        if (!mOpenedMemory)
        {
            mOpenedMemory = openSharedMem ();

            if (!mOpenedMemory)
            {
                ownerProcessID = 0;
                failed = true;
                return false;
            }
        }

        // infrastructure is working
        failed = false;

        bool gotLock = tryLockSharedVariable (ownerProcessID);
        return (gotLock);
    }

    void releaseSharedVariable ()
    {
        if (mHaveLock)
        {
            __atomic_store_n (&mSharedData->mutex, 0, __ATOMIC_SEQ_CST);
            mHaveLock = false;
        }
    }
};

使用例 - ここでは、アプリケーションのインスタンスが 1 つだけ実行されるようにするために使用しています。

int main(int argc, char *argv[])
{
    typedef struct { } tsEmpty;
    InterprocessSharedVariable<tsEmpty> programMutex ("/run-once");

    bool memOpenFailed;
    pid_t ownerProcessID;
    if (!programMutex.acquireSharedVariable (memOpenFailed, ownerProcessID))
    {
        if (memOpenFailed)
        {
            std::cerr << "Failed to open shared memory" << std::endl;
        }
        else
        {
            std::cerr << "Program already running - process ID " << ownerProcessID << std::endl;
        }
        return -1;
    }

    ... do stuff ...

    return 0;
}

Linux では、プロセスの境界を越えて C++ ミューテックスを機能させることができます。ただし、いくつかのブラック マジックが関与しているため、プロダクション コードにはあまり適していません。

説明:

標準ライブラリの std::mutex および std::shared_mutex pthread の struct pthread_mutex_s を使用 と pthread_rwlock_t フードの下。 native_handle() メソッドは、これらの構造体の 1 つへのポインターを返します。

欠点は、特定の詳細が標準ライブラリから抽象化され、実装でデフォルト設定されることです。例:std::shared_mutex 基礎となる pthread_rwlock_t を作成します NULL を渡して構造化 pthread_rwlock_init() の 2 番目のパラメータとして .これは pthread_rwlockattr_t へのポインタであるはずです 共有ポリシーを決定する属性を含む構造。

public:
    __shared_mutex_pthread()
    {
        int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
        ...

理論的には、デフォルトの属性を受け取る必要があります。 pthread_rwlockattr_getpshared() のマニュアルページによると :

<ブロック引用>

プロセス共有属性のデフォルト値は PTHREAD_PROCESS_PRIVATE です。

とはいえ、どちらも std::shared_mutex そして std::mutex とにかくプロセス全体で動作します。 Clang 6.0.1 (x86_64-unknown-linux-gnu / POSIX スレッド モデル) を使用しています。私が確認したことの説明は次のとおりです。

  • shm_open で共有メモリ領域を作成します .

  • fstat で領域のサイズを確認します 所有権を決定します。 .st_size の場合 ゼロの場合、ftruncate() それと呼び出し元は、それがリージョンの作成プロセスであることを認識しています。

  • mmap に電話する

    • クリエーター プロセスはプレースメントを使用します -new std::mutex を構築する または std::shared_mutex 共有領域内のオブジェクト。
    • 後のプロセスでは reinterpret_cast<>() を使用します 同じオブジェクトへの型指定されたポインターを取得します。
  • プロセスは trylock() の呼び出しでループするようになりました と unlock() 間隔で。 printf() を使用して、それらが互いにブロックしているのを見ることができます trylock() の前後 そして unlock() の前 .

追加情報:C++ ヘッダーまたは pthreads の実装に問題があるかどうかに興味があったので、pthread_rwlock_arch_t を掘り下げました。 . __shared が見つかります ゼロで __flags の属性 __PTHREAD_RWLOCK_INT_FLAGS_SHARED で示されるフィールドでもゼロである属性 .したがって、デフォルトでは、この構造は共有されることを意図していないようですが、とにかくこの機能を提供しているようです (2019 年 7 月現在)。

まとめ

多少偶然ですが、うまくいくようです。ドキュメントに反して動作する製品ソフトウェアを作成する際には注意が必要です。


Linux
  1. Linux –名前付きセマフォはどこに保存されますか?

  2. Linuxで$Path変数を設定する方法

  3. Linux 上の C++/アセンブリ IDE

  1. Linux 上の Python でのシステム全体のミューテックス

  2. Linuxで変数を永続的にエクスポートする方法は?

  3. Linux での環境変数のアクセシビリティ

  1. Linux環境変数のヒントとコツ

  2. Linux –ライブラリ関数への呼び出しのシステム全体の監視?

  3. Linux 上の C++ 動的共有ライブラリ