Можно ли хранить указатели в разделяемой памяти без использования смещений? - PullRequest
8 голосов
/ 22 марта 2010

При использовании разделяемой памяти каждый процесс может отображать совместно используемую область в другую область своего соответствующего адресного пространства. Это означает, что при хранении указателей в общей области вам нужно сохранить их как смещения начала общей области. К сожалению, это усложняет использование атомарных инструкций (например, если вы пытаетесь написать алгоритм без блокировки ). Например, скажем, у вас есть куча узлов с подсчетом ссылок в общей памяти, созданных одним писателем. Автор периодически обновляет указатель «p», чтобы он указывал на действительный узел с положительным счетчиком ссылок. Читатели хотят атомарно записать в «p», потому что он указывает на начало узла (структуры), первый элемент которого является счетчиком ссылок. Поскольку p всегда указывает на действительный узел, увеличение счетчика ссылок безопасно и позволяет безопасно разыменовывать p и обращаться к другим членам. Однако это работает только тогда, когда все находится в одном и том же адресном пространстве. Если узлы и указатель 'p' хранятся в разделяемой памяти, клиенты испытывают состояние гонки:

  1. x = read p
  2. y = x + offset
  3. Инкремент увеличения счета при y

На шаге 2 p может измениться, и x может больше не указывать на действительный узел. Единственный обходной путь, который я могу придумать, - это как-то заставить все процессы договориться о том, где отображать общую память, чтобы реальные области, а не смещения могли быть сохранены в области mmap. Есть ли способ сделать это? Я вижу MAP_FIXED в документации mmap, но не знаю, как выбрать адрес, который был бы безопасным.

Редактировать: Используя встроенную сборку и префикс 'lock' на x86, возможно, возможно построить "инкремент ptr X со смещением Y на значение Z"? Эквивалентные варианты на других архитектурах? Не написано много сборок, не знаю, существуют ли необходимые инструкции.

Ответы [ 5 ]

3 голосов
/ 22 марта 2010

На низком уровне атомная инженерия x86 может выполнить все шаги этого дерева сразу:

  1. х = читать р
  2. y = x + смещение Увеличение
  3. refcount at y
//
      mov  edi, Destination
      mov  edx, DataOffset
      mov  ecx, NewData
 @Repeat:
      mov  eax, [edi + edx]    //load OldData
//Here you can also increment eax and save to [edi + edx]          
      lock cmpxchg dword ptr [edi + edx], ecx
      jnz  @Repeat
//
3 голосов
/ 20 апреля 2010

Это тривиально в системе UNIX;просто используйте функции совместно используемой памяти:

shgmet, shmat, shmctl, shmdt

void * shmat (int shmid, const void * shmaddr, int shmflg);

shmat () присоединяет сегмент общей памяти, идентифицируемый shmid, к адресному пространству вызывающего процесса.Адрес подключения указывается с помощью shmaddr с одним из следующих критериев:

Если shmaddr равен NULL, система выбирает подходящий (неиспользуемый) адрес для присоединения сегмента.

Просто укажите свой адрес здесь;Например, 0x20000000000

Если вы используете shmget (), используя один и тот же ключ и размер в каждом процессе, вы получите один и тот же сегмент совместно используемой памяти.Если вы используете shmat () по одному и тому же адресу, виртуальные адреса будут одинаковыми во всех процессах.Ядро не заботится о том, какой диапазон адресов вы используете, если оно не конфликтует с тем, где оно обычно назначает объекты.(Если вы пропустите адрес, вы увидите общую область, в которую он любит помещать вещи; также проверьте адреса в стеке и вернулся из malloc () / new [].)

В Linux сделайтеУбедитесь, что root устанавливает SHMMAX в / proc / sys / kernel / shmmax на достаточно большое число, чтобы вместить ваши сегменты общей памяти (по умолчанию 32 МБ).

Что касается атомарных операций, вы можете получить их все из ядра Linuxисточник, например

include / asm-x86 / atomic_64.h

/*
 * Make sure gcc doesn't try to be clever and move things around
 * on us. We need to use _exactly_ the address the user gave us,
 * not some alias that contains the same information.
 */
typedef struct {
        int counter;
} atomic_t;

/**
 * atomic_read - read atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */
#define atomic_read(v)          ((v)->counter)

/**
 * atomic_set - set atomic variable
 * @v: pointer of type atomic_t
 * @i: required value
 *
 * Atomically sets the value of @v to @i.
 */
#define atomic_set(v, i)                (((v)->counter) = (i))


/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic_add(int i, atomic_t *v)
{
        asm volatile(LOCK_PREFIX "addl %1,%0"
                     : "=m" (v->counter)
                     : "ir" (i), "m" (v->counter));
}

64-разрядная версия:

typedef struct {
        long counter;
} atomic64_t;

/**
 * atomic64_add - add integer to atomic64 variable
 * @i: integer value to add
 * @v: pointer to type atomic64_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic64_add(long i, atomic64_t *v)
{
        asm volatile(LOCK_PREFIX "addq %1,%0"
                     : "=m" (v->counter)
                     : "er" (i), "m" (v->counter));
}
2 голосов
/ 20 апреля 2010

Вы не должны бояться составлять адрес случайным образом, потому что ядро ​​просто отклонит адреса, которые ему не нравятся (конфликтующие). Смотрите мой shmat() ответ выше, используя 0x20000000000

С mmap:

void * mmap (void * addr, size_t length, int prot, int flags, int fd, off_t offset);

Если addr не равен NULL, то ядро принимает это как подсказку о том, где поместите отображение; в Linux отображение будет создано на следующем более высокая граница страницы. Адрес новое отображение возвращается как Результат звонка.

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

MAP_SHARED Поделиться этим отображением. Обновления для отображения видны другие процессы, которые отображают это файл, и переносятся на основной файл. Файл не может на самом деле обновляться до msync (2) или munmap () вызывается.

ОШИБКА

EINVAL Нам не нравятся адреса, длины или смещение (например, они слишком велики или не выровнен по границе страницы).

2 голосов
/ 22 марта 2010

У нас есть код, который похож на описание вашей проблемы. Мы используем отображенный в памяти файл, смещения и блокировку файла. Мы не нашли альтернативу.

1 голос
/ 18 августа 2016

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

...