Ядро Linux/Драйвери символьних пристроїв (Character device drivers)
Ідентифікатори пристроїв (Major і minor)
ред.В UNIX, пристрої як правило мають пов'язані із ними унікальні, фіксовані ідентифікатори. Ця традиція збереглась у Linux, хоча ідентифікатори можна розподіляти динамічно (з міркувань збереження зворотної сумісності, більшість драйверів досі використовують статичні ідентифікатори). Ідентифікатор складається з двох частин: старшої і другорядної (англ. major and minor). Перша частина ідентифікатора задає тип пристрою (IDE disk, SCSI disk, послідовний порт, та ін.) а друга частина задає сам пристрій (перший дисковий накопичувач, другий послідовний порт, та ін.). В більшості випадків, старший номер визначає драйвер, а другорядний визначає кожний конкретний фізичний пристрій, який обслуговується драйвером. В загальному випадку, драйвер матиме призначений йому старший ідентифікатор і відповідальний за всі другорядні пов'язані із цим старшим.
# ls -la /dev/hda? /dev/ttyS? brw-rw---- 1 root disk 3, 1 2004-09-18 14:51 /dev/hda1 brw-rw---- 1 root disk 3, 2 2004-09-18 14:51 /dev/hda2 crw-rw---- 1 root dialout 4, 64 2004-09-18 14:52 /dev/ttyS0 crw-rw---- 1 root dialout 4, 65 2004-09-18 14:52 /dev/ttyS1
Як видно з вищенаведеного прикладу, інформацію про тип пристрою можна отримати використовуючи команду ls. Спеціальні символьні файли позначені символом c в першій колонці виводу команди, тип блокових пристроїв позначений символом b. У стовпчиках 5 і 6 результату виводу ви можете побачити старший ідентифікатор, і відповідно minor для кожного пристрою.
Певні значення старших ідентифікаторів статично визначені для деяких пристроїв (і описані в документі Documentation/admin-guide/devices.txt, що поширюється із кодом ядра). При виборі ідентифікатора для нового пристрою, можна піти двома шляхами: надати йому статичний ідентифікатор (вибрати номер, який здається ще не зайнятим) або динамічний. В файлі /proc/devices знаходяться завантажені пристрої, із вказаним major ідентифікатором.
Структури даних для символьних пристроїв
ред.В ядрі, пристрої символьного типу представлені структурою struct cdev, яка використовується для реєстрації його в системі. У більшості операціях із драйвером використовуються ще три важливі структури: struct file_operations, struct file і struct inode.
struct file_operations
ред.Як згадувалося раніше, драйвери символьних пристроїв отримують unaltered системні виклики, які виконуються користувачами over device-type files. Відповідно, реалізація драйверу символьного пристрою означає необхідну імплементацію системних викликів, що мають відношення до операцій над файлами: open, close, read, write, lseek, mmap, та ін. Ці операції описані в полях структури struct file_operations:
#include <linux/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 *); [...] long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); [...] int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); [...]
Можна помітити, що сигнатури цих функцій відрізняються від сигнатур системних викликів, з якими працює користувач. Операційна система займає місце між користувачем і драйвером пристрою, з метою спростити реалізацію драйверу пристрою. open в параметрах не отримує шлях до файлу або інші параметри, що контролюють режим доступу до файлу. Аналогічно, read, write, release, ioctl, lseek не отримують в якості параметра файловий дескриптор. Натомість, Ці функції отримують в якості параметрів дві структури: file і inode. Обидві ці структури описують файл, але із іншої точки зору.
Більшість параметрів для наведених операцій мають інтуїтивно зрозуміле значення:
- file і inode визначають файл пристрою;
- size визначає кількість байт які необхідно прочитати або записати;
- offset визначає зміщення від початку з якого необхідно виконувати читання або запис (і який відповідно до того оновлюється);
- user_buffer користувацький буфер, з якого відбувається читання або в який виконується запис;
- whence визначає спосіб пошуку (позицію з якої починається операція пошуку);
- cmd і arg це параметри, які передаються в рамках користувацьких викликів ioctl (IO control).
Структури inode і file
ред.inode представляє файл з точки зору файлової системи. Атрибутами inode є розмір, права, відмітки часу, що пов'язані із файлом. inode унікальним способом визначає файл в файловій системі.
Структура file це також файл, але за змістом ближче до користувацької точки зору. Із атрибутів цієї структури можна навести: inode, ім'я файлу, атрибути файлу, позицію у файлі. Всі відкриті на даний момент у системі файли мають пов'язану із ними структуру file. Аби зрозуміти відмінність між inode і file, приведемо аналогії із об’єктно-орієнтованого програмування: Якщо ми розглянемо inode як клас, тоді файли є його об'єктами, таким чином, екземплярами класу inode. Inode представляє статичну картину про файл (inode не має станів), в той час як file представляє динамічну картину файла (файл має певний стан).
Повертаючись до драйверів пристроїв, дві сутності мають завжди майже стандартне використання: inode використовується для визначення головного і другорядного ідентифікатора пристрою над яким здійснюється операція, а файл використовується для визначення прапорів стану, які допомагають визначити який файл було відкрито, а також для збереження і доступу до приватних даних. Серед багатьох інших полів, структура file містить наступні поля:
- f_mode, визначає режим запису чи читання (FMODE_READ, FMODE_WRITE);
- f_flags, визначає режими відкриття файлу (O_RDONLY, O_NONBLOCK, O_SYNC, O_APPEND, O_TRUNC, та ін.);
- f_op, визначає операції пов'язані із файлом (вказівник на структуру file_operations );
- private_data, вказівник, який розробник драйверу може використовувати для збереження даних даного пристрою; Вказівник буде вказувати на місце у пам'яті, яке було ініціалізоване розробником.
- f_pos, зміщення від початку файлу
Серед усієї інформації, структура inode містить поле i_cdev, яке вказує на структуру яка визначає символьний пристрій (коли inode відповідає символьному пристрою).