Реализация в UNIX
Для завершения реализации системы буферов в разделяемой памяти нужны еще два компонента. Это способ выделения блока разделяемой памяти и отображения его на адресное пространство процесса, а также механизм синхронизации для предотвращения одновременного доступа к списку свободных. Для работы с разделяемой памятью следует воспользоваться механизмом, разработанным в свое время для версии SysV. Можно было бы вместо него применить отображенный на память файл, как в Windows. Кроме того, есть еще разделяемая память в стандарте POSIX - для систем, которые ее поддерживают.
Для работы с разделяемой памятью SysV понадобятся только два системных вызова:
#include <sys/shm.h>
int shmget( key_t key, size_t size, int flags );
Возвращаемое значение: идентификатор сегмента разделяемой памяти в случае успеха, -1 - в случае ошибки.
void shmat( int segid, const void *baseaddr, int flags );
Возвращаемое значение: базовый адрес сегмента в случае успеха, -1 - в случае ошибки.
Системный вызов shmget применяется для выделения сегмента разделяемой памяти. Первый параметр, key, - это глобальный для всей системы уникальный идентификатор, сегмента. Сегмент будет идентифицироваться целым числом, представление которого в коде ASCII равно SMBM.
Примечание: Использование пространства имен, отличного от файловой системы, считается одним из основных недостатков механизмов IPC, появившихся еще в системе SysV. Для отображения имени файла на ключ IPС можно применить функцию ft ok, но это отображение не будет уникальным. Кроме того, как отмечается в книге [Stevens 1999], описанная в стандарте SVR4 функцияft ok дает коллизию (то есть два имени файла отображаю на один и тот же ключ) с вероятностью 75%.
Параметр size задает размер сегмента в байтах. Во многих UNIX-систем его значение округляется до величины, кратной размеру страницы. Параметру flags задает права доступа и другие атрибуты сегмента. Значения SHM_R и SHM определяют соответственно права на чтение и на запись для владельца. Права для группы и для всех получают путем сдвига этих значений вправо на три (для группы) или шесть (для всех) бит. Иными словами, право на запись для группы - это SHM_W » 3, а право на чтение для всех - SHM_R >> 6. Когда в параметр flags с помощью побитовой операции OR включается флаг IPC_CREATE, создается сегмент, если раньше его не было. При дополнительном включении флага IPC_EXCL ghmget вернет код ошибки EEXIST, если сегмент уже существует.
Вызов shmget только создает сегмент в разделяемой памяти. Для отображения его в адресное пространство процесса нужно вызвать shmat. Параметр segid- это идентификатор сегмента, который вернул вызов shmget. При желании можно указать адрес baseaddr, на который ядро должно отобразить сегмент, но обычно этот параметр оставляют равным NULL, позволяя ядру самостоятельно выбрать адрес. Параметр flags используется, если значение baseaddr не равно NULL, - он управляет выравниваем заданного адреса на приемлемую для ядра границу.
Для построения механизма взаимного исключения следует воспользоваться SysV-семафорами. Хотя они небезупречны (в частности, им присуща та же проблема нового пространства имен, что и разделяемой памяти), SysV-семафоры широко используются в современных UNIX-системах и, следовательно, обеспечивают максимальную переносимость. Как и в случае разделяемой памяти, сначала надо получить и инициализировать семафор, а потом уже его применять. В данной ситуации понадобятся три относящихся к семафорам системных вызова.
Вызов semget аналогичен shmget: он получает у операционной системы семафор и возвращает его идентификатор. Параметр key имеет тот же смысл, что и для shmget - он именует семафор. В SysV-семафоры выделяются группами, и параметр nsems означает, сколько семафоров должно быть в запрашиваемой группе. Параметр flags такой же, как для shmget.
#include <sys/sem.h>
int semget( key_t key, int nsems, int flags );
Возвращаемое значение: идентификатор семафора в случае успеха, -1 - в случае ошибки.
int semctl( int semid, int semnum, int cmd, ... );
Возвращаемое значение: неотрицательное число в случае успеха, -1 - в случае ошибки.
int semop( int semid, struct sembuf *oparray, size_t nops ); Возвращаемое значение: 0 в случае успеха, -1 - в случае ошибки.
Здесь использована semctl для задания начального значения семафора. Этот вызов служит также для установки и получения различных управляющих параметров, связанных с семафором. Параметр semid - это идентификатор семафора, ращенный вызовом semget. Параметр semnum означает конкретный семафор из группы. Поскольку будет выделяться только один семафор, значение этого параметра всегда равно нулю. Параметр cmd- это код выполняемой операции.
У вызова semget могут быть и дополнительные параметры, о чем свидетельствует многоточие в прототипе.
Вызов semop используется для увеличения или уменьшения значения семафора. Когда процесс пытается уменьшить семафор до отрицательного значения, он переводится в состояние ожидания, пока другой процесс не увеличит семафор до значения, большего или равного тому, на которое первый процесс пытался его уменьшить. Поскольку надо использовать семафоры в качестве мьютексов, следует уменьшать значение на единицу для блокировки списка свободных и увеличивать на единицу - для разблокировки. Так как начальное значение семафора равно единице, в результате процесс, пытающийся заблокировать уже блокированный список свободных, будет приостановлен.
Параметр semid- это идентификатор семафора, возвращенный semget. Параметр ораrrау указывает на массив структур sembuf, в котором заданы операции над одним или несколькими семафорами из группы. Параметр nops задает число элементов в массиве ораггау.
Показанная ниже структура sembuf содержит информацию о том, к какому семафору применить операцию (sem_num), увеличить или уменьшить значение семафора (sem_op), а также флаг для двух специальных действий (sem_f lg):
struct sembuf {
u_short sem__num; /* Номер семафора. */
short sem_op; /* Операция над семафором. */
short sem_flg; /* Флаги операций. */
};
В поле sem_f lg могут быть подняты два бита флагов:
Теперь рассмотрим UNIX-зависимую часть кода системы буферов в разделяемой памяти (листинг 3.30).
Листинг 3.30. Функция init_smb для UNIX
1 #include <sys/shm.h>
2 #include <sys/sem.h>
3 #define MUTEX_KEY Ox534d4253 /* SMBS */
4 #define SM_KEY Ox534d424d /* SMBM */
5 #define lock_buf() if ( semop( mutex, &lkbuf, 1 ) < 0 ) \
6 error( 1, errno, "ошибка вызова semop" )
7 #define unlock_buf () if ( semop ( mutex, unlkbuf, 1 )<0) \
8 error( 1, errno, "ошибка вызова semop" )
9 int mutex;
10 struct sembuf lkbuf;
11 struct sembuf unlkbuf;
12 void init_smb( int init_freelist )
13 {
14 union semun arg;
15 int smid;
16 int i;
17 int rc;
18 Ikbuf.sem_op = -1;
19 Ikbuf.sem_flg = SEM_UNDO;
20 unlkbuf.sem_op = 1;
21 unlkbuf.sem_flg = SEM_UNDO;
22 mutex = semget( MUTEX_KEY, 1,
23 IPC_EXCL | IPC_CREAT | SEM_R | SEM_A );
24 if ( mutex >= 0 )
25 {
26 arg.val = 1;
27 rc = semctl ( mutex, 0, SETVAL, arg );
28 if ( rc < 0 )
29 error( 1, errno, "semctl failed" );
30 }
31 else if ( errno == EEXIST )
32 {
33 mutex = semget( MUTEX_KEY, 1, SEM_R I SEM_A );
34 if ( mutex < 0 )
35 error( 1, errno, "ошибка вызова semctl" );
36 }
37 else
38 error( 1, errno, "ошибка вызова semctl" );
39 smid = shmget( SM_KEY, NSMB * sizeof( smb_t )+sizeof(int ),
40 SHM_R | SHM_W | IPC_CREAT );
41 if ( smid < 0 )
42 error( 1, errno, "ошибка вызова shmget" );
43 smbarray = ( smb_t * )shmat( smid, NULL, 0 );
44 if ( smbarray == ( void * )-1 )
45 error( 1, errno, "ошибка вызова shmat" );
46 if ( init_freelist )
47 {
48 for ( i = 0; i < NSMB - 1; i++ )
49 smbarray[ i ].nexti = i + 1;
50 smbarray[ NSMB - 1 ].nexti = -1;
51 FREE_LIST = 0;
52 }
53 }
Макросы и глобальные переменные
3- 4 Определяем ключи сегмента разделяемой памяти (SMBM) и семафЛpa (SMBS).
5-8 Определяем примитивы блокировки и разблокировки в терминах операций над семафорами.
9-11 Объявляем переменные для семафоров, используемых для реализащЯ мьютекса.
Получение и инициализация семафора
18-21 Инициализируем операции над семафорами, которыми будем пользоваться для блокировки и разблокировки списка свободных.
22-38 Этот код создает и инициализирует семафор. Вызываем semget с флагами IPC_EXCL и IPC_CREAT. В результате семафор будет создан, если он еще не существует, и в этом случае semget вернет идентификатор семафора, который инициализируем единицей (разблокированное состояние). Если же семафор уже есть, то снова вызываем semget, уже не задавая флагов IPC_EXCL и IPC_CREAT, для получения идентификатора этого семафора. Как отмечено в книге [Stevens 1999], теоретически здесь возможна гонка, но не в данном случае, поскольку сервер вызывает init_smb перед вызовом listen, а клиент не сможет обратиться к нему, пока вызов connect не вернет управление.
Примечание: В книге [Stevens 1999] рассматриваются условия, при которых возможна гонка, и показывается, как ее избежать.
Получение, отображение и инициализация буферов в разделяемой памяти
39-45 Выделяем сегмент разделяемой памяти и отображаем его на свое адресное пространство. Если сегмент уже существует, то shmget возвращает его идентификатор.
46-53 Если init_smb была вызвана с параметром init_freelist, равным TRUE, то помещаем все выделенные буферы в список свободных и возвращаем управление.