volkoff@home

Начало » Заметки

11 Декабря 2009

Драйвер канального уровня без сокетов. Часть 1

Неожиданный поворот в блоге. Выставляю для критики оригинальное решение реализации межпроцессорного взаимодействия между приложением и драйвером канального уровня. Сразу оговорюсь, что это не бред. Речь пойдёт о протокольном стеке V5[1], который не предполагает наличия таких вещей, как сокет и пакет. Welcome back to the world of circuit switching :)

Представленное решение не претендует на элегантность и является результатом форсированного портирования драйвера не совсем стандартного сетевого устройства с VxWorks на Linux.


Рис.1 Integrated Call Server

Итак, что мы имеем: очень упрощённо - классическая TDM-станция, которая кроме своей прямой обязанности - коммутации тайм-слотов (каналов), т.е. соединения абонентов сети PSTN / ISDN, выполняет на своём ядре встроенное интерактивное приложение, которое воспроизводит абонентам различные голосовые меню. Если в случае с двумя обычными абонентами станция лишь соединяет между собой два канала (приём одного на передачу другого), а дальше напрягаются только голосовые связки собеседников, то в случае с интерактивным абонентом, коим является голосовое меню, станция сама должна заполнять тайм-слот дозвонившегося абонента живым содержимым (оставим пока за кадром его происхождение). Заполнять FIFO-буфер канала необходимо непрерывно, не допуская задержек. Учитывая скорость передачи 8 КБ/c, каждые 4 мс необходимо "подкладывать" очередные 32 байта. А ещё учтите обработку встречного потока - запись речи абонента для сообщений "голосовой почты", обработку логики самого приложения, учтите параллельную обработку нескольких подобных интерактивных вызовов, и получится вполне серьёзная нагрузка для скромненькой машинки MPC860, на базе которой построена наша станция.

Хорошие новости: новые коммуникационные контроллеры, начиная с MPC8260, позволяют принимать/отправлять сколь угодно большие (в разумных пределах, конечно) блоки данных благодаря многоканальному контроллеру (MCC) с поддержкой DMA[2], так что не нужно "подкладывать" байты в FIFO вручную, нагрузка на процессор снизилась. Но есть и плохая новость: каналов стало много - 256 на PQII, а на PQII PRO - 512. Требования к оптимизации использования ресурсов и задержкам остались прежними, если не жёстче.

Архитектура приложения.
Интерактивное приложение, о котором идет речь, это кросс-платформенный интерпретатор VoiceXML[3]. Интерпретатор вызывается при звонках на префиксы, зарезервированные для интерактивных приложений (автосекретарь, голосовая почта и тп). Станция коммутирует входящий вызов на один из доступных тайм-слотов из пула голосовых приложений, и запускает в интерпретаторе соответствующий скрипт. Скрипт взаимодействует с абонентом - воспроизводит звуковые записи в указанный канал, захватывает голос с линии, слушает DTMF-тоны и реагирует на них, загружая другие скрипты или перенаправляя вызовы.


Рис.2 Интерактивная голосовая подсистема

Запросы интерпретатора на приём / передачу голосовых данных выполняет платформо-зависимый бекенд - голосовой драйвер. Драйвер обеспечивает прозрачную потоковую передачу голосовых данных блоками по 8 КБ. Он должен гарантировать максимальную масштабируемость - параллельный приём и передачу по максимально возможному количеству каналов без задержек и потерь, с минимальными накладными расходами на пересылку, поскольку вычислительные мощности ядра необходимы для "тяжёлой" логики - интерпретации скрипта, загрузки файлов со встроенного диска или сервера, сохранения принимаемых голосовых сообщений.

Интерфейс
Голосовой драйвер предоставляет интерпретатору очень простой интерфейс: единственный "системный вызов" с небольшим набором команд, а интерпретатор, в свою очередь, предоставляет драйверу callback-вызов с набором уведомлений о событиях.

/* Commands */
typedef enum {
 IVR_INIT_CHANNEL = 0x100, 
 IVR_PLAY_BLOCK, /* Starts playing a block */
 IVR_RECORD_BLOCK, /* Starts recording a block */
 IVR_STOP_PLAY, /* Stops playing */
 IVR_STOP_RECORD, /* Stops recording */
 IVR_RELEASE_CHANNEL
} t_ivrDrvCmd; 
/* Responses */
typedef enum {
 IVR_PLAY_GET_NEXT_BLOCK = 0x200, /* Request for next block */
 IVR_BLOCK_RECORDED, /* Block is recorded */
 IVR_PLAY_END_OF_DATA, /* No more data to play */
 IVR_CHANNEL_RELEASED /* Channel released by driver */
} t_ivrDrvResponse; 
/* Driver message */
typedef struct {
  ushort  link; /* TDM link */
  ushort  channel; /* channel number */
  ushort  buffer; /* pointer to data buffer */
  ushort  bufferSize; /* block size */
  ulong msgType; /* command or response */
} t_ivrDrvMsg;
/* Driver command */
int ivrCmd(t_ivrDrvMsg *msg); 
/* Application callback */
int ivrCallBack(t_ivrDrvMsg *msg);  

Префикс "IVR" обозначает Interactive Voice Response. Он сохранился с прежней реализации подсистемы - встроенного приложения "Announcement" с жёсткой логикой, которое использовалось для голосовых оповещений абонентов. Сохранив реализацию канального уровня и интерфейс, мы интегрировали её с интерпретатором VXML и получили возможность создания голосовых приложений произвольной логики.

VxWorks имеет плоское адресное пространство, поэтому драйвер и приложение имеют общие данные, и указатель на буфер присутствует в каждой команде и уведомлении. Кроме того, приложение и драйвер могут напрямую вызывать функции друг друга. Это преимущество, упрощающее дизайн, является одновременно и главным недостатком данной ОС[4]

Реализация
Команды выполняются асинхронно - указатель буфера ставится в кольцо буфердескрипторов канала (RX/TX BD Ring), активируется приём / передача, и управление возращается приложению. После завершения операции обработчик прерывания посылает оповещение специальному процессу посредством msgQSend(), и тот в своём контексте вызывает call-back функцию приложения, которая и выполняет основную логику - сохраняет принятые блоки и ставит на воспроизведение очередные.


Рис.3 Модульная схема

Так это было спроектировано и реализовано, и работает много лет вполне стабильно. По моим бенчмаркам, loop-back тест драйвера на 128 каналах нагружает CPU всего на 1-2%, без потерь блоков.

А теперь всё это нужно спортировать на Linux. Без потерь производительности, и сохранив по возможности интерфейс

Комментарии