Не удается прочитать через общую память - PullRequest
0 голосов
/ 04 июля 2018

Я пытаюсь опубликовать некоторые случайные вещи в общей памяти; и по какой-то странной причине читатель не понимает, что написал отправитель

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <cstdio>

class SHM {
    volatile char* _ptr;
public:
    SHM() {
        const auto handle = shm_open("myTest", O_RDWR|O_CREAT, 0666);
        const auto size =  4 * 1024 * 1024;
        if (-1 == ftruncate(handle, size)) {
            throw;
        }
        _ptr = (volatile char*)mmap(0,size , PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);

        if(_ptr == MAP_FAILED){
            throw;
        }

                int rc = fchmod(handle, 0666);
                if (rc == -1) {
            throw;
                }
    }

    bool read(uint64_t& magic, uint64_t& time) {
        const uint64_t newVal = *(uint64_t*)_ptr;
        if (newVal != magic) {
            magic = newVal;
            printf("value changed!!!\n");
            time = *(uint64_t*)(_ptr + sizeof(magic));
            return true;
        }
        //printf("old value: %lu\n", newVal);
        return false;
    }

    void publish(const uint64_t time) {
        __sync_fetch_and_add((uint64_t*)_ptr, time);
        __sync_synchronize();
        *(uint64_t*)(_ptr + sizeof(uint64_t)) = time;
    }
};

Вот отправитель:

#include <ctime>
#include <unistd.h>
#include <cstdlib>
#include <cstdint>
#include "shm.h"

int main() {
    SHM shm;
    timespec t;
    for (auto i = 0; i < 10000; i++) {
        if (0 == clock_gettime(CLOCK_REALTIME, &t)) {
            const uint64_t v = t.tv_sec * 1000 * 1000 * 1000 + t.tv_nsec;
            shm.publish(v);
            printf("published %lu\n", v);
            usleep(100);
        }
    }
}

Вот читатель:

#include <iostream>
#include "shm.h"

int main() {
    SHM shm;
    uint64_t magic = 0;
    uint64_t t = 0;
    while (true) {
        if (shm.read(magic, t)) {
            printf("%lu, %lu\n", magic, t);
        }
    }
}

Если я перезапущу устройство чтения, оно действительно сможет прочитать последнее значение, записанное отправителем.

Однако, если я сначала запускаю считыватель, а затем отправителя, все значения, которые записывает отправитель, не воспринимаются читателем.

Чтобы сделать это еще более странным, если я раскомментирую оператор printf в SHM :: read (), тогда читатель может иногда его поднять.

Есть идеи?

Версия GCC:

g++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)

1 Ответ

0 голосов
/ 04 июля 2018

Я заметил несколько проблем, но не уверен, что они решат вашу проблему.

  1. name для shm_open должно начинаться с / для портативного использования.
  2. В read и publish приведения не должны сбрасывать volatile. Например: const uint64_t newVal = *(uint64_t volatile*)_ptr;. Еще лучше, бросьте volatile и используйте std::atomic.

Несмотря на то, что задействованы различные процессы, это все равно имеет место для доступа к одним и тем же объектам более чем одним потоком выполнения, и хотя бы один из этих потоков изменяет общие объекты.


Я внес вышеуказанные изменения. Использование std::atomic исправило это:

class SHM {
    void* _ptr;
public:
    SHM() {
        const auto handle = shm_open("/myTest", O_RDWR|O_CREAT, 0666);
        const auto size =  4 * 1024 * 1024;
        if (-1 == ftruncate(handle, size))
            throw;

        _ptr = mmap(0,size , PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);

        if(_ptr == MAP_FAILED)
            throw;
    }

    bool read(uint64_t& magic, uint64_t& time) {
        auto p = static_cast<std::atomic<uint64_t>*>(_ptr);
        const uint64_t newVal = p[0];
        if (newVal != magic) {
            magic = newVal;
            printf("value changed!!!\n");
            time = p[1];
            return true;
        }
        return false;
    }

    void publish(const uint64_t time) {
        auto p = static_cast<std::atomic<uint64_t>*>(_ptr);
        p[0] += time;
        p[1] = time;
    }
};

void sender() {
    SHM shm;
    timespec t;
    for (auto i = 0; i < 10000; i++) {
        if (0 == clock_gettime(CLOCK_REALTIME, &t)) {
            const uint64_t v = t.tv_sec * 1000 * 1000 * 1000 + t.tv_nsec;
            shm.publish(v);
            printf("published %lu\n", v);
            usleep(100);
        }
    }
}

void reader() {
    SHM shm;
    uint64_t magic = 0;
    uint64_t t = 0;
    while (true) {
        if (shm.read(magic, t)) {
            printf("%lu, %lu\n", magic, t);
        }
    }
}

int main(int ac, char**) {
    if(ac > 1)
        reader();
    else
        sender();
}

С std::atomic вы можете иметь больше контроля. E.g.:

struct Data {
    std::atomic<uint64_t> time;
    std::atomic<uint64_t> generation;
};

// ...

    bool read(uint64_t& generation, uint64_t& time) {
        auto data = static_cast<Data*>(_ptr);

        auto new_generation = data->generation.load(std::memory_order_acquire); // 1. Syncronizes with (2).
        if(generation == new_generation)
            return false;

        generation = new_generation;
        time = data->time.load(std::memory_order_relaxed);
        printf("value changed!!!\n");
        return true;
    }

    void publish(const uint64_t time) {
        auto data = static_cast<Data*>(_ptr);

        data->time.store(time, std::memory_order_relaxed);
        data->generation.fetch_add(time, std::memory_order_release);  // 2. (1) Synchronises with this store.
    }
...