#1
|
|
Вес репутации:
0
Регистрация: 27.02.2009
Адрес: Москва
Сообщений: 7,302
Сказал(а) спасибо: 578
Спасибок 2,623
в 1,832 сообщениях |
Пишем свой драйвер под Linux -
25.10.2010, 23:02
Хочу признаться сразу, что я вас отчасти обманул, ибо драйвер, если верить википедии — это компьютерная программа, с помощью которой другая программа (обычно операционная система) получает доступ к аппаратному обеспечению некоторого устройства. А сегодня мы создадим некую заготовку для драйвера, т.к. на самом деле ни с каким железом мы работать не будем. Эту полезную функциональность вы сможете добавить сами, если пожелаете. То, что мы сегодня создадим, корректнее будет назвать LKM (Linux Kernel Module или загрузочный модуль ядра). Стоит сказать, что драйвер – это одна из разновидностей LKM. Писать модуль мы будем под ядра линейки 2.6. LKM для 2.6 отличается от 2.4. Я не буду останавливаться на различиях, ибо это не входит в рамки поста. Мы создадим символьное устройство /dev/test, которое будет обрабатываться нашим модулем. Хочу сразу оговориться, что размещать символьное устройство не обязательно в каталоге /dev, просто это является частью «древнего магического ритуала». Немного теории Если кратко, то LKM – это объект, который содержит код для расширения возможностей уже запущенного ядра Linux. Т.е. работает он в пространстве ядра, а не пользователя. Так что не стоит экспериментировать на рабочем сервере. В случае ошибки, закравшейся в модуль, получите kernel panic. Будем считать, что я вас предупредил. Модуль ядра должен иметь как минимум 2 функции: функцию инициализации и функцию выхода. Первая вызывается во время загрузки модуля в пространство ядра, а вторая, соответственно, при выгрузке его. Эти функции задаются с помощью макроопределений: module_init и module_exit. Стоит сказать несколько слов о функции printk(). Основное назначение этой функции — реализация механизма регистрации событий и предупреждений. Иными словами эта функция для записи в лог ядра некой информации. Т.к. драйвер работает в пространстве ядра, но он отграничен от адресного пространства пользователя. А нам хотелось бы иметь возможность вернуть некий результат. Для этого используется функция put_user(). Она как раз и занимается тем, что перекидывает данные из пространства ядра в пользовательское. Хочу ещё сказать пару слов о символьных устройствах. Выполните команду ls -l /dev/sda*. Вы увидите что-то вроде: brw-rw---- 1 root disk 8, 0 2010-10-11 10:23 /dev/sda brw-rw---- 1 root disk 8, 1 2010-10-11 10:23 /dev/sda1 brw-rw---- 1 root disk 8, 2 2010-10-11 10:23 /dev/sda2 brw-rw---- 1 root disk 8, 5 2010-10-11 10:23 /dev/sda5 Файлы устройства создаются с помощью команты mknod, например: mknod /dev/test c 12. Этой командой мы создадим устройство /dev/test и укажем для него старший номер (12). Я не буду сильно углубляться в теорию, т.к. кому интересно – тот сможет сам почитать про это подробнее. Я дам ссылку в конце. Прежде чем начать Нужно знать несколько «волшебных» команд:
В debian/ubutnu их можно легко поставить так (к примеру для 2.6.26-2-686): apt-get install linux-headers-2.6.26-2-686 fakeroot make-kpkg kernel_headers Сборка модуля Ну а теперь можем написать небольшой Makefile: PHP код:
root@joker:/tmp/test# make make -C /lib/modules/2.6.26-2-openvz-amd64/build M=/tmp/test modules make[1]: Entering directory `/usr/src/linux-headers-2.6.26-2-openvz-amd64' CC [M] /tmp/1/test.o Building modules, stage 2. MODPOST 1 modules CC /tmp/test/test.mod.o LD [M] /tmp/test/test.ko make[1]: Leaving directory `/usr/src/linux-headers-2.6.26-2-openvz-amd64' root@joker:/tmp/test# ls -la drwxr-xr-x 3 root root 4096 Окт 21 12:32 . drwxrwxrwt 12 root root 4096 Окт 21 12:33 .. -rw-r--r-- 1 root root 219 Окт 21 12:30 demo.sh -rw-r--r-- 1 root root 161 Окт 21 12:30 Makefile -rw-r--r-- 1 root root 22 Окт 21 12:32 modules.order -rw-r--r-- 1 root root 0 Окт 21 12:32 Module.symvers -rw-r--r-- 1 root root 2940 Окт 21 12:30 test.c -rw-r--r-- 1 root root 10364 Окт 21 12:32 test.ko -rw-r--r-- 1 root root 104 Окт 21 12:32 .test.ko.cmd -rw-r--r-- 1 root root 717 Окт 21 12:32 test.mod.c -rw-r--r-- 1 root root 6832 Окт 21 12:32 test.mod.o -rw-r--r-- 1 root root 12867 Окт 21 12:32 .test.mod.o.cmd -rw-r--r-- 1 root root 4424 Окт 21 12:32 test.o -rw-r--r-- 1 root root 14361 Окт 21 12:32 .test.o.cmd drwxr-xr-x 2 root root 4096 Окт 21 12:32 .tmp_versions root@joker:/tmp/test# modinfo test.ko filename: test.ko description: My nice module author: Alex Petrov <[email protected]> license: GPL depends: vermagic: 2.6.26-2-openvz-amd64 SMP mod_unload modversions root@joker:/tmp/test# insmod test.ko root@joker:/tmp/test# lsmod | grep test test 6920 0 root@joker:/tmp/test# dmesg | tail [829528.598922] Test module is loaded! [829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'. Наш модуль подсказываем нам что нужно сделать. Последуем его совету: root@joker:/tmp/test# mknod /dev/test c 249 0 root@joker:/tmp/test# cat /dev/test test root@joker:/tmp/test# echo 1 > /dev/test bash: echo: ошибка записи: Недопустимый аргумент root@joker:/tmp/test# dmesg | tail [829528.598922] Test module is loaded! [829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'. [829747.462715] Sorry, this operation isn't supported. root@joker:/tmp/test# rmmod test root@joker:/tmp/test# dmesg | tail [829528.598922] Test module is loaded! [829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'. [829747.462715] Sorry, this operation isn't supported. [829893.681197] Test module is unloaded! Удалим файл устройства, что бы он нас не смущал: root@joker:/tmp/test# rm /dev/test Дальнейшее развитие этой «заготовки» зависит только от вас. Можно превратить её в настоящий драйвер, который будет предоставлять интерфейс к вашему девайсу, либо использовать для дальнейшего изучения ядра Linux. Только что в голову пришла совершенно безумная идея сделать sudo через файл устройства. Т.е. посылаем в /dev/test команду и она выполняется от имени root. Литература И под конец дам ссылку на книгу заклинаний LKMPG (Linux Kernel Module Programming Guide) UPD: У некоторых может не собраться модуль через Makefile, описанный выше. Решение: Создаём Makefile только с одной строкой: obj-m += test.o И запускаем сборку так: make -C /usr/src/linux-headers-`uname -r` SUBDIRS=$PWD modules Поправил ошибки в исходнике. Парсер глючит и сохраняет 'MODULE_DEscriptION( «My nice module» );'. Естественно в module_description все буквы заглавные. UPD3: несколько поправок к посту: 1) В функции device_open() находится race condition: PHP код:
процессом команд между if (is_device_open) и is_device_open++, то в итоге файл откроется 2 раза. Для атомарных действий нужно использовать функцию из серии atomic_XXX(). Атомарные операции нужно использовать во всех местах работы с данными. В данном случае и в close(). 2) device_write() можно было вообще не писать, т.к. обработчик по умолчанию write() сам возвращает ошибку. 3) у put_user() ОБЯЗАТЕЛЬНО нужно проверять результат. Если не ноль, то нужно либо а) вернуть результат -EFAULT и сделать вид, что ничего не было (т.е. не удалять не до конца считанные данные из внутренних буферов, в данном случае данные константные и ничего изменять не надо) б) вернуть кол-во УЖЕ записанный байт (это называется partial read, позволено POSIX'ом). При этом нужно следить за тем, чтобы не вернуть 0: read() = 0 означает, что файл подошёл к концу, а это не так. 4) В ядре в качестве успешного кода завершения используется 0, а не задефайненная константа SUCCESS. Есть исключения, например, в обработчике сетевого пакета, но там, где возвращается либо -EXXX (код ошибки), либо 0 (всё хорошо), используется именно константа 0. Ещё много функций можно заменить на более подходящие аналоги, но это усложнило бы понимание статьи новичками Источник <!-- Вопросы задаем на форуме, не в ЛС --> |
Ответить |
Опции темы | |
Опции просмотра | |
|
|