GNU/Linux >> Linux Esercitazione >  >> Linux

Variabile globale a livello di sistema / semaforo / mutex in C++/Linux?

Per l'esclusione reciproca tra processi, è possibile utilizzare il blocco dei file. Con Linux, il codice è semplice come proteggere la sezione critica con una chiamata a flock .

int fd_lock = open(LOCK_FILE, O_CREAT);

flock(fd_lock, LOCK_EX);

// do stuff

flock(fd_lock, LOCK_UN);

Se hai bisogno della compatibilità POSIX, puoi usare fcntl .


Puoi utilizzare un semaforo denominato se riesci a far concordare tutti i processi su un nome comune.

Un semaforo con nome è identificato da un nome nella forma /somename; ovvero una stringa con terminazione null composta da un massimo di NAME_MAX-4 (ovvero 251) caratteri composta da una barra iniziale, seguita da uno o più caratteri, nessuno dei quali è una barra. Due processi possono operare sullo stesso semaforo con nome passando lo stesso nome a sem_open(3) .


Ho esaminato l'utilizzo della soluzione shared-pthread-mutex ma non mi è piaciuta la gara logica in essa contenuta. Quindi ho scritto una classe per farlo usando i built-in atomic

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

Esempio di utilizzo:qui lo stiamo semplicemente usando per garantire che venga eseguita solo un'istanza dell'applicazione.

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

Puoi far funzionare i mutex C++ oltre i limiti del processo su Linux. Tuttavia, è coinvolta della magia nera che lo rende meno appropriato per il codice di produzione.

Spiegazione:

Il std::mutex della libreria standard e std::shared_mutex usa struct pthread_mutex_s di pthread e pthread_rwlock_t sotto il cappuccio. L'native_handle() Il metodo restituisce un puntatore a una di queste strutture.

Lo svantaggio è che alcuni dettagli vengono estratti dalla libreria standard e predefiniti nell'implementazione. Ad esempio, std::shared_mutex crea il relativo pthread_rwlock_t sottostante struttura passando NULL come secondo parametro di pthread_rwlock_init() . Questo dovrebbe essere un puntatore a un pthread_rwlockattr_t struttura contenente un attributo che determina la politica di condivisione.

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

In teoria, dovrebbe ricevere attributi predefiniti. Secondo le pagine man per pthread_rwlockattr_getpshared() :

Il valore predefinito dell'attributo process-shared è PTHREAD_PROCESS_PRIVATE.

Detto questo, entrambi std::shared_mutex e std::mutex lavorare comunque attraverso i processi. Sto usando Clang 6.0.1 (modello thread x86_64-unknown-linux-gnu / POSIX). Ecco una descrizione di quello che ho fatto per controllare:

  • Crea una regione di memoria condivisa con shm_open .

  • Controlla la dimensione della regione con fstat per determinare la proprietà. Se .st_size è zero, quindi ftruncate() esso e il chiamante sa che è il processo di creazione della regione.

  • Chiama mmap su di esso.

    • Il processo di creazione utilizza il posizionamento -new per costruire un std::mutex o std::shared_mutex oggetto all'interno della regione condivisa.
    • I processi successivi usano reinterpret_cast<>() per ottenere un puntatore digitato allo stesso oggetto.
  • I processi ora si ripetono chiamando trylock() e unlock() ad intervalli. Puoi vederli bloccarsi a vicenda usando printf() prima e dopo trylock() e prima di unlock() .

Ulteriori dettagli:ero interessato a sapere se le intestazioni c++ o l'implementazione di pthreads fossero in errore, quindi ho scavato in pthread_rwlock_arch_t . Troverai un __shared attributo che è zero e un __flags attributo che è zero anche per il campo indicato da __PTHREAD_RWLOCK_INT_FLAGS_SHARED . Quindi sembra che per impostazione predefinita questa struttura non sia destinata alla condivisione, anche se sembra fornire comunque questa struttura (a partire da luglio 2019).

Riepilogo

Sembra funzionare, anche se un po' per caso. Consiglierei cautela nello scrivere software di produzione che funzioni in contrasto con la documentazione.


Linux
  1. Come impostare la tua variabile $PATH in Linux

  2. Linux:dove viene archiviato un semaforo denominato??

  3. Come impostare la variabile $ Path in Linux

  4. IDE C++/Assembly su Linux

  5. Rileva Windows o Linux in C, C++

Come compilare ed eseguire programmi C, C++ in Linux

Come memorizzare un comando Linux come variabile nello script della shell

Spiegazione del comando di esportazione in Linux

Che cos'è Subshell in Linux?

Come esportare permanentemente una variabile in Linux?

accessibilità delle variabili d'ambiente in Linux