12 Декабря 2009
Драйвер канального уровня без сокетов. Часть 2
Итак, описанную в предыдущей части подсистему необходимо портировать на Linux.
Ядро Linux обладает богатыми возможностями, но вот разобраться в них человеку, испорченному VxWorks, крайне тяжело - необходимо в корне менять представления о межпроцессорной коммуникации, организации адресного пространства, синхронизации. Всё, что я понял после беглого ознакомления с Библией[5] - наш дизайн идеологически не совместим с Linux, потому что:
Существующий интерфейс с "прямыми" указатели не укладывался в стандартную модель ввода / вывода. Использование сокетов, пайпов не гарантировало прямого доступа к данным ("zero-copy"). Поскольку готового решения асинхронного взаимодействия с нулевым копированием на поверхности не обнаружилось[6], решено было изучить каждый вопрос в отдельности.
Выбор интерфейса
Итак, драйвер оформлен в качестве модуля ядра и регистрирует в системе устройство /dev/ivrDrv. Его можно открыть и закрыть. Что дальше? Нам необходимо управлять 256 каналами коммуникационного контроллера. Самое простое - реализовать вызов ioctl(), и можно использовать старую систему команд для управления каналами и операциями приёма / передачи. Таким образом, мы почти сохранили интерфейс.
Доступ к данным
Для прямого доступа к данным ядра из приложения в Linux существует метод "отображения", который реализуется системным вызовом mmap(). Он возвращает "мапированный" в адресное пространство пользователя указатель на регион памяти, выделенный ядром. Мы можем скрыть использование этого метода в реализации команды инициализации канала (IVR_INIT_CHANNEL), и возвращать пользователю уже готовый к использованию указатель на буфер канала. Этот буфер будет автоматически освобождаться при освобождении канала. Тем самым мы слегка изменили интерфейс, но освободили приложение от необходимости выделять и освобождать блоки памяти под передачу. (Под приём их и так выделяет драйвер)
Система уведомлений
Буфер наполнен данными и готов к отправке, передатчик активирован, и вот-вот произойдёт прерывание, подтверждающее успешную отправку. Как уведомить об этом приложение? Поизучав интернет, я нашёл массу сравнений производительности различных методов обработки сетевых соединений: select(), poll(), epoll(). Самым эффективным признан метод epoll() [7, 8], который имеет сложность O(1). Это значит, что его задержка не зависит от количества подключений или событий[9, 10]. Это как раз то, что нужно для нашей реал-тайм системы. Но имеется один небольшой недостаток - epoll(), как и остальные методы, работает с сокетами и прочими дескрипторами. А у нас их нет. Нам нужен некий объект, который умел бы уведомить epoll() о произвольном событии ядра, таком как прерывание нашего контроллера
Правильно сформулированный запрос выдал искомый ответ: eventfd() - файлдескриптор, служащий для уведомления пользовательского процесса о событии ядра[11]. Совместим с epoll().
Идея решения
Концепция решения теперь стала очевидна: на базе eventfd() можно реализовать "дескриптор канала", который будет:
Дескриптор будет создаваться при инициализации канала и возвращаться функцией ioctl(..., IVR_INIT_CHANNEL), соответственно удаляться будет с освобождением канала.

Рис.4 Новый дизайн
Дескриптор реализует следующие методы:
| write() | Отправка команды каналу |
| poll() | Ожидание окончания операции. Обработчик прерывания выводит дескриптор из состояния ожидания |
| read() | Чтение результата операции. |
| mmap() | Прямой доступ к буферу канала. Используется неявно |
Комментарии