Наши
Сайдбары
   
IRC-канал
Linuxportal.ru
   
Импорт новостей
 
Вход Быстрая регистрация На главную

Поиск:
 
   БИБЛИОТЕКА
     Другие статьи этого раздела:
 

    Система
Другие разделы библиотеки:
   БИБЛИОТЕКА
     последние поступления:
 

Hardware
Перевод Serial-Programming-HOWTO
15.05.2006 | San АНДРЕЕВ
Перевод Linux Power Management Support
03.04.2004 | San АНДРЕЕВ
Варианты решения проблем со смещением геометрии монитора.
10.10.2003 | EsTaF
Multimedia
VideoCD для пингвинов
10.10.2003 | Buddha
Смотрим любимые фильмы с помощью MPlayer
23.07.2003 | Alexey Dmitriev
X-сервер
Включаем Bytecode Interpreter в пакете freetype в некоторых дистрибутивах
05.07.2006 | Alexey Dmitriev
НАСТРОЙКА СЕРВЕРА XDMCP ДЛЯ WINDOWS-КЛИЕНТОВ
09.09.2004 | Kotjara
Type1 и truetype шрифты в LaTeX.
03.05.2004 | geekkoo
Разное
Как самому написать концепцию информационной безопасности
22.03.2007 | Crion
Список рекомендуемой литературы
11.09.2006 | San АНДРЕЕВ
The Multi Router Traffic Grapher
02.06.2004 | Crion
Сеть
"Огненная стена" или строим файрвол на базе iptables
15.03.2005 | Alexey Dmitriev
Защищенная почтовая система
08.09.2004 | Crion
Postfix+Cyrus-SASL
18.12.2003 | geekkoo
Система
Написание драйверов под Linux: рекомендации, типичные ошибки и ловушки.
16.01.2007 | Mr.Nobody
Перевод Linux Daemon HOWTO
21.08.2006 | San АНДРЕЕВ
Усыпляем компьютер
11.01.2006 | San АНДРЕЕВ
   БИБЛИОТЕКА Система    
Автор статьи: Mr.Nobody
Дата: 16.01.2007

Написание драйверов под Linux: рекомендации, типичные ошибки и ловушки.

Посвящается людям, которые несли в этот мир доброту, любовь, знания.


1. Аннотация.

В данной статье указываются типичные ошибки и ловушки, возникающих при написании драйверов ОС Linux, высказываются рекомендации, которые могут при этом оказаться полезными.


2. Автор.

Mr.Nobody


3. Ключевые слова для поиска: драйвер, написание, программирование, Linux, ошибки, ловушки, рекомендации, советы, практика


4. Введение.

Данная статья написана с целью помочь программистам, начинающим писать драйверы устройств для ОС Linux. Автор понимает, что обычно люди берущиеся за написание драйверов обладают как правило достаточно высоким интеллектом и серьёзными знаниями в области программировании, что позволяет им самостоятельно без чьей-либо непосредственной помощи решить задачу любой сложности в плане программирования, в том числе и написание правильно работающих драйверов под ОС Linux. Поэтому данная статья написана лишь с целью сократить время, требуемое на решение проблем возникающих при написании драйверов под ОС Linux посредством указания типичных ошибок (большей частью из личного опыта автора), потенциально опасных мест в коде, а также высказыванием ряда рекомендаций, которых следует придерживаться при написании драйверов (и вообще при написании кода, выполняющегося на уровне ядра).

Автор надеется и будет рад если данная статья окажется кому-либо полезной т.к. по мнению автора на данный момент ощущается нехватка публикаций на данную тему. Приведённые в статье примеры относятся к ядрам версий 2.6.x, хотя автор старался (по мере возможности) не привязываться к каким-либо конкретным версиям. Необходимо также понимать, что несмотря на то, что приведены типичные симптомы, возникающие при указанных в данной статье ошибках, поведение системы/ядра может отличаться (возможно незначительно) в разных версиях ядер ОС Linux.


5. Инструменты.

Необходимый минимум инструментария:
1. magic Sys Req-клавиши
2. отладочные сообщения
3. gdb и kdb

Использование п.1 по мнению автора обязательно. Настоятельно рекомендуется (по крайней мере до времени получения правильно работающего kernelspace-кода) использовать журналируемые файловые системы. Отладочные сообщения служат для определения области кода, приведшей к ошибке. Cледует избегать (по возможности) использования отладчиков т.к. на работу с ними уходит значительное время. Хотя необходимо отметить, что как правило совсем обойтись без них нельзя поэтому следует заранее побеспокоится например о поиске патча kdb под отлаживаемое ядро и о свободном пространстве на жёстком диске (около 1Гб). Оба отладчика являются достаточно полезными (kdb в большей степени), но имеют свою специфику например: gdb позволяет следить за данными, но не может вмешиваться в работу ядра (например расставлять точки останова) при этом система функционирует как обычно; kdb позволяет выставлять точки останова но при попадании в них вся система будет остановлена и нельзя будет скажем переключится в другую консоль и посмотреть результаты какой-то программы, кроме того kdb плохо совместим с X Window. Рекомендуется (для упрощения отладки) встраивать драйверы жёстко в ядро.

Ну и в целом при анализе кода смотрите на него глазами машины (т.е. не что он должен делать, а что делает).


6. Типичные ошибки.

1) неблокируемый вызов функций.
Симптомы: код то работает, то не работает как предполагается.
Пример: регистрация нового usb-устройства. В случае вызова usb_register_dev() происходит немедленный возврат и например последующая (сразу же) заливка прошивки может закончится не успешно.
Решение: необходимо добавить временнУю задержку, которая (гарантированно) обеспечит правильное функционирование в случае выполнения некоторых действий, зависимых от действий неблокируемой функции или использовать другое решение (например, посредством переноса зависимых действий в другие методы драйвера, которые могут будут вызваны только лишь после окончания успешной регистрации устройства).

2) возвращение функцией (методом) драйвера неожидаемых значений при выполнении какого-либо системного вызова.
Симптомы: вывод ошибки ядром (например oops).
Пример: возврат методом драйвера open() (положительного) значения, полученного от вышерасположенной функции:

static int usb_device_open(struct inode *inode, struct file *file) {
        int retval = 0;
        ...
// some initialization
        retval = usb_device_init();
        if ( retval < 0)
                info("Failed initialization");
        ...
        return retval;
}

Решение: проверка возвращаемых значений на допустимость:
static int usb_device_open(struct inode *inode, struct file *file) {
        int retval = 0;
        ...
// some initialization
        retval = usb_device_init();
        if ( retval < 0)
                info("Failed initialization");
        ...
// failed
        if ( retval < 0)
                return retval;
// success
        else
                return 0;
}

(в данном случае 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;
}

Решение: использование выравнивания там где это необходимо
static inline void *mem_alloc(size_t size) {
        void *mem;
// align on page
        mem = (void *)__get_free_pages(GFP_ATOMIC, get_order((PAGE_ALIGN(size)));
        ...
        return mem;
}


4) освобождение ресурсов во время их использования.
Симптомы: от зависания и выдачи ошибки ядром до полного отсутствия каких-либо ошибок непосредственно после "досрочного" освобождения ресурсов (могут проявится позже).
Пример: отсоединение USB-устройства во время его работы, приводящее к "досрочному" освобождению памяти, выделенной под его структуру.
Решение: осуществление освобождения ресурсов, только тогда, когда в них уже нет необходимости (т.н. deffered freeing).

5) Неправильное использование механизмов синхронизации из-за ошибок в коде или по причине неправильного понимания их реализации (напр. отличие в приоритете захвата семафоров от rw-семафоров).
Симптомы: неправильная работа ПО (userspace), работающего с драйвером (как правило, выражающаяся в блокировании на уровне ядра и создание т.н. "неубиваемых" процессов/потоков, расходующих ресурсы системы).
Пример: проявление некорректной работы USB-устройства в некоторых режимах (получение синхронно/асинхронно данных с устройства) из-за (внесения) реализации сложного взаимодействия потоков ядра.
Решение: правильное использование наиболее подходящих механизмов синхронизации (для разных случаев могут использоваться разные механизмы синронизации - для правильного выбора необходимо чётко представлять их особенности и специфику; как правило очень полезна информация (директория /Documentation исходного кода ядра), описывающая особенности реализации и специфику использования механизмов синхронизации). В случае сложного взаимодйствия можно порекомендовать вынести код, отвечающий за синронизацию из kernelspace в userspace (если это приемлимо/возможно).


7. Ловушки (потенциальные ошибки).

1) отсутствие барьеров между записями в регистры устройства.
Симптомы: устройство не работает или работает не так как предполагалось (также возможно различное поведение устройства в разных системах).
Пример: запись значений в регистры устройства, с последующим стартом работы устройства (получения данных).
Решение: использование соответствующих барьеров.

2) неправильное указание флагов выделения памяти.
Симптомы: редко проявляющаяся некорректная работа или даже зависания системы.
Пример: использование GFP_KERNEL в kmalloc() в контексте прерывания.
Решение: использование GFP_ATOMIC.

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. Рекомендации.

1) использование стиля кодирования являющегося фактически стандартом при программировании на уровне ядра - kernel coding style.
2) использование вместо специфичных для ядра типов данных типов имеющихся в стандарте языка С (вместо __u8 следует использовать uint8_t имеющийся в C99).
3) использование стандартных интерфейсов предоставляемых той или иной подсистемой ядра (не "изобретать колесо" в случаях, когда без этого можно обойтись).
4) инициализация (обнуление) памяти в случае если её содержимое передаётся/используется в userspace.
5) после каждого изменения, вносимого в код, выполняющийся на уровне ядра необходимо провести тщательное тестирование (на правильность функционирования, в различных режимах работы, в том числе и на защиту от некорректных действий).
6) не усложняйте без необходимости код, выполняемый на уровне ядра - придерживайтесь принципа "чем проще - тем лучше".


9. Список использованных источников.

1. Documentation/CodingStyle, исходный код ядра Linux ()
2. Allessandro Rubini, Jonathan Corbet - "Linux Device Drivers", 2-nd Edition, O'Reilly
3. Arjan van de Ven - "How to NOT write kernel driver"



Все статьи раздела "Система"

©"Linuxportal.Ru". Материалы сайта можно
использовать свободно при условии
сохранения этой свободы при дальнейшем
распространении, если явно не указано иное

Дизайн и программирование:

Поставьте
нашу кнопку:
Получить код кнопки
Linux Portal.ru ::: Линукс Портал.ру
Наш партнер:
 | Наши направления продажа предложений в Москве |  |  |  |