Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внешние интерфейсы модуля | Вперед |
Драйверы: интерфейс устройства
Смысл операций с интерфейсом /dev состоит в связывании именованного устройства в каталоге /dev с разрабатываемым модулем, а в самом коде модуля реализации разнообразных операций на этом устройстве (таких как open(), read(), write() и множества других). В таком качестве модуль ядра и называется драйвером устройства. Некоторую сложность в проектировании драйвера создаёт то, что для этого действа предлагаются несколько альтернативных, совершенно исключающих друг друга техник написания. Связано это с давней историей развития подсистемы /dev (одна из самых старых подсистем UNIX и Linuz), и с тем, что на протяжении этой истории отрабатывались несколько отличающихся моделей реализации, а удачные решения закреплялись как альтернативы. В любом случае, при проектировании нового драйвера предстоит ответить для себя на три группы вопросов (по каждому из них возможны альтернативные ответы):
- Каким способом драйвер будет регистрироваться в системе, как станет известно системе, что у неё появился в распоряжении новый драйвер?
- Каким образом драйвер создаёт (или использует) имя соответствующего ему устройства в каталоге /dev, и как он (драйвер) увязывается с старшим и младшим номерами этого устройства?
- После того, как драйвер увязан с устройством, какие будут использованы особенности в реализации основных операций устройства (open(), read(), ...?
Но прежде, чем перейти к созданию интерфейса устройства, очень коротко вспомним философию устройств, общую не только для Linux, но и для всех UNIX/POSIX систем. Каждому устройству в системе соответствует имя этого устройства в каталоге /dev. Каждое именованное устройство в Linux однозначно характеризуется двумя (байтовыми: 0...255) номерами: старшим номером (major) — номером отвечающим за отдельный класс устройств, и младшим номером (minor) — номером конкретного устройства внутри своего класса. Например, для диска SATA:
$ ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 Июн 16 11:03 /dev/sda brw-rw---- 1 root disk 8, 1 Июн 16 11:04 /dev/sda1 brw-rw---- 1 root disk 8, 2 Июн 16 11:03 /dev/sda2 brw-rw---- 1 root disk 8, 3 Июн 16 11:03 /dev/sda3
Здесь 8 — это старший номер для любого из дисков SATA в системе, а 2 — это младший номер для 2-го (sda2) раздела 1-го (sda) диска SATA. Связать модуль с именованным устройством и означает установить ответственность модуля за операции с устройством, характеризующимся парой major/minor. В таком качестве модуль называют драйвером устройства. Связь номеров устройств с конкретными типами оборудования — жёстко регламентирована (особенно в отношении старших номеров), и определяется содержимым файла в исходных кодах ядра: Documentation/devices.txt (больше 100Kb текста, приведено в каталоге примеров /dev).
Номера major для символьных и блочных устройств составляют совершенно различные пространства номеров и могут использоваться независимо, пример чему — набор разнообразных системных устройств:
$ ls -l /dev | grep ' 1,'
... crw-r----- 1 root kmem 1, 1 Июн 26 09:29 mem crw-rw-rw- 1 root root 1, 3 Июн 26 09:29 null ... crw-r----- 1 root kmem 1, 4 Июн 26 09:29 port brw-rw---- 1 root disk 1, 0 Июн 26 09:29 ram0 brw-rw---- 1 root disk 1, 1 Июн 26 09:29 ram1 brw-rw---- 1 root disk 1, 10 Июн 26 09:29 ram10 ... brw-rw---- 1 root disk 1, 15 Июн 26 09:29 ram15 brw-rw---- 1 root disk 1, 2 Июн 26 09:29 ram2 brw-rw---- 1 root disk 1, 3 Июн 26 09:29 ram3 ... crw-rw-rw- 1 root root 1, 8 Июн 26 09:29 random crw-rw-rw- 1 root root 1, 9 Июн 26 09:29 urandom crw-rw-rw- 1 root root 1, 5 Июн 26 09:29 zero
Примечание: За времена существования систем UNIX сменилось несколько парадигм присвоения номеров устройствам и их классам. С этим и связано наличие заменяющих друг друга нескольких альтернативных API связывания устройств с модулем в Linux. Самая ранняя парадигам (мы её рассмотрим последней) утверждала, что старший major номер присваивается классу устройств, и за все 255 minor номеров отвечает модуль этого класса и только он (модуль) оперирует с этими номерами. Позже модулю (и классу устройств) отнесли фиксированный диапазон ответственности этого модуля, таким образом для устройств с одним major, устройства с minor, скажем, 0...63 могли бы обслуживаться модулем xxx1.ko (и составлять отдельный класс), а устройства с minor 64...127 — другим модулем xxx2.ko (и составлять совершенно другой класс). Ещё позже, когда под статические номера устройств, определяемые в devices.txt, стало катастрофически не хватать номеров, была создана модель динамического распределения номеров, поддерживающая её файловая система sysfs, и обеспечивающий работу sysfs в пользовательском пространстве программный проект udev.
Практически вся
полезная работа модуля в интерфейсе /dev
(точно так же, как и в интерфейсах /proc
и /sys,
рассматриваемых позже), реализуется через таблицу (структуру)
файловых операций file_operations,
которая определена в файле
<linux/fs.h
>
и содержит указатели на функции драйвера, которые отвечают за
выполнение различных операций с устройством. Эта большая структура
настолько важна, что она стоит того, чтобы быть приведенной полностью
(ядро 2.6.37):
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 *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); 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 *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, 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 (*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 (*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); int (*setlease)(struct file *, long, struct file_lock **); };
Если мы переопределяем в своём коде модуля какую-то из функций таблицы, то эта функция становится обработчиком, вызываемым для обслуживания этой операции. Если мы не переопределяем операцию, то используется обработчик по умолчанию, а не отсутствует обработчик. Такая ситуация (отсутствие переопределённых обработчиков) имеет место достаточно часто, например, в отношении операций open и release на устройстве, но тем не менее устройства замечательно открываются и закрываются.
Ещё одна структура, которая менее значима, чем file_operations, но также широко используется:
struct inode_operations { int (*create) (struct inode *, struct dentry *, int, struct nameidata *); struct dentry * (*lookup) (struct inode *, struct dentry *, struct nameidata *); int (*link) (struct dentry *, struct inode *, struct dentry *); int (*unlink) (struct inode *, struct dentry *); int (*symlink) (struct inode *, struct dentry *, const char *); int (*mkdir) (struct inode *, struct dentry *,int); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *, struct dentry *, int, dev_t); int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *); ... }
Примечание: Отметим, что структура inode_operations относится к операциям, которые оперируют с устройствами по их путевым именам, а структура file_operations — к операциям, которые оперируют с таким представлением устройств, более понятным программистам, как файловый дескриптор. Но ещё важнее то, что имя ассоциируется с устройством одно, а файловых дескрипторов может быть ассоциировано много. Это имеет следствием то, что указатель структуры inode_operations, передаваемый в операцию (например int (*open) (struct inode *, struct file *)) будет всегда один и тот же (до выгрузки модуля), а вот указатель структуры file_operations, передаваемый в ту же операцию, будет меняться при каждом открытии устройства. Вытекающие отсюда эффекты мы увидим в примерах в дальнейшем.
Возвращаемся к регистрации драйвера в системе. Некоторую путаницу в этом вопросе создаёт именно то, что, во-первых, это может быть проделано несколькими разными, альтернативными способами, появившимися в разные годы развития Linux, а, во-вторых, то, что в каждом из этих способов, если вы уже остановились на каком-то, нужно строго соблюсти последовательность нескольких предписанных шагов, характерных именно для этого способа. Именно на этапе связывания устройства и возникает, отмечаемое многими, изобилие операторов goto, когда при неудаче очередного шага установки приходится последовательно отменять результаты всех проделанных шагов. Для создания связи (интерфейса) модуля к /dev, в разное время и для разных целей, было создано несколько альтернативных (во многом замещающих друг друга) техник написания кода. Мы рассмотрим далее некоторые из них:
- Новый способ, использующий структуру struct cdev (<linux/cdev.h>), позволяющий динамически выделять старший номер из числа свободных, и увязывать с ним ограниченный диапазон младших номеров.
- Способ полностью динамического создания именованных устройств, так называемая техника misc (misccellaneous) drivers.
- Старый способ (использующий register_chrdev()), статически связывающий модуль со старшим номером, тем самым отдавая под контроль модуля весь диапазон допустимых младших номеров; название способа как старый не отменяет его актуальность и на сегодня.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Внешние интерфейсы модуля | Драйверы: примеры реализации |