Действительно ли возможно сохранить указатели в общей памяти, не используя смещения?

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

  1. x = считайте p
  2. y = x + смещение
  3. Увеличьте refcount в y

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

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

8
задан Joseph Garvin 22 March 2010 в 04:21
поделиться

4 ответа

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

  1. x = читать p
  2. y = x + смещение Приращение
  3. refcount at y
 // 
mov edi, Destination 
mov edx, DataOffset 
mov ecx, NewData 
 @Repeat: {{1} } mov eax, [edi + edx] // загружаем OldData 
 // Здесь вы также можете увеличить eax и сохранить в [edi + edx] 
lock cmpxchg dword ptr [edi + edx], ecx 
jnz @Repeat 
 // 
 
3
ответ дан 5 December 2019 в 15:22
поделиться

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

2
ответ дан 5 December 2019 в 15:22
поделиться

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

shgmet, shmat, shmctl, shmdt

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

shmat () подключает общую память {{1} } сегмент, идентифицированный с помощью 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));
}
3
ответ дан 5 December 2019 в 15:22
поделиться

Вы не должны бояться составить адрес наугад, потому что ядро ​​просто отклонит адреса, которые ему не нравятся (конфликтующие). См. Мой ответ 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
ответ дан 5 December 2019 в 15:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: