volkoff@home

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

12 Декабря 2009

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

Итак, описанную в предыдущей части подсистему необходимо портировать на Linux.

Ядро Linux обладает богатыми возможностями, но вот разобраться в них человеку, испорченному VxWorks, крайне тяжело - необходимо в корне менять представления о межпроцессорной коммуникации, организации адресного пространства, синхронизации. Всё, что я понял после беглого ознакомления с Библией[5] - наш дизайн идеологически не совместим с Linux, потому что:

  • нельзя вызывать функции драйвера напрямую - необходимо реализовать настоящий драйвер со стандартным интерфейсом;
  • ядро не может вызывать никаких колбэков из пользовательского пространства, необходим какой-то механизм асинхронных уведомлений;
  • и главное, ни о каких copy_to_user() / copy_from_user() не может быть и речи с нашими блоками по 8К - станция просто не справится с бессмысленным перекладыванием данных из одного места в другое
  • Существующий интерфейс с "прямыми" указатели не укладывался в стандартную модель ввода / вывода. Использование сокетов, пайпов не гарантировало прямого доступа к данным ("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()Прямой доступ к буферу канала. Используется неявно

    Комментарии