GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Systemweite globale Variable / Semaphor / Mutex in C++/Linux?

Für den gegenseitigen Ausschluss zwischen Prozessen können Sie Dateisperren verwenden. Unter Linux ist der Code so einfach wie das Schützen des kritischen Abschnitts mit einem Aufruf von flock .

int fd_lock = open(LOCK_FILE, O_CREAT);

flock(fd_lock, LOCK_EX);

// do stuff

flock(fd_lock, LOCK_UN);

Wenn Sie POSIX-Kompatibilität benötigen, können Sie fcntl verwenden .


Sie können ein benanntes Semaphor verwenden, wenn Sie alle Prozesse dazu bringen können, sich auf einen gemeinsamen Namen zu einigen.

Eine benannte Semaphore wird durch einen Namen der Form /somename identifiziert; das heißt, eine nullterminierte Zeichenfolge von bis zuNAME_MAX-4 (d. h. 251) Zeichen, bestehend aus einem anfänglichen Schrägstrich, gefolgt von einem oder mehreren Zeichen, von denen keines Schrägstriche sind. Zwei Prozesse können auf demselben benannten Semaphor operieren, indem sie denselben Namen an sem_open(3) übergeben .


Ich habe mir die Shared-pthread-Mutex-Lösung angeschaut, aber das logische Rennen darin hat mir nicht gefallen. Also habe ich eine Klasse geschrieben, um dies mit den atomaren Builtins

zu tun
#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;
        }
    }
};

Beispielverwendung - hier verwenden wir es einfach, um sicherzustellen, dass nur eine Instanz der Anwendung ausgeführt wird.

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;
}

Sie können dafür sorgen, dass C++-Mutexe unter Linux über Prozessgrenzen hinweg funktionieren. Es ist jedoch etwas schwarze Magie involviert, was es für Produktionscode weniger geeignet macht.

Erklärung:

std::mutex der Standardbibliothek und std::shared_mutex Verwenden Sie struct pthread_mutex_s von pthread und pthread_rwlock_t unter der Haube. Die native_handle() Methode gibt einen Zeiger auf eine dieser Strukturen zurück.

Der Nachteil besteht darin, dass bestimmte Details aus der Standardbibliothek abstrahiert und in der Implementierung vorgegeben werden. Beispiel:std::shared_mutex erstellt seinen zugrunde liegenden pthread_rwlock_t Struktur durch Übergabe von NULL als zweiten Parameter an pthread_rwlock_init() . Dies soll ein Zeiger auf pthread_rwlockattr_t sein Struktur, die ein Attribut enthält, das die Freigaberichtlinie bestimmt.

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

Theoretisch sollte es Standardattribute erhalten. Gemäß den Manpages für pthread_rwlockattr_getpshared() :

Der Standardwert des prozessgemeinsamen Attributs ist PTHREAD_PROCESS_PRIVATE.

Das heißt, beide std::shared_mutex und std::mutex sowieso prozessübergreifend arbeiten. Ich verwende Clang 6.0.1 (x86_64-unknown-linux-gnu / POSIX-Thread-Modell). Hier ist eine Beschreibung dessen, was ich getan habe, um Folgendes zu überprüfen:

  • Erstellen Sie eine gemeinsam genutzte Speicherregion mit shm_open .

  • Überprüfen Sie die Größe der Region mit fstat Eigentum zu bestimmen. Wenn .st_size Null ist, dann ftruncate() es und der Anrufer weiß, dass es der Schöpfungsprozess der Region ist.

  • Rufen Sie mmap an drauf.

    • Der Erstellungsprozess verwendet Platzierung -new um einen std::mutex zu konstruieren oder std::shared_mutex Objekt innerhalb der gemeinsam genutzten Region.
    • Spätere Prozesse verwenden reinterpret_cast<>() um einen typisierten Zeiger auf dasselbe Objekt zu erhalten.
  • Die Prozesse laufen jetzt beim Aufruf von trylock() in einer Schleife und unlock() in Intervallen. Sie können sehen, wie sie sich gegenseitig mit printf() blockieren vor und nach trylock() und vor unlock() .

Zusätzliches Detail:Ich war daran interessiert, ob die c++-Header oder die pthreads-Implementierung schuld waren, also habe ich mich mit pthread_rwlock_arch_t beschäftigt . Sie finden einen __shared Attribut, das Null und ein __flags ist -Attribut, das für das mit __PTHREAD_RWLOCK_INT_FLAGS_SHARED bezeichnete Feld ebenfalls Null ist . Es scheint also, dass diese Struktur standardmäßig nicht dafür vorgesehen ist, gemeinsam genutzt zu werden, obwohl sie diese Möglichkeit trotzdem zu bieten scheint (Stand:Juli 2019).

Zusammenfassung

Es scheint zu funktionieren, wenn auch eher zufällig. Ich rate zur Vorsicht beim Schreiben von Produktionssoftware, die der Dokumentation zuwiderläuft.


Linux
  1. So setzen Sie die $Path-Variable in Linux

  2. C++/Assembly-IDE unter Linux

  3. Erkennen Sie Windows oder Linux in C, C++

  4. Wirkung von usleep(0) in C++ unter Linux

  5. Wie exportiere ich eine Variable dauerhaft in Linux?

So weisen Sie einer Variablen die Ausgabe eines Linux-Befehls zu

So kompilieren und führen Sie C- und C++-Programme unter Linux aus

So speichern Sie einen Linux-Befehl als Variable im Shell-Skript

Linux – Wo wird ein benanntes Semaphor gespeichert?

Exportbefehl in Linux erklärt

Was ist Subshell in Linux?