Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
27
Добавлен:
27.03.2016
Размер:
99.84 Кб
Скачать

Лабороторна робота №2_6 Написання драйверів в Linux

Драйвер пристрою це низькорівнева програма, яка містить специфічний код для роботи з пристроєм та дозволяє користувачу програмам(і самої ОС) керувати ним стандартним чином.

Пристрої можна розділити на:

Символьні. Читання і запис пристрою йде посимвольний. Приклади таких пристроїв: клавіатура, послідовні порти.

Блочні. Читання і запис пристрою можливі тільки блоками, зазвичай по 512 або 1024 байта. Приклад - жорсткий диск.

Мережеві інтерфейси. Приклад - мережева карта (eth0). Відрізняються тим, що не відображаються на файлову систему, тобто не мають відповідних файлів в директорії /dev, оскільки через специфіку цих пристроїв робота з мережевими пристроями як з файлами неефективна.

Для символьних і блочних пристроїв - взаємодія з драйвером реалізується через спеціальні файли, розташовані в директорії /dev. Кожен файл пристрою має два номери

- старший, який визначає тип пристрою

- молодший, який визначає конкретний номер пристрою(у системі може бути декілька пристроїв одного типу - наприклад, жорстких дисків). Багато з старших номерів пристроїв вже зарезервовані; їх можна подивитися в документації на ядро. Це файл /usr/share/doc/kernel-doc-2.4.2/devices.txt.

У перших версіях Linux драйвера пристроїв були "зашиті" в ядро. Недоліки такого рішення очевидні:

1. Драйвера, включені в ядро, завантажуються навіть за відсутності пристрою в системі - і споживають системні ресурси.

2. При підключенні нового пристрою (або нової версії драйвера) потрібна перекомпіляція ядра. Наявність цих проблем призвело до створення в ядрі механізму модулів що завантажуються динамічно. Механізм дає можливість встановлювати драйвери нових пристроїв "на льоту" - без перекомпіляції ядра і навіть перезавантаження системи. Встановлювати можна не всі драйвера, а лише драйвера для пристроїв, які реально присутні в системі. Динамічні модулі, на відміну від звичайних програм, є об'єктні файли, скомпільовані за певними правилами. Розглянемо створення такого модуля для символьного пристрою.

Найпростіший модуль. Компіляція та встановлення модуля в систему. Текст найпростішого модуля представлений нижче (файл module.c).

1 /*

2 ====================================

3 Приклад найпростішого модуля ядра

4 лаб.2_6_1

5 2014

6 ====================================

7 */

8 #define MODULE

9 #define __KERNEL__

10 #include <module.h>

11

12 int init_module()

13 {

14 return 0;

15 }

16

17 void cleanup_module()

18 {

19 return;

20 }

Як видно - є дві функції (вони обов'язкові):

int init_module() - викликається при завантаженні модуля ядром. Якщо повертається значення "0", все нормально; інакше - сталася помилка.

void cleanup_module() - Викликається при видаленні модуля з системи. Рядки 8-9 змушують компілятор генерувати код динамічно завантаження модуля. У заголовному файлі module.h містяться визначення, необхідні для створення динамічного модуля. Нижче наведено текст Makefile для збірки нашого модуля:

CC=gcc

MODFLAGS:= -O3 -Wall -DLINUX

module.o: module.c

$(CC) $(MODFLAGS) -c module.c

Директива - DLINUX каже компілятору про необхідність генерувати код під Linux. Ключ змушує компілятор генерувати саме об'єктний, а НЕ виконуваний файл. Збірка здійснюється командою make в директорії, де лежить вихідний файл module.c. Результат збірки - файл module.o . Для установки модуля в систему потрібні права суперкористувача root. Сама установка здійснюється командою insmod. Перегляд встановлених модулів доступний root -у по команді lsmod. Видалення модуля (теж з правами root ) - rmmod .

Удосконолимо модуль:

1. Непогано було б дати користувачеві можливість зрозуміти, що і як робить модуль, і як зв'язатися з автором. Для цього в module.h визначені макроси MODULE_DESCRIPTION і MODULE_AUTHOR. Отримати інформацію про автора можна командою modinfo - a, опис модуля - modinfo - d. Для виконання цих команд знову ж потрібні права root.

2. Модуль може виводити на консоль повідомлення; для цього є функція printk. Звичайно, для реальних модулів це зазвичай ні до чого, однак на етапі налагодженні такі повідомлення бувають дуже корисні.

3. Не завжди зручно називати функцію ініціалізації init_module, а функцію вивантаження cleanup_module. У файлі init.h визначено функції module_init() і module_exit(), що дозволяють зняти обмеження .

Новий код модуля наведено нижче:

/*

====================================

* Приклад найпростійшого модуля ядра

лаб.2_6_1

2014

====================================

*/

#define MODULE

#define __KERNEL__

#include <module.h> // визначення для модуля

#include <init.h> // module_init и module_exit

#include <kernel.h> // printk

MODULE_AUTHOR("Mike Goblin mgoblin@mail.ru");

MODULE_DESCRIPTION("Test module for linux kernel");

int module_start()

{

printk("This is a test module startup message\n");

return 0;

}

void module_stop()

{

printk("Module is dead\n");

return;

}

module_init(module_start);

module_exit(module_stop);

При завантаженні і вивантаженні модуля ви побачите в консолі тестові повідомлення модуля.

Реєстрація пристрою і захоплення ресурсів Наш модуль нічого не робить, та й взагалі недоступний для програм. Щоб зробити пристрій доступним - при завантаженні модуля його необхідно зареєструвати в системі і вказати в ресурси які використовуються.

Для реєстрації різних типів пристроїв в заголовному файлі fs.h визначено відповідні функції з префіксом register. Так, нам для реєстрації нашого "драйвера символьного пристрою" необхідно використовувати функцію register_chrdev. Функція оголошена так:

extern int register_chrdev(unsigned int, const char *, struct file_operations *);

Перший параметр - старший номер файлу пристрою (тип пристрою). Якщо цей параметр дорівнює 0 , то функція повертає вільний старший номер для нашого типу пристрою. Краще так і робити, тому що при цьому виключаються конфлікти старших номерів пристроїв. Другий параметр — ім'я пристрою. Під цим ім'ям пристрій буде відображатися в списку пристроїв. Третій параметр - структура з покажчиками на функції драйвера. Тут ми підходимо до питання про те, як система працює з драйвером. Драйвер зберігає таблицю доступних функцій, а система викликає ці функції, коли хтось намагається виконати деякі дії (відкриття, запис, читання) з файлом пристрою, який має старший номер нашого пристрою. Для символьного пристрою — таблиця функцій драйвера зберігається в структурі file_operations. Ця структура і передається при реєстрації пристрою. Поки що ми будемо передавати структуру незаповненою (адже функції роботи з пристроями що не написані ). У прикладі , наведеному нижче , структура оголошена в рядку 32. Драйвер реєструється в рядках 42-48. У рядку 31 оголошена змінна Major для зберігання старшого номера пристрою, одержуваного від ОС при реєстрації. Змінна оголошена як static, т.як. старший номер буде потрібен і при знятті реєстрації перед вивантаженням модуля. Після реєстрації драйвера , як правило , відбувається пошук присутніх у системі пристроїв даного типу та їх параметрів (номери переривань, порти вводу-виводу і т.д.). Методика пошуку — своя для кожного типу пристроїв, тому важко привести який-небудь код. У нашому прикладі - будемо вважати, що є два пристрої, що використовують один діапазон портів введення-виведення і одне переривання. Перше з наших пристроїв має молодший номер 0, а друге - 1. Для подальшої роботи - створимо файли даних пристроїв, набравши (з правами root) в директорії /dev команди:

mknod my_dev c 254 0

mknod my_dev c 254 1

Замість 254, можливо, знадобитися підставити інший старший номер(він видається модулем на консоль при завантаженні). Нижче приведений новий варіант нашого модуля.

1 /*

2 ===========================================

3 Приклад захоплення ресурсів модулем ядра

4 лаб.2_6_2

5 2014

6 ===========================================

7 */

Соседние файлы в папке SEM2