как выполняется синхронизация в совместно используемой памяти данных Linux - PullRequest
0 голосов
/ 12 декабря 2018

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

#define  NOT_READY  -1
#define  FILLED     0
#define  TAKEN      1

struct Memory {
     int  status;
     int  data[4];
};

Предположим, что сервер и клиент находятся в текущем каталоге.Сервер использует ftok () для генерации ключа и использует его для запроса общей памяти.Перед заполнением общей памяти данными, состояние устанавливается в NOT_READY.После заполнения общей памяти сервер устанавливает статус FILLED.Затем сервер ожидает, пока состояние не станет TAKEN, что означает, что клиент принял данные.

Ниже приведена программа сервера.Нажмите здесь, чтобы загрузить копию этой серверной программы server.c.

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

#include  "shm-02.h"

void  main(int  argc, char *argv[])
{
     key_t          ShmKEY;
     int            ShmID;
     struct Memory  *ShmPTR;

     if (argc != 5) {
          printf("Use: %s #1 #2 #3 #4\n", argv[0]);
          exit(1);
     }

     ShmKEY = ftok(".", 'x');
     ShmID = shmget(ShmKEY, sizeof(struct Memory), IPC_CREAT | 0666);
     if (ShmID < 0) {
          printf("*** shmget error (server) ***\n");
          exit(1);
     }
     printf("Server has received a shared memory of four integers...\n");

     ShmPTR = (struct Memory *) shmat(ShmID, NULL, 0);
     if ((int) ShmPTR == -1) {
          printf("*** shmat error (server) ***\n");
          exit(1);
     }
     printf("Server has attached the shared memory...\n");

     ShmPTR->status  = NOT_READY;
     ShmPTR->data[0] = atoi(argv[1]);
     ShmPTR->data[1] = atoi(argv[2]);
     ShmPTR->data[2] = atoi(argv[3]);
     ShmPTR->data[3] = atoi(argv[4]);
     printf("Server has filled %d %d %d %d to shared memory...\n",
            ShmPTR->data[0], ShmPTR->data[1], 
            ShmPTR->data[2], ShmPTR->data[3]);
     ShmPTR->status = FILLED;

     printf("Please start the client in another window...\n");

     while (ShmPTR->status != TAKEN)
          sleep(1);

     printf("Server has detected the completion of its child...\n");
     shmdt((void *) ShmPTR);
     printf("Server has detached its shared memory...\n");
     shmctl(ShmID, IPC_RMID, NULL);
     printf("Server has removed its shared memory...\n");
     printf("Server exits...\n");
     exit(0);
}

Клиентская часть аналогична серверной.Он ждет, пока статус ЗАПОЛНЕН.Затем клиенты извлекают данные и устанавливают статус в TAKEN, информируя сервер о том, что данные были приняты.Ниже приведена клиентская программа.Нажмите здесь, чтобы загрузить копию этой серверной программы client.c.

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

#include  "shm-02.h"

void  main(void)
{
     key_t          ShmKEY;
     int            ShmID;
     struct Memory  *ShmPTR;

     ShmKEY = ftok(".", 'x');
     ShmID = shmget(ShmKEY, sizeof(struct Memory), 0666);
     if (ShmID < 0) {
          printf("*** shmget error (client) ***\n");
          exit(1);
     }
     printf("   Client has received a shared memory of four integers...\n");

     ShmPTR = (struct Memory *) shmat(ShmID, NULL, 0);
     if ((int) ShmPTR == -1) {
          printf("*** shmat error (client) ***\n");
          exit(1);
     }
     printf("   Client has attached the shared memory...\n");

     while (ShmPTR->status != FILLED)
          ;
     printf("   Client found the data is ready...\n");
     printf("   Client found %d %d %d %d in shared memory...\n",
                ShmPTR->data[0], ShmPTR->data[1], 
                ShmPTR->data[2], ShmPTR->data[3]);

     ShmPTR->status = TAKEN;
     printf("   Client has informed server data have been taken...\n");
     shmdt((void *) ShmPTR);
     printf("   Client has detached its shared memory...\n");
     printf("   Client exits...\n");
     exit(0);
}

1 Ответ

0 голосов
/ 12 декабря 2018

Может кто-нибудь сказать, есть ли лучший способ синхронизации в разделяемой памяти?

Определенно, да.Я бы сказал, что то, как вы тратите циклы ЦП в режиме «занят-ожидание» (while (ShmPTR->status != FILLED) ;), уже является фатальной ошибкой.

Обратите внимание, что общая память POSIX имеет гораздо более разумный интерфейс, чем старый SysV.Подробнее см. man 7 shm_overview .

Для примитивов синхронизации есть две разные цели:

  1. Синхронизация данных

    Для защитыданные против одновременной модификации, и для того, чтобы каждый читатель получал согласованное представление данных, существует три основных подхода:

    • Атомный доступ

      Атомный доступ требует аппаратной поддержки,и, как правило, поддерживается только для стандартных единиц измерения машинного слова (32 или 64 бита).

    • Мьютексы и переменные условия

      Мьютексы являются взаимоисключающими блокировками.Идея состоит в том, чтобы захватить мьютекс перед проверкой или изменением значения.

      Переменные условия - это в основном неупорядоченные очереди для потоков или процессов, ожидающих «условия».Библиотека pthreads POSIX включает средства для атомарного освобождения мьютекса и ожидания условной переменной.Это делает ожидание изменения набора данных тривиальным для реализации, если каждый модификатор сигнализирует или передает переменную условия после каждой модификации.

    • Блокировки чтения-записи.

      rwlock - это примитив, который позволяет любое количество одновременных «блокировок чтения», но только одна «блокировка записи» может быть удержана на нем в любое время.Идея состоит в том, что каждый читатель захватывает блокировку чтения перед проверкой данных, а каждый писатель - блокировку записи перед ее изменением.Это работает лучше всего, когда данные чаще проверяются, чем изменяются, и механизм ожидания изменения не требуется.

  2. Синхронизация процесса

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

    • Семафоры

      A Семафор POSIX - это в основном непрозрачный неотрицательный счетчик, который вы инициализируете для чего угодно (ноль)или положительное значение, в пределах, установленных реализацией).

      sem_wait() проверяет счетчик.Если он ненулевой, он уменьшает счетчик и продолжает выполнение.Если счетчик равен нулю, он блокируется, пока другой поток / процесс не вызовет sem_post() на счетчике.

      sem_post() увеличивает счетчик.Это один из редких примитивов синхронизации, которые можно использовать в обработчике сигналов .

    • Барьеры

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

      Linux не реализует барьеры POSIX (pthread_barrier_init(), pthread_barrier_wait(), pthread_barrier_destroy()),но вы можете легко добиться того же, используя мьютекс, счетчик (подсчитывающий количество дополнительных процессов, необходимых для освобождения всех официантов) и переменную условия.


Существует много лучших способов реализации упомянутой пары сервер-клиент (где совместно используемая память содержит флаг и некоторые данные).

Для целостности данных и управления изменениями - мьютекс и один или двапеременные условия должны быть использованы.(Если сервер может изменить данные в любое время, достаточно одной переменной условия (changed); если сервер должен подождать, пока клиент прочитает данные, прежде чем их изменять, потребуется две (changed и observed).)

Вот пример структуры, которую вы можете использовать для описания сегмента общей памяти:

#ifndef   SHARED_H
#define   SHARED_H
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

struct shared_data {
    /* Shared memory data */
};

struct shared {
    pthread_mutex_t     lock;
    pthread_cond_t      change;     /* Condition variable for clients waiting on data changes */
    pthread_cond_t      observe;    /* Condition variable for server waiting on data observations */
    unsigned long       changed;    /* Number of times data has been changed */
    unsigned long       observed;   /* Number of times current data has been observed */
    struct shared_data  data;
};

/* Return the size of 'struct shared', rounded up to a multiple of page size. */
static inline size_t  shared_size_page_aligned(void)
{
    size_t  page, size;
    page = (size_t)sysconf(_SC_PAGESIZE);
    size = sizeof (struct shared) + page - 1;
    return size - (size % page);
}

#endif /* SHARED_H */

Поля changed и observed являются счетчиками, которые помогают избежатьокна проверки на время использования.Важно, чтобы перед обращением к разделяемой памяти поток выполнял pthread_mutex_lock(&(shared_memory->lock)), чтобы обеспечить согласованное представление данных.

Если поток / процесс проверяет данные, он должен выполнить

    shared_memory->observed++;
    pthread_cond_broadcast(&(shared_memory->observe));
    pthread_mutex_unlock(&(shared_memory->lock));

, а если поток / процесс изменяет данные, он должен выполнить

    shared_memory->modified++;
    shared_memory->observed = 0;
    pthread_cond_broadcast(&(shared_memory->change));
    pthread_mutex_unlock(&(shared_memory->lock));

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

...