Ядро 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 відповідає символьному пристрою).

Література[ред.]