Система буферов в разделяемой памяти
Описанную систему буферов в разделяемой памяти легко реализовать. Основная сложность в том, как получить область разделяемой памяти, отобразить ее на собственное адресное пространство и синхронизировать доступ к буферам. Конечно, это зависит от конкретной системы, поэтому далее будет приведена реализация как для UNIX, так и для Windows.
Но прежде чем перейти к системно-зависимым частям, обратимся к API и его реализации. На пользовательском уровне система состоит из пяти функций:
#include “etcp.h”
void init_smb( int init_freelist);
void *smballoc( void );
Возвращаемое значение: указатель на буфер в разделяемой памяти.
void smbfrее( void *smbptr );
void smbsend( SOCKET s, void * smbptr );
void *smbrecv( SOCKET s );
Возвращаемое значение: указатель на буфер в разделяемой памяти.
Перед тем как пользоваться системой, каждый процесс должен вызвать функцию init_smb для получения и инициализации области разделяемой памяти и синхронизирующего мьютекса. При этом только один процесс должен вызвать init_smb с параметром init_f reelist, равным TRUE.
Для получения буфера в разделяемой памяти служит функция smballoc, возвращающая указатель на только что выделенный буфер. Когда буфер уже не нужен, процесс может вернуть его системе с помощью функции smb_frее.
Построив сообщение в буфере разделяемой памяти, процесс может передать буфер другому процессу, вызвав smbsend. Как уже говорилось, при этом передается только индекс буфера в массиве. Для получения буфера от отправителя процесс-получатель вызывает функцию smbrecv, которая возвращает указатель на буфер.
В данной системе для передачи индексов буферов используется TCP в качестве механизма IPC, но это не единственное и даже не оптимальное решение. Так Удобнее, поскольку этот механизм работает как под UNIX, так и под Windows, к тому же можно воспользоваться уже имеющимися средствами, а не изучать другие методы IPC. В системе UNIX можно было бы применить также сокеты в адресом домене UNIX или именованные каналы. В Windows доступны SendMessage, QueueUserAPC и именованные каналы.
Начнем рассмотрение реализации с функций smballoc и smbfrее (листинг 3.28).
Листинг 3.28. Функции smballoc и smbfree
1 #include "etcp.h"
2 #define FREE_LIST smbarray[ NSMB ].nexti
3 typedef union
4 {
5 int nexti;
6 char buf[ SMBUFSZ ];
7 }smb_t;
8 smb_t *smbarray;
9 void *smballoc( void )
10 {
11 smb_t *bp;
12 lock_buf();
13 if ( FREE_LIST < 0 )
14 error( 1, 0, "больше нет буферов в разделяемой памяти\n" }
15 bр = smbarray + FREE_LIST;
16 FREE_LIST = bp->nexti;
17 unlock_buf ();
18 return bp;
19 }
20 void smbfree( void *b )
21 {
22 smb_t *bp;
23 bp = b;
24 lock_buf();
25 bp->nexti = FREE_LIST;
26 FREE_LIST = bp - smbarray;
27 unlock_buf();
28 }
Заголовок
2-8 Доступные буфера хранятся в списке свободных. При этом в первых sizeof( int ) байтах буфера хранится индекс следующего свободного буфера. Такая организация памяти отражена в объединении smb_t. В конце массива буферов есть одно целое число, которое содержит либо индекс первого буфера в списке свободных, либо -1, если этот список пуст. Доступ к этому числу вы получаете, адресуя его как smbarray [ NSMB ] . nexti. Для удобства это выражение инкапсулировано в макрос FREE_LIST. На сам массив буферов указывает переменная smbarray. Это, по сути, указатель на область разделяемой памяти, которую каждый процесс отображает на свое адресное пространство. В массиве использованы индексы, а не адреса элементов, так как в разных процессах эти адреса могут быть различны.
smballoc
12 Вызываем функцию lock_buf, чтобы другой процесс не мог обратиться к списку свободных. Реализация этой функции зависит от системы. В UNIX будут использованы семафоры, а в Windows - мыотексы.
13-16 Получаем буфер из списка свободных. Если больше буферов нет, то выводим диагностическое сообщение и завершаем сеанс. Вместо этого можно было бы вернуть NULL.
17-18 Открываем доступ к списку свободных и возвращаем указатель на буфер.
smbfree
23- 27 После блокировки списка свободных, возвращаем буфер, помещая его индекс в начало списка. Затем разблокируем список свободных и возвращаем управление.
Далее рассмотрим функции smbsend и smbrecv (листинг 3.29). Они посылают и принимают целочисленный индекс буфера, которым обмениваются процессы. Эти функции несложно адаптировать под иной механизм межпроцессного взаимодействия.
Листинг 3.29. Функции smbsend и smbrecv
smb.c
1 void smbsend( SOCKET s, void *b )
2 {
3 int index;
4 index = ( smb_t * )b - smbarray;
5 if ( send( s, ( char * )&index, sizeoff (index ), 0 ) < 0 )
6 error( 1, errno, "smbsend: ошибка вызова send" );
7 }
8 void *smbrecv( SOCKET s )
9 {
10 int index;
11 int rc;
12 rc = readn( s, ( char * )&index, sizeoff index ) );
13 if ( rc == 0 ) *,
14 error( 1, 0, "smbrecv: другой конец отсоединился\n" };
15 else if ( rc != sizeof( index ) )
16 error( 1, errno, "smbrecv: ошибка вызова readn" );
17 return smbarray + index;
18 }
smbsend
4-6 Вычисляем индекс буфера, на который указывает Ь, и посылаем его другому процессу с помощью send.
smbrecv
12-16 Вызываем readn для чтения переданного индекса буфера. В случае ошибки чтения или при получении неожиданного числа байт, выводим сообщение и завершаем работу.
17 В противном случае преобразуем индекс буфера в указатель на негеи возвращаем этот указатель вызывающей программе.