LINUX-BG Адрес : http://www.linux-bg.org |
Линукс модули |
От: PxL Публикувана на: 22-02-2007 Адрес на статията: http://www.linux-bg.org/cgi-bin/y/index.pl?page=article&id=devs&key=390713057 |
.::Linux драйвъри::.
.:: Кой? Къде? Защо? Преди известно време бях много запален да напиша драйвър за ядрото на Linux.Известно ми беше горе-долу как се пише драйвър и как работи такъв, съответно имах и известни познания по C.Общо взето това би трябвало да е изискването за писане на драйвъри за устройства. Като цяло подобни материали не липсват в интернет, но не съм срещал на български. .:: Нива на комуникация Както предполагам е известно на повечето Linux потребители, устройствата там са представени като файлове, намират се в /dev часта...Отсъства портовата комуникация...? По-скоро комуникацията с физическите устройства се осъществява на две нива. Реалната комуникация с хардуерното устройството се съществява от т.нар. kernel space ниво, където ядрото и модулите му се грижат да предават информацията от и за физическите устройства към съответстващите им такива представени от файлове. Другата част се нарича user space, където апликациите комуникират с тези файлове. .:: Kernel space / User space Като начало можем да кажем накратко, че модулите са приложения, чиято идея е да се разшири и лесно да се добавя функционалност към ядрото. В зависимост от типа на ядрото (в конкретният случай визирам Линукс ядро) модулите могат да се добавят динамично, т.е. не е нужна прекомпилация и рестарт на самото ядро за да бъде добавен нов модул. По-специално ни интересуват т.нар. monolitic тип ядра, тъй като Linux ядрото е такъв тип. При monolitic типовете ядра (за разлика например от microkernel типовете) модулите използват паметта заделена за ядрото. Това означава, че всички глобални данни са видими за всички модули. Именно поради това писането на модули трябва да е много строго стандартизирано и е за предпочитане да се ползват static променливи.. Както споменах по-горе целта на драйвърите в kernel space е да свържат файловете на устройствата в /dev със самит ехардуерни устройства.Модулите в kernel space (LKM - Loadable Kernel Modul) обменят данни с потребителските апликации в user space чрез callback функции в ядрото.Представено визуално това би изглеждало така: За да не объркам някого или себе си направо ще дам пример за прост kernel модул.
Както забелязвате използвам няколко макроса, чрез които определяме лиценз и информация за модула. Дефинирани са в module.h необходимост за всеки един kernel модул (vim /usr/include/linux/module.h). За да компилираме ще ни трябва елементарен Makefile
Запазваме и компилираме:
Компилираният модул simple.ko можем да заредим от user space в ядрото, чрез insmod, и да разгледаме с lsmod.
В горният пример създадохме модул, който дефакто не прави нищо освен да се зареди в kernel space. При зареждане на модули в повечето случай е необходимо да извършите операция, например инициализация на устройство, данни и т.н. За целта са предоставени две функций: module_init() и module_exit(). Нека разширим нашият пример:
Използваме callback функциите предоставени от ядрото, за да изпълним операция при зареждане и премахване на нашият модул. В случая ползвам kernel функцията printk, която запазва системни съобщения в системният лог. За да ги видим можем да ползваме dmesg. По аналогичен начин както първият път зареждам модула и търся за съобщенията в лог-а:
.::Device файлове Както споменах вече апликациите от user space комуникират с модулите в ядрото чрез файл-ове намиращи се в /dev частта. За всяко устройство предоставено в /dev отговаря съответен модул в ядрото. За да се /назначи/ даден модул към даден файл се използват уникални числа (желателно е те да са уникални, за да не се объркват модулите, но е възможно да се дублират, което би довело до най-различни /неприятни/ последствия). Тези числа са наречени major numbers, чрез тях ядрото знае кой модул за кой файл отговаря.Всяко устройство има и minor number, което не играе роля при ядрото, то е необходимо на самият модул да различава свойте устроства. Важно е да знаем, че тези устройства могат да са два вида: character и block.Главната разликата както можеби се досещате е, че block устройствата предават данните на отделни фиксирани в зависимост от типа на устройството блокове, докато character устройствата нямат фиксиран размер на предаваните данни. Ето един пример:
Виждате типа на устройството [color=blue]b[/color]rw-rw----. Както и неговите major и minor номера brw-rw---- 1 root disk [color=red]3[/color], [color=blue]8[/color] Feb 18 13:18 /dev/hda8 Можете да забележите от горният пример, че за тези устройства отговаря един модул, тъй като имат един и същи major номер. Съответно minor номерата са различни, за да може модулът да различава устройствата. За да знаем кога процес се опитва да чете или пише в дадено устройство е необходимо да ползваме структура наречена file_operations, дефинирана в fs.h.
или C99 стандарта:
Недефинираните членове на структурата ще се дефинират автоматично от gcc като NULL. В примера, ще използвам коментари директно, за да е по-разбираемо:
След като заредим модула ще трябва да създадем файл за устройството в /dev с mknod като му зададем Major number според това, какъв Major number ни е определен от ядрото
Вече знаем как да създадем модул и да го асоцираме с user space устройство. Остана единствено момента, в който реално ще комуникираме с хардуер... .:: Операции с хардуер Линукс предоставя няколко функций за операции с хардуера. Преди да се зареди драйвър, подобен на горният той трябва да /резервира/ даден IO порт за ползване.Това се осъществява чрез функцията request_region изискваща номер на порт, обхват ако са повече от 1 входно-изходен порт и име на драйвъра. Преди нея обаче, за да се увери, че някой друг модул не ползва порта драйвъра трябва да извика check_region, изискваща два параметъра: номер на порт и дължина (1 за конкретен порт). След приключване на работа с външното у-во Драйъвъра трябва да освободи порт-а за да е възможно други драйвъри да работят с него, за целта се ползва release_region изискваща отново същите параметри. За комуникация с устройствата се ползват функции от библиотеката io.h (asm/io.h). Двете по-съществени функций за писане и четене от порт са: outb и inb .:: Хардуерни прекъсвания За да работи с хардуерни прекъсвания (IRQ - Interrupt Request), драйвърът ползва request_irq и free_irq. Също можем да ползваме две функций за временно прекратяване на прекъсванията и съответно възстановяването им: cli и sti. .:: DMA DMA (Direct Memory Access) каналите ползват директно RAM паметта, без да налагат прекъсване на процесорната работа. За работа с DMA се използва библиотеката asm/dma.h. Устройствата ползващи DMA са обикновено прикрепени към дънната платка. .:: КонклузИонето Най-добрият начин да разберете нещо е да разгледате готов пример и да тествате. п.с.: Тази статия е провокирана от основното соло в Sweet child o' mine на Guns 'n' Roses Референций: http://www.faqs.org/docs/kernel/ http://kernel.org/ Благодарско на @Angel от http://forums.bgdev.org/ за съветите относно kernel/user space графиката. Димитър Т. Димитров PxL 2007 http://insecurebg.org/ pxl at insecurebg --EOF Съжалявам ако имам неточности,помагайте да ги оправим. << | Програмиране графичен интерфейс (GUI) с Lazarus и freepascal >> |
Авторите на сайта, както и техните сътрудници запазват авторските права върху собствените си материали публикувани тук,
но те са copyleft т.е. могат свободно да бъдат копирани и разпространявани с изискването изрично да се упоменава името на автора,
както и да се публикува на видно място, че те са взети от оригиналния им URL-адрес на този сървър (http://www.linux-bg.org). Авторските права на преводните материали принадлежат на техните автори. Ако с публикуването тук на някакъв материал неволно са нарушени нечии права - след констатирането на този факт материалът ще бъде свален.
All trademarks, logos and copyrights mentioned on this site are the property of their respective owners.
|