Запомняне

» Здравейте
[ Вход :: Регистриране ]
 » Начало » Li Форуми » Li Статии » Интересно » Linux драйвер (модул) - що е то?
  • Страница 1 от 1 Skip to Page:
  • 1
Тема: Linux драйвер (модул) - що е то?
Мнение #1 Skip to the next post in this topic.
Написано на: Февруари 19 2007, 17:18
PxL
 

Avatar




Група: Li fans
Мнения: 3
Регистриран: Юни 2006

Оценка: няма

Offline
.:: Кой? Къде? Защо? ::.
Преди известно време бях много запален да напиша драйвер (модул) за ядрото на 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 Module) обменят данни с потребителските софтуерни приложения в 'user space’ чрез callback функции в ядрото. Представено визуално това би изглеждало така:
Код: 

+-----------+
| UserSpace |--------+
+-----------+            |
     |                       |
+-----------+  +-------------+
|    Ядро    |->|   Файлови  |
|   Модули |<-|  Устройства |
+-----------+  +-------------+
     |
+-----------+
|  Хардуер  |
+-----------+


За да не объркам някого или себе си, направо ще дам пример за елементарен kernel модул:

simple.c
Код: 

#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dimiter Todorov Dimitrov");
MODULE_DESCRIPTION("Simple kernel-space module");
MODULE_SUPPORTED_DEVICE("testdevice");


Както забелязвате използвам няколко макроса, чрез които определяме лиценз и информация за модула. Те са дефинирани в ‘module.h’ - необходимост за всеки един kernel модул (/usr/include/linux/module.h).

За да компилираме ще ни трябва елементарен Makefile

Makefile
Код: 

KDIR:=/lib/modules/$(shell uname -r)/build

obj-m:=simple.o

default:
       $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
       $(RM) .*.cmd *.mod.c *.o *.ko -r .tmp* *.symvers


Необходимо е за компилация да се ползва ядрото, в което ще заредите модула. Добавил съм и опция за clean след компилация.

Запазваме и компилираме:
Цитат:


jorko tut $ make; ls
make -C /lib/modules/2.6.18/build SUBDIRS=/pxl/Devel/tut modules
make[1]: Entering directory `/usr/src/linux-2.6.18'
 CC [M]  /pxl/Devel/tut/simple.o
 Building modules, stage 2.
 MODPOST
 CC      /pxl/Devel/tut/simple.mod.o
 LD [M]  /pxl/Devel/tut/simple.ko
make[1]: Leaving directory `/usr/src/linux-2.6.18'
Makefile        simple.c   simple.mod.c  simple.o
Module.symvers  simple.ko  simple.mod.o
jorko tut $


Компилирания модул ‘simple.ko’ можем да заредим от ‘user space’ в ядрото, чрез командата
'insmod' (или ‘modprobe’), и да проверим резултата с командата 'lsmod'.
Цитат:


jorko tut # insmod simple.ko
jorko tut #
jorko tut # lsmod | grep simple
simple                  2176  0
jorko tut #      


Проверката показва, че модулът е зареден и работи в 'kernel space’. Можем да го премахнем с командата 'rmmod' (или ‘modprobe –r’).
Цитат:


jorko tut # rmmod simple


.:: Съобщения в системния лог файл ::.
В горния пример създадохме модул, който дефакто не прави нищо освен да се зареди в ‘kernel space’. При зареждане на модули в повечето случай е необходимо да извършите някаква операция (например инициализация на устройство, данни и т.н.). За целта са предоставени две функции: 'module_init()' и  'module_exit()'.

Нека разширим нашия пример:
Код: 

#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dimiter Todorov Dimitrov");
MODULE_DESCRIPTION("Simple kernel-space module");
MODULE_SUPPORTED_DEVICE("testdevice");

static int simple_init(void) {
       printk("Simple module sayes: Hello!\n");
       return 0;
}

static void simple_exit(void) {
       printk("Simple module unloaded\n");
}

module_init(simple_init);
module_exit(simple_exit);


Използваме callback функциите предоставени от ядрото, за да изпълним операция при зареждане и премахване на нашия модул. В случая ползваме kernel функцията 'printk', която запазва системни съобщения в системния лог. За да ги видим, можем да използваме командата 'dmesg'. По аналогичен начин както първия път, зареждаме модула и търсим за съответни съобщения в системния лог файл:
Цитат:


jorko tut # insmod simple.ko
jorko tut # dmesg | grep Simple
Simple module sayes: Hello!
jorko tut # rmmod simple
jorko tut # dmesg | grep Simple
Simple module sayes: Hello!
Simple module unloaded
jorko tut #



.:: Device файлове ::.
Както споменах вече софтуерните приложения са от 'user space’ частта и комуникират с модулите на ядрото чрез файлове намиращи се в '/dev’ частта на файловата система. За всяко устройство предоставено в '/dev’ отговаря съответен модул в ядрото. За да се назначи даден модул към даден файл, се използват уникални числа (желателно е те да са уникални, за да не се объркват модулите, но е възможно да се дублират, което би довело до най-различни /неприятни/ последствия). Тези числа са наречени major numbers и чрез тях ядрото знае кой модул за кой файл отговаря. Всяко устройство има и minor number, което не играе роля при ядрото, а е необходимо на самия модул, за да различава своите устройства. Важно е да знаем, че тези устройства могат да са два вида: character и block. Главната разлика както може би се досещате е, че block устройствата предават данните на отделни фиксирани в зависимост от типа на устройството блокове, докато character устройствата нямат фиксиран размер на предаваните данни. Ето един пример:

Цитат:


jorko tut $ ls -la /dev/hda*
brw-rw---- 1 root disk 3, 0 Feb 18 13:18 /dev/hda
brw-rw---- 1 root disk 3, 1 Feb 18 13:18 /dev/hda1
brw-rw---- 1 root disk 3, 2 Feb 18 13:18 /dev/hda2
brw-rw---- 1 root disk 3, 5 Feb 18 13:18 /dev/hda5
brw-rw---- 1 root disk 3, 6 Feb 18 13:18 /dev/hda6
brw-rw---- 1 root disk 3, 7 Feb 18 13:18 /dev/hda7
brw-rw---- 1 root disk 3, 8 Feb 18 13:18 /dev/hda8


Виждате типа на устройството - brw-rw----, както и неговите major и minor номера – brw-rw---- 1 root disk 3, 8 Feb 18 13:18 /dev/hda8
Можете да забележите от горния пример, че за тези устройства отговаря един модул, тъй като имат един и същи major номер. Съответно minor номерата са различни, за да може модулът да различава устройствата.
За да знаем кога процес се опитва да чете или пише в дадено устройство е необходимо да ползваме структура наречена 'file_operations', дефинирана в 'fs.h'.

Код: 

struct file_operations {
       struct module *owner;
       loff_t (*llseek) (struct file *, loff_t, int);
       ssize_t (*read) (struct file *, char user *, size_t, loff_t);
       ssize_t (*write) (struct file *, const char user *, size_t, loff_t);
       int (*readdir) (struct file *, void *, filldir_t);
       unsigned int (*poll) (struct file *, struct poll_table_struct *);
       int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
       long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
       long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
       int (*mmap) (struct file *, struct vm_area_struct *);
       int (*open) (struct inode *, struct file *);
       int (*flush) (struct file *);
       int (*release) (struct inode *, struct file *);
       int (*fsync) (struct file *, struct dentry *, int datasync);
       int (*aio_fsync) (struct kiocb *, int datasync);
       int (*fasync) (int, struct file *, int);
       int (*lock) (struct file *, int, struct file_lock *);
       ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
       ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
       ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
       ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
       unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
       int (*check_flags)(int);
       int (*dir_notify)(struct file *filp, unsigned long arg);
       int (*flock) (struct file *, int, struct file_lock *);
       ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
       ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};


Както сами забелязвате структурата предоставя доста указатели, като повечето от тях в случая няма да ни интересуват, така че не се стряскайте. За целта можем да ползваме GCC разширения начин за дефиниране на тази структура:
Код: 

     struct file_operations fops = {
           read: device_read,
           write: device_write,
           open: device_open,
           release: device_release
     };


или C99 стандарта:

Код: 

     struct file_operations fops = {
           .read = device_read,
           .write = device_write,
           .open = device_open,
           .release = device_release
     };


Забележка: Недефинираните членове на структурата ще се дефинират автоматично от gcc като NULL.

В примера по-долу ще използвамe коментари директно, за да е по-разбираемо:

Код: 

#include <linux/module.h> /* definicii na modul fukciite */
#include <linux/kernel.h> /* Definiciq na printk() i konstanti za neq */
#include <linux/fs.h> /* Definiciq na file_operations strukturata */
#include <linux/uaccess.h> /* Za prenos ot user space kym kernel space */

#define BUF_LEN 64 /* maksimalna dyljina na syobshteniqta */

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dimiter Todorov Dimitrov");
MODULE_DESCRIPTION("Simple kernel-space module");
MODULE_SUPPORTED_DEVICE("testdevice");

/* Prototipi na funkciite  */
static int simple_init(void);
static void simple_exit(void);

static int simple_open(struct inode *inode, struct file *file);
static int simple_release(struct inode *inode, struct file *file);
static ssize_t simple_read(struct file *filp, char *buffer, size_t length, loff_t *offset);
static ssize_t simple_write(struct file *filp, const char *buff, size_t len, loff_t *off);


/* Deklaraciq na init i exit funkciite */
module_init(simple_init);
module_exit(simple_exit);


/* Deklaraciq na strukturata za rabota s fajlove */
struct file_operations fops = {
     read: simple_read,
     write: simple_write,
     open: simple_open,
     release: simple_release
     };

/* Shte ni e neobhodim za da razberem kakyv Major nomer shte poluchi ustrojstvoto pri registraciq*/
static int Major;      // Promenliva za Major nomera daden ni ot qdroto
static int Device_Open = 0;  // Flag, koito shte polzvame dokato proces izpolzva ustrojstvoto
static char msg[BUF_LEN]; // Bufer za syobshteniqta
static char *msgPtr;      // Ukazatel kym bufera za syobshteniq



/* Init i Exit funkcii */
static int simple_init(void) {
     Major = register_chrdev(0, "simple", &fops); // Registrirane na ustrojstvoto

     if (Major < 0) {
           printk ("Registering the character device failed with %d\n", Major);
           return Major;
     }

     printk("Simple module loaded into kernel space!\n");
     printk("Simple module has been assigned to Major number %d\n", Major);
     printk("Create device file with: mknod /dev/simple c %d 0\n", Major);
     return 0;
}

static void simple_exit(void) {
     int ret = unregister_chrdev(Major, "simple");
     if (ret < 0)
           printk("Error in unregister_chrdev: %d\n", ret);

     printk("Simple module unloaded successfuly!\n");
}


/* Metodi ot file_operations */


/*Izvikva se pri opit za otwarqne na fajlovoto ustrojstvo  */
static int simple_open(struct inode *inode, struct file *file)
{
     static int counter = 0;
     if (Device_Open)
           return -EBUSY; // Ustrojstvoto se izpolzva ot drug procesz

     Device_Open ++; // Vdigame flag, che ustrojstvoto e zaeto
     sprintf(msg,"You have accessed this device %d times!\n", counter++);
     msgPtr = msg;

     return 0;
}

static int simple_release(struct inode *inode, struct file *file)
{
     Device_Open --; // Nulirame flaga, za da mojem da priemame i drugi procesi      
     return 0;
}


/* Izvikva se pri opit za chetene ot ustroistvoto */
static ssize_t simple_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
{
     int bytes_read = 0; // Broqch

     if (*msgPtr == 0)
           return 0;

     while(length && *msgPtr)
     {
           put_user(*(msgPtr++), buffer++); // Funkciqta kopira dannite ot kernel space kym user space

           length --;
           bytes_read ++;
     }

     return bytes_read; // Vryshtame kato rezultat prochetenite baitove
}

static ssize_t simple_write(struct file *filp, const char *buff, size_t len, loff_t *off)
{

     printk( "You don`t wanna write to me :)");
     return -EINVAL;
}


След като заредим модула, ще трябва да създадем файл за устройството в ‘/dev’ с командата ‘mknod’ като му зададем major number според това, какъв major number ни е определен от ядрото
Цитат:


jorko tut # insmod simple.ko
jorko tut # dmesg| tail -n 1
Create device file with: mknod /dev/simple c 253 0
jorko tut # mknod /dev/simple c 253 0
jorko tut # cat /dev/simple
You have accessed this device 0 times!
jorko tut # cat /dev/simple
You have accessed this device 1 times!
jorko tut # cat /dev/simple
You have accessed this device 2 times!
jorko tut # cat /dev/simple
You have accessed this device 3 times!
jorko tut # rmmod simple
jorko tut # dmesg| tail -n 1
Simple module unloaded successfuly
jorko tut # rm /dev/simple


Вече знаем как да създадем модул и да го асоциираме с 'user space’ устройство.
Остана единствено момента, в който реално ще комуникираме с хардуер ...

.:: Операции с хардуер ::.
Линукс предоставя няколко функции за операции с хардуера. Преди да се зареди даден драйвер (модул) подобен на горния той трябва да резервира даден IO порт за ползване. Това се осъществява чрез функцията 'request_region', изискваща номер на порт, обхват ако са повече от 1 IO (входно-изходен) порт и име на драйвера. Преди нея обаче, за да се увери, че някой друг модул не ползва порта, драйвeрът трябва да извика функцията '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 са обикновено прикрепени към дънната платка.

.:: Заключение ::.
Най-добрият начин да разберете нещо е да разгледате готов пример и да тествате.

P.S.: Тази статия е провокирана от основното соло в Sweet child o' mine на Guns 'n' Roses

Референции:
http://www.faqs.org/docs/kernel/
http://kernel.org/

Благодарско на @Angel от http://forums.bgdev.org/ за съветите относно kernel/user space графиката


Редактирано от stumps на Февруари 28 2007, 14:57
Мнение #2
Skip to the previous post in this topic. Написано на: Юли 06 2007, 13:42
stumps
 

Avatar




Група: Li psychos
Мнения: 1311
Регистриран: Април 2005

Offline
Изключително добър материал по темата:
http://www.linuxdevcenter.com/pub....ux.html

Два месеца работа с компютъра спестява два часа четене на документация

Бизнесът на Microsoft е бизнес за трима: Един пише вируси, друг прави антивирусни, а третият продава (псевдо)операционна система за тях.
Контакти:  stumps

  • AOL  AOL:
  • ICQ  ICQ:
  • MSN  MSN:
  • YIM  Yahoo IM:
Общо 1 отговор(а) от Февруари 19 2007, 17:18 до сега
  • Страница 1 от 1 Skip to Page:
  • 1
 » Начало » Li Форуми » Li Статии » Интересно » Linux драйвер (модул) - що е то?

© 2014 Linux Index Project
Powered by iF 1.0.0 © 2006 ikonForums