От shm_open
справочная страница:
Новый объект разделяемой памяти изначально имеет нулевую длину. Размер
Объект может быть установлен с помощью ftruncate (2). […] Функция shm_open ()
Сам по себе не создает общий объект указанного размера, потому что
Это дублирует существующую функцию, которая устанавливает размер
объект, на который ссылается дескриптор файла.
Разве это не подвергает приложение состоянию гонки? Рассмотрим следующий псевдокод:
int fd = shm_open("/foo", CREATE);
if ( fd is valid ) {
// created shm object, so set its size
ftruncate(fd, 128);
} else {
fd = shm_open("/foo", GET_EXISTING);
}
void* mem = mmap(fd, 128);
Так как shm_open
а также ftruncate
вызовы (вместе) не являются атомарными, вы можете иметь состояние гонки, при котором один процесс вызывает shm_open
(CREATE
дело) но перед звонком ftruncate
другой процесс вызывает shm_open
(GET_EXISTING
случай) и попытки mmap
объект размером 0 и, возможно, даже написать в него.
Я могу придумать два способа избежать этого состояния гонки:
Используйте мьютекс / семафор IPC для синхронизации всего этого, или …
Если это безопасно (для POSIX), позвоните ftruncate
в обоих CREATE
а также GET_EXISTING
случаев.
Какой метод является предпочтительным для избежания этого состояния гонки?
Ваш подход ftruncate
из обоих) должно работать, но нужен способ синхронизации использование содержимого сегмента общей памяти в любом случае. Поскольку память изначально пуста (заполнена нулями) и, следовательно, не содержит допустимого объекта синхронизации, если вы не собираетесь использовать свою собственную атомарность, вам все равно понадобится вторичная форма синхронизации для управления доступом к общей памяти.
Я бы подумал, что вместо того, чтобы использовать несколько процессов для создания или открытия сегмента совместно используемой памяти с фиксированным именем, вы бы хотели, чтобы процесс владельца отвечал за создание сегмента с случайный имя, используя O_EXCL
чтобы избежать случайных или злонамеренных коллизий, а затем передать имя, как только вы успешно открыли его, измерили его размер и создали в нем объекты синхронизации, другим процессам, которым необходимо получить к нему доступ.
Как @R. ссылаясь на еще одну проблему здесь, это то, что после создания файла все еще есть окно до того, как содержимое, такое как мьютекс, будет инициализировано и готово к использованию.
Немного отличающееся решение от вышеупомянутого:
Попробуйте открыть ().
Если open () завершается успешно, просто отобразите map () и используйте с необходимой гарантией (см. Ниже), что содержимое уже инициализировано и готово к работе.
Если open () завершается неудачно, создайте и инициализируйте временный файл, затем попробуйте жестко связать () временный файл как нужный файл и отсоединить () временное имя.
Если link () успешно выполняется, мы теперь сделали инициализированный файл доступным для себя и других процессов. Если link () завершается неудачно с EEXIST, сначала появляется другой процесс (в отличие от rename (), link () завершается ошибкой, если целевое имя существует). В любом случае, наше повторное открытие () теперь должно завершиться успешно с инициализированным готовым к использованию файлом.
С этой стратегией явно существует условие состязания для инициализации временного файла, однако при условии, что процесс инициализации идемпотентен, не слишком дорог для ресурсов, и каждый процесс выбирает уникальный временный файл, это не имеет никакого значения. Если многократная инициализация может быть проблемой, решение состоит в том, чтобы разделить инициализацию на двухэтапный процесс, причем первый этап является просто мьютексом в файле для использования для защиты от многократной инициализации остальной части файла во время второго этапа.