GNU/Linux >> Tutoriels Linux >  >> Linux

Variable globale à l'échelle du système / sémaphore / mutex en C++/Linux ?

Pour l'exclusion mutuelle interprocessus, vous pouvez utiliser le verrouillage de fichiers. Avec Linux, le code est aussi simple que de protéger la section critique avec un appel à flock .

int fd_lock = open(LOCK_FILE, O_CREAT);

flock(fd_lock, LOCK_EX);

// do stuff

flock(fd_lock, LOCK_UN);

Si vous avez besoin de la compatibilité POSIX, vous pouvez utiliser fcntl .


Vous pouvez utiliser un sémaphore nommé si vous pouvez obtenir que tous les processus s'accordent sur un nom commun.

Un sémaphore nommé est identifié par un nom de la forme /somename; c'est-à-dire une chaîne terminée par un caractère nul contenant jusqu'à NAME_MAX-4 (c'est-à-dire 251) caractères composé d'une barre oblique initiale, suivie d'un ou plusieurs caractères, dont aucun n'est une barre oblique. Deux processus peuvent fonctionner sur le même sémaphore nommé en passant le même nom à sem_open(3) .


J'ai envisagé d'utiliser la solution shared-pthread-mutex mais je n'ai pas aimé la course logique qu'elle contient. J'ai donc écrit une classe pour faire cela en utilisant les fonctions intégrées atomiques

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

Exemple d'utilisation - ici, nous l'utilisons simplement pour nous assurer qu'une seule instance de l'application s'exécute.

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

Vous pouvez faire fonctionner les mutex C++ au-delà des limites de processus sous Linux. Cependant, il y a une certaine magie noire impliquée qui le rend moins approprié pour le code de production.

Explication :

std::mutex de la bibliothèque standard et std::shared_mutex utiliser le struct pthread_mutex_s de pthread et pthread_rwlock_t sous la capuche. Le native_handle() renvoie un pointeur vers l'une de ces structures.

L'inconvénient est que certains détails sont extraits de la bibliothèque standard et mis par défaut dans l'implémentation. Par exemple, std::shared_mutex crée son pthread_rwlock_t sous-jacent structure en passant NULL comme deuxième paramètre à pthread_rwlock_init() . Ceci est censé être un pointeur vers un pthread_rwlockattr_t structure contenant un attribut qui détermine la politique de partage.

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

En théorie, il devrait recevoir des attributs par défaut. Selon les pages de manuel pour pthread_rwlockattr_getpshared() :

La valeur par défaut de l'attribut partagé par processus est PTHREAD_PROCESS_PRIVATE.

Cela dit, les deux std::shared_mutex et std::mutex travailler à travers les processus de toute façon. J'utilise Clang 6.0.1 (modèle de thread x86_64-unknown-linux-gnu / POSIX). Voici une description de ce que j'ai fait pour vérifier :

  • Créer une région de mémoire partagée avec shm_open .

  • Vérifiez la taille de la région avec fstat pour déterminer la propriété. Si .st_size est zéro, alors ftruncate() et l'appelant sait qu'il s'agit du processus de création de la région.

  • Appelez le mmap dessus.

    • Le processus de création utilise le placement -new pour construire un std::mutex ou std::shared_mutex objet dans la région partagée.
    • Les processus ultérieurs utilisent reinterpret_cast<>() pour obtenir un pointeur typé vers le même objet.
  • Les processus bouclent maintenant sur l'appel de trylock() et unlock() à intervalles. Vous pouvez les voir se bloquer en utilisant printf() avant et après trylock() et avant unlock() .

Détail supplémentaire :je voulais savoir si les en-têtes c++ ou l'implémentation de pthreads étaient en cause, alors j'ai creusé dans pthread_rwlock_arch_t . Vous trouverez un __shared attribut qui vaut zéro et un __flags attribut qui vaut aussi zéro pour le champ noté __PTHREAD_RWLOCK_INT_FLAGS_SHARED . Il semble donc que par défaut cette structure ne soit pas destinée à être partagée, bien qu'elle semble fournir cette fonctionnalité de toute façon (à partir de juillet 2019).

Résumé

Cela semble fonctionner, bien qu'un peu par hasard. Je conseillerais la prudence lors de l'écriture d'un logiciel de production qui fonctionne contrairement à la documentation.


Linux
  1. Trucs et astuces pour les variables d'environnement Linux

  2. Linux - Surveillance à l'échelle du système des appels à une fonction de bibliothèque ?

  3. Comment définir la variable $Path sous Linux

  4. pthreads mutex vs sémaphore

  5. C++ obtient le nom de la distribution Linux\version

Comment affecter la sortie d'une commande Linux à une variable

Comment compiler et exécuter des programmes C, C++ sous Linux

Comment stocker une commande Linux en tant que variable dans un script shell

Explication de la commande d'exportation sous Linux

Qu'est-ce que le sous-shell sous Linux ?

accessibilité des variables d'environnement sous Linux