Написание драйверов под Linux: рекомендации, типичные ошибки и ловушки. | Версия для печати |
Посвящается людям, которые несли в этот мир доброту, любовь, знания. 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" Опубликовал: Mr.Nobody |