Эффективное программирование TCP-IP

       

Реализация в 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 могут быть подняты два бита флагов:

  • IPC_NOWAIT - означает, что semop должна вернуть код EAGAIN, а не приостанавливать процесс, если в результате операции значение семафора окажется отрицательным;


  • SEMJJNDO - означает, что semop должна отменить действие всех операций над семафором, если процесс завершается, то есть мьютекс будет освобожден.


  • Теперь рассмотрим 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, то помещаем все выделенные буферы в список свободных и возвращаем управление.


    Содержание раздела