GNU/Linux >> Tutoriales Linux >  >> Linux

¿Variable global de todo el sistema/semáforo/mutex en C++/Linux?

Para la exclusión mutua entre procesos, puede utilizar el bloqueo de archivos. Con Linux, el código es tan simple como proteger la sección crítica con una llamada a flock .

int fd_lock = open(LOCK_FILE, O_CREAT);

flock(fd_lock, LOCK_EX);

// do stuff

flock(fd_lock, LOCK_UN);

Si necesita compatibilidad con POSIX, puede usar fcntl .


Puede usar un semáforo con nombre si puede hacer que todos los procesos acuerden un nombre común.

Un semáforo con nombre se identifica por un nombre de la forma /somename; es decir, una cadena terminada en nulo de hasta NOMBRE_MAX-4 (es decir, 251) caracteres que consta de una barra diagonal inicial, seguida de uno o más caracteres, ninguno de los cuales son barras diagonales. Dos procesos pueden operar en el mismo semáforo con nombre pasando el mismo nombre a sem_open(3) .


Miré el uso de la solución shared-pthread-mutex pero no me gustó la carrera lógica en ella. Así que escribí una clase para hacer esto usando los componentes atómicos

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

Ejemplo de uso:aquí simplemente lo estamos usando para garantizar que solo se ejecute una instancia de la aplicación.

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

Puede hacer que los mutexes de C++ funcionen a través de los límites del proceso en Linux. Sin embargo, hay algo de magia negra involucrada que lo hace menos apropiado para el código de producción.

Explicación:

std::mutex de la biblioteca estándar y std::shared_mutex usa el struct pthread_mutex_s de pthread y pthread_rwlock_t bajo el capó. El native_handle() El método devuelve un puntero a una de estas estructuras.

El inconveniente es que ciertos detalles se abstraen de la biblioteca estándar y se establecen de forma predeterminada en la implementación. Por ejemplo, std::shared_mutex crea su pthread_rwlock_t subyacente estructura pasando NULL como segundo parámetro para pthread_rwlock_init() . Se supone que esto es un puntero a un pthread_rwlockattr_t estructura que contiene un atributo que determina la política de uso compartido.

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

En teoría, debería recibir atributos predeterminados. De acuerdo con las páginas man para pthread_rwlockattr_getpshared() :

El valor predeterminado del atributo de proceso compartido es PTHREAD_PROCESS_PRIVATE.

Dicho esto, ambos std::shared_mutex y std::mutex trabajar a través de los procesos de todos modos. Estoy usando Clang 6.0.1 (x86_64-unknown-linux-gnu/modelo de hilo POSIX). Aquí hay una descripción de lo que hice para verificar:

  • Crea una región de memoria compartida con shm_open .

  • Compruebe el tamaño de la región con fstat para determinar la propiedad. Si .st_size es cero, entonces ftruncate() y la persona que llama sabe que es el proceso de creación de la región.

  • Llama al mmap en él.

    • El proceso de creación utiliza ubicación -new para construir un std::mutex o std::shared_mutex objeto dentro de la región compartida.
    • Los procesos posteriores usan reinterpret_cast<>() para obtener un puntero escrito al mismo objeto.
  • Los procesos ahora se repiten al llamar a trylock() y unlock() a intervalos. Puedes verlos bloqueándose unos a otros usando printf() antes y después de trylock() y antes de unlock() .

Detalle adicional:me interesaba saber si los encabezados de C++ o la implementación de pthreads tenían la culpa, así que busqué en pthread_rwlock_arch_t . Encontrarás un __shared atributo que es cero y un __flags atributo que también es cero para el campo indicado por __PTHREAD_RWLOCK_INT_FLAGS_SHARED . Por lo tanto, parece que, de forma predeterminada, esta estructura no está destinada a ser compartida, aunque parece proporcionar esta función de todos modos (a partir de julio de 2019).

Resumen

Parece funcionar, aunque un poco por casualidad. Aconsejaría precaución al escribir software de producción que funcione de forma contraria a la documentación.


Linux
  1. Consejos y trucos de variables de entorno de Linux

  2. Linux:¿dónde se almacena un semáforo con nombre?

  3. Cómo establecer la variable $Path en Linux

  4. C++/IDE de ensamblaje en Linux

  5. Mutex de todo el sistema en Python en Linux

Cómo compilar y ejecutar programas C, C++ en Linux

Cómo almacenar un comando de Linux como una variable en el script de Shell

Linux:¿monitoreo de todo el sistema de llamadas a una función de biblioteca?

Comando de exportación en Linux explicado

¿Qué es Subshell en Linux?

¿Cómo exportar permanentemente una variable en Linux?