Академический Документы
Профессиональный Документы
Культура Документы
Написание драйверов под Linux
Написание драйверов под Linux
1. Аннотация.
В данной статье указываются типичные ошибки и ловушки, возникающих при написании драйверов ОС Linux,
высказываются рекомендации, которые могут при этом оказаться полезными.
2. Автор.
Mr.Nobody
3. Ключевые слова для поиска: драйвер разработка написание программирование Linux ошибки ловушки трудности
рекомендации советы практика
4. Введение.
Данная статья написана с целью помочь программистам, начинающим писать драйверы устройств для ОС Linux. Автор
понимает, что обычно люди берущиеся за написание драйверов обладают как правило достаточно высоким
интеллектом и серьёзными знаниями в области программировании, что позволяет им самостоятельно без чьей-либо
непосредственной помощи решить задачу любой сложности в плане программирования, в том числе и написание
правильно работающих драйверов под ОС Linux. Поэтому данная статья написана лишь с целью сократить время,
требуемое на решение проблем возникающих при написании драйверов под ОС Linux посредством указания типичных
ошибок (большей частью из личного опыта автора), потенциально опасных мест в коде, а также высказыванием ряда
рекомендаций, которых следует придерживаться при написании драйверов (и вообще при написании кода,
выполняющегося на уровне ядра).
Автор надеется и будет рад если данная статья окажется кому-либо полезной т.к. по мнению автора на данный момент
ощущается нехватка публикаций на данную тему. Приведённые в статье примеры относятся к ядрам версий 2.6.x, хотя
автор старался (по мере возможности) не привязываться к каким-либо конкретным версиям. Необходимо также
понимать, что несмотря на то, что приведены типичные симптомы, возникающие при указанных в данной статье
ошибках, поведение системы/ядра может отличаться (возможно незначительно) в разных версиях ядер ОС Linux.
5. Инструменты.
Использование п.1 по мнению автора обязательно. Настоятельно рекомендуется (по крайней мере до времени
получения правильно работающего kernelspace-кода) использовать журналируемые файловые системы. Отладочные
сообщения служат для определения области кода, приведшей к ошибке. Cледует избегать (по возможности)
использования отладчиков т.к. на работу с ними уходит значительное время. Хотя необходимо отметить, что как
правило совсем обойтись без них нельзя поэтому следует заранее побеспокоится например о поиске патча kdb под
отлаживаемое ядро и о свободном пространстве на жёстком диске (около 1Гб). Оба отладчика являются достаточно
полезными (kdb в большей степени), но имеют свою специфику например: gdb позволяет следить за данными, но не
может вмешиваться в работу ядра (например расставлять точки останова) при этом система функционирует как обычно;
kdb позволяет выставлять точки останова но при попадании в них вся система будет остановлена и нельзя будет
скажем переключится в другую консоль и посмотреть результаты какой-то программы, кроме того kdb плохо совместим с
X Window. Рекомендуется (для упрощения отладки) встраивать драйверы жёстко в ядро.
Ну и в целом при анализе кода смотрите на него глазами машины (т.е. не что он должен делать, а что делает).
6. Типичные ошибки.
2) возвращение функцией (методом) драйвера неожидаемых значений при выполнении какого-либо системного вызова.
Симптомы: вывод ошибки ядром (например oops).
Пример: возврат методом драйвера open() (положительного) значения, полученного от вышерасположенной функции:
(в данном случае open() должен вернуть в случае успеха нулевое значение или отрицательное в случае ошибки, возврат
положительного значения приведёт к выводу ошибки ядром; это связано с тем, что перед вызовом определённого
метода драйвера при выполнении системного вызова происходит выполнение некоторого кода ядра (функций), который
при возвращении из метода драйвера выполняет ряд определённых действий в зависимости от возвращённого значения
(какие значения допустимо возвращать зависит от системного вызова).
3) Запрос памяти, чей размер не кратен размеру страниц, у низкоуровневых функций распределения памяти (напр.
__get_free_pages).
Симптомы: наиболее вероятно зависание системы вместе с отладчиком (в случае использования низкоуровневых
функций), менее вероятен вывод ошибки ядром (oops) или возращение кода ошибки (это связано с тем, что как правило
в низкоуровневых функциях отдаётся предпочтение производительности, чем проверкам на правильность/допустимость
(не везде, а как правило в некоторых частях ядра, особенно влияющих на производительность системы)), а также
возможно по причине некорректной работы низкоуровневых (вспомогательных) алгоритмов изначально не расчитанных
на работу с памятью, чей размер не кратен размеру страниц). Необходимо также отметить, что как правило число таких
функций невелико и обычно большинство функций из набора API, предоставляемого подсистемой памяти позволяет
работать с памятью не кратной размеру страницы (например, remap_pfn_range()).
Пример:
static inline void *mem_alloc(size_t size) {
void *mem;
// if size is not page aligned then system will die...
mem = (void *)__get_free_pages(GFP_ATOMIC, get_order(size));
...
return mem;
}
6) Неправильное использование механизмов синхронизации из-за ошибок в коде или по причине неправильного
понимания их реализации (напр. непонимание отличия в приоритете захвата семафоров от rw-семафоров; захват
семафора с удерживаемой спин-блокировкой).
Симптомы: неправильная работа ПО (userspace), работающего с драйвером (как правило, выражающаяся в
блокировании на уровне ядра и создание т.н. "неубиваемых" процессов/потоков, расходующих ресурсы системы).
Пример: проявление некорректной работы USB-устройства в некоторых режимах (получение синхронно/асинхронно
данных с устройства) из-за (внесения) реализации сложного взаимодействия потоков ядра.
Решение: правильное использование наиболее подходящих механизмов синхронизации (для разных случаев могут
использоваться разные механизмы синронизации - для правильного выбора необходимо чётко представлять их
особенности и специфику; как правило очень полезна информация (директория /Documentation исходного кода ядра),
описывающая особенности реализации и специфику использования механизмов синхронизации). В случае сложного
взаимодйствия можно порекомендовать вынести код, отвечающий за синронизацию из kernelspace в userspace (если это
приемлимо/возможно).
3) Отсутствие проверок при захвате ресурсов (подключение большого числа устройств одного типа, интенсивно
использующих какой-либо ресурс системы - например полосу пропускания USB-шины).
Симптомы: деградация системы (в плане производительности) вплоть до отстуствия реакции, некорректная работа
некоторых подсистем ядра.
Пример: подключение и одновременная работа N высокоскоростных USB-устройств (где N, это максимально возможное
число одновременно подключённых USB-устройств, согласно стандарту (спецификации) Universal Serial Bus Specification
Revision 2.0; необходимо также учитывать, что степень реализации спецификации в ядре ОС Linux может изменяться в
разных версиях ядер, например реакция на превышение пропускной способности usb-шины существенно отличается в
ядрах серии 2.4.x и 2.6.x).
Решение: расчитать и ввести в драйвере ограничение на максимальное количество устройств одного типа с которыми
возможна одновременная работа (например, исходя из ограничения по пропускной способности USB-шины или объёмам
используемой памяти).
4) отсутствие защиты от некорректных действий (userspace-программы и пользователя напр. решившего во время
работы userspace-программы, получающей данные с USB-устройства отключить его (и возможно не только отключить, но
и успеть снова включить!) - т.н. "защита от дурака".
Симптомы: неправильная работа ПО (userspace), ошибки в работе ядра (например, некорректная работа USB-
подсистемы), зависание системы - зависит от значительности ошибки и степени её влияния на работу системы.
Пример: отключение устройства во время его работы.
Решение: реализация "защиты от дурака" (в том числе и достаточно маловероятных действий - система должна
функционировать надёжно при любых условиях).
8. Советы по оптимизации.
3) использование likely()/unlikely() для подсказки компилятору о возможных ветвлениях (улучшение branch prediction).
Пример:
if (unlikely(status != 0)) {
dbg("%s - nonzero read bulk status received: %d",
__FUNCTION__, status);
return;
}
9. Общие рекомендации.
1) использование стиля кодирования являющегося фактически стандартом при программировании на уровне ядра -
kernel coding style.
2) использование вместо специфичных для ядра типов данных типов имеющихся в стандарте языка С (вместо __u8
следует использовать uint8_t имеющийся в C99).
3) использование стандартных интерфейсов предоставляемых той или иной подсистемой ядра (не "изобретать колесо" в
случаях, когда без этого можно обойтись).
4) инициализация (обнуление) памяти в случае если её содержимое передаётся/используется в userspace.
5) после каждого изменения, вносимого в код, выполняющийся на уровне ядра необходимо провести тщательное
тестирование (на правильность функционирования, в различных режимах работы, в том числе и на защиту от
некорректных действий).
6) не усложняйте без необходимости код, выполняемый на уровне ядра - придерживайтесь принципа "чем проще - тем
лучше".