GNU/Linux >> Znalost Linux >  >> Linux

Celosystémová globální proměnná / semafor / mutex v C++/Linuxu?

Pro vzájemné vyloučení mezi procesy můžete použít zamykání souborů. V linuxu je kód stejně jednoduchý jako ochrana kritické sekce voláním flock .

int fd_lock = open(LOCK_FILE, O_CREAT);

flock(fd_lock, LOCK_EX);

// do stuff

flock(fd_lock, LOCK_UN);

Pokud potřebujete kompatibilitu s POSIX, můžete použít fcntl .


Můžete použít pojmenovaný semafor, pokud můžete dosáhnout toho, aby se všechny procesy dohodly na společném názvu.

Pojmenovaný semafor je identifikován jménem ve tvaru/somename; to znamená řetězec zakončený nulou o délce až NAME_MAX-4 (tj. 251) znaků sestávající z počátečního lomítka, po němž následuje jeden nebo více znaků, přičemž žádný z nich není lomítko. Dva procesy mohou fungovat na stejném namesemaforu předáním stejného názvu do sem_open(3) .


Podíval jsem se na použití řešení shared-pthread-mutex, ale nelíbilo se mi v něm logické závody. Napsal jsem tedy třídu, která to udělá pomocí vestavěných atomů

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

Příklad použití – zde jej jednoduše používáme, abychom zajistili, že bude spuštěna pouze jedna instance aplikace.

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

Můžete zajistit, aby mutexy C++ fungovaly přes hranice procesů v Linuxu. Je zde však obsažena určitá černá magie, která jej činí méně vhodným pro produkční kód.

Vysvětlení:

Standardní knihovna je std::mutex a std::shared_mutex použijte struct pthread_mutex_s pthread a pthread_rwlock_t pod kapotou. native_handle() metoda vrací ukazatel na jednu z těchto struktur.

Nevýhodou je, že určité detaily jsou abstrahovány ze standardní knihovny a v implementaci jsou výchozí. Například std::shared_mutex vytvoří základní pthread_rwlock_t strukturu předáním NULL jako druhý parametr na pthread_rwlock_init() . Toto má být ukazatel na pthread_rwlockattr_t struktura obsahující atribut, který určuje politiku sdílení.

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

Teoreticky by měl obdržet výchozí atributy. Podle manuálových stránek pro pthread_rwlockattr_getpshared() :

Výchozí hodnota atributu sdíleného procesu je PTHREAD_PROCESS_PRIVATE.

To znamená, že obě std::shared_mutex a std::mutex pracovat napříč procesy. Používám Clang 6.0.1 (x86_64-unknown-linux-gnu / model vlákna POSIX). Zde je popis toho, co jsem zkontroloval:

  • Vytvořte oblast sdílené paměti s shm_open .

  • Zkontrolujte velikost oblasti pomocí fstat k určení vlastnictví. Pokud .st_size je nula, pak ftruncate() on a volající ví, že je to proces vytváření regionu.

  • Zavolejte na číslo mmap na to.

    • Proces tvůrce používá umístění -new vytvořit std::mutex nebo std::shared_mutex objekt ve sdílené oblasti.
    • Pozdější procesy používají reinterpret_cast<>() získat typizovaný ukazatel na stejný objekt.
  • Procesy nyní při volání trylock() opakují a unlock() v intervalech. Můžete je vidět, jak se navzájem blokují pomocí printf() před a za trylock() a před unlock() .

Další podrobnosti:Zajímalo mě, zda byly chybné hlavičky c++ nebo implementace pthreads, a tak jsem se pustil do pthread_rwlock_arch_t . Najdete __shared atribut, který je nula a __flags atribut, který je také nulový pro pole označené __PTHREAD_RWLOCK_INT_FLAGS_SHARED . Zdá se tedy, že ve výchozím nastavení není tato struktura určena ke sdílení, i když se zdá, že tuto možnost stejně poskytuje (od července 2019).

Shrnutí

Zdá se, že to funguje, i když trochu náhodou. Doporučil bych opatrnost při psaní produkčního softwaru, který funguje v rozporu s dokumentací.


Linux
  1. pthreads mutex vs semafor

  2. C++ získat název distribuce linux\verze

  3. Systémový mutex v Pythonu na Linuxu

  1. Jak nastavit proměnnou $PATH v Linuxu

  2. Jak přiřadit výstup příkazu Linux k proměnné

  3. Vliv usleep(0) v C++ na Linuxu

  1. Tipy a triky pro proměnné prostředí Linuxu

  2. Linux – kde je uložen pojmenovaný semafor?

  3. Linux – celosystémové monitorování volání do funkce knihovny?