C Linux pthreads: отправка данных из одного потока в другой с использованием общей памяти дает неожиданный результат

#c #linux #pthreads #shared-memory

#c #linux #pthreads #разделяемая память

Вопрос:

Я пишу программу, которая передаст 50 целых чисел из одного потока в другой, используя общую память, и после получения целых чисел поток получателя распечатает их.

Код компилируется без каких-либо ошибок или предупреждений, но когда я запускаю его, я получаю следующий вывод:

 pthread_create() for thread 1 returns: 0
pthread_create() for thread 2 returns: 0
Segmentation fault (core dumped)
  

Вот мой код на данный момент:

 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/msg.h>
#include <string.h>

//define declarations
#define SIZE     50

//function declarations
void *send_data();
void *receive_data();

int main()
{
     pthread_t thread1;
     pthread_t thread2;
     int ret_val_t1;
     int ret_val_t2;

     //create thread1
     ret_val_t1 = pthread_create( amp;thread1, NULL, send_data, NULL);
     if(ret_val_t1)
     {
         fprintf(stderr,"Error - pthread_create() return value: %dn",ret_val_t1);
         exit(EXIT_FAILURE);
     }

     //create thread2
     ret_val_t2 = pthread_create( amp;thread2, NULL, receive_data, NULL);
     if(ret_val_t2)
     {
         fprintf(stderr,"Error - pthread_create() return value: %dn",ret_val_t2);
         exit(EXIT_FAILURE);
     }

     printf("pthread_create() for thread 1 returns: %dn",ret_val_t1);
     printf("pthread_create() for thread 2 returns: %dn",ret_val_t2);

     //wait untill threads are done with their routines before continuing with main thread
     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 

     exit(EXIT_SUCCESS);
}

void *send_data(){
        int shmid;
    int *array;
    int i = 0;
    int SizeMem;
    key_t key = 12345;

    SizeMem = sizeof(*array)*SIZE;

    shmid = shmget(key, SIZE*sizeof(int), IPC_CREAT);

    array = (int *)shmat(shmid, 0, 0);


    for(i=0; i<SIZE; i  )
    {
          array[i] = i;
    }

    for(i=0; i<SIZE; i  )
    {
            printf("n%d---n", array[i]);
    }

    printf("nWritting to memory succesful--n");

    shmdt((void *) array);
}

void *receive_data(){
    int shmid;
    int *array;
    int i = 0;
    key_t key = 12345;

    shmid = shmget(key, SIZE*sizeof(int), IPC_EXCL);

    array = shmat(shmid, 0, SHM_RDONLY);

    for(i=0; i<SIZE; i  )
        {
            printf("n%d---n", array[i]);
        }

        printf("nRead to memory succesful--n");

        shmdt((void *) array);
}
  

Комментарии:

1. Зачем вам использовать общую память SysV для передачи данных между потоками одного и того же процесса, мне непонятно.

2. Мне было интересно то же самое, что и @EOF. Это просто упражнение по использованию общей памяти? Я, по общему признанию, мало что знаю об общей памяти, но я думал, что она использовалась для обмена данными между различными процессами

3. @EOF amp; yano Это всего лишь упражнение.

4. @Nikolaj Несмотря на это, я бы не советовал использовать разделяемую память SysV. Эквивалентные возможности POSIX намного более разумны и применимы.

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

Ответ №1:

Воспользуйтесь ipcs утилитой и посмотрите на результат из ipcs -m

Согласно справочной странице shmget:

КРАТКОЕ ОПИСАНИЕ top

    #include <sys/ipc.h>
   #include <sys/shm.h>

   int shmget(key_t key, size_t size, int shmflg);
  

В дополнение к вышеупомянутым флагам, 9 младших разрядов shmflg определяют разрешения, предоставленные владельцу, группе и другим. Эти биты имеют тот же формат и то же значение, что и аргумент mode в open(2). В настоящее время разрешения на выполнение не используются системой.

В строке кода

 shmid = shmget(key, SIZE*sizeof(int), IPC_CREAT);
  

«9 младших значащих бит shmflg» установлены равными нулю.

Ни у кого нет прав на чтение / запись вашего сегмента общей памяти.

Это было бы лучше:

 shmid = shmget(key, SIZE*sizeof(int), IPC_CREAT | 0600 );
  

Это дало бы пользователю-владельцу разрешение на чтение / запись.

Комментарии:

1. @Nikolaj Каков результат ipcs -m ? Вы также не проверяете возвращаемые значения на наличие ошибок. Поскольку у вас нет доступа на чтение или запись, ваш shmat() вызов вполне может завершиться ошибкой.

2. ipcs -m вывод показал, что общая память не выделена.

3. Это не имеет большого значения, поскольку у создателя всегда есть доступ для записи к ресурсу. Это первый метод блокировки, реализованный в unix (и единственный до версии 7) Поскольку сегмент памяти создается в процессе и используется только внутри, доступ на запись гарантирован, даже если mod mask установлен в 0 .

4. Отсутствие выделенной общей памяти, вероятно, является результатом того, что SIGSEGV запускался до создания общей памяти.

5. @Nikolaj Если ipcs -m не отображаются какие-либо сегменты общей памяти, это означает, что ваш вызов shmget() завершается неудачей, поскольку ваш код никогда не удаляет какой-либо сегмент общей памяти.

Ответ №2:

В этой ситуации разделяемая память является дорогостоящим делом. И вы пытаетесь получить доступ из того же процесса, здесь нет никакой пользы.

Лучший способ справиться с вашей ситуацией — это:

Потоки родительского процесса будут иметь доступ к кучной памяти родительского процесса. Это простая концепция в использовании. Создайте объект кучи для родительского процесса и предоставьте общий доступ к своим потокам.

Как сказано во многих комментариях, разделяемая память является одной из концепций «межпроцессной коммуникации», такой как каналы или именованные каналы или очередь сообщений и т.д….

Если вы используете для практики, то все в порядке. Но, помимо использования PROD, это дорогостоящее дело.

Если вы не применяете блокировки и ограничение доступа к вашей подключенной / совместно используемой памяти должным образом, и вы не находитесь за защищенным брандмауэром, то вы приветствуете злоумышленников.

Комментарии:

1. Я использую это просто как упражнение.

2. Это нормально. Я добавил это в информационных целях 🙂

Ответ №3:

Одно условие гонки возникает из вашего кода. Ваш второй поток может сначала получить доступ к процессору и попытаться использовать еще не созданный сегмент памяти. Поскольку он не существует, и поскольку вы не проверяете наличие ошибок, вы можете получить SIGSEGV от попытки доступа к памяти по NULL адресу.

Вы должны установить устройства синхронизации, которые запрещают второму потоку получать доступ к общей памяти до того, как она была создана и заполнена данными. Что-то вроде.

 pthread_mutex_t lock;
pthread_cond_t shm_created_and_filled;
  

затем в main вы инициализируете lock и shm_created_and_filled .

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