Итак, продолжим тему системного программирования на php.

Демоны, shared memory и семафоры

Мы уже научились работать с очередями, по которым можно пересылать небольшие системные сообщения, и наверняка, столкнулись с задачей передавать большие объёмы данных. В наших любимых системах типа System V уже давно решена задача быстрой передачи и сохранения больших данных в памяти. И этот механизм называется Shared Memory.

Если вкратце, то данные в Shared Memory живут до перезагрузки системы. Так как данные находятся в памяти, то работают они намного быстрее чем если бы сохранялись в базе, где-нибудь в файле, или, прости господи, на стороннем сетевом ресурсе.

Работа с shared memory. Основы.

Давайте попробуем написать простой пример сохранения данных.

shared-memory-write-base.php

// Это самый правильный и рекомендуемый способ получать уникальный идентификатор. 
// На основе полученного пути, система вычленит иноды из таблицы ФС,
// и для пущей уникальности преобразует это число на основани второго параметра.
// Вторым параметром всегда идёт одна буква
$id = ftok(__FILE__, 'A');

// Создаём или открываем блок памяти
// Тут можно указать дополнительные параметры, например осообо большой объём блока
// или же права доступа для других пользователей к этому блоку памяти.

// Халявщики могут вместо id просто указать любое целочисленное значение :-)
$shmId = shm_attach($id);

// В шареде у нас есть переменные (любое целочисленное значение)
$var = 1;

// Смотрим, есть ли у нас требуемая переменная
if (shm_has_var($shmId, $var)) 
{
    // Если есть, считываем данные
    $data = (array)shm_get_var($shmId, $var);
} 
else 
{
    // Это случай, если данных никаких не было. На всякий пожарный
    $data = array();
}

// Сохраняем в полученном массиве значение этого файла
$data[time()] = file_get_contents(__FILE__);

// И записываем этот массив в память, указываем переменную где сохранить
shm_put_var($shmId, $var, $data);

// Просто?

Запустите этот скрипт несколько раз, что бы в памяти сохранились значения. А теперь давайте напишем скрипт только для чтения из памяти.

shared-memory-read-base.php

// Читаем данные из памяти 
$id = ftok(__DIR__ . '/shared-memory-write-base.php', 'A');
$shmId = shm_attach($id);
$var = 1;

// Смотрим, есть ли у нас требуемая переменная
if (shm_has_var($shmId, $var)) 
{
    // Если есть, считываем данные
    $data = (array)shm_get_var($shmId, $var);
} 
else 
{
    // Это случай, если данных никаких не было. На всякий пожарный
    $data = array();
}

// Перебираем полученные значения и сохраняем их в файлы
foreach ($data as $key => $value) 
{
    // Простой пример, создаём файлы из тех что мы сохранили ранее
    $path = '/tmp/' . $key . '.php';
    file_put_contents($path, $value);
    
    echo $path . PHP_EOL;
}

Работа с семафорами

Итак, в общих чертах уже должно быть понятно как работать с shared memory. И осталось выяснить пару ньюансов, например: “А что сделать если два процесса одновременно захотят записывать в один блок памяти?” или “Как сохранять бинарные файлы произвольного размера?”.

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

В коде это ещё выглядит ещё понятнее:

shared-memory-semaphors.php

// Давайте попробуем сохранить какой-нибудь бинарный файл, размером в пару мегабайт
// Этот скрипт выполняет следующий алгоритм:
// Если есть данные - он их читает, нет - Записывает данные в память
// При этом в моменты записи в память мы ставим признак блокировки - семафор

// Тут всё как обычно, читаем предыдущие комментарии
$id = ftok(__FILE__, 'A');

// Получаем ресурс семафора - признака блокировки. Нет ничего страшного, если мы
// используем тот же id что используется для получения ресурса shared memory
$semId = sem_get($id);

// Ставим блокировку. Тут есть ньюанс. Если другой процесс столкнётся с этой 
// блокировкой, он будет ожидать пока её не снимут
sem_acquire($semId);

// Укажите свой файл, например картинку
$data = file_get_contents(__DIR__.'/06050396.JPG', FILE_BINARY);

// Данные могут быть большими, поэтому предусмотрительно надо выделить так, что бы хватило
$shmId = shm_attach($id, strlen($data)+4096);
$var = 1;

if (shm_has_var($shmId, $var)) 
{
    // Получаем данные из памяти
    $data = shm_get_var($shmId, $var);
    
    // Сохраняем наш файл в другом месте
    $filename = '/tmp/' . time();
    file_put_contents($filename, $data, FILE_BINARY);
    
    // Удаляем блок памяти, что бы всё началось сначала :-)
    shm_remove($shmId);
} 
else 
{
    shm_put_var($shmId, $var, $data);    
}

// Освобождаем блокировку
sem_release($semId);

Теперь можно с помощью консольной утилиты md5summ сравнить оба файла, полученный и исходный. Или же, можно запустить полученный файл в графическом редакторе или в чём он у вас должен запускаться?

На этом знакомство с shared memory и семафорами я считаю законченным. А на домашнее задание хочу вас попросить написать код, который в демоне будет использовать семафоры и общую память.