Как записать в общую память из дочернего процесса - PullRequest
0 голосов
/ 02 апреля 2020

Я экспериментирую с дочерними процессами, потому что это кажется единственным способом заставить старую библиотеку C работать параллельно, которую нельзя использовать в потоках.

Мой минимальный пример записывает три целочисленных значения в отображенную память , В дочернем процессе я хочу изменить эти значения. Изменение последнего (третьего) значения работает, но при изменении только второго значения я получаю ошибку с таким выводом:

initial mapped memory in parent process:                      000000000000000000000000000000010000000000000000000000000000001000000000000000000000000000000011
mapped memory in child process before writing:                000000000000000000000000000000010000000000000000000000000000001000000000000000000000000000000011
mapped memory in child process after writing:                 0000000000000000000000000000000100000000000000000000000000011011
mapped memory in parent process after child process finished: 0000000000000000000000000000000100000000000000000000000000011011
1
27
terminate called after throwing an instance of 'std::invalid_argument'
  what():  bitset::_M_copy_from_ptr

Process finished with exit code 134 (interrupted by signal 6: SIGABRT)

Пример минимального кода такой:

#include <iostream>
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
#include <semaphore.h>

using namespace std;

int main(int /*argc*/, char* /*argv*/[])
{
    const char * shm_name = "/a_shm_name";
    const int SIZE = 4096;
    std::vector<int> v{1, 2, 3};

    int shm_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);

    ftruncate(shm_fd, 32 * v.size());

    void * ptr0 = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    //write into the memory segment
    void * ptr = ptr0;
    for(int i = 0; i < v.size(); i++)
    {
        std::string s = std::bitset<32>(v[i]).to_string();
        int count = sprintf((char*)ptr, "%s", s.c_str());
        ptr = ptr + count;
    }

    cout << "initial mapped memory in parent process:                      " << (char*)ptr0 << endl << flush;

    //fork
    int n1 = fork();
    if(n1 == 0)
    {
        //client process
        shm_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);

        ptr0 = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

        cout << "mapped memory in child process before writing:                " << (char*)ptr0 << endl << flush;
        //write into the memory segment
        //sem_wait(semId);
        ptr = ptr0 + 32 * 1;
        sprintf((char*)ptr, "%s", std::bitset<32>(27).to_string().c_str());
        cout << "mapped memory in child process after writing:                 " << (char*)ptr0 << endl << flush;

        exit(0);
    }
    else
    {
        //parent process
        wait(nullptr);
    }

    //child process finished, back in the parent process
    //client process
    shm_fd = shm_open(shm_name, O_RDONLY, 0666);

    ptr = mmap(0, SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);

    cout << "mapped memory in parent process after child process finished: " << (char*)ptr0 << endl << flush;
    for(int i = 0; i < v.size(); i++)
    {
        cout << std::bitset<32>(std::string((char*)ptr, 32)).to_ulong() << endl;
        ptr = ptr + 32;
    }

    munmap(ptr0, SIZE);

    shm_unlink(shm_name);

    return 0;
}

I У меня два вопроса:

  • Почему я не могу изменить только второе целое значение?
  • Существуют ли более эффективные методы для обмена данными между отдельными дочерними процессами?

1 Ответ

0 голосов
/ 02 апреля 2020

Это на самом деле не имеет отношения к процессам или mmap вообще: Более простая программа с похожими результатами .

#include <vector>
#include <bitset>
#include <iostream>

int main() {
    using std::cout, std::endl, std::flush;

    const int SIZE = 4096;
    std::vector<int> v{1, 2, 3};

    char buffer[SIZE];
    char * ptr0 = buffer;

    //write into the memory segment
    char * ptr = ptr0;
    for(unsigned int i = 0; i < v.size(); i++)
    {
        std::string s = std::bitset<32>(v[i]).to_string();
        int count = sprintf(ptr, "%s", s.c_str());
        ptr = ptr + count;
    }

    cout << "initial memory:                                " << ptr0 << endl << flush;

    //write into the memory segment
    ptr = ptr0 + 32 * 1;
    sprintf(ptr, "%s", std::bitset<32>(27).to_string().c_str());
    cout << "mapped memory in child process after writing:  " << ptr0 << endl << flush;
}

(я изменил все void* на char*, поскольку добавление чисел к void* не является стандартным C ++, хотя некоторые компиляторы имеют расширение, которое рассматривает его как добавление к char*.)

sprintf записывает нулевой терминатор после конца фактического вывод. Поэтому, когда вы используете его для записи «второго значения» в дочернем элементе, он помещает '\0' в первый байт «третьего значения». Кроме того, вы фактически полагались на '\0' в ((char*)ptr0)[3*32] после «третьего значения» для std::cout, чтобы знать, когда прекратить печать.

Вместо sprintf и operator<< с const char*, которые предназначены для строк с нулевым символом в конце, я бы предложил функции, которые просто копируют байты:

std::string data = std::bitset<32>(value).to_string();
std::copy(data.begin(), data.end(), (char*) ptr);

cout.write((char*) ptr, 32);
...